news 2026/2/14 2:19:55

IO重定向

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
IO重定向

第一部分:重定向的本质

1. 核心规则:最小分配原则

Linux 在open一个文件时,有一个铁律:

给新文件分配的 fd,永远是当前files_struct数组中 最小的、未被占用的 下标。

2. 手动实现重定向 ( The "Hack" Way )

利用这个规则,我们可以玩一个魔术:

  1. 我们知道printf默认是往stdout(也就是fd 1) 打印数据。
  2. 如果我们先close(1),把 1 号下标空出来。
  3. 然后立刻open("log.txt", ...)
  4. 根据“最小分配原则”,系统会把1 号下标分配给log.txt
  5. 此时,printf依然傻傻地往 fd 1 写数据,但 fd 1 已经不再指向显示器,而是指向了log.txt

代码验证

#include <stdio.h> #include <unistd.h> #include <fcntl.h> int main() { // 1. 关闭标准输出 (显示器) close(1); // 2. 打开新文件 // 系统发现 1 号坑是空的,于是把 fd 1 给到了 log.txt int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666); // 3. 正常打印 // printf 底层是 write(1, ...),它不知道 1 号变了 printf("fd: %d\n", fd); printf("hello redirection\n"); // 4. 刷新缓冲区 (重要!如果是文件,默认是全缓冲,不刷新可能写不进去) fflush(stdout); close(fd); return 0; }

现象:屏幕上什么都没有,但cat log.txt会发现内容都在里面。这就是>的雏形。


第二部分:dup2系统调用

手动closeopen这种方法有风险(比如多线程环境下可能有竞争,或者代码写起来麻烦)。Linux 提供了一个专门的系统调用来做这件事:dup2

1. 函数原型
#include <unistd.h> int dup2(int oldfd, int newfd);
2. 核心逻辑 (面试必问)

很多人容易搞混参数顺序。记忆口诀:newfd成为oldfd的副本

  • 动作
    1. 如果newfd已经被打开了,先把它close掉。
    2. 把内核数组中oldfd指向的那个file结构体指针,复制newfd的下标位置。
    3. 结果newfdoldfd现在同时指向同一个文件(原来oldfd打开的那个文件)。
    4. 通常我们会让oldfd是刚打开的文件(如 fd 3),newfd是 1。这样 1 就指向了 3 指向的文件。
3. 代码实战
#include <stdio.h> #include <unistd.h> #include <fcntl.h> int main() { int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666); if (fd < 0) { perror("open"); return 1; } // 【核心】把 fd(3) 的内容复制给 1 // 此时 1 号下标也指向了 log.txt dup2(fd, 1); printf("This will go to file!\n"); fprintf(stdout, "This too!\n"); // 现在 1 和 3 都指向 log.txt,关闭 3 不影响 1 close(fd); return 0; }

第三部分:标准输出 (1) vs 标准错误 (2)

我们在 Linux 命令中常看到> log.txt 2>&1,这是什么意思?

  • stdout (1):正常的打印信息。
  • stderr (2):专门用于打印错误信息。
  • 区分意义
    • 当我们执行./program > log.txt时,Shell 只把fd 1重定向到了文件。
    • 此时fd 2依然指向显示器。
    • 好处:程序正常跑的日志写文件里,程序报错的信息直接打在屏幕上让你看到。

如何把错误也写进文件?./program > log.txt 2>&1

  • 先把 1 重定向到文件。
  • 再把 2 重定向到 1(也就是 2 也指向文件)。

第四阶段: 缓冲区 (Buffer) 的坑

1. 现象:Fork 导致的“双倍快乐”

看下面这段诡异的代码:

#include <stdio.h> #include <string.h> #include <unistd.h> int main() { // C库函数 const char *s1 = "hello printf\n"; printf("%s", s1); // 系统调用 const char *s2 = "hello write\n"; write(1, s2, strlen(s2)); // 创建子进程 fork(); return 0; }

实验:

  1. 直接运行(./test):屏幕上打印两行,非常正常。
  2. 重定向运行(./test > log.txt):打开log.txt,你会发现:
  • hello write出现1 次
  • hello printf竟然出现了2 次
2. 原理揭秘:缓冲策略的改变

