Как заставить функцию execvp выполнять каждый аргумент командной строки?

Я пытаюсь написать программу, которая принимает несколько аргументов командной строки из одной и той же строки ввода (например, /bin/uname /bin/date) перед их выполнением и указывает, что процесс успешно завершен. Когда я использую бесконечный цикл, программа отлично печатает каждое выполнение, однако вопрос предусматривает, что родительский процесс должен завершиться после завершения всех дочерних процессов. Попытка сделать это приводит к завершению только одного процесса, несмотря на то, что это цикл for, который должен позволять ему повторяться. Любая помощь, говорящая мне, как я должен это сделать, была бы потрясающей.

Я попытался переместить цикл for в разные места, а также удалить вызовы выхода, чтобы попытаться сделать его успешным.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

#define KGRN "\x1B[32m"
#define RESET "\x1B[0m"

int main(int argc,char* argv[]) 
{ 
    int counter;
    int status;
    int COMMAND_LINE_SIZE = 64;
    char command[COMMAND_LINE_SIZE];

    pid_t pid;

    printf("$ ");
    scanf("%s", command);//read a line from user and store it in array command
    printf("\n");

    for(counter=0;counter<argc;counter++){
        if ((pid = fork()) <  0) {
            perror("forking child process failed\n");
            exit(1); 
        }

        if (pid==0){ // child
            char *argv[] = {command, NULL};
            execvp(argv[counter], argv);
            exit(0);
        }
        //in parent
        pid = wait(&status);
        if(WIFEXITED(status) == 1)
            printf(KGRN "Command %s has completed successfully\n" RESET, command);
        else
            printf("failure\n");
    }
}

Ожидаемый результат:

$ /bin/uname /bin/date /bin/ls
Linux  
Command /bin/uname has completed successfully
$ 
Sun Sep 15 12:24:39 UTC 2019
Command /bin/date has completed successfully
$ 
a.out  new.c  q3.c  trial.c
Command /bin/ls has completed successfully

Текущий выход:

$ /bin/ls /bin/uname

a.out  new.c  q3.c  trial.c
Command /bin/ls has completed successfully

person user433367    schedule 15.09.2019    source источник
comment
Родитель fork()s (потенциально) несколько потомков, но wait()s только один раз. Если вы хотите, чтобы он собирал несколько дочерних элементов, он должен wait() для каждого.   -  person John Bollinger    schedule 15.09.2019
comment
@JohnBollinger означает ли это добавление его в цикл for или это не так? Как я могу гарантировать, что это произойдет?   -  person user433367    schedule 15.09.2019
comment
Кроме того, похоже, что вы execvp(argv[0]), но вы, вероятно, не хотите этого делать. Обычно он запускает новый экземпляр родительского процесса, создавая таким образом облегченную форк-бомбу.   -  person John Bollinger    schedule 15.09.2019
comment
Вам нужно выполнить детское действие в цикле fork (использовать execvp()), а затем подождать в цикле waitpid(), пока все дети не умрут.   -  person Jonathan Leffler    schedule 15.09.2019
comment
Если родитель wait()s находится в том же цикле, в котором он fork()s, то вы будете собирать каждый дочерний элемент, прежде чем разветвлять следующий. Это нормально, если это то, чего ты хочешь. Однако если вы хотите предоставить дочерним элементам возможность одновременного запуска, вместо этого вы должны использовать второй, отдельный цикл для сбора дочерних элементов.   -  person John Bollinger    schedule 15.09.2019
comment
Почему вы читаете со стандартного ввода? Вы должны выполнять команды в аргументах.   -  person Jonathan Leffler    schedule 15.09.2019
comment
@JohnBollinger, не могли бы вы привести примеры двух циклов, допускающих параллелизм, это моя конечная цель.   -  person user433367    schedule 15.09.2019
comment
Почему вы заменяете argv новой переменной и используете argc, зная, что вторая argv содержит только элемент 1 помимо NULL, но counter может быть больше, потому что она принимает значение argc. Это может вызвать неопределенное поведение.   -  person XBlueCode    schedule 15.09.2019
comment
@XBlueCode меня это немного смущает, не могли бы вы предложить что-то, что я могу сделать, чтобы у меня не было проблем   -  person user433367    schedule 15.09.2019


Ответы (1)


