Skip to content

Instantly share code, notes, and snippets.

@uzluisf
Created November 23, 2023 23:59
Show Gist options
  • Save uzluisf/72d9ee885d0dcb7253615e69cc32ed1c to your computer and use it in GitHub Desktop.
Save uzluisf/72d9ee885d0dcb7253615e69cc32ed1c to your computer and use it in GitHub Desktop.

Revisions

  1. uzluisf created this gist Nov 23, 2023.
    94 changes: 94 additions & 0 deletions ls-wc-pipes-example.c
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,94 @@
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/wait.h>

    /*
    * C program that implements the pipeline
    *
    * ls -l /tmp/ | wc -l
    *
    * to count the number of files under the /tmp/ directory.
    *
    * The parent process declares a pipe, and then forks twice to create two
    * childs processes for the ls and wc commands. The process doesn't send or
    * receive any output from its children, it simply forks them and then waits
    * for them to finish execution.
    *
    * The first child process represents the ls command, and reassigns its STDOUT
    * to the pipe's write end so the output from the process doesn't go to the
    * console and instead goes to the pipe's write end.
    *
    * The second child process represents the wc command, and reassign its STDIN
    * to the pipe's read end so the input to the process comes from the pipe, and
    * not from the keyboard.
    *
    * NOTE: It might benefit from more error handling.
    */
    int main(int argc, char *argv[]) {
    pid_t ls_pid, wc_pid;
    int pipefd[2];

    pipe(pipefd);

    if ((ls_pid = fork()) == 0) {
    // we assign this process's output to the pipe's write end, i.e.,
    // instead of sending output to the screen, it sends it to the pipe's
    // write end.
    dup2(pipefd[1], STDOUT_FILENO);

    // now this process's stdout refers to the pipe's write end too so we
    // can close this descriptor.
    close(pipefd[1]);

    // this process doesn't use the pipe's read end, and thus we close this
    // file descriptor.
    close(pipefd[0]);

    // replace process's current image with this new process image, i.e., the ls command.
    if (execlp("ls", "ls", "-l", "/tmp/", (char *) NULL) < 0) {
    fprintf(stderr, "failed trying to execute the ls command");
    exit(0);
    };
    }
    else if (ls_pid < 0) {
    fprintf(stderr, "failed forking ls process");
    }

    if ((wc_pid = fork()) == 0) {
    // we assign this process's input to the pipe's read end, i.e., instead
    // of taking input from the keyboard, it takes it from the pipe's read end.
    dup2(pipefd[0], STDIN_FILENO);

    // now this process's stdin refers to the pipe's read end too so we
    // can close this descriptor.
    close(pipefd[0]);

    // this process doesn't use the pipe's write end, and thus we close this
    // file descriptor.
    close(pipefd[1]);

    // replace process's current image with this new process image, i.e., the wc command.
    if (execlp("wc", "wc", "-l", (char *) NULL) < 0) {
    fprintf(stderr, "failed trying to execute the wc command");
    exit(0);
    };
    }
    else if (wc_pid < 0) {
    fprintf(stderr, "failed forking wc process");
    exit(0);
    }

    // the parent process doesn't use the pipe so we close both ends. Also
    // needed to send EOF so the children can continue (children blocks until
    // all input has been processed).
    close(pipefd[0]);
    close(pipefd[1]);

    // the parent process waits for both child process to finish their execution.
    int ls_status, wc_status;
    pid_t ls_wpid = waitpid(ls_pid, &ls_status, 0);
    pid_t wc_wpid = waitpid(wc_pid, &wc_status, 0);

    return 0;
    }