C++实现链式管道命令时dup2重定向STDIN_FILENO失败求助
链式Shell管道命令阻塞问题修复
问题场景
实现Shell风格的链式管道命令(如ls | cat | wc),要求子进程通过管道传递执行结果,重定向stdin/stdout后调用execvp执行命令。但测试ls | cat时程序阻塞,cat未读取管道输入,反而等待用户手动输入,dup2重定向未生效。
原错误代码
#include <iostream> #include <vector> #include <string> #include <unistd.h> #include <sys/wait.h> #include <cstring> #include <cerrno> #include <boost/algorithm/string.hpp> using namespace std; #define BUF_SIZ 1024 int main() { string s; cout << "$ "; while(getline(std::cin, s)) { if(s == "exit") { return EXIT_SUCCESS; } // initialize a pipe int fd[2]; if(pipe(fd) == -1) { perror("pipe creation failed!"); return EXIT_FAILURE; } // split the command into multiple parts vector<string> tokens; boost::algorithm::split(tokens, s, boost::is_any_of("|"),boost::token_compress_on); for(auto& command: tokens) { // prepare to run the current command // get the current command boost::algorithm::trim(command); // split the command into an array of args vector<string> args; boost::algorithm::split(args,command,boost::is_any_of(" "),boost::token_compress_on); int argc = args.size(); if(argc < 1) { cerr << "We need a command!" << endl; return EXIT_FAILURE; } // run the current command pid_t child = fork(); if(child == 0) { // setup the file name and input arguments const char* filename = args[0].c_str(); char** argv = new char*[argc + 1]; for(int i = 0; i < argc; i++) { string args_str = args[i]; argv[i] = new char[10]; strcpy(argv[i],args_str.c_str()); } argv[argc] = nullptr; // write the pipe value into stdin dup2(fd[0], STDIN_FILENO); close(fd[0]); // write stdout to the pipe dup2(fd[1], STDOUT_FILENO); close(fd[1]); // use execvp() to run the commmand execvp(filename,argv); // exec didn't work, so an error must have been occurred cerr << strerror(errno) << endl; delete[] argv; return EXIT_FAILURE; } // wait for the child process to complete int status; waitpid(child,&status,0); } // read out the pipe char* buffer = new char[BUF_SIZ]; int count = read(fd[0],buffer,BUF_SIZ); if(count > 0) { fprintf(stdout, "%s", buffer); } delete buffer; close(fd[0]); close(fd[1]); cout << "$ "; } return 0; }
问题根源分析
- 单管道复用错误:所有命令共用同一个管道,导致
ls的输出写入管道后,自己又尝试从同一个管道读,后续cat也无法正确获取前序命令的输出,数据流完全混乱。 - 进程等待时机错误:每次循环都立即
waitpid等待当前子进程结束,此时管道的写端未关闭,后续进程读管道时会一直阻塞等待EOF。 - 管道FD管理混乱:父进程未及时关闭不需要的管道端,导致管道引用计数不为0,无法触发EOF信号。
- 参数内存分配错误:固定分配10字节存储参数,长参数会被截断,导致
execvp执行失败。
修复后的代码
#include <iostream> #include <vector> #include <string> #include <unistd.h> #include <sys/wait.h> #include <cstring> #include <cerrno> #include <boost/algorithm/string.hpp> using namespace std; #define BUF_SIZ 1024 int main() { string s; cout << "$ "; while(getline(cin, s)) { if(s == "exit") { return EXIT_SUCCESS; } vector<string> tokens; boost::algorithm::split(tokens, s, boost::is_any_of("|"), boost::token_compress_on); int prev_in = STDIN_FILENO; // 初始输入为标准输入 for(size_t i = 0; i < tokens.size(); i++) { string& command = tokens[i]; boost::algorithm::trim(command); vector<string> args; boost::algorithm::split(args, command, boost::is_any_of(" "), boost::token_compress_on); int argc = args.size(); if(argc < 1) { cerr << "Invalid command!" << endl; break; } int fd[2]; if(pipe(fd) == -1) { perror("pipe creation failed"); return EXIT_FAILURE; } pid_t child = fork(); if(child == 0) { // 重定向输入:如果不是第一个命令,用前一个管道的读端 if(prev_in != STDIN_FILENO) { dup2(prev_in, STDIN_FILENO); close(prev_in); } // 重定向输出:如果不是最后一个命令,用当前管道的写端 if(i != tokens.size() - 1) { dup2(fd[1], STDOUT_FILENO); close(fd[1]); } // 关闭当前管道的读端(子进程不需要) close(fd[0]); // 构建argv数组,按实际参数长度分配内存 char** argv = new char*[argc + 1]; for(int j = 0; j < argc; j++) { const char* arg_str = args[j].c_str(); argv[j] = new char[strlen(arg_str) + 1]; strcpy(argv[j], arg_str); } argv[argc] = nullptr; execvp(argv[0], argv); // exec失败才会走到这里 cerr << "Exec failed: " << strerror(errno) << endl; for(int j = 0; j < argc; j++) { delete[] argv[j]; } delete[] argv; _exit(EXIT_FAILURE); } // 父进程关闭不需要的管道端 close(fd[1]); if(prev_in != STDIN_FILENO) { close(prev_in); } // 保存当前管道的读端,作为下一个命令的输入 prev_in = fd[0]; } // 读取最后一个命令的输出 char buffer[BUF_SIZ]; ssize_t count; while((count = read(prev_in, buffer, BUF_SIZ)) > 0) { write(STDOUT_FILENO, buffer, count); } close(prev_in); // 等待所有子进程结束 while(wait(nullptr) > 0); cout << "\n$ "; } return 0; }
修复要点说明
- 管道链式传递:为每两个命令之间创建新管道,用
prev_in保存前一个管道的读端,作为下一个命令的输入。 - 正确重定向:第一个命令输入用标准输入,最后一个命令输出用标准输出,中间命令才重定向到管道。
- 及时关闭FD:父进程和子进程都关闭不需要的管道端,确保管道能正确触发EOF。
- 参数内存修复:按参数实际长度分配内存,避免截断。
- 延迟进程等待:所有子进程启动后再统一等待,避免阻塞数据流传递。
内容的提问来源于stack exchange,提问作者Gwen
相关产品推荐
相关产品推荐

