Fixing 'Broken Pipe' Errors in Shell Scripts and Pipelines
A command in the middle of a pipeline suddenly dies with a 'Broken pipe' error, sometimes only when piped into head or a similar early-exiting command. This is a specific, well-defined signal, not a random failure.
A “Broken pipe” error appearing in a shell pipeline — often specifically when piping into head, grep -m, or another command that can exit before consuming all its input — is a well-defined, specific signal behavior, not a random or unexplainable failure.
Step 1: understand what’s actually happening
yes | head -3
yes produces output indefinitely; head -3 reads exactly 3 lines and exits immediately afterward. Once head closes its end of the pipe, yes receives SIGPIPE — a signal specifically sent to a process attempting to write to a pipe whose reading end has already closed — and typically terminates as a result, hence “broken pipe.”
Step 2: recognize this as normal, expected pipeline behavior
This isn’t a bug in either yes or head — it’s the intended, standard mechanism for stopping an upstream command once a downstream command no longer needs more input, letting head -3 cleanly stop an infinitely-producing command like yes without needing any special coordination between the two.
Step 3: check whether the “broken pipe” error is actually harmless
some-long-running-command | head -20
echo "Exit status: ${PIPESTATUS[0]}"
If the downstream command (head, in this example) got everything it needed before the upstream command was terminated by SIGPIPE, the overall pipeline typically achieved its intended result despite the visible error message — worth confirming before treating it as an actual failure needing a fix.
Step 4: suppress a specific, known-harmless SIGPIPE error in a script
some-command 2>/dev/null | head -5
If a specific pipeline’s SIGPIPE-caused error message is expected and harmless, redirecting standard error away is a reasonable, deliberate way to suppress the specific noise without affecting the pipeline’s actual functional result.
Step 5: handle SIGPIPE explicitly within a script that shouldn’t die from it
trap '' PIPE
A script that specifically shouldn’t terminate on SIGPIPE (uncommon, but occasionally needed for a specific control-flow reason) can trap and ignore the signal explicitly — though this should be used deliberately and narrowly, not as a default habit, since SIGPIPE’s default behavior exists for good reason in most pipelines.
Step 6: check for a genuinely different problem masquerading as a broken pipe
some-command | another-command
# if another-command crashed or was killed for an
# UNRELATED reason, the upstream command will also
# see a broken pipe as a downstream consequence
If a downstream command in a pipeline crashes or is killed for a completely separate reason (an actual bug, an out-of-memory kill), the upstream command will also receive SIGPIPE as a direct consequence — worth checking the downstream command’s own exit status and logs specifically, rather than assuming the upstream command’s broken-pipe error is the primary problem.
Step 7: check exit statuses of every stage in a pipeline, not just the last one
some-command | another-command
echo "${PIPESTATUS[@]}"
$? alone only reflects the last command in a pipeline’s exit status — ${PIPESTATUS[@]} (Bash-specific) shows the exit status of every stage, letting you distinguish “the upstream command was cleanly stopped by SIGPIPE as expected” from “something further down the pipeline actually failed.”
Why understanding SIGPIPE as a deliberate mechanism, not an error, changes how you troubleshoot it
Treating every “broken pipe” message as a bug to eliminate leads to suppressing genuinely useful signals or adding unnecessary complexity to scripts — recognizing SIGPIPE as the specific, intentional mechanism that lets a pipeline’s downstream command cleanly stop an upstream producer is what lets you correctly distinguish expected, harmless pipeline termination from an actual problem elsewhere in the pipeline.