标准输入流,输出流,错误流 以及 重定向 的原理
·
标准输入流、输出流、错误流在操作系统与C语言中的表达
1. 操作系统层面(Linux/Unix)
在操作系统层面,标准输入、标准输出和标准错误流通过文件描述符(File Descriptor) 来标识:
| 流类型 | 文件描述符 (fd) | 默认设备 |
|---|---|---|
| 标准输入 | 0 | 通常是终端/键盘 |
| 标准输出 | 1 | 通常是终端/屏幕 |
| 标准错误 | 2 | 通常是终端/屏幕 |
这些文件描述符本质上是进程打开的文件句柄,可以指向终端设备、普通文件或管道。
2. C语言层面
在C语言中,标准库 <stdio.h> 提供了三个预定义的 FILE* 指针:
#include <stdio.h>
// 三种标准流指针
stdin // 标准输入流
stdout // 标准输出流
stderr // 标准错误流
对应关系
| C语言流 | 文件描述符 | 说明 |
|---|---|---|
| stdin | 0 | fgets()、scanf() 从此读取 |
| stdout | 1 | printf()、puts() 往此写入(有缓冲) |
| stderr | 2 | fprintf(stderr, …) 往此写入(无缓冲) |
示例代码
#include <stdio.h>
#include <unistd.h> // for close()
int main() {
// C语言方式使用标准流
fprintf(stdout, "正常输出\n");
fprintf(stderr, "错误信息\n");
// 获取底层文件描述符
int fd_in = fileno(stdin); // 0
int fd_out = fileno(stdout); // 1
int fd_err = fileno(stderr); // 2
// 直接使用系统调用写入
write(fd_out, "hello\n", 6);
return 0;
}
3. stdout 与 stderr 的关键区别
| 特性 | stdout | stderr |
|---|---|---|
| 缓冲 | 有缓冲(行缓冲或全缓冲) | 无缓冲(立即写出) |
| 用途 | 程序正常输出 | 错误/诊断信息 |
| 重定向 | 可单独重定向 | 可单独重定向 |
重定向示例
# 只重定向 stdout 到文件
./prog > output.txt
# 只重定向 stderr 到文件
./prog 2> error.log
# 将 stdout 和 stderr 都重定向到同一文件
./prog > all.txt 2>&1
4. 重定向的实现原理
./prog > output.txt 这个重定向是如何实现的?
这是通过 shell 在调用程序前修改文件描述符 实现的。核心机制如下:
>是 shell 的语法,不是程序本身的行为- shell 会:
- 打开目标文件
- 把文件的文件描述符设为 1(标准输出)
- 再执行你的程序
- 程序看到的 fd 1 已经不是终端,而是文件了
用系统调用还原 ./prog > output.txt
// shell 实际做的事(简化版)
pid_t pid = fork();
if (pid == 0) {
// 子进程:执行重定向
int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
dup2(fd, 1); // 让 1 指向 fd
close(fd);
// 执行目标程序
execvp("./prog", argv);
}
dup2(fd, 1) 的作用是:让文件描述符 1 和 fd 指向同一个文件。之后程序里所有的 printf()、write(1, ...) 都写到文件里了。
验证实验
// test.c
#include <stdio.h>
#include <unistd.h>
int main() {
printf("hello\n"); // 走 stdout (fd 1)
write(1, "world\n", 6); // 直接写 fd 1
return 0;
}
$ gcc test.c -o test
$ ./test # 输出到屏幕
$ ./test > out.txt # 输出到文件
$ cat out.txt # 输出:hello\nworld\n
程序代码完全没变,变的只是 shell 在 exec 前把 fd 1 换成了文件。
5. 重定向的恢复问题
dup2(fd, 1) 后,何时重新把 1 指回标准输出流?
不会自动指回来——重定向的影响会持续到这个进程结束。
dup2(fd, 1) 之后,这个进程的 fd 1 就永远指向文件了,直到:
- 进程退出(所有文件描述符自动关闭)
- 进程自己再用
dup2()改回去
shell 为什么能"恢复"?
因为 shell 是在子进程中做重定向:
+------------------+
| 父 shell 进程 |
| fd 1 = 终端 | ← 从来不会被改变
+------------------+
|
fork()
|
v
+------------------+
| 子进程 |
| fd 1 = 终端 | ← 刚 fork 时继承自父进程
| dup2(fd, 1) |
| fd 1 = 文件 | ← 只有子进程被改了
| exec("./prog") |
+------------------+
|
退出
v
子进程销毁,fd 全部关闭
父 shell 的 fd 1 一直是终端,不受影响。
在程序内部恢复的方法
如果想在程序内部恢复,可以在重定向前先保存原来的 fd 1:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main() {
// 保存原始标准输出
int saved_stdout = dup(1); // 复制 fd 1,得到比如 fd 3
// 重定向到文件
int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
dup2(fd, 1);
close(fd);
// ... 现在 printf 都写到文件 ...
printf("写入文件\n");
// 恢复标准输出
dup2(saved_stdout, 1); // 把 fd 1 改回原来的终端
close(saved_stdout);
// ... 现在 printf 又写到屏幕了 ...
printf("回到屏幕\n");
return 0;
}
6. 总结
| 场景 | 何时恢复 |
|---|---|
shell 重定向 (./prog > out.txt) |
子进程退出,父进程从未受影响 |
程序内自己 dup2 重定向 |
不会自动恢复,需要手动 dup2(saved_fd, 1) |
通过理解文件描述符、标准流和重定向机制,可以更好地掌握Linux/Unix系统编程和C语言中I/O操作的本质。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)