Skip to content
Applying Redirections
step 1/5

Reading — step 1 of 5

Read

~3 min readExecution & Pipelines

Applying Redirections

The parser (chapter 0) turned sort < in.txt > out.txt 2> err.log into a clean to-do list of stream operations. Now comes the executor's half of the bargain: in the child process, in the fork/exec gap, turn each redirect into file-descriptor surgery — open, dup2, close — so the program execs into a world where its streams already point at the right places.

The mechanism: dup2 onto the standard fds

The contract every Unix program relies on: fd 0 is stdin, fd 1 is stdout, fd 2 is stderr. Programs read 0 and write 1 and 2; they neither know nor care what those descriptors are attached to. Redirection is therefore just: make fd N point somewhere else before exec.

> out.txt      →  fd = open("out.txt", WRITE|CREATE|TRUNCATE); dup2(fd, 1); close(fd);
>> out.txt     →  same, but APPEND instead of TRUNCATE
< in.txt       →  fd = open("in.txt", READ);                  dup2(fd, 0); close(fd);
2> err.log     →  open for write;                              dup2(fd, 2); close(fd);

dup2(fd, n) means "make descriptor n a duplicate of fd" — closing whatever n was first, atomically. After the dup, the original fd is a redundant extra handle: close it (the pipeline lesson's hygiene rule, same reasoning). Open-dup-close: three calls per file redirect, every time, in that order.

Failure is a real path here: < missing.txt must error ("no such file"), before exec, and the child exits with status 1 without running the program at all. The shell prints the message; the program never knows it was almost invoked.

Order matters — apply left to right

The parser preserved redirect order in a list; now you see why. Redirects apply sequentially, each seeing the world the previous ones made. The canonical puzzle, solved by tracing:

prog > file 2>&1        prog 2>&1 > file

Left version: fd1 → file; then 2>&1 = dup2(1, 2) → fd2 becomes a copy of fd1, which now points at file. Both streams → file. ✔ (This is how everyone captures all output.)

Right version: dup2(1, 2) first — fd2 copies fd1 while fd1 still points at the terminal → stderr goes to the terminal; then fd1 → file. Streams split. Almost never what the user meant — but exactly what they wrote, and your executor's job is faithful application, not mind-reading.

The rule that resolves every such puzzle: n>&m copies where m points RIGHT NOW — a snapshot, not a live link. dup2's semantics, nothing more. If you can trace the two commands above and predict both outcomes, you understand Unix redirection completely.

Composing with pipelines

A command inside a pipeline can also have redirects: a | b > f | c (legal! weird! b's stdout goes to f, so c reads... nothing — EOF immediately). The composition rule is simple and mechanical: pipe dups first, then the command's own redirects, left to right. Later operations override earlier ones (each dup2 clobbers), so an explicit > f beats the pipe's stdout — which yields exactly the weird-but-correct behavior above. One ordering rule, no special cases.

Your exercise: Redirection Plan

Given commands with parsed redirects (possibly in pipeline positions), output the fd operation sequence — opens, dup2s, closes, in order. It's the executor's checklist, tested on the cases that matter: truncate vs append flags, the 2>&1 dup (no open call! — the parser's dup-vs-file distinction pays off), the two orderings above, and pipe+redirect composition. Byte-precise fd bookkeeping — three syscalls in the right order, times each redirect — is the entire implementation; the understanding is knowing why the order is the semantics.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…