build-a-shell / lesson-27.md
Lesson 27 · Pipes & control operators

A two-command pipe

The pipe is the idea that makes Unix Unix - one program's output becomes another's input. Today you connect two commands with `|` by wiring a pipe between their file descriptors.

The goal

Run `a | b` by connecting the first command's standard output to the second command's standard input.

Start here - the target
TO DO
Scenario: Connecting two commands with a pipe
Giventhe line: echo hi | cat
Whenthe shell runs it
Thenthe output is "hi\n" (echo's output flowed through cat)
Andboth commands run as separate processes and the shell waits for both
Background

A pipe is a one-way channel in the kernel with two ends: whatever is written to the write end can be read from the read end. To run a | b, the shell creates a pipe, then forks two children - the first with its standard output dup2’d onto the pipe’s write end, the second with its standard input dup2’d onto the read end. Now a’s output flows straight into b’s input, no file on disk involved.

The part that bites everyone is closing descriptors. Every process that holds the write end open keeps the pipe from signaling end-of-input, so b (and the shell) would hang forever. The rule: each child closes both original pipe descriptors after dup-ing the one it needs, and the parent closes both ends too before waiting. A pipe only reports “done” when every write end is closed. A command connected by pipes is a pipeline - introduce that list-of-commands shape now, because the next lessons grow it.

Make it work
int fd[2]; pipe(fd); // fd[0]=read end, fd[1]=write end
if (fork()==0){ dup2(fd[1],1); close(fd[0]); close(fd[1]); exec(a); }
if (fork()==0){ dup2(fd[0],0); close(fd[0]); close(fd[1]); exec(b); }
close(fd[0]); close(fd[1]); // parent closes BOTH ends, then waits
CheckpointDONE
`echo hi | cat` works - your shell has pipes. Commit and stop here.