Почему команда wc в execvp() возвращает неправильные результаты при вводе данных из канала в моей оболочке?

#c #unix #pipe #fork

#c #unix #канал #fork

Вопрос:

Я работаю над написанием оболочки на C для целей обучения, и я пытаюсь разрешить переменное количество каналов. В целом, похоже, это отлично работает. Но я заметил проблему с wc командой.

Когда я передаю какой-либо вывод другой программы в wc like ls | wc , он всегда возвращает
1 3 35 независимо от того, что я передаю в него. Другие команды работают так, как ожидалось, когда я подключаюсь к ним. В моей обычной zsh оболочке wc работает нормально. Я изо всех сил пытаюсь найти проблему. Я пробовал добавлять waitpid после вилок, но без кубиков.

Вот основной цикл оболочки в main функции:

 while (1) {
        printf("33[31;1mshell:33[0m ");

        line = read_cmds();

        if (strstr(line, "|")) {
            // check for pipes first
            pipe_exec(line);
        } else {
            // we have a single command
            tokens = split(line, " trn");
            if (*tokens != NULL) shell_exec(tokens);
            free(tokens);
        }
    }
 

Вот функция, которая перебирает команды:

 void pipe_exec(char *line)
{
    int in, status;
    int pipe_no; // keep track of ptr to bring it back to free 
    int pfd[2];
    pid_t rc;
    char **cmd, **pipe_cmds;
    
    // split takes a string and splits into array of strings based on delimiter
    pipe_cmds = split(line, "|"); 

    in = 0;
    pipe_no = 0;
    while (*pipe_cmds) {
        cmd = split(*pipe_cmds, " trn");

        if (pipe(pfd) < 0) perror("pipe");

        make_proc(in, pfd[1], cmd);
        close(pfd[1]);
        in = pfd[0];
        pipe_cmds  ; // move ptr ahead one
        pipe_no  ;
    }
    // move pointer back and free
    pipe_cmds -= pipe_no;
    free(pipe_cmds);

    rc = fork();
    if (rc == 0) {
        if (in != 0) dup2(in, STDIN_FILENO);
        execvp(*cmd, cmd);
    } 
}
 

А затем make_proc функция, которую вызывает вышеупомянутая функция:

 void make_proc(int in, int out, char **cmd)
{
    pid_t rc;
    rc = fork();
    if (rc  == 0) {
        if (in != STDIN_FILENO) {
            dup2(in, STDIN_FILENO);
            close(in);
        }
        if (out != STDOUT_FILENO) {
            dup2(out, STDOUT_FILENO);
            close(out);
        }
        execvp(*cmd, cmd);
    } 
}
 

Я убрал часть проверки ошибок, чтобы сэкономить место здесь.
Любая помощь приветствуется!

Комментарии:

1. Попробуйте использовать ls | cat , чтобы увидеть, какие входные данные получает второй процесс.

2. @barmar ls | cat , похоже, работает правильно. Он возвращает все 7 элементов в каталоге. Я пытался ls | cat | wc , но безрезультатно. Я продолжу работать над этим. Спасибо.

3. Я не могу придумать ни одной причины wc , по которой это работало бы по-другому.

4. Попробуйте использовать /usr/bin/wc

5. Я предлагаю использовать трассировку системных вызовов в wc процессе, чтобы увидеть, что он на самом деле читает.

Ответ №1:

Вы выполняете последнюю команду дважды и передаете ее первый экземпляр второму. Добавление чего-то вроде:

     while (*pipe_cmds) { 
        cmd = split(*pipe_cmds, " trn");
        if (!pipe_cmds[1]) {
            break;
        }

        if (pipe(pfd) < 0) perror("pipe");

        make_proc(in, pfd[1], cmd);
        close(pfd[1]);
        in = pfd[0];
        pipe_cmds  ; // move ptr ahead one
        pipe_no  ;
    }
 

предотвратило бы ненужный экземпляр, хотя я бы предпочел немного переработать эту функцию.

Комментарии:

1. Да, это делает это! Спасибо. Да, я согласен. Теперь, когда я знаю, что происходит, я проведу рефакторинг.