C: программа Shell получает аномальные дополнительные операнды

Я создаю свою собственную оболочку на C, используя fork и execvp. Я разбираю cmd и его аргументы, используя strtok. Печать проанализированных токенов подтверждает мне, что я действительно получаю все аргументы, и оболочка в целом работает, хотя это, конечно, очень просто, потому что я довольно новичок. Однако вот два типичных сценария, когда я запускаю оболочку, которые сбивают меня с толку, обратите внимание, как команды 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 ./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.

Я пробовал memset, устанавливая tokens на все нули, free вставляя его и повторно устанавливая malloc в начале каждой итерации цикла while. Я старался всегда изменять размер tokens в соответствии с количеством аргументов, находящихся в нем в данный момент. Я сделал это, потому что убедился, что tokens каким-то образом сохраняет контент из предыдущих команд, который по какой-то причине читается более поздними командами. Однако повторное преобразование malloc и 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] = "\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;
}

TL; DR, но вы должны NULL завершить списки аргументов, переданные в execvp, вероятно, это проблема.

Jean-François Fabre 09.03.2018 14:34

@ Jean-FrançoisFabre Спасибо, я проверю это

Totem 09.03.2018 14:38
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
2
119
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

execvp требует, чтобы этот список аргументов был завершен NULL (иначе он не может указать количество аргументов)

Вы не добавляете NULL при построении своей команды. Итак, это поведение undefined, и в 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.

Спасибо! Это исправило это.

Totem 09.03.2018 14:42

Другие вопросы по теме