代码之家  ›  专栏  ›  技术社区  ›  Totem

C: 接收异常额外操作数的Shell程序

  •  3
  • Totem  · 技术社区  · 7 年前

    我正在用C创建自己的shell fork execvp 。我正在使用解析cmd及其参数 strtok 。打印解析后的标记向我证实,我确实收到了所有参数,而且shell通常都能工作,尽管它是非常基本的,因为我是一个十足的noob。然而,当我运行shell时,有两个典型的场景让我感到困惑,请注意 ls pwd 命令第一次工作,但随后开始从某处获取这些额外的操作数:

    ➜  a2 ./shell
    (user)># pwd
    
    /home/user/ClionProjects/unix_programming/a2
    (user)># ls -la
    
    total 32
    drwxrwxr-x 2 user user  4096 Mar  9 13:18 .
    drwxrwxr-x 9 user user  4096 Mar  6 14:18 ..
    -rwxrwxr-x 1 user user 13616 Mar  9 13:18 shell
    -rw-rw-r-- 1 user user  3809 Mar  9 13:17 shell.c
    -rw-rw-r-- 1 user user   545 Mar  9 12:58 shell.h
    (user)># pwd
    
    pwd: ignoring non-option arguments
    /home/user/ClionProjects/unix_programming/a2
    (user)># 
    

    以及

    a2/壳
    (用户)>#pwd公司
    
    /主页/用户/客户端项目/unix\u编程/a2
    (用户)>#ls-la
    
    总计32
    drwxrwxr-x 2用户用户4096 3月9日13:18。
    drwxrwxr-x 9用户用户4096 3月6日14:18。。
    -RWXR-x 1用户用户13616 Mar 9 13:18 shell
    -rw-rw-r—1用户用户3809 Mar 9 13:17 shell。c
    -rw-rw-r--1个用户用户545 Mar 9 12:58 shell。h类
    (用户)>#pwd公司
    
    pwd:忽略非选项参数
    /主页/用户/客户端项目/unix\u编程/a2
    (用户)>#
    

    在下面的代码中,您可以看到我将参数存储在 tokens 。第一个标记是cmd文件名本身。

    我试过了 memset ting公司 代币 全部为零, free ing it和re- malloc 在while循环的每次迭代开始时对其进行初始化。我一直在尝试调整大小 代币 到其中当前的参数数。我做这些事是因为我相信, 代币 正在保留以前命令中由于某种原因被以后的命令读取的内容。然而,re- 马洛克 ing和 realloc ing公司 代币 没有触及这个问题,所以我现在不知所措。任何朝着正确方向提出的建议或建议都将不胜感激。

    壳h类

    #ifndef UNIX_PROGRAMMING_SHELL_H
    #define UNIX_PROGRAMMING_SHELL_H
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <errno.h>
    #include <limits.h>
    #include <signal.h>
    
    int sig_int = 0;
    
    void signal_handler(int sig_num);
    void start_shell();
    void prompt();
    void change_dir(char *path, pid_t *pid, int *status);
    void execute_cmd(const char *file_name, char *const *args);
    void parse_cmd(char *cmd, char **tokens, size_t *index);
    
    #endif //UNIX_PROGRAMMING_SHELL_H
    

    壳c

    #include "shell.h"
    
    
    void signal_handler(int sig_num) {
        printf("\n"); // Do nothing when Ctrl-C is pressed
    }
    
    void prompt() {
        /* Get current users username and display prompt */
        char *user = getlogin();
        printf("(%s)># ", (user==NULL)? "": user);
    }
    
    void change_dir(char *path, pid_t *pid, int *status) {
        *pid = fork();
    
        if (*pid == -1) {
            printf("Error changing directory..\n");
        } else if (*pid == 0) {
            chdir(path);
        } else {
            *pid = wait(status);
    
            if (*pid == -1) {
                printf("%s\n", strerror(errno));
                return;
            }
        }
    }
    
    void execute_cmd(const char *file_name, char *const *args) {
        execvp(file_name, args);
    }
    
    /** parse commands into tokens... **/
    void parse_cmd(char *cmd, char **tokens, size_t *index) {
        char *tok;
        const char *delim = " ";
        *index = 0;
    
        // TODO: realloc tokens, so it can start from 2 and build up as needed
        tok = strtok(cmd, delim);
        if (tok != NULL) {
            tokens[*index] = tok;
            (*index)++;
        } else {
            tokens[*index] = "\0";
            return;
        }
    
        while ((tok = strtok(NULL, delim)) != NULL) {
            tokens[*index] = tok;
            (*index)++;
        }
    //    for (size_t i = 0; i < *index; i++) {
    //        printf("arg[%zu]: %s\n", i, tokens[i]);
    //    }
        printf("\n");
    }
    
    void start_shell() {
        ssize_t c;
        size_t cmd_size = 20;
        size_t num_args = 5;
        int *status = NULL;
        pid_t pid;
    
        char *cmd = (char *) malloc(sizeof(char));                    /* command line input */
        char **tokens = (char **) malloc(sizeof(char *) * num_args);  /* command line input parsed into tokens */
        size_t *index = (size_t *) malloc(sizeof(size_t));            /* number of tokens parsed */
    
        if (tokens == NULL) {
            printf("Error: Out of memory..");
            exit(EXIT_FAILURE);
        }
    
        prompt();
    
        /* main loop - get input, parse, process - until termination */
        while ( (c = getline(&cmd, &cmd_size, stdin)) != EOF ) {
    
            cmd[strcspn(cmd, "\n")] = '\0';  /* trim newline */
            parse_cmd(cmd, tokens, index);
    
            /* resize tokens to fit only it's current contents */
    //        if (*index < num_args && *index != 0 && *index != 1) {
            tokens = realloc(tokens, *index);
    //        }
    
            const char *file_name = tokens[0];
            char *const *args = tokens;
    
            /* If command is blank */
            if ( (c = strcspn(file_name, "\n\r\0") == 0) ) {
                tokens[0] = "\0";
                prompt();
                continue;
    
            } else if ( (c = strcmp(file_name, "exit")) == 0 ) {
                break;
    
            } else if ( (c = strcmp(file_name, "cd")) == 0 ) {
    
                if (*index == 1) { /* no path provided */
                    chdir(getenv("HOME"));
                } else {
                    char *path = realpath(args[1], NULL);
                    if (path == NULL) {
                        printf("%s\n", strerror(errno));
                        break;
                    } else {
                        change_dir(path, &pid, status);
                    }
                }
            }
    
            // fork here ... success: parent < pid .. child << 0 -- failure: parent << -1
            pid = fork();
    
            if (pid == -1) {
                printf("Error executing command\n");
                continue;
            } else if (pid == 0) {
                execute_cmd(file_name, args);
            } else {
    
                pid = wait(status);
                if (pid == -1) {
                    printf("%s\n", strerror(errno));
                    exit(EXIT_FAILURE);
                }
            }
    
            tokens[0] = "\0";
            prompt();
        }
    
        free(cmd);
        free(tokens);
        free(index);
        printf("\n"); /* avoids unwanted terminal output for ctrl-D */
    }
    
    int main(void) {
        signal(SIGINT, signal_handler);
        start_shell();
        return 0;
    }
    
    1 回复  |  直到 7 年前
        1
  •  2
  •   Jean-François Fabre    7 年前

    execvp 需要参数列表 NULL 已终止(否则无法告诉参数计数)

    您没有添加 空值 当你建立你的命令时。这是未定义的行为,额外的一个或两个ghost args被传递给 执行副总裁 (直到最终找到 空值 或者只是个好老头 分段故障 )

    因此:

    char **tokens = (char **) malloc(sizeof(char *) * num_args);  /* command line input parsed into tokens */
    

    应该是

    char **tokens = malloc(sizeof(char *) * (num_args + 1));  /* command line input parsed into tokens */
    

    并且在 parse_cmd 添加 空值 项目:

    while ((tok = strtok(NULL, delim)) != NULL) {
        tokens[*index] = tok;
        (*index)++;
    }  // this is your code, now
    tokens[*index] = NULL;
    (*index)++; // maybe not necessary
    

    现在 执行副总裁 通过了a 空值 终止的字符串。