Вы рядом, но есть ряд проблем, многие из них указали в комментариях к вопросу:

  • Вы повторно выполняете argv[0] — программу — что не предвещает ничего плохого.
  • Обычно вы должны выполнять дочернее действие в цикле, который разветвляет дочерние элементы.
  • Если вы хотите, чтобы дочерние элементы выполнялись одновременно, вам нужен вызов wait() вне цикла, который их разветвляет.
  • Вы читаете команду из стандартного ввода, хотя в спецификации указано, что нужно выполнять аргументы командной строки.
  • Вы используете данные, считанные из стандартного ввода, в качестве имени команды для выполняемого процесса, а не в качестве аргумента командной строки.
  • У вас есть локальная переменная argv, скрывающая (скрывающая) аргумент argv для main(). Таким образом, код в execvp() обращается за пределами массива argv после того, как counter достигает 2, и вы используете нулевой указатель, когда counter равен 1.
  • Вы не должны сообщать об успехе (exit(0);) после того, как дочерний элемент не смог выполниться.
  • Я думаю, вы должны сообщать о том, какой процесс не удалось выполнить — конечно, по стандартной ошибке — когда дочерний процесс не выполняется.

Между прочим, все ваши примеры командных строк используют полный путь к команде, что делает ненужным использование execvp(). Вы вполне можете ввести только имя команды, как в командной строке, а затем execvp() найдет команду и запустит ее (см. мой пример выполнения).

Асинхронное выполнение

Исправление их и добавление нескольких незначительных настроек, а также асинхронный запуск аргументов командной строки (возможно, одновременное выполнение многих команд) дает такой код:

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    for (int counter = 1; counter < argc; counter++)
    {
        pid_t pid = fork();
        if (pid <  0)
        {
            perror("forking child process failed\n");
            exit(EXIT_FAILURE);
        }
        else if (pid == 0) // child
        {
            char *args[] = { argv[counter], NULL };
            execvp(args[0], args);
            fprintf(stderr, "%s: failed to execute %s: (%d) %s\n",
                    argv[0], argv[counter], errno, strerror(errno));
            exit(EXIT_FAILURE);
        }
    }

    int corpse;
    int status;
    int failed = 0;
    while ((corpse = waitpid(0, &status, 0)) > 0)
    {
        if (WIFEXITED(status))
        {
            printf("Process %d exited with status %d (0x%.4X)\n",
                   corpse, WEXITSTATUS(status), status);
            if (WEXITSTATUS(status) != 0)
                failed++;
        }
        else if (WIFSIGNALED(status))
        {
            printf("Process %d was signalled %d (0x%.4X)\n",
                   corpse, WTERMSIG(status), status);
            failed++;
        }
    }
    return((failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE);
}

Переменная failed отслеживает, не выполнилась ли какая-либо из команд, и процесс сообщает об успехе (завершается с 0 или EXIT_SUCCESS), только если каждая из команд выполнена успешно; в противном случае он завершается с индикацией ошибки (выходит с EXIT_FAILURE, что обычно равно 1). При запуске на моем Mac (как args41, созданном из args41.c) он произвел:

$ args41 uname date ls
Sun Sep 15 07:26:58 MDT 2019
Darwin
Process 1907 exited with status 0 (0x0000)
Process 1908 exited with status 0 (0x0000)
20000-leagues-under-the-sea.txt bin                             maxargs.sh
LICENSE.md                      conn11                          overlap.data
Metriseis_2012.dat              conn11.c                        overlap47.c
README.md                       conn11.dSYM                     overlap61.c
Safe                            conn13                          overlap73
Untracked                       conn13.c                        overlap73.c
acr.list                        conn13.dSYM                     overlap73.dSYM
acronym29.c                     conn17                          packages
acronym43                       conn17.c                        pseudo-json.md
acronym43.c                     conn17.dSYM                     question.md
acronym43.dSYM                  conn29                          sll43.c
acronym47                       conn29.c                        so-0167-2112
acronym47.c                     conn29.dSYM                     so-0167-2112.c
acronym47.dSYM                  doc                             so-0167-2112.dSYM
acronym53                       dr41                            so-1043-1305
acronym53.c                     dr41.c                          so-4921-8019
acronym53.dSYM                  dr41.dSYM                       so-4970-8730
acronym59                       dw41                            so-4971-1989
acronym59.c                     dw41.c                          so-4985-0919
acronym59.dSYM                  dw41.dSYM                       so-5102-0102
argc37                          etc                             so-5134-1743
argc37.c                        gccw67                          so-5225-1783
argc37.dSYM                     gccw67.c                        so-5279-4924
args41                          gccw67.dSYM                     so-5358-5962
args41.c                        gccw67.o                        so-5394-5215
args41.dSYM                     get.jl.activity                 so-5416-6308
argv89                          inc                             so-5424-4465.md
argv89.c                        lib                             src
argv89.dSYM                     makefile
Process 1909 exited with status 0 (0x0000)
$ args41 many things to do
args41: failed to execute many: (2) No such file or directory
args41: failed to execute things: (2) No such file or directory
args41: failed to execute to: (2) No such file or directory
args41: failed to execute do: (2) No such file or directory
Process 1939 exited with status 1 (0x0100)
Process 1941 exited with status 1 (0x0100)
Process 1940 exited with status 1 (0x0100)
Process 1942 exited with status 1 (0x0100)
$