这跟fork无关,跟C 语言标准库 (FILE) 的缓冲策略有关。

  • C 库缓冲区策略
  • 无缓冲:立刻刷新。
  • 行缓冲 (Line Buffered):遇到\n才刷新。(显示器默认是行缓冲)。
  • 全缓冲 (Full Buffered):缓冲区填满才刷新。(普通文件默认是全缓冲)。

分析案发现场

  1. 直接运行时(向显示器写):
  • printf遇到\n,触发行缓冲,立马把数据刷给内核(write)。
    • fork时,C 库缓冲区是空的。父子进程各自退出,没啥可刷的。
    • write是系统调用,直接写内核。

重定向时(向文件写):

  • printf虽然有\n,但因为目标变成了普通文件,策略变为全缓冲。数据暂存在 C 库的用户级缓冲区里,没有刷给内核。
    • 退出时
    • 写时拷贝:子进程复制了父进程的内存,包括那个没刷新的 C 库缓冲区
    • Fork 发生:父进程创建子进程。
    • write直接写内核,不受影响(先写进去了)。
  • 父进程退出,刷新自己的缓冲区 -> 写入一次 "hello printf"。
    • 子进程退出,刷新自己的缓冲区 ->写入一次 "hello printf"。

结论

  • 库函数(printf,fwrite)自带用户级缓冲区,操作文件时是全缓冲。
  • 系统调用(write)没有用户级缓冲区。
  • fork会拷贝用户级缓冲区的数据。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/11 3:14:21

[SDOI2016] 征途题解

P4072 [SDOI2016] 征途 题目描述 Pine 开始了从 SSS 地到 TTT 地的征途。 从 SSS 地到 TTT 地的路可以划分成 nnn 段&#xff0c;相邻两段路的分界点设有休息站。 Pine 计划用 mmm 天到达 TTT 地。除第 mmm 天外&#xff0c;每一天晚上 Pine 都必须在休息站过夜。所以&…

作者头像 李华
网站建设 2026/2/13 0:06:08

你的测试团队为何倦怠?重塑动机的心理学家方案

当代码遇见人心 在软件测试领域&#xff0c;我们常聚焦于缺陷追踪、用例设计或自动化脚本&#xff0c;却鲜少深入探讨测试活动背后的核心驱动力——人的动机。根据自我决定理论&#xff0c;人类行为受自主性、能力感与归属感三大心理需求影响。对测试工程师而言&#xff0c;动…

作者头像 李华
网站建设 2026/2/8 21:49:55

测试变革的推动:从执行者到价值创造者的演进

在数字化转型加速的今天&#xff0c;软件已渗透至各行各业&#xff0c;从金融交易到医疗健康&#xff0c;从智能家居到自动驾驶&#xff0c;软件的可靠性与安全性直接关系到用户体验乃至生命财产安全。作为软件质量的守护者&#xff0c;测试从业者正面临前所未有的挑战与机遇。…

作者头像 李华
网站建设 2026/2/12 9:39:11

SQL必会必知整理-12-使用子查询

12.1 子查询任何SQL语句都是查询。但此术语一般指SELECT语句。SQL还允许创建子查询&#xff08;subquery&#xff09;&#xff0c;即嵌套在其他查询中的查询。12.2 利用子查询进行过滤SELECT cust_id FROM orders WHERE order_num IN (SELECT order_numFROM orderitemsWHERE pr…

作者头像 李华
网站建设 2026/2/11 17:05:32

SSE换环境导致502问题

华为云 必须加固定请求头 headers.add("Content-Type", "text/event-stream");headers.add("Transfer-Encoding", "chunked");阿里云 // 阿里云不可以加 Transfer-Encoding&#xff0c;不然阿里云原生网关报错 502 // 可能原因 阿里云…

作者头像 李华
网站建设 2026/2/12 2:49:31

同花顺短线大赚副图 源码分享

{}IF(PERIODNAME<>"日线") { 统计:"该指标只在日线周期下有效。"; RETURN; } r:((ZDMR[-1]BDMR[-1])-(ZDMC[-1]BDMC[-1]))/SHGZG*100; 大单净量:r; D3:EMA(EMA(r,30),3)*30,color00ffff; D5:EMA(EMA(D3,5),3),colorff00cc; D10:EMA(EMA(D3,10),3),co…

作者头像 李华