You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

寻求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;
}

问题分析与解决方案

你的自定义分割函数存在几个关键问题:

  1. substr函数错误:循环条件i <= end会读取到str[end](当end设为字符串长度时,str[end]是'\0'),且malloc的大小应为end - begin + 1(需要额外空间存储终止符),否则会导致内存越界或字符串截断。
  2. 引号处理不完整:仅处理了单引号(ASCII 39),未处理双引号,且未考虑引号未闭合的情况。
  3. 分隔符查找逻辑混乱:delim_pos的判断条件存在逻辑漏洞,会导致错误识别分隔符位置。
  4. 空字符串未正确过滤:管道前后的空格会被分割成空字符串,后续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);

额外优化

  1. 原failed_allocation函数的拼写错误:Faild改为Failed。
  2. 增加内存泄漏检查:在start_proc中,子进程退出前需free(list),父进程也需正确释放所有动态分配的内存。

内容的提问来源于stack exchange,提问作者Domantas Motiejunas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 10:50:37