自定义多管道Shell故障求助:execute_pipeline函数疑似异常
自定义Shell多管道功能故障排查
问题现象
使用C语言开发支持多管道的自定义Shell,单命令(如ls -l)可正常执行,但带管道的命令无法运行。执行cat check.txt | sort | uniq > cmp_sort_uniq.txt时出现以下错误:
sort: read failed: -: Bad file descriptor uniq: '>': No such file or directory
怀疑问题出在execute_pipeline函数中,可能是管道未及时关闭或文件描述符传递错误。
相关代码
execute_pipeline函数
void execute_pipeline(char *args[MAX_ARGS], int n_args) { char *pipe_args[MAX_PIPES][MAX_ARGS]; int pipe_fds[MAX_PIPES][2]; int n_pipes = 0, n_comm = 0; memset(pipe_args, 0, sizeof(pipe_args)); for (int i = 0; i < n_args; i++) { if (strcmp(args[i], "|") == 0) { if (n_pipes >= MAX_PIPES) { fprintf(stderr, "Error: too many pipes\n"); return; } n_pipes++; n_comm = 0; } else { pipe_args[n_pipes][n_comm % MAX_ARGS] = args[i]; pipe_args[n_pipes][n_comm % MAX_ARGS + 1] = NULL; n_comm++; } } memset(pipe_fds,0,sizeof(pipe_fds)); // Create pipes for (int i = 0; i <= n_pipes; i++) { if (pipe(pipe_fds[i]) == -1) { perror("pipe"); return; } // Determine inputfds int input_fd = (i == 0)? STDIN_FILENO : pipe_fds[i-1][1]; // Execute command execute_command(pipe_args[i], input_fd, pipe_fds[i][1]); // Close pipe fds if not needed if (i>0) { close(pipe_fds[i-1][0]); close(pipe_fds[i-1][1]); } } }
execute_command函数
void execute_command(char *args[MAX_ARGS], int input_fd, int output_fd) { // Fork to execute command pid_t pid = fork(); if (pid == 0) { // Child process // Set signal handler for SIGINT (Ctrl+C) signal(SIGINT, SIG_DFL); // Redirect input/output if necessary if (input_fd != STDIN_FILENO) { if (dup2(input_fd, STDIN_FILENO) < 0) { perror("dup2"); exit(1); } close(input_fd); } if (output_fd != STDOUT_FILENO) { if (dup2(output_fd, STDOUT_FILENO) < 0) { perror("dup2"); exit(1); } close(output_fd); } // Execute command execvp(args[0], args); perror("execvp"); exit(1); } else if (pid < 0) { perror("fork"); exit(1); } // Wait for child process to complete int status; wait(&status); }
问题分析与修复方案
1. 核心错误点
- 管道文件描述符误用:管道的读端是
pipe_fds[i][0],写端是pipe_fds[i][1],原代码将前一个管道的写端作为下一个命令的输入,完全颠倒了读写方向。 - 多余管道创建:
n_pipes个管道对应n_pipes+1个命令,原代码循环创建n_pipes+1个管道,最后一个管道无意义,导致最后一个命令输出被写入无效管道。 - 重定向未解析:
>符号被当作uniq的参数传递,说明Shell未在管道拆分前处理重定向逻辑。 - 管道关闭时机不当:父进程过早关闭管道描述符,可能导致子进程未完成读写就报错。
2. 修复后的execute_pipeline函数
void execute_pipeline(char *args[MAX_ARGS], int n_args) { char *pipe_args[MAX_PIPES][MAX_ARGS]; int pipe_fds[MAX_PIPES][2]; int n_pipes = 0, n_comm = 0; int output_fd = STDOUT_FILENO; // 提前处理输出重定向(简单实现,仅支持最后一个>) for (int i = 0; i < n_args; i++) { if (strcmp(args[i], ">") == 0) { if (i + 1 >= n_args) { fprintf(stderr, "Error: missing filename after >\n"); return; } int fd = open(args[i+1], O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd == -1) { perror("open"); return; } output_fd = fd; args[i] = NULL; n_args = i; break; } } // 拆分管道命令 memset(pipe_args, 0, sizeof(pipe_args)); for (int i = 0; i < n_args; i++) { if (strcmp(args[i], "|") == 0) { if (n_pipes >= MAX_PIPES) { fprintf(stderr, "Error: too many pipes\n"); return; } n_pipes++; n_comm = 0; } else { pipe_args[n_pipes][n_comm] = args[i]; pipe_args[n_pipes][n_comm + 1] = NULL; n_comm++; } } // 创建所有需要的管道 for (int i = 0; i < n_pipes; i++) { if (pipe(pipe_fds[i]) == -1) { perror("pipe"); // 清理已创建的管道 for (int j = 0; j < i; j++) { close(pipe_fds[j][0]); close(pipe_fds[j][1]); } return; } } // 启动所有命令进程 for (int i = 0; i <= n_pipes; i++) { int input_fd = (i == 0) ? STDIN_FILENO : pipe_fds[i-1][0]; int cmd_output_fd = (i == n_pipes) ? output_fd : pipe_fds[i][1]; execute_command(pipe_args[i], input_fd, cmd_output_fd); // 父进程关闭当前不需要的管道描述符 if (i > 0) { close(pipe_fds[i-1][0]); close(pipe_fds[i-1][1]); } if (i == n_pipes && output_fd != STDOUT_FILENO) { close(output_fd); } } }
3. 额外优化建议
- 并行执行管道命令:原
execute_command中的wait会导致命令串行执行,可改为在execute_pipeline最后用waitpid循环等待所有子进程,提升管道效率。 - 完善重定向处理:增加对
<、>>等重定向符号的支持,在命令解析阶段完成重定向逻辑,避免参数污染。
内容的提问来源于stack exchange,提问作者Achiya Ben Natan
相关产品推荐
相关产品推荐

