Reading — step 1 of 5
Read
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…