Очевидно, что если вы хотите, чтобы процессы выполнялись синхронно, вы должны поместить цикл waitpid() внутри цикла for. Вы по-прежнему должны использовать цикл, потому что процесс может наследовать потомков, которых он не разветвлял, при неясных обстоятельствах. В этом случае вы можете предпочесть использовать waitpid(pid, &status, 0); тогда вам не нужен цикл вокруг waitpid().

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

Синхронное выполнение

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    int failed = 0;

    for (int counter = 1; counter < argc; counter++)
    {
        pid_t pid = fork();
        if (pid <  0)
        {
            perror("forking child process failed\n");
            exit(EXIT_FAILURE);
        }
        else if (pid == 0) // child
        {
            char *args[] = { argv[counter], NULL };
            execvp(args[0], args);
            fprintf(stderr, "%s: failed to execute %s: (%d) %s\n",
                    argv[0], argv[counter], errno, strerror(errno));
            exit(EXIT_FAILURE);
        }
        else
        {
            int status;
            if (waitpid(pid, &status, 0) < 0)
            {
                fprintf(stderr, "failed to wait for %s process %d: (%d) %s\n",
                        argv[counter], (int)pid, errno, strerror(errno));
                failed++;
            }
            else if (WIFEXITED(status))
            {
                printf("Process %s exited with status %d (0x%.4X)\n",
                        argv[counter], WEXITSTATUS(status), status);
                if (WEXITSTATUS(status) != 0)
                    failed++;
            }
            else if (WIFSIGNALED(status))
            {
                printf("Process %s was signalled %d (0x%.4X)\n",
                        argv[counter], WTERMSIG(status), status);
                failed++;
            }
            else
            {
                printf("Process %s died unexpectedly (0x%.4X)\n",
                        argv[counter], status);
                failed++;
            }
        }
    }

    return((failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE);
}

Пример вывода (args89 создан из args89.c):

$ args89 date uname ls
Sun Sep 15 08:34:00 MDT 2019
Process date exited with status 0 (0x0000)
Darwin
Process uname exited with status 0 (0x0000)
20000-leagues-under-the-sea.txt argv89.c                        makefile
LICENSE.md                      argv89.dSYM                     maxargs.sh
Metriseis_2012.dat              bin                             overlap.data
README.md                       conn11                          overlap47.c
Safe                            conn11.c                        overlap61.c
Untracked                       conn11.dSYM                     overlap73
acr.list                        conn13                          overlap73.c
acronym29.c                     conn13.c                        overlap73.dSYM
acronym43                       conn13.dSYM                     packages
acronym43.c                     conn17                          pseudo-json.md
acronym43.dSYM                  conn17.c                        question.md
acronym47                       conn17.dSYM                     sll43.c
acronym47.c                     conn29                          so-0167-2112
acronym47.dSYM                  conn29.c                        so-0167-2112.c
acronym53                       conn29.dSYM                     so-0167-2112.dSYM
acronym53.c                     doc                             so-1043-1305
acronym53.dSYM                  dr41                            so-4921-8019
acronym59                       dr41.c                          so-4970-8730
acronym59.c                     dr41.dSYM                       so-4971-1989
acronym59.dSYM                  dw41                            so-4985-0919
argc37                          dw41.c                          so-5102-0102
argc37.c                        dw41.dSYM                       so-5134-1743
argc37.dSYM                     etc                             so-5225-1783
args41                          gccw67                          so-5279-4924
args41.c                        gccw67.c                        so-5358-5962
args41.dSYM                     gccw67.dSYM                     so-5394-5215
args89                          gccw67.o                        so-5416-6308
args89.c                        get.jl.activity                 so-5424-4465.md
args89.dSYM                     inc                             src
argv89                          lib
Process ls exited with status 0 (0x0000)
$

