Piping output to a pager

• 3 min read

When you run git diff on a project where you’ve made more than a screenful of changes, the output is automatically redirected to a pager and you can browse through your changes using the familiar less interface. This post shows a minimal example piping output to less.

To achieve this, git creates a pipe and spawns a new process, whose purpose is to exec the pager.

int fds[2];
if (pipe(fds) == -1) {
  perror_die("pipe");
}
int child_pid = fork();

Let’s start by looking at the parent process, even though in the code this case is handled last. The STDOUT of the parent process is aliased to the write end of the pipe, so any further printing in the parent process writes stuff to fds[WRITE_END]. Once we’re done, we close the write end of the pipe to signal EOF to the child process.

We wait for the child process to exit since otherwise control returns to the shell.

switch (child_pid)
// ...
  default: { /* Parent */
    /* STDOUT is now fds[WRITE_END] */
    dup2(fds[WRITE_END], STDOUT_FILENO);
    /* parent doesn't read from pipe */
    close(fds[READ_END]);

    /* "Business" logic which determines what to actually print */
    int num_lines = 1024;
    if (argc > 1) {
      num_lines = atoi(argv[1]);
    }
    print_n_lines(num_lines);


    fflush(stdout);

    /* Signal EOF to the pager process */
    close(STDOUT_FILENO);

    int stat_loc;
    waitpid(child_pid, &stat_loc, 0);
    break;
  }

The STDIN of the new process is aliased to the read end of the pipe. Anything being printed in the parent process is thus now an input to the pager process. After setting up STDIN, this process runs less.

switch (child_pid) {
case 0: {    /* Child(pager) */
    /* Pager process doesn't write to pipe */
    close(fds[WRITE_END]);

    /* Make READ_END of pipe pager's STDIN */
    dup2(fds[READ_END], STDIN_FILENO);

    /* F -> quit-if-one-screen */
    /* R -> preserve color formatting */
    /* X -> don't send some special instructions eg. to clear terminal screen before starting */
    char *less_argv[] = {"less", "-FRX", NULL};
    int exec_status = execvp(less_argv[0], less_argv);

    fprintf(stderr,
            "execvp failed with status: %d and errno: %d\n", exec_status, errno);
    break;
  }
  
// ...
} // switch

The full example can be found in this gist. You can also check out the Ruby and Rust implementations of this.

Be the first to cheers
Now look what you've done 🌋
Stop clicking and run for your life! 😱
Uh oh, I don't think the system can't handle it! 🔥
Stop it, you're too kind 😄
Thanks for the love! ❤️
Thanks, glad you enjoyed it! Care to share?
Hacker News Reddit

×

Recommended Posts ✍🏻

See All »
• 6 min read
TIL: Sum Types With `instructor_ex`
Read Post »
• 1 min read
TIL: Creating `sentence-transformers` Embedding...
Read Post »
• 1 min read
TIL: File Uploads Using the Req Elixir Library
Read Post »