如何通过传入的argv参数实现多命令管道(替代硬编码)
多命令管道从硬编码改为通过argv参数执行的实现方法
问题描述
我已经实现了多命令管道功能,但目前命令是硬编码形式。现希望改为通过传入的argv参数执行命令,参数以逗号分隔各命令(示例传入参数:echo "test" , wc , cowsay)。尝试过execve系列其他函数但无进展,考虑将argv整理为指针数组并在每个命令末尾添加NULL后遍历执行,寻求可行实现方法。
当前硬编码实现代码
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/wait.h> void loop_pipe(char ***cmd); int main(int argc, char *argv[]) { char *echo[] = {"echo", "test", NULL}; char *rev[] = {"rev", NULL}; char *wc[] = {"wc", NULL}; char **cmd[] = {echo, wc, rev, NULL}; loop_pipe(cmd); return (0); } void loop_pipe(char ***cmd) { int p[2]; pid_t pid; int fd_in = 0; while (*cmd != NULL) { pipe(p); if ((pid = fork()) == -1) { exit(EXIT_FAILURE); } else if (pid == 0) { dup2(fd_in, 0); // 继承上一个命令的输出作为输入 if (*(cmd + 1) != NULL) dup2(p[1], 1); // 非最后命令时将输出写入管道 close(p[0]); execvp((*cmd)[0], *cmd); exit(EXIT_FAILURE); } else { wait(NULL); close(p[1]); fd_in = p[0]; // 保存管道读端作为下一个命令的输入 cmd++; } } }
解决方案
核心思路是解析argv参数,将逗号分隔的命令拆分为独立的参数列表,构建成和硬编码格式一致的char ***cmd结构,再传入原有的loop_pipe函数执行。具体实现步骤如下:
1. 解析argv参数
- 遍历
argv[1]到argv[argc-1],收集当前命令的参数; - 遇到参数为
","时,结束当前命令收集,在参数列表末尾添加NULL,然后开始下一个命令; - 最后一个命令收集完成后,同样添加
NULL,并在整个命令数组末尾添加NULL。
2. 动态内存分配
由于命令数量和每个命令的参数数量不确定,使用malloc/realloc动态分配内存,避免栈溢出。
3. 内存释放
使用完动态构建的命令数组后,逐层释放内存,避免内存泄漏。
完整实现代码
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/wait.h> #include <string.h> void loop_pipe(char ***cmd); char ***parse_args(int argc, char *argv[]); void free_cmd_array(char ***cmd); int main(int argc, char *argv[]) { if (argc < 2) { fprintf(stderr, "Usage: %s <command1> , <command2> , ...\n", argv[0]); exit(EXIT_FAILURE); } char ***cmd = parse_args(argc, argv); if (!cmd) { fprintf(stderr, "Failed to parse arguments\n"); exit(EXIT_FAILURE); } loop_pipe(cmd); free_cmd_array(cmd); return 0; } char ***parse_args(int argc, char *argv[]) { char ***cmd_array = NULL; char **current_cmd = NULL; int cmd_count = 0; int arg_count = 0; for (int i = 1; i < argc; i++) { // 遇到逗号,结束当前命令 if (strcmp(argv[i], ",") == 0) { if (current_cmd == NULL) { fprintf(stderr, "Invalid argument: consecutive commas\n"); free_cmd_array(cmd_array); return NULL; } // 当前命令末尾加NULL current_cmd = realloc(current_cmd, sizeof(char *) * (arg_count + 1)); current_cmd[arg_count] = NULL; // 将当前命令加入命令数组 cmd_array = realloc(cmd_array, sizeof(char **) * (cmd_count + 1)); cmd_array[cmd_count] = current_cmd; cmd_count++; // 重置当前命令的参数计数器 current_cmd = NULL; arg_count = 0; } else { // 收集当前命令的参数 current_cmd = realloc(current_cmd, sizeof(char *) * (arg_count + 1)); current_cmd[arg_count] = argv[i]; arg_count++; } } // 处理最后一个命令 if (current_cmd != NULL) { current_cmd = realloc(current_cmd, sizeof(char *) * (arg_count + 1)); current_cmd[arg_count] = NULL; cmd_array = realloc(cmd_array, sizeof(char **) * (cmd_count + 1)); cmd_array[cmd_count] = current_cmd; cmd_count++; } // 命令数组末尾加NULL cmd_array = realloc(cmd_array, sizeof(char **) * (cmd_count + 1)); cmd_array[cmd_count] = NULL; return cmd_array; } void free_cmd_array(char ***cmd) { if (!cmd) return; for (int i = 0; cmd[i] != NULL; i++) { free(cmd[i]); } free(cmd); } void loop_pipe(char ***cmd) { int p[2]; pid_t pid; int fd_in = 0; while (*cmd != NULL) { if (pipe(p) == -1) { perror("pipe failed"); exit(EXIT_FAILURE); } if ((pid = fork()) == -1) { perror("fork failed"); exit(EXIT_FAILURE); } else if (pid == 0) { dup2(fd_in, 0); if (*(cmd + 1) != NULL) dup2(p[1], 1); // 关闭管道无用端 close(p[0]); close(p[1]); execvp((*cmd)[0], *cmd); perror("execvp failed"); exit(EXIT_FAILURE); } else { wait(NULL); close(p[1]); if (fd_in != 0) { close(fd_in); // 关闭上一个管道读端,避免资源泄漏 } fd_in = p[0]; cmd++; } } close(fd_in); // 关闭最后剩余的管道读端 }
说明
- 编译运行:编译后执行
./pipe_demo echo "hello world" , wc -l , cowsay即可实现多命令管道; - 输入校验:处理了连续逗号、无命令等非法输入情况;
- 内存管理:新增
free_cmd_array函数释放动态分配的内存,避免泄漏; - 函数优化:对原
loop_pipe函数补充了文件描述符关闭逻辑,减少资源泄漏风险。
内容的提问来源于stack exchange,提问作者LIsa
相关产品推荐
相关产品推荐

