Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Bash & the POSIX Shell  Module 6 – Redirection, pipes and substitution

Pipelines, PIPESTATUS and pipefail

Connect commands with | and |&, read every stage's exit status with PIPESTATUS, catch failures with pipefail, and see why yes | head returns 141.

  • Intermediate
  • 10 minutes
  • Examples run with Bash 5.3.20, dash 0.5.13.5, shellcheck 0.11.0 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 10 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Connect commands with | and |&, and explain why each stage runs as a separate process
  • Read every stage's status from a copy of PIPESTATUS, and predict the status pipefail reports
  • Recognise the 141 of a writer stopped by SIGPIPE, and decide stage by stage when to accept it

Before you start

What you’ll learn · Before you start

What you will learn

  • Connect commands with | and |&, and explain why each stage runs as a separate process
  • Read every stage's status from a copy of PIPESTATUS, and predict the status pipefail reports
  • Recognise the 141 of a writer stopped by SIGPIPE, and decide stage by stage when to accept it

Before you start

On this page

false | true ends with status 0, although its first command failed. A pipeline: Commands joined by | or |&, each one's standard output flowing into the next one's standard input, all running at the same time. Its exit status is the last command's unless pipefail is set. reports the status of its last command only, so a failure in any earlier stage is invisible unless you look for it. Bash keeps every stage’s status in the array PIPESTATUS, and set -o pipefail makes the whole pipeline fail when any stage fails: in the session below, false | true | (exit 4) left PIPESTATUS at 1 0 4 and returned 4.

You will connect commands with | and |&, read each stage’s status before the next command replaces it, predict what pipefail reports, and tell a real failure from the 141 a pipeline returns when its reader stops early. The streams of the previous lesson are all the background you need.

A pipeline’s status is its last command’s

cmd1 | cmd2 connects the standard output: Descriptor 1, where a command writes its results: the terminal, unless > file, >> file or a pipe points it somewhere else. Also called stdout. More in “Redirection: stdin, stdout, stderr, /dev/null” of cmd1 to the standard input of cmd2 through a pipe the shell creates, and a longer pipeline does the same for every pair of neighbours. When the pipeline ends, the shell reports one status, and it is the last command’s:

Pipeline status, PIPESTATUS and pipefail Python · pipestatus_session.py
"""Pipelines, PIPESTATUS and pipefail: the status of a pipeline, every stage's status, and the pipefail option.

The session shows:
1. A pipeline's status is its last command's: false | true gives 0.
2. With pipefail, the status is that of the last failing stage.
3. PIPESTATUS holds every stage's status, but only until the next command.
4. Each stage is a separate process (its own subshell), so variables set there are lost.
5. |& sends stderr as well.
6. dash 0.5.13.5 supports pipefail.
"""
from session import run

run("""
# A pipeline's status is its last command's
false | true
echo "false | true: $?"

# A three-stage pipeline: only the last status counts
false | true | (exit 4); echo "status: $?"

# PIPESTATUS holds every stage's status (copy it at once!)
false | true | (exit 4); p=("${PIPESTATUS[@]}"); echo "PIPESTATUS: ${p[0]} ${p[1]} ${p[2]}"

# The next command replaces PIPESTATUS
false | true | (exit 4); p=("${PIPESTATUS[@]}"); echo "Saved: ${p[*]}"; echo "After echo: ${PIPESTATUS[*]}"

# With pipefail, the status is the last non-zero
set -o pipefail
false | true | (exit 4); echo "With pipefail: $?"

# The last failing stage, not the first
(exit 2) | true | (exit 7); echo "pipefail picks the rightmost failure: $?"
set +o pipefail

# Each stage runs in a subshell: variables are lost
count=0
echo one | while read line; do count=$((count+1)); done
echo "count after the pipe: $count"

# |& sends stderr as well (short for 2>&1 |)
ls /no-such-dir |& cat

# dash supports pipefail (POSIX.1-2024)
dash -c 'set -o pipefail; false | true; echo "dash pipefail: $?"'
""")

Output

# A pipeline's status is its last command's
$ false | true
$ echo "false | true: $?"
false | true: 0
# A three-stage pipeline: only the last status counts
$ false | true | (exit 4); echo "status: $?"
status: 4
# PIPESTATUS holds every stage's status (copy it at once!)
$ false | true | (exit 4); p=("${PIPESTATUS[@]}"); echo "PIPESTATUS: ${p[0]} ${p[1]} ${p[2]}"
PIPESTATUS: 1 0 4
# The next command replaces PIPESTATUS
$ false | true | (exit 4); p=("${PIPESTATUS[@]}"); echo "Saved: ${p[*]}"; echo "After echo: ${PIPESTATUS[*]}"
Saved: 1 0 4
After echo: 0
# With pipefail, the status is the last non-zero
$ set -o pipefail
$ false | true | (exit 4); echo "With pipefail: $?"
With pipefail: 4
# The last failing stage, not the first
$ (exit 2) | true | (exit 7); echo "pipefail picks the rightmost failure: $?"
pipefail picks the rightmost failure: 7
$ set +o pipefail
# Each stage runs in a subshell: variables are lost
$ count=0
$ echo one | while read line; do count=$((count+1)); done
$ echo "count after the pipe: $count"
count after the pipe: 0
# |& sends stderr as well (short for 2>&1 |)
$ ls /no-such-dir |& cat
ls: /no-such-dir: No such file or directory
# dash supports pipefail (POSIX.1-2024)
$ dash -c 'set -o pipefail; false | true; echo "dash pipefail: $?"'
dash pipefail: 1

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 pipestatus_session.py

false | true gave 0 because true came last. The three-stage pipeline ended with 4, the status of (exit 4), and the 1 from false left no trace in $?. The Bash manual says it in one sentence: “The exit status of a pipeline is the exit status of the last command in the pipeline”, unless pipefail is on.

The stages are separate programs that run at the same time. Bash starts every stage as its own process, each in its own subshell: A copy of the shell that runs commands apart from it, as Bash does for each command of a pipeline. It starts with the shell's variables, and every change it makes to them is lost when it exits. More in “Reading files line by line with read”, joins them with pipes, and waits until all of them have ended before it reports the status. Data moves while they run: each stage reads whatever the stage before it has written so far, and a writer whose pipe is full waits for its reader. That is how yes | head -n 1, later in this lesson, can end at all, although yes never stops writing by itself.

Interactive · stage by stage Three stages, three processes Step through a pipeline where each stage runs in its own subshell. The final status comes from the last stage, unless pipefail is set.
Three stages, three processes, step 6 of 6. Showing false (status 1), true (status 0), (exit 4) (status 4).falsestatus 1truestatus 0(exit 4)status 4PIPESTATUS[0]=1

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. The pipeline starts. Each stage runs in its own subshell, a copy of the shell that cannot change the parent's variables.
  2. Stage 1 fails. false returns 1. Its output (nothing here) goes to stage 2.
  3. Stage 2 succeeds. true returns 0, even though stage 1 failed.
  4. Stage 3 fails. (exit 4) returns 4. It is the last stage.
  5. Without pipefail. The pipeline's status is the last stage's: 4. The failures of stages 1 and 3 look the same.
  6. With pipefail. set -o pipefail keeps 4 here, but shows 1 when the first stage alone fails. PIPESTATUS holds 1 0 4.

Because each stage is a subshell, no stage can change the variables of the shell that started the pipeline. The session’s count stayed 0 after echo one | while read line; do …; done, since the loop counted in its own copy of count. The lesson on reading files line by line shows how to feed a loop without losing what it sets.

A pipe carries descriptor 1 only. ls /no-such-dir |& cat sent the error of ls through cat as well: |& is Bash’s short form of 2>&1 |, which its NEWS lists among the changes of Bash 4.0. POSIX defines only |, so a script for sh writes 2>&1 | in full.

Errors that never enter the pipe

Wrong: ./build.sh | grep -c error to count a build’s errors when build.sh writes them on standard error: they go to the terminal, and the count is 0. Right: ./build.sh 2>&1 | grep -c error, or |& in place of 2>&1 | in Bash.

Checkpoint

Check: Pipeline status and stages

  1. Question 1 of 2 ./export.sh | gzip > rows.gz runs without pipefail, and export.sh exits with status 1 halfway through. What does $? hold after the line?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 0, the status of gzip

    The Bash manual: the exit status of a pipeline is the exit status of its last command, unless pipefail is on. The failure of export.sh is visible only in PIPESTATUS, here 1 0.

  2. Question 2 of 2 A script runs count=0; echo data | while read line; do count=$((count+1)); done; echo $count. What does it print?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: 0, because the while loop ran in a subshell and its count is lost

    The session showed count staying 0 for the same reason. The lesson on reading files line by line shows how to feed a loop without losing the variables it sets.

PIPESTATUS holds every stage’s status

After a pipeline, Bash sets the array PIPESTATUS to the statuses of its commands in order: ${PIPESTATUS[0]} is the first stage, ${PIPESTATUS[1]} the second, and so on. It lasts only until the next command. Bash sets it again after simple commands, subshells and the compound commands [[ ]] and (( )), so the next echo, test or assignment replaces it with an array of one element, its own status. The session read it twice after the same pipeline: copied at once into p, it held 1 0 4; read again after an echo, it held 0.

Key idea

Copy PIPESTATUS on the same line as the pipeline, before anything else runs, with p=("${PIPESTATUS[@]}"), and let every later check read the copy.

The copy is an ordinary array, so the usual array forms work on it: ${#p[@]} is the number of stages, 3 here, and ${p[*]} prints them all on one line. Walking it from index 0 upwards finds the stage that failed first, which neither $? nor pipefail can tell you. PIPESTATUS is one of Bash’s own variables: POSIX defines no such variable, and sh has no arrays to hold one, so a script for sh that needs more than the last status relies on pipefail.

Checkpoint

Check: PIPESTATUS

  1. One question cmd1 | cmd2 | cmd3 runs, and immediately afterwards you run echo "${PIPESTATUS[1]}". What does that print?

    Choose one answer.

    Show the answer: One question

    Answer: 0, because echo replaced PIPESTATUS before printing

    The session copied PIPESTATUS at once: p=("${PIPESTATUS[@]}"). Then ${p[1]} has the second stage's status.

pipefail makes the pipeline’s status the rightmost failure

set -o pipefail changes which status the pipeline reports: the status of the rightmost command that exited with a non-zero status, or 0 when every command succeeded. In the session it kept 4 for false | true | (exit 4), and (exit 2) | true | (exit 7) returned 7, not 2. pipefail changes nothing about how the stages run. Every stage still runs to its own end, and only the number reported afterwards differs.

Turn it on where a failed producer must not look like success: a script that builds a report with extract | sort | uniq -c should not deliver an empty report because extract failed while sort and uniq succeeded on no input. POSIX.1-2024 says the setting that counts is the one in force when the pipeline starts, so set -o pipefail goes before the pipelines it should cover, usually near the top of the script.

The option has been in Bash since 3.0, whose NEWS introduced it, and POSIX.1-2024 made it part of the standard. dash 0.5.13.5 has it too: the session’s dash -c 'set -o pipefail; false | true; echo "dash pipefail: $?"' printed 1, so a script that uses pipefail also works under a current dash.

pipefail does not pick the first failure

(exit 2) | true | (exit 7) returns 7, not 2: pipefail keeps the rightmost non-zero status. When you need to know which stage failed first, read a copy of PIPESTATUS.

Checkpoint

Check: pipefail

  1. One question With pipefail on, (exit 3) | true | (exit 5) returns what status?

    Choose one answer.

    Show the answer: One question

    Answer: 5, the rightmost non-zero status

    The session showed (exit 2) | true | (exit 7) returning 7, not 2. pipefail is about the rightmost failure.

SIGPIPE and the 141 from a healthy pipeline

head -n 1 exits as soon as it has printed one line. When the writer before it tries to write again, nothing reads the pipe any more, and the system sends the writer SIGPIPE: Signal 13, sent to a process that writes to a pipe nobody has open for reading any more, for example after head has exited. By default it ends the writer, and Bash reports that as status 128 + 13 = 141.. Left to that signal’s default action, the writer terminates. For a command that a fatal signal stopped, Bash sets the status to 128 plus the signal’s number; SIGPIPE is signal 13, so the writer’s status is 141:

SIGPIPE and the 141 status Python · sigpipe_session.py
"""SIGPIPE and pipefail: when a reader stops early, the writer receives SIGPIPE.

The session shows:
1. yes | head -n 1: head exits after one line, yes gets SIGPIPE.
2. Without pipefail, the status is 0 (head succeeded).
3. With pipefail, the status is 141 (128 + 13, where SIGPIPE is signal 13).
4. PIPESTATUS shows "141 0": yes was killed, head succeeded.
5. A healthy pipeline can look failed because of this, so decide when 141 is acceptable.
"""
from session import run

run("""
# yes writes forever; head stops after one line
yes | head -n 1

# Without pipefail, status is 0 (head's status)
yes | head -n 1; echo "status: $?"

# With pipefail, status is 141 (yes was killed by SIGPIPE)
set -o pipefail
yes | head -n 1; echo "status: $?"

# PIPESTATUS shows both stages' statuses
yes | head -n 1; p=("${PIPESTATUS[@]}"); echo "PIPESTATUS: ${p[*]}"

# 141 = 128 + 13 (SIGPIPE)
echo "128 + 13 = $((128 + 13))"

# A pipeline with cat | head also shows 141
cat /dev/urandom | head -c 100 | wc -c; p=("${PIPESTATUS[@]}"); echo "PIPESTATUS: ${p[*]}"
set +o pipefail

# How to accept 141 as success
set -o pipefail
yes | head -n 1; rc=$?
if [ "$rc" -eq 0 ] || [ "$rc" -eq 141 ]; then echo "OK (status $rc)"; else echo "FAILED (status $rc)"; fi
set +o pipefail
""")

Output

# yes writes forever; head stops after one line
$ yes | head -n 1
y
# Without pipefail, status is 0 (head's status)
$ yes | head -n 1; echo "status: $?"
y
status: 0
# With pipefail, status is 141 (yes was killed by SIGPIPE)
$ set -o pipefail
$ yes | head -n 1; echo "status: $?"
y
status: 141
# PIPESTATUS shows both stages' statuses
$ yes | head -n 1; p=("${PIPESTATUS[@]}"); echo "PIPESTATUS: ${p[*]}"
y
PIPESTATUS: 141 0
# 141 = 128 + 13 (SIGPIPE)
$ echo "128 + 13 = $((128 + 13))"
128 + 13 = 141
# A pipeline with cat | head also shows 141
$ cat /dev/urandom | head -c 100 | wc -c; p=("${PIPESTATUS[@]}"); echo "PIPESTATUS: ${p[*]}"
     100
PIPESTATUS: 141 0 0
$ set +o pipefail
# How to accept 141 as success
$ set -o pipefail
$ yes | head -n 1; rc=$?
y
$ if [ "$rc" -eq 0 ] || [ "$rc" -eq 141 ]; then echo "OK (status $rc)"; else echo "FAILED (status $rc)"; fi
OK (status 141)
$ set +o pipefail

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 sigpipe_session.py

yes | head -n 1 printed one y every time. Without pipefail its status was 0, head’s; with pipefail it was 141, and PIPESTATUS read 141 0: yes was killed, head succeeded. cat /dev/urandom | head -c 100 | wc -c showed the pattern one stage further left, 141 0 0, with wc counting exactly 100 bytes. Nothing went wrong in either pipeline. The reader had what it needed, and SIGPIPE is how the system stops a producer whose output nobody reads.

Interactive · message by message yes | head -n 1: the reader stops early Step through what happens when head reads one line and exits before yes finishes writing.
yes | head -n 1: the reader stops early, step 6 of 6. Messages so far: yes to pipe: "y\n"; pipe to head -n 1: "y\n"; yes to pipe: "y\n".yesexit 141pipehead -n 1exit 0"y\n""y\n""y\n"

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. Both start. The shell starts yes and head as separate processes, with a pipe between them.
  2. yes writes "y". yes writes "y\n" to the pipe forever, one line at a time.
  3. head reads one line. head reads from the pipe, prints "y" and exits with status 0.
  4. yes writes again. yes writes another "y\n", but the pipe's reader is gone.
  5. SIGPIPE. The kernel sends SIGPIPE to yes, whose default action ends the process. Bash reports a command ended by a signal as 128 plus its number, 13, so 141.
  6. The result. PIPESTATUS holds 141 0. Without pipefail, the status is 0. With pipefail, 141.

Under pipefail, that 141 reaches $? and every test of the pipeline’s status. The session’s last block accepted it for the whole pipeline: it kept the status in rc and treated 0 and 141 as success. That rule is coarse once a pipeline has more stages, because a 141 can come from any of them. PIPESTATUS is exact: accept 141 only from the stage right before a reader that stops early by design, such as head, and only when that reader returned 0. Any other non-zero entry is a real failure.

Key idea

Under pipefail, 141 usually means that a reader stopped early on purpose and the writer before it was told to stop. Decide stage by stage, from PIPESTATUS, where a 141 is expected.

To pick lines out of a log by hand rather than in a pipeline, the filter lines tool keeps or removes the lines that contain your words and shows both sets side by side.

Checkpoint

Check: SIGPIPE and 141

  1. One question With pipefail on, yes | head -n 1 returns what status?

    Type a number.

    Show the answer: One question

    Answer: 141

    head exits after one line, yes writes to a closed pipe and receives SIGPIPE (signal 13). Bash reports 128 + 13 = 141.

Practise: report the stage that failed

Build run_checked in your own terminal: a function that runs a three-stage pipeline and names the first stage that failed.

  1. Define three functions as stand-in stages: generate prints five lines, one of them starting with #; filter drops that line with grep -v '^#'; count runs wc -l. Make each fail on request: when the variable FAIL_AT holds its stage number, the stage reads its input to the end with cat > /dev/null and returns 3.
  2. Write run_checked. It runs generate | filter | count, copies PIPESTATUS on the same line, and walks the copy from index 0. At the first non-zero entry it prints on standard error the stage’s number, name and status, such as stage 2 (filter) failed with status 3, and returns that status; when every entry is 0 it returns 0.
  3. Run it with FAIL_AT empty, then 1, 2 and 3. Its status must be 0, then 3 three times, and each message must name the stage you chose.
  4. Now let count return 3 at once, without reading its input, and run FAIL_AT=3 again. The message may name stage 2 with status 141. Explain why: filter wrote into a pipe whose reader had already gone.

The questions below use pipelines the lesson did not run. Work out each status before you open the explanation:

Practice that adapts to you

Practise this lesson

10 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 10 What does false | true; echo $? print?

    Type a number.

    Show the answer: Question 1 of 10

    Answer: 0

    The pipeline's status is true's 0. The failure of false is invisible without PIPESTATUS or pipefail.

  2. Question 2 of 10 What status does true | (exit 2) | false return?

    Choose one answer.

    Show the answer: Question 2 of 10

    Answer: 1 (from false)

    Without pipefail, only the last command's status matters. Here it is false, which returns 1.

  3. Question 3 of 10 After cmd1 | cmd2 | cmd3, which index of PIPESTATUS holds cmd1's status?

    Choose one answer.

    Show the answer: Question 3 of 10

    Answer: 0

    PIPESTATUS[0] is the first stage, PIPESTATUS[1] the second, and so on.

  4. Question 4 of 10 How do you save PIPESTATUS so that you can use it after another command runs?

    Choose one answer.

    Show the answer: Question 4 of 10

    Answer: p=("${PIPESTATUS[@]}"); then use ${p[0]}, ${p[1]}, etc.

    The session did exactly this: p=("${PIPESTATUS[@]}"). Then ${p[0]} and ${p[1]} are safe.

  5. Question 5 of 10 With pipefail on, true | true | true returns what status?

    Type a number.

    Show the answer: Question 5 of 10

    Answer: 0

    All commands succeed, so the pipeline succeeds with status 0.

  6. Question 6 of 10 With pipefail on, which stage of cmd1 | cmd2 | cmd3 failed first? How do you check?

    Choose one answer.

    Show the answer: Question 6 of 10

    Answer: Check PIPESTATUS: the first non-zero entry is the first failure

    pipefail gives 7 for (exit 2) | true | (exit 7), not 2. Only PIPESTATUS shows every stage.

  7. Question 7 of 10 What is the signal number for SIGPIPE?

    Type a number.

    Show the answer: Question 7 of 10

    Answer: 13

    Bash reports 128 + signal. 141 means 128 + 13, so SIGPIPE is signal 13.

  8. Question 8 of 10 A script runs set -o pipefail; generate_logs | head -n 100 > top.txt. In a nightly job it ends with 141, and top.txt holds 100 lines. What should the script do?

    Choose one answer.

    Show the answer: Question 8 of 10

    Answer: Accept 141 from generate_logs when head returned 0, and treat every other non-zero status as a failure

    The session accepted 0 and 141 for the whole pipeline. Reading PIPESTATUS is more exact: accept 141 only from the stage before head, and only when head itself returned 0.

  9. Question 9 of 10 cat rates.json | jq .items runs under set -o pipefail. rates.json is missing, so cat fails with status 1, and jq reads nothing and exits 0. What is the pipeline status?

    Choose one answer.

    Show the answer: Question 9 of 10

    Answer: 1, the status of cat

    This is the case pipefail exists for: without it, the failure of cat would be hidden behind the success of jq.

  10. Question 10 of 10 What does |& do?

    Choose one answer.

    Show the answer: Question 10 of 10

    Answer: Sends both stdout and stderr to the next command (short for 2>&1 |)

    The session's ls |& cat passed the error message through the pipe. Without |&, the error goes to the terminal.

Interview questions

Warm-up: what status does a pipeline return, how do you see every stage’s status, and what does pipefail change (fresher to 2–5 years)?

A pipeline’s status is its last command’s: false | true returns 0 because true came last. PIPESTATUS holds every stage’s status as an array, ${PIPESTATUS[0]} for the first stage; copy it on the same line, because the next command replaces it. With set -o pipefail, the pipeline returns the status of the rightmost command that failed, or 0 when all of them succeeded, so false | true returns 1. pipefail has been in Bash since 3.0 and is part of POSIX.1-2024.

The two harder questions are about a monitor that stopped alerting on a busy day although the error line was there, and a clean-up step that emptied the very file it was meant to tidy. For each, work out what every stage was doing at the moment it ended before you open the first step:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Mid to senior

    A monitor runs under set -o pipefail and pages when if dump_journal | grep -q 'disk full' succeeds. On quiet days it pages as it should. On a busy day the dump was 100,000 lines long with 'disk full' near the top, and no page fired. Why, and how would you fix the check?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Fresher to mid

    A clean-up step runs sort users.txt | uniq > users.txt to remove duplicate lines. Afterwards users.txt is empty, every time, although sort and uniq both exit 0. What happened, and how would you remove the duplicates in place safely?

    Model answer, Pro. First step and follow-ups

Flashcards · 6 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. Which status does a pipeline return by default?

    Its last command's, so false | true returns 0 and the failure of false is lost unless you look for it.

  2. Why does a variable set inside a pipeline stage vanish afterwards?

    The stages run at the same time as separate processes, each in a subshell, so whatever a stage sets is gone when the pipeline ends.

  3. Which stream does | carry, and how do you send standard error through too?

    Only standard output. |& or 2>&1 | adds standard error to what the next command reads.

  4. How do you keep every stage's status of a pipeline?

    Copy the PIPESTATUS array on the same line, before anything else runs, with p=("${PIPESTATUS[@]}"), and read the copy, because the next command replaces it.

  5. What does set -o pipefail change?

    The pipeline returns the status of its rightmost command that failed, or 0 when every stage succeeded. Bash has had it since 3.0, and POSIX.1-2024 and dash 0.5.13.5 have it too.

  6. Why can a healthy pipeline such as cmd | head -n 1 report 141 under pipefail?

    When the reader stops early, the writer is killed by SIGPIPE, signal 13, and Bash reports 128 plus 13. Decide from PIPESTATUS, stage by stage, where a 141 is expected.

Key takeaways

  • A pipeline reports its last command’s status, so false | true returns 0 and the failure of false is lost unless you look for it.
  • The stages run at the same time as separate processes, each in a subshell, so a variable set inside a stage does not survive the pipeline, and | carries standard output only unless |& or 2>&1 | adds standard error.
  • PIPESTATUS holds every stage’s status until the next command replaces it, so copy it at once with p=("${PIPESTATUS[@]}") and read the copy.
  • set -o pipefail makes the pipeline return the status of its rightmost failing command, or 0, and it is in Bash since 3.0, in POSIX.1-2024 and in dash 0.5.13.5.
  • When a reader stops early, the writer before it is killed by SIGPIPE and Bash reports its status as 141, 128 + 13, so under pipefail a healthy pipeline can look failed; decide from PIPESTATUS where a 141 is expected.

Terms in this lesson

pipeline
Commands joined by | or |&, each one's standard output flowing into the next one's standard input, all running at the same time. Its exit status is the last command's unless pipefail is set.
SIGPIPE
Signal 13, sent to a process that writes to a pipe nobody has open for reading any more, for example after head has exited. By default it ends the writer, and Bash reports that as status 128 + 13 = 141.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.