在C语言中实现基础Shell:嵌套命令列表的数据结构选型
用C实现基础Shell:命令列表的最佳表示方案
我正在用C语言实现一个基础Shell,卡在了命令列表的最佳表示方式上——每个条目可能包含多个并行执行的命令。比如解析以下批处理文件:
ls -l directory cat README.md & du -sh directory exit
会生成三层嵌套的逻辑结构:
[ [ ["ls", "-l", "directory"], ], [ ["cat", "README.md"], ["du", "-sh", "directory"], ], [ ["exit"], ], ]
我现在纠结的是exec_many函数的参数数据结构选择:
int exec_many(/* list of list of list of strings */) { ... }
我愿意实现vector的子集,但希望采用更符合C语言风格的方案。
已尝试方案
我想到的方案是定义多层结构体:
struct Command { char **args; int argc; }; struct Commands { struct Command *commands; int count; }; int exec_many(struct Commands *commands, int count) { ... }
但这个方案有两个问题:
- 为每个嵌套层定义结构体显得繁琐冗余
- 构建结构需要预先知晓长度
优化方案
语义化结构体+动态扩展
C语言中,语义清晰的结构体其实是比多级指针更易维护的选择,我们可以通过typedef简化命名,再配合动态扩展的辅助函数解决预先知长的问题:
#include <stdlib.h> #include <string.h> // 单个命令的参数集合:argc为参数数量,args以NULL结尾(适配execvp) typedef struct { char** args; int argc; } CmdArgs; // 一组并行执行的命令:count为并行命令数,cmds是命令数组 typedef struct { CmdArgs* cmds; int count; } ParallelCmds; // exec_many接收串行执行的命令组列表,group_count是串行组的数量 int exec_many(ParallelCmds* serial_groups, int group_count) { // 实现逻辑示例: // 1. 遍历每个串行组 // 2. 对组内每个命令fork子进程并exec执行 // 3. 等待当前组所有子进程结束后,再处理下一个串行组 return 0; }
动态扩展辅助函数
实现类似vector的动态添加功能,这样构建结构时无需预先知道长度:
// 初始化空的CmdArgs CmdArgs cmd_args_init() { CmdArgs args = {NULL, 0}; return args; } // 向CmdArgs添加参数,成功返回0,失败返回-1 int cmd_args_add(CmdArgs* args, const char* arg) { // 预留NULL结尾的位置,所以长度+2 char** new_args = realloc(args->args, sizeof(char*) * (args->argc + 2)); if (!new_args) return -1; args->args = new_args; args->args[args->argc] = strdup(arg); if (!args->args[args->argc]) return -1; args->argc++; args->args[args->argc] = NULL; // 适配execvp的参数要求 return 0; } // 初始化空的ParallelCmds ParallelCmds parallel_cmds_init() { ParallelCmds cmds = {NULL, 0}; return cmds; } // 向ParallelCmds添加一个CmdArgs,成功返回0,失败返回-1 int parallel_cmds_add(ParallelCmds* p_cmds, CmdArgs cmd) { CmdArgs* new_cmds = realloc(p_cmds->cmds, sizeof(CmdArgs) * (p_cmds->count + 1)); if (!new_cmds) return -1; p_cmds->cmds = new_cmds; p_cmds->cmds[p_cmds->count] = cmd; p_cmds->count++; return 0; }
解析时的使用示例
解析批处理文件时,可以这样动态构建结构:
// 示例:构建第二个串行组(cat & du) ParallelCmds serial_group = parallel_cmds_init(); CmdArgs cat_cmd = cmd_args_init(); cmd_args_add(&cat_cmd, "cat"); cmd_args_add(&cat_cmd, "README.md"); parallel_cmds_add(&serial_group, cat_cmd); CmdArgs du_cmd = cmd_args_init(); cmd_args_add(&du_cmd, "du"); cmd_args_add(&du_cmd, "-sh"); cmd_args_add(&du_cmd, "directory"); parallel_cmds_add(&serial_group, du_cmd); // 将该串行组加入到全局的串行组列表中
方案优势
- 语义清晰:每个结构体对应明确的执行单元(单个命令参数、并行命令组、串行命令序列),代码可读性和维护性远高于
char****这类多级指针。 - 动态构建:通过
realloc和辅助函数,解析时可以逐步添加内容,无需预先知晓所有命令的长度。 - 适配系统调用:
CmdArgs的args以NULL结尾,可以直接传给execvp等系统调用,无需额外处理。
内容的提问来源于stack exchange,提问作者Kungfunk
相关产品推荐
相关产品推荐

