Piping output to a pager
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.