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

C: Shell program receiving anomolous extra operands

我正在使用 forkexecvp 在 C 中创建自己的 shell。我正在使用 strtok 解析 cmd 及其参数。打印已解析的标记向我确认我确实收到了所有参数,并且 shell 通常有效,尽管它当然是非常基本的,因为我是一个菜鸟。然而,这里有两个典型的场景,当我 运行 和 shell 时,这让我感到困惑,请注意 lspwd 命令是如何第一次工作的,但随后开始得到这些来自某处的额外操作数:

➜  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)># 

AND

➜  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)># 

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

我已经尝试 memsettokens 设置为全零,free 设置它并在 while 循环的每次迭代开始时重新设置它 malloc .我一直尝试将 tokens 的大小调整为当前其中的 args 数量。我做了这些事情是因为我确信 tokens 以某种方式保留了先前命令的内容,这些内容出于某种原因被以后的命令读取。但是重新mallocing和reallocing tokens都没有触及问题,所以我现在很茫然。非常感谢任何正确方向的建议或推动。

shell.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

shell.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] = "[=14=]";
        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")] = '[=14=]';  /* 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[=14=]") == 0) ) {
            tokens[0] = "[=14=]";
            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] = "[=14=]";
        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;
}

execvp 需要参数列表被 NULL 终止(否则它不能告诉参数计数)

您在构建命令时没有添加 NULL。所以这是未定义的行为,一个或两个额外的幽​​灵参数被传递给 execvp (直到它最终找到一个 NULL 或者只是一个很好的旧 segfault

所以:

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 中添加 NULL 项:

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

现在 execvp 传递了一个 NULL 终止字符串。