寻求C Shell解释器strtok替代方案:正确分割含管道与引号的命令
问题
我需要实现一个支持多|(管道)运算符的C Shell解释器,类似cat test.txt | sort | uniq -c | sort -nr的简单管道命令可正常运行,但遇到含引号参数的复杂命令(如cat test.txt | awk '/" 404 / {print$7}' | sort | uniq -c | sort -nr | head)时,原使用strtok的分割逻辑会失效。
我自行编写了字符串分割函数,但运行时会随机生成空字符串,导致execvp执行错误。以下是可正常运行的基础代码及尝试的自定义分割代码,请求帮助实现能正确处理此类场景的字符串分割函数:
可正常运行的基础代码
#define TRUE 1 #define FALSE 0 #define BUF_SIZE 1024 #define ROW_SIZE 64 #define MIN 100000 void failed_allocation(){ fprintf(stderr, "Faild to allocate memory."); exit(EXIT_FAILURE); } char* read_line(){ int buf_size = BUF_SIZE; int pos = 0; char* buffer = malloc(sizeof(char) * buf_size); int c; // using int because EOF is -1 if(buffer == NULL) failed_allocation(); // read char by char while(TRUE){ c = getchar(); // look for EOF or end of line if(c == EOF || c == '\n'){ buffer[pos] = '\0'; return buffer; } else{ buffer[pos] = c; } pos++; // if buffer max size is reached, then extend buffer if(pos >= buf_size){ buf_size += BUF_SIZE; buffer = realloc(buffer, buf_size); if(buffer == NULL) failed_allocation(); } } } char** split_to_lines(char* str, char* delim){ int buf_size = ROW_SIZE; int pos = 0; char* buffer; char** buffer_list = malloc(buf_size * sizeof(char*)); if(buffer_list == NULL) failed_allocation(); // split into list buffer = strtok(str, delim); while(buffer != NULL){ buffer_list[pos] = buffer; pos++; // if buffer max size is reached, then extend buffer if(pos >= buf_size){ buf_size += ROW_SIZE; buffer_list = realloc(buffer_list, buf_size * sizeof(char*)); if(buffer_list != NULL) failed_allocation(); } buffer = strtok(NULL, delim); // continue reading str } buffer_list[pos] = NULL; // end list return buffer_list; } int start_proc(char** args){ int fd[2]; int prev_fd = STDIN_FILENO; int i; char** list = NULL; for(i = 0; args[i + 1] != NULL; ++i){ if(pipe(fd) == -1){ perror("Pipe error: "); return 1; } int pid = fork(); if(pid < 0){ perror("Fork error:"); return 1; } else if(pid == 0){ if(prev_fd != STDIN_FILENO){ dup2(prev_fd, STDIN_FILENO); close(prev_fd); } dup2(fd[1], STDOUT_FILENO); close(fd[1]); list = split_to_lines(args[i], " \t\r\n"); execvp(list[0], list); perror("Execvp error:"); exit(EXIT_FAILURE); } close(prev_fd); close(fd[1]); prev_fd = fd[0]; free(list); } if(prev_fd != STDIN_FILENO){ dup2(prev_fd, STDIN_FILENO); close(prev_fd); } list = split_to_lines(args[i], " \t\r\n"); execvp(list[0], list); perror("Execvp error:"); } int main(){ int flag = TRUE; while(flag == TRUE){ // input printf("\n> "); char* input = read_line(); char** list = NULL; // exit condition if(strcmp(input, "exit") == 0) flag = FALSE; if(flag == TRUE){ list = split_to_lines(input, "|"); start_proc(list); } // free memory free(input); free(list); } return 0; }
尝试的自定义分割代码
int get_length(char* str){ int counter = 0; for(int i = 0; str[i] != '\0'; ++i){ ++counter; } return counter; } int find_in_string(char* str, char look_for, int from){ int length = get_length(str); if(from > length) return -1; for(int i = from; i < length; ++i){ if(str[i] == look_for){ return i; } } return -1; } char* substr(char* str, int begin, int end){ int length = get_length(str); if(end > length || begin > length){ fprintf(stderr, "Substr error: invalid interval values."); exit(EXIT_FAILURE); } if(end < 0) end = length; else if(begin < 0) begin = 0; char* buffer = malloc((end - begin) * sizeof(char)); int pos = 0; for(int i = begin; i <= end; ++i){ buffer[pos] = str[i]; pos++; } buffer[pos] = '\0'; return buffer; } char** test_split_to_lines(char* str, char* delim){ char* buffer; char** buffer_list = malloc(ROW_SIZE * sizeof(char*)); int pos = 0; int cursor_pos = 0; int cursor_delim = 0; int length = get_length(str); loop: int delim_pos = MIN; for(int i = 0; delim[i] != '\0'; ++i){ int temp = find_in_string(str, delim[i], cursor_delim); if((temp < delim_pos && temp > 0) || (temp < 0 && delim_pos == MIN) || (delim_pos < 0 && temp > -1)) delim_pos = temp; } if(delim_pos == -1){ buffer = substr(str, cursor_pos, -1); if(get_length(buffer) != 0){ buffer_list[pos] = buffer; pos++; } buffer_list[pos] = NULL; return buffer_list; } int q_begin = find_in_string(str, 39, cursor_pos); int q_end = find_in_string(str, 39, q_begin + 1); if(delim_pos < q_begin || delim_pos > q_end){ buffer = substr(str, cursor_pos, delim_pos - 1); buffer_list[pos] = buffer; pos++; cursor_pos = delim_pos + 1; cursor_delim = cursor_pos; } else{ cursor_delim = q_end; } goto loop; }
问题分析与解决方案
你的自定义分割函数存在几个关键问题:
substr函数错误:循环条件i <= end会读取到str[end](当end设为字符串长度时,str[end]是'\0'),且malloc的大小应为end - begin + 1(需要额外空间存储终止符),否则会导致内存越界或字符串截断。- 引号处理不完整:仅处理了单引号(ASCII 39),未处理双引号,且未考虑引号未闭合的情况。
- 分隔符查找逻辑混乱:
delim_pos的判断条件存在逻辑漏洞,会导致错误识别分隔符位置。 - 空字符串未正确过滤:管道前后的空格会被分割成空字符串,后续
execvp调用时会因为第一个参数为空而报错。
下面是修复后的分割函数,分为两个部分:先按管道符分割命令(忽略引号内的|),再对每个命令按空格分割参数(忽略引号内的空格):
修复后的分割函数
#include <ctype.h> #include <string.h> // 修剪字符串前后的空白字符 char* trim_whitespace(char* str) { while(isspace((unsigned char)*str)) str++; if(*str == 0) return str; char* end = str + strlen(str) - 1; while(end > str && isspace((unsigned char)*end)) end--; end[1] = '\0'; return str; } // 按分隔符分割字符串,忽略引号内的分隔符 char** split_with_quotes(char* str, char delim) { int buf_size = ROW_SIZE; int pos = 0; char** result = malloc(buf_size * sizeof(char*)); if(!result) failed_allocation(); int len = strlen(str); int start = 0; int in_single_quote = 0; int in_double_quote = 0; for(int i = 0; i <= len; i++) { // 遇到分隔符且不在引号内,或到达字符串末尾 if((i == len || str[i] == delim) && !in_single_quote && !in_double_quote) { // 提取子串并修剪空白 char* token = malloc(i - start + 1); if(!token) failed_allocation(); strncpy(token, str + start, i - start); token[i - start] = '\0'; trim_whitespace(token); // 非空的token才加入结果 if(strlen(token) > 0) { result[pos++] = token; if(pos >= buf_size) { buf_size += ROW_SIZE; result = realloc(result, buf_size * sizeof(char*)); if(!result) failed_allocation(); } } else { free(token); } start = i + 1; } else if(str[i] == '\'' && !in_double_quote) { in_single_quote = !in_single_quote; } else if(str[i] == '"' && !in_single_quote) { in_double_quote = !in_double_quote; } } result[pos] = NULL; return result; } // 替代原split_to_lines:先按管道分割命令 char** split_pipes(char* str) { return split_with_quotes(str, '|'); } // 分割单个命令的参数 char** split_args(char* cmd) { return split_with_quotes(cmd, ' '); }
替换原代码中的调用
在start_proc函数中,将:
list = split_to_lines(args[i], " \t\r\n");
替换为:
list = split_args(args[i]);
在main函数中,将:
list = split_to_lines(input, "|");
替换为:
list = split_pipes(input);
额外优化
- 原
failed_allocation函数的拼写错误:Faild改为Failed。 - 增加内存泄漏检查:在
start_proc中,子进程退出前需free(list),父进程也需正确释放所有动态分配的内存。
内容的提问来源于stack exchange,提问作者Domantas Motiejunas
相关产品推荐
相关产品推荐