Асинхронное выполнение с отслеживанием имени процесса

Этот вариант запускает команды асинхронно, но также отслеживает, какой процесс принадлежит каждому PID. Обратите внимание, что он использует функцию (шок, ужас) для определения аргумента, соответствующего только что умершему PID. Массив настроен так, чтобы было удобно отслеживать числа — элемент 0 массива PID не используется.

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

static int find_pid(int pid, int num_pids, pid_t pids[num_pids])
{
    for (int i = 1; i < num_pids; i++)
    {
        if (pids[i] == pid)
            return i;
    }
    return -1;
}

int main(int argc, char *argv[])
{
    pid_t pids[argc];
    for (int counter = 1; counter < argc; counter++)
    {
        pid_t pid = fork();
        if (pid <  0)
        {
            perror("forking child process failed\n");
            exit(EXIT_FAILURE);
        }
        else if (pid == 0) // child
        {
            char *args[] = { argv[counter], NULL };
            execvp(args[0], args);
            fprintf(stderr, "%s: failed to execute %s: (%d) %s\n",
                    argv[0], argv[counter], errno, strerror(errno));
            exit(EXIT_FAILURE);
        }
        else
            pids[counter] = pid;
    }

    int corpse;
    int status;
    int failed = 0;
    while ((corpse = waitpid(0, &status, 0)) > 0)
    {
        int index = find_pid(corpse, argc, pids);
        if (index < 0)
        {
            fprintf(stderr, "Unrecognized PID %d exited with status 0x%.4X\n",
                    corpse, status);
            failed++;
        }
        else if (WIFEXITED(status))
        {
            printf("Process %s (PID %d) exited with status %d (0x%.4X)\n",
                   argv[index], corpse, WEXITSTATUS(status), status);
            if (WEXITSTATUS(status) != 0)
                failed++;
        }
        else if (WIFSIGNALED(status))
        {
            printf("Process %s (PID %d) was signalled %d (0x%.4X)\n",
                   argv[index], corpse, WTERMSIG(status), status);
            failed++;
        }
        else
        {
            printf("Process %s (PID %d) died from indeterminate causes (0x%.4X)\n",
                   argv[index], corpse, status);
            failed++;
        }
    }
    return((failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE);
}

Пример запуска (args79 создан из args79.c):

$ args79 uname date pwd
/Users/jleffler/soq
Process pwd (PID 2105) exited with status 0 (0x0000)
Darwin
Sun Sep 15 09:04:37 MDT 2019
Process uname (PID 2103) exited with status 0 (0x0000)
Process date (PID 2104) exited with status 0 (0x0000)
$
person Jonathan Leffler    schedule 15.09.2019
comment
Привет, Джонатон, спасибо, что прояснил мне это! Я только что получил еще одну вещь в отношении достижения параллелизма? Я поместил цикл while в цикл for, и он позволил работать только одному аргументу командной строки? - person user433367; 15.09.2019
comment
Посмотрите мою последнюю версию — у вас есть как асинхронный код (с ожиданием, выполняемым вне цикла, запускающего дочерние процессы), так и синхронный код (с ожиданием, выполняемым внутри цикла, запускающего дочерние процессы). Асинхронный код обеспечивает параллелизм; синхронный код не работает. Я не уверен, что это то, что вы видели до того, как напечатали свой комментарий — я предполагаю, что вы не видели обновление, поскольку оно, похоже, охватывает оба случая, которые могут вам понадобиться. - person Jonathan Leffler; 15.09.2019
comment
чтобы сказать что-то вроде /bin/ls завершено успешно, мне не нужно было бы добавлять обратно массив команд, который я использовал в своей предыдущей сборке, или есть какой-то вызов, который я могу использовать для печати имени аргумента? - person user433367; 15.09.2019
comment
См. пример args79 в моем обновленном ответе. Любые дальнейшие варианты вы можете изучить самостоятельно или в новом вопросе. - person Jonathan Leffler; 15.09.2019