head closes the pipe early¶
Level: 201 · you have seen status 141, or Broken pipe, from a pipeline that ends in head
One line: When the last reader of a pipe exits, the next write into the pipe kills the writer with SIGPIPE, and the shell reports that as 141, which is 128 + 13. That is the only reason yes | head -n 1 ever ends. A writer that had already finished reports 0. A writer that ignores SIGPIPE gets an error instead, prints Broken pipe and exits 1. And a loop in fish is not stopped at all unless it checks whether its echo failed.
Measured¶
Verified output of head_closes_sh.sh, identical on Linux and macOS — regenerated by tools/run_examples.py, never hand-typed.
$ yes | head -n 2; echo "PIPESTATUS ${PIPESTATUS[*]}"
y
y
PIPESTATUS 141 0
$ echo "141 is 128 + $((141 - 128)), and signal 13 is $(kill -l 13)"
141 is 128 + 13, and signal 13 is PIPE
$ seq 30000 | head -n 1; echo "PIPESTATUS ${PIPESTATUS[*]}"
1
PIPESTATUS 141 0
$ seq 3 | head -n 1; echo "PIPESTATUS ${PIPESTATUS[*]}"
1
PIPESTATUS 0 0
$ last=none; for i in $(seq 30000); do echo "$i"; last=$i; done | head -n 1; echo "PIPESTATUS ${PIPESTATUS[*]}, last is $last"
1
PIPESTATUS 141 0, last is none
yesnever stops by itself.head -n 2printed two lines and exited, which closed the only read end of the pipe.yeswas still writing, and its next write killed it with SIGPIPE: status 141, and no message, because the signal endedyesbefore it could print one.- 141 is 128 + 13. A shell reports "killed by signal N" as 128 + N, and
kill -l 13names signal 13:PIPE. - Any writer with more left to write dies the same way.
seq 30000writes about 170 KB, more than the pipe andheadtake in beforeheadexits. - A writer that has finished does not.
seq 3was not killed: its three short lines fit in the pipe, so it finished writing without waiting forhead. The pipeline is the same shape and its status is 0 instead of 141. What decides it is how much the left side still had to write when the right side stopped reading. - A shell loop is a writer too. The
forloop is one stage, run in a child bash (each stage runs in a subshell). One of itsechos hit the closed pipe, the signal killed the whole child, and the shell'slastis stillnone.
set -o pipefail counts each of these 141s as a failure. A pipeline reports its last command measures that.
On a Mac¶
The key above is shared: BSD yes, seq and head die of SIGPIPE in the same way, and bash 3.2 reports 141. The machines part only when SIGPIPE is ignored, because then yes survives the write and reports the failure in its own words. The report goes to a file and is printed after the pipeline, so it cannot race head's line:
Verified output of head_closes_ignored_sh.sh on Linux — regenerated by tools/run_examples.py, never hand-typed.
$ trap "" PIPE; yes 2>err | head -n 1; echo "PIPESTATUS ${PIPESTATUS[*]}"; cat err; trap - PIPE
y
PIPESTATUS 1 0
yes: standard output: Broken pipe
$ yes 2>err | head -n 1; echo "PIPESTATUS ${PIPESTATUS[*]}"; cat err
y
PIPESTATUS 141 0
$ trap '' PIPE; bash -c 'trap - PIPE; yes 2>err | head -n 1; echo "PIPESTATUS ${PIPESTATUS[*]}"; cat err'; trap - PIPE
y
PIPESTATUS 1 0
yes: standard output: Broken pipe
Verified output of head_closes_ignored_sh.sh on macOS — regenerated by tools/run_examples.py, never hand-typed.
$ trap "" PIPE; yes 2>err | head -n 1; echo "PIPESTATUS ${PIPESTATUS[*]}"; cat err; trap - PIPE
y
PIPESTATUS 1 0
yes: stdout: Broken pipe
$ yes 2>err | head -n 1; echo "PIPESTATUS ${PIPESTATUS[*]}"; cat err
y
PIPESTATUS 141 0
$ trap '' PIPE; bash -c 'trap - PIPE; yes 2>err | head -n 1; echo "PIPESTATUS ${PIPESTATUS[*]}"; cat err'; trap - PIPE
y
PIPESTATUS 1 0
yes: stdout: Broken pipe
trap "" PIPEmakes the shell ignore SIGPIPE, and the commands it starts inherit the setting. The write that would have killedyesfails with an error instead, andyesreports it and exits 1. GNUyescalls the streamstandard outputand BSDyescalls itstdout. That wording is the whole reason this key is split.trap - PIPErestores the default for the commands that follow: the second line is 141 again, with nothing inerr.- A shell that started with SIGPIPE ignored cannot restore it. The third line starts a child bash while its parent ignores SIGPIPE. The child's
trap - PIPEchanged nothing, andyesstill reportedBroken pipe. The bash manual says why: a signal that was ignored when a non-interactive shell started cannot be trapped or reset. So if something starts your script with SIGPIPE ignored,yes | headin that script prints the message and returns 1, and nothing inside the script can change it.
This library's runner starts every example with SIGPIPE at its default (see CONTRIBUTING), which is why the pages show 141.
In zsh and fish¶
zsh does what bash does:
Verified output of head_closes_zsh.zsh, identical on Linux and macOS — regenerated by tools/run_examples.py, never hand-typed.
$ yes | head -n 1; echo "pipestatus $pipestatus"
y
pipestatus 141 0
$ last=none; for i in $(seq 30000); do echo $i; last=$i; done | head -n 1; echo "pipestatus $pipestatus, last is $last"
1
pipestatus 141 0, last is none
$ trap "" PIPE; yes 2>/dev/null | head -n 1; echo "pipestatus $pipestatus"; trap - PIPE
y
pipestatus 1 0
yes is killed, 141. The for loop in the first stage runs in a child zsh, the signal kills that child, and last stays none. trap "" PIPE turns 141 into 1. The message is yes's own and is shown above, so here it goes to /dev/null.
fish agrees about yes and disagrees about the loop:
Verified output of head_closes_fish.fish, identical on Linux and macOS — regenerated by tools/run_examples.py, never hand-typed.
$ yes | head -n 1; echo "pipestatus $pipestatus"
y
pipestatus 141 0
$ set last none; for i in (seq 30000); echo $i; set last $i; end | head -n 1; echo "pipestatus $pipestatus, last is $last"
1
pipestatus 1 0, last is 30000
$ set last none; for i in (seq 30000); echo $i; or break; set last $i; end | head -n 1; if test $last -lt 30000; echo "the loop stopped early"; end
1
the loop stopped early
$ trap "" PIPE; yes | head -n 1; echo "pipestatus $pipestatus"; trap - PIPE
y
pipestatus 141 0
yesis an external command, and fish reports its death as 141 like the other shells.- The
forloop is not a separate process. fish runs it inside the fish process (each stage runs in a subshell), fish itself was not killed, and the loop carried on afterheadhad gone:last is 30000, every iteration run. The stage's status is 1, not 141. or breakis the fix. Once the pipe is closed,echofails, and the loop can stop on that.the loop stopped earlymeans the test$last -lt 30000came out true. The iteration it stopped at depends on timing, so the example does not print it.trap "" PIPEdid not reachyes: still 141, where bash and zsh gave 1.
| bash | zsh | fish | |
|---|---|---|---|
yes \| head -n 1 |
141 | 141 | 141 |
a loop in the first stage, feeding head |
killed, 141 | killed, 141 | runs to its end, status 1 |
trap "" PIPE first, then yes \| head -n 1 |
1, and Broken pipe |
1 | 141 |
If you are coming from another library¶
- Python. stdin, stdout and pipes ↗ is the same event from inside the writer: a
print()into a closed pipe raisesBrokenPipeErrorrather than ending the process. That page is still an outline, with the measurement noted. - Concurrency. Who waits when main returns? ↗ reads an exit status the same way from the other side: a C++
std::threaddestroyed without being joined aborts the process, and the shell reports 134, which is 128 + 6,SIGABRT.
See also¶
- A pipeline reports its last command —
pipefail, which turns these 141s into failures - Each stage runs in a subshell — why the bash loop is a child and the fish loop is not
- bash manual, Bourne Shell Builtins ↗ —
trap, and the signals it cannot reset - fish language, piping ↗