Pipe two or more shell commands in C using a loop

In order to pipe multiple commands together, you’ll need to keep the parent running to keep fork()ing for each command.

Using a for loop, you will need to do this for the first n - 1 commands (the last one will be executed in the main program):

  1. Create a pipe.
  2. Execute fork().
  3. In the child: overwrite standard input with the read end of the previous pipe, and standard output with the write end of the current pipe.
  4. In the child: execute execve().
  5. In the parent: close unneeded pipes and save read end of current pipe to be used in the next iteration.

Then, after the loop ends, overwrite standard input with the read end of the last pipe and execute execve() of the last command.


Below I’ve written a simple working example that executes:

ls | wc -l | xargs printf "0x%x\n" | cowsay

It should work for any number of commands (including only 1 single command).

NOTE: I did not add error checks in this code apart for execvp() just to make it short, but you should definitely check for errors after each call to pipe(), dup2(), fork() and any other function.

Code:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define MAX_ARGC 3

int main(void) {
    char *commands[][MAX_ARGC + 1] = {
        {"ls", NULL},
        {"wc", "-l", NULL},
        {"xargs", "printf", "0x%x\n", NULL},
        {"cowsay", NULL}
    };

    size_t i, n;
    int prev_pipe, pfds[2];

    n = sizeof(commands) / sizeof(*commands);
    prev_pipe = STDIN_FILENO;

    for (i = 0; i < n - 1; i++) {
        pipe(pfds);

        if (fork() == 0) {
            // Redirect previous pipe to stdin
            if (prev_pipe != STDIN_FILENO) {
                dup2(prev_pipe, STDIN_FILENO);
                close(prev_pipe);
            }

            // Redirect stdout to current pipe
            dup2(pfds[1], STDOUT_FILENO);
            close(pfds[1]);

            // Start command
            execvp(commands[i][0], commands[i]);

            perror("execvp failed");
            exit(1);
        }

        // Close read end of previous pipe (not needed in the parent)
        close(prev_pipe);

        // Close write end of current pipe (not needed in the parent)
        close(pfds[1]);

        // Save read end of current pipe to use in next iteration
        prev_pipe = pfds[0];
    }

    // Get stdin from last pipe
    if (prev_pipe != STDIN_FILENO) {
        dup2(prev_pipe, STDIN_FILENO);
        close(prev_pipe);
    }

    // Start last command
    execvp(commands[i][0], commands[i]);

    perror("execvp failed");
    exit(1);
}

Output on my machine (since ls printed 41 lines and 41 = 0x29):

 ______
< 0x29 >
 ------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

Leave a Comment