如何在C程序中实现与grep进程的输入输出交互?
问题分析与解决
核心问题:管道缓冲与未关闭的文件描述符
你的代码中grep没有输出的原因有两个关键因素:
1. grep的全缓冲策略
当程序的标准输出不是终端时(比如管道),grep会采用全缓冲模式:只有当输出缓冲被填满、程序主动调用fflush,或者程序退出时,才会将缓冲内容写入管道。
- 你的代码中,
grep匹配到的内容只有7字节(apples\n),远小于默认的4KB缓冲大小,所以不会主动刷新。 - 但
grep本应在读完输入(父进程关闭main2grep.write后,grep的stdin收到EOF)后退出,退出时自动刷新缓冲。问题出在第二个因素:
2. 未关闭的管道写端文件描述符
父进程创建grep2main管道后,一直保留着grep2main.write的打开状态。而管道的规则是:只有当所有写端文件描述符都关闭时,读端的read才会返回EOF。
- 即使
grep退出并刷新了缓冲内容到管道,父进程的read在读完数据后会一直阻塞(因为写端还有父进程自己的grep2main.write打开着)。同时子进程在execvp前未清理多余的管道fd,进一步干扰了管道的正常状态。
代码修复方案
修改两处即可解决问题:
- 子进程中关闭多余的管道文件描述符:在
execvp前,关闭不需要的管道读/写端,避免残留的fd影响管道状态。 - 父进程关闭
grep2main.write:父进程只需要从该管道读,不需要写,关闭写端让管道状态正确。
修复后的代码:
#include <stdio.h> #include <unistd.h> #include <fcntl.h> struct Pipe { int read; int write; }; struct Pipe main2grep; struct Pipe grep2main; int main() { pipe((int*) &main2grep); pipe((int*) &grep2main); char *arguments[4]; arguments[0] = "grep"; arguments[1] = "apples"; arguments[2] = 0; int result = fork(); if (result == 0) { dup2(main2grep.read, STDIN_FILENO); dup2(grep2main.write, STDOUT_FILENO); // 关闭多余的管道fd close(main2grep.read); close(main2grep.write); close(grep2main.read); close(grep2main.write); execvp(arguments[0], arguments); return 0; } // 父进程关闭不需要的管道fd close(main2grep.read); close(grep2main.write); char *input = "apples\n"; int write_result = write(main2grep.write, input, 7); printf("wrote %d bytes to %s\n", write_result, arguments[0]); close(main2grep.write); char buffer[128]; while (1) { int n_read = read(grep2main.read, buffer, 128); if (n_read == -1) { perror("read error"); break; } else if (n_read == 0) { // 所有写端关闭,读到EOF,退出循环 printf("grep exited, no more data\n"); break; } buffer[n_read] = 0; printf("from grep: %s\n", buffer); } return 0; }
关于命令行echo hello | grep hello | cat的疑问
grep并不会误以为cat是终端。grep通过isatty(STDOUT_FILENO)可以明确检测到stdout是管道(不是终端),所以依然用全缓冲。
- 命令行中
echo输出一行数据后就退出,grep读完stdin的EOF后处理匹配行,随后立即退出。程序退出时会强制刷新所有标准IO缓冲,所以grep的输出会被写入管道,cat读到后输出到终端,因此你能看到结果。
内容的提问来源于stack exchange,提问作者Jack M
相关产品推荐
相关产品推荐

