GitHub

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.

C++20POSIXProcess management
Explore the repository (opens in a new tab)

The command

cat file.txt | sort

Assume 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.

cat
producer
→ pipe →
byte stream
sort
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.

Execution sketch · pseudocode
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.