rybur
rybur

Reputation: 51

C executing a command with another commands output with execvp

I'm fine executing commands like "ls" and stuff like that but I want to do something like "ls | sort" but the execvp system call doesn't support "|". How can I do this using only system calls? when I try something like

char *arg[] = {"ls","|","sort",NULL};
execvp(arg[0],arg);

it doesn't work, how can I do this?

Edit:

char* execString (char string[]){
int link[2];
pipe(link);

if (fork() == 0){
    int i = 0;
    char *p = strtok(string," ");
    char *x[spacecount(string)+2];

    while(p){
        x[i++] = p;
        p = strtok(NULL," ");
    }
    x[i] = NULL;

    dup2(link[1],1);
    close(link[0]);
    close(link[0]);

    execvp(x[0],x);
    _exit(0);
} else {
    wait(NULL);
    close(link[1]);
    char buf[512];
    int i = 0;

    while (read(link[0],&buf[i++],1) == 1);

    close(link[0]);
    buf[i-2] = '\0';

    return strdup(buf); 
    }
}

This is the function i'm executing to exec a string that contains a command, its return value is a pointer to a string that contains the output from that command, how can I use that output as the input to a new command using execvp or another function from the exec family?

Edit2: So I made a new function that receives two strings as argument and execs the first one then the second one using as input the output from the first exec, I thought it was working fine it worked with ls | head -1 and other variations of ls but when I do something like ls | sort -R it doesn't work, i've tried several things and I can't understand why this is happening, here is the code:

char* execStrings (char previousstring[], char string[]){
int link[2];
pipe(link);

if (fork() == 0){
    int i = 0;
    char *previouscommand[spacecount(previousstring)+2];
    char *temp = strtok(previousstring," ");
    while(temp){
        previouscommand[i++] = temp;
        temp = strtok(NULL," ");
    }
    previouscommand[i] = NULL;

    dup2(link[1],1); /*  stdout result redrecting to write end of pipe */
    close(link[1]);
    close(link[0]);
    execvp(previouscommand[0],previouscommand);

} else {
    wait(NULL);
    int res[2];
    pipe(res);

    if(fork() == 0){
        int i = 0;
        char *temp = strtok(string," ");
        char *command[spacecount(string)+2];

        while(temp){
            command[i++] = temp;
            temp = strtok(NULL," "); 
        }
        command[i] = NULL;

        dup2(link[0],0);
        close(link[0]);
        close(link[1]);

        dup2(res[1],1);
        close(res[1]);
        close(res[0]);

        execvp(command[0],command)
    } else {
        wait(NULL);

        close(res[1]);
        char buf[512];
        int i = 0;

        while (read(res[0],&buf[i++],1) == 1);

        close(res[0]);
        buf[i-2] = '\0';

        return strdup(buf);
    }
}
}

Upvotes: 0

Views: 554

Answers (2)

Achal
Achal

Reputation: 11921

you want to do something like ls | sort but the way you are doing like

char *arg[] = {"ls","|","sort",NULL};
execvp(arg[0],arg); /*it won't work */

won't work because here you are calling execvp on ls and sort which are two separate process not single process. Also

ls     |    sort  => output of process-1 make as input to process-2 & execute it   
|            |
process-1  process-2

To achieve the above create two process by calling fork() and use exec() family function to replace ls and sort in child & parent process.

here is the sample code

int main(void) {
        int p[2];
        pipe(p);
        char *arg[] = {"ls","sort",NULL};
        if(fork()==0) {
                close(0);/* close the stdin stream so that this 
process shoulbn't read from stdin */
                dup(p[0]);/* read from read end of pipe */
                close(p[1]);
                execlp(arg[1],arg[1],(char*)NULL);
        }
        else{
                close(1);/* close the stdout stream, so that o/p shouldn't print on monitor */
                dup(p[1]); /*  stdout result redrecting to write end of pipe */
                close(p[0]);
                execlp(arg[0],arg[0],(char*)NULL);
        }
        return 0;
}

Upvotes: 2

melpomene
melpomene

Reputation: 85827

| is a shell feature. You need to do the same thing the shell does, i.e. use pipe, fork, dup2, execvp to create a pipe, spawn a new process, and connect the pipe to the processes' stdin and stdout, respectively.

Take the code from your execString function but replace the else block. In the parent process you should dup2(link[0], 0) (connect the read end of your pipe to stdin), then execvp the other program (e.g. sort).

Remember to close the pipe ends you don't need! I.e. in the producer process, close(link[0]); in the consumer process, close(link[1]). If you forget this part, things may get "stuck" (commands seemingly hanging forever).

Of course, if you want your original program to keep going, you need to wrap that code inside another fork (and waitpid for it).


As an aside, your execString is broken if the command outputs a lot of data. At some point the pipe will get full and the command will pause, waiting for another process to drain the pipe (by reading from it). Your program will be stuck in wait, waiting for the command to terminate. The result is deadlock.

To fix this issue, only call wait after you're done reading from the pipe.

Upvotes: 1

Related Questions