POSIX C中如何将stdout重定向至stdin?
首先,咱们先拆解你这段代码挂起的原因,再聊聊能不能不用管道实现这种stdout到stdin的闭环重定向。
一、代码的核心问题:管道规则与stdio缓冲坑
你的代码里有两个关键问题导致程序挂起:
1. 管道写端未完全关闭,读操作会无限阻塞
管道的核心规则是:只要还有任意一个进程打开着管道的写端,读操作就会一直等待新数据,直到所有写端都被关闭。你用dup2(pipe_desc[1], 1)把stdout绑到了管道写端,但原来的pipe_desc[1]文件描述符还处于打开状态——相当于现在有两个写端(pipe_desc[1]和重定向后的stdout)。哪怕数据真的进了管道,scanf读完后也会因为写端没关而继续等待,更别说你还没把数据真正写入管道了。
2. printf的行缓冲导致数据没进入管道
默认情况下,stdout是行缓冲模式:只有遇到换行符\n、缓冲区满,或者手动调用fflush时,才会把用户空间的缓冲区数据写入内核的管道。你写的printf("foo")没有换行,所以数据一直留在用户态缓冲区里,根本没进管道,scanf自然读不到任何东西,直接阻塞。
二、代码实际执行的完整流程
咱们一步步理清楚发生了什么:
- 创建管道
pipe_desc,此时pipe_desc[0]是读端,pipe_desc[1]是写端。 dup2(pipe_desc[0], 0):把stdin(文件描述符0)替换成管道读端,原来的stdin被关闭。dup2(pipe_desc[1], 1):把stdout(文件描述符1)替换成管道写端,原来的stdout被关闭。- 调用
printf("foo"):数据被存在stdout的用户态缓冲区,没有真正写入管道。 - 调用
scanf("%s", bar):尝试从管道读端读取数据,但管道是空的,且写端还开着,所以scanf一直阻塞等待数据。
三、修复后的代码示例
要让这段代码正常运行,需要解决上面两个问题:
#include <stdio.h> #include <unistd.h> int main() { int pipe_desc[2]; pipe(pipe_desc); dup2(pipe_desc[0], 0); dup2(pipe_desc[1], 1); // 关闭原来的管道描述符,只保留重定向后的stdin/stdout close(pipe_desc[0]); close(pipe_desc[1]); printf("foo\n"); // 加换行触发行缓冲自动刷新 // 或者用fflush(stdout); 替代换行,手动强制刷新缓冲 char bar[20]; scanf("%s", bar); fprintf(stderr, "%s\n", bar); // 加换行让输出更清晰 return 0; }
修改后,printf的内容会进入管道,关闭写端后,scanf读完数据就会知道没有更多输入,不会继续阻塞。
四、能不能不用管道实现这种重定向?
答案是不能,至少在常规进程环境下做不到。
你最初尝试的dup2(0, 1),只是把stdout指向了stdin对应的文件(比如终端),但终端的输入和输出是完全独立的数据流:printf写的是终端的输出设备,scanf读的是终端的输入设备,两者没有缓冲区共享。换句话说,你用printf输出的内容不会出现在终端的输入缓冲区里,scanf还是会等待用户手动输入。
要让一个进程的输出成为自己的输入,必须借助中间的缓冲/存储结构——管道是最轻便的选择,其他比如临时文件、socketpair也可以,但本质都是类似的辅助结构。因为进程的stdin和stdout默认是两个独立的文件描述符,没有内置的闭环通路。
内容的提问来源于stack exchange,提问作者Joald

