简体   繁体   中英

How can I make the execvp function execute each command line argument?

I'm trying to write a program that takes multiple command line arguments from the same line of input (eg /bin/uname /bin/date) before executing them and indicating that the process has completed successfully. When I use an infinite loop the program prints each execution out perfectly, however the question stipulates that the parent process must terminate after all of the children terminate. Attempting to make this happen leads to only one process completing, despite the fact that their is a for loop that should allow it to repeat. Any help telling me how I should go about it would be awesome.

I have tried moving the for loop to various different locations, as well as remove exit calls to try and make it successfully loop

#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");
    }
}

Expected output:

$ /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

Current output:

$ /bin/ls /bin/uname

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

You're close, but there are a number of problems, many of them pointed out in the comments to the question:

  • You re-execute argv[0] — the program — which bodes ill.
  • You should normally execute the child's action in the loop that forks the children.
  • If you want the children executing concurrently, you need the wait() call outside the loop that forks them.
  • You read a command from standard input even though the specification says to execute the command line arguments.
  • You use the data that was read from standard input as the command name for the executed process, rather than the command line argument.
  • You have a local variable argv shadowing (hiding) the argv argument to main() . Thus, the code in the execvp() is accessing out of bounds of the argv array after counter reaches 2, and you use a null pointer when counter is 1.
  • You should not report success ( exit(0); ) after a child fails to execute.
  • I think you should report on which process failed to execute — to standard error, of course — when the child fails to execute.

Incidentally, your sample command lines all use the full path to the command, making the use of execvp() unnecessary. You could perfectly well type just the name of the command, like you would at the shell prompt, and then execvp() would find the command and run it (see my example runs).

Asynchronous execution

Fixing those and adding a few minor tweaks, and running the command line arguments asynchronously (possibly many commands running at the same time) yields code like this:

#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);
}

The failed variable tracks whether any of the commands failed, and the process only reports success (exits with 0 or EXIT_SUCCESS) if every one of the commands succeeded; otherwise, it exits with a failure indication (exits with EXIT_FAILURE, which is usually 1). When run on my Mac (as args41 created from args41.c ), it produced:

$ 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)
$

Clearly, if you want the processes run synchronously, you should place the waitpid() loop inside the for loop. You should still use a loop because a process can inherit children it didn't fork in obscure circumstances. You might prefer to use waitpid(pid, &status, 0) in this case; then you don't need the loop around waitpid() .

If you want to track process names and yet run the processes asynchronously, then the parent process woul need to keep a record of the PID of each child in an array as they're forked off, and the reporting loop would search through the list of known children to report which one died.

Synchronous execution

#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);
}

Example output ( args89 created from 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)
$

Asynchronous execution with process name tracking

This variant runs the commands asynchronously but also tracks which process belonged to each PID. Note that it uses a function (shock, horror) to determine the argument that corresponds to the PID that just died. The array is set up so that it is easy to track numbers — the element 0 of the array of PIDs is not used.

#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);
}

Example run ( args79 created from 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)
$

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM