gotchabashMajor
Exit code propagation in pipes: pipefail and PIPESTATUS
Viewed 0 times
pipefailPIPESTATUSexit codepipelinepipe failureerror propagation
Problem
In bash without pipefail, a pipeline's exit code is the exit code of the last command only. A failure in an earlier pipeline segment is silently ignored. Example: false | true exits 0.
Solution
Enable pipefail with 'set -o pipefail'. For finer control, use PIPESTATUS array immediately after the pipeline.
set -o pipefail
false | true # now exits 1
# Check individual segments
cmd1 | cmd2 | cmd3
statuses=("${PIPESTATUS[@]}")
echo "cmd1=${statuses[0]} cmd2=${statuses[1]} cmd3=${statuses[2]}"
set -o pipefail
false | true # now exits 1
# Check individual segments
cmd1 | cmd2 | cmd3
statuses=("${PIPESTATUS[@]}")
echo "cmd1=${statuses[0]} cmd2=${statuses[1]} cmd3=${statuses[2]}"
Why
Without pipefail, the shell uses only the rightmost exit code. PIPESTATUS is an array set after each pipeline containing each segment's exit code, but it is overwritten by the next command.
Gotchas
- PIPESTATUS is overwritten immediately by the next command — save it before doing anything else
- pipefail makes 'cmd | grep pattern' fail if grep finds no matches (exit 1) — use 'grep ... || true'
- pipefail doesn't apply inside $() command substitution — check $? explicitly there
- Subshells reset PIPESTATUS; you must read it in the same shell that ran the pipeline
Code Snippets
PIPESTATUS array usage after a pipeline
set -o pipefail
# Without pipefail this would exit 0
false | true # now exits 1
# Save PIPESTATUS immediately
cat big.log | grep ERROR | sort | uniq -c
pipe_status=("${PIPESTATUS[@]}")
if [[ ${pipe_status[0]} -ne 0 ]]; then
echo "cat failed" >&2
elif [[ ${pipe_status[1]} -ne 0 ]] && [[ ${pipe_status[1]} -ne 1 ]]; then
echo "grep error (1=no match is ok)" >&2
fiContext
Any script using command pipelines where intermediate failures matter
Revisions (0)
No revisions yet.