Inside the Build / 01
myShell: the life of a pipeline
A command looks like a single line. Underneath it are processes, file descriptors, signals, and a terminal that needs to find its way back to the shell.
The command
cat file.txt | sortAssume file.txt exists and both programs are available. cat reads the file, sort orders its lines, and the pipe connects them. This is an illustrative execution trace of the architecture described in myShell’s project overview.
producer→ pipe →
byte streamsort
consumer
From input to prompt
Expand a stage to inspect its input, output, and the reasoning behind it. Execution sketches are pseudocode, not source excerpts.
- INPUT
- cat file.txt | sort
- OUTPUT
- [ ["cat", "file.txt"], ["sort"] ]
The parser separates the unquoted pipe operator from command arguments. That distinction matters: a pipe inside quotes belongs to an argument, rather than starting another process. Parsing completes before execution begins.
parse_pipeline(line)
→ command(argv: ["cat", "file.txt"])
→ command(argv: ["sort"])WHY THIS MATTERS
Keep syntax handling separate from execution so malformed input can be rejected before any child processes start.
What this reveals
The interesting part is the coordination: start the stages concurrently, close the right descriptors, give the job terminal control, then restore the shell when the job finishes or stops.
Those boundaries also explain what needs testing: quoted operators, pipe end-of-file, foreground signals, stopped jobs, and final-stage exit status. myShell’s project overview includes CTest and pseudo-terminal integration coverage for these behaviors.