Search code examples
cforkexeccommand-line-argumentsargv

How to execute multiple commands from argv with an specific delimiter


Recently I had an assignment where I had to make a program that takes from command line two different commands, separated with a '+', for example:

ps -lu myUsername + ls -la

The objective of the program was to run any two orders simultaneously, with any number of parameters per order, by using fork() and exec(). This was my solution to this problem:

Note: the original problem was meant to be run on a machine with Solaris 5.10 and c89 or c99 standard (gcc 3.4.3)

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <strings.h>

int main (int argc, char* argv[]) {
    char delimiter = '+';
    char* auxp; //auxiliar pointer
    int i = 1;
    int position;
    
    while(i < argc){
        if (strcmp("+", argv[i]) == 0) {
            argv[i] = NULL;
            position = i;
        }
        i++;
    }

    if (fork() == 0) {
        execvp(argv[1], &argv[1]);
        exit(1);
    }

    if (fork() == 0) {
        execvp(argv[position+1], &argv[position+1]);
        exit(1);
    }

    wait(NULL);
    wait(NULL);
    exit(0);
}

This was enough for the assignment but I wanted to make it work with N arguments instead of only 2. I can't reach a systematic way to find the all the addresses. Any help is appreciated, thanks in advice.


Solution

  • For the general case of N commands, need to keep track of where each command starts and ends, and fork a new child whenever the end of a command is found. That could be at a + separator argument, or at the end of the original argument list.

    #include <sys/types.h>
    #include <sys/wait.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main (int argc, char* argv[]) {
        /*
         * Note: argc could be in range 0 to INT_MAX.
         * Using unsigned int values to avoid arithmetic overflow.
         */
        unsigned int start = 1;
        unsigned int end = 1;
        unsigned int child_count = 0;
    
        /* Note: argv[argc] is a terminating null pointer. */
        while(end <= (unsigned int)argc){
            if(end == (unsigned int)argc || strcmp("+", argv[end]) == 0){
                /* Reached the terminating null pointer or a command separator. */
                argv[end] = NULL;
                if(start != end){
                    /*
                     * Command is not empty.
                     * Fork a child process to execute the command.
                     */
                    pid_t child = fork();
                    if(child > 0){
                        /* Parent forked child successfully. */
                        child_count++;
                    }else if(child == 0){
                        /* This is the child process. Execute command. */
                        execvp(argv[start], &argv[start]);
                        exit(1);
                    }
                }
                /* Next command starts after this one. */
                start = end + 1;
            }
            /* Looking for terminating null pointer or command separator. */
            end++;
        }
    
        /* Wait for the child processes to terminate. */
        while(child_count){
            wait(NULL);
            child_count--;
        }
        exit(0);
    }
    

    Note: the argv[end] = NULL; line could be moved into the if(child == 0){ } block (but before the call to execvp) to leave the parent's original argument list intact.