On Mon, 1 Sept 2025 at 04:35, Maria <[email protected]> wrote:

> Bash will crash with exit code 141.


For complicated historical reasons, when a process is killed by a signal,
the parent shell pretends that it exited with a status of 128 plus the
terminating signal number. This is why you see what looks like "exit code
141".

It is important to understand that what you are seeing is the shell being
killed by the kernel using signal 13; despite this being reported as "exit
code", the shell does NOT exit voluntarily, and has no chance to perform
cleanup.

(It's technically possible to catch a signal, or to ignore it. Historically
you would not ordinarily have a login shell process writing to a pipe, even
from a built-in, so the standard is for the shell *not* to attempt to
intercept SIGPIPE, so the standard is to leave the default behaviour
(dying) unmodified, even in a login shell. The addition of the >(...)
extension is what enables a login shell to easily wind up writing to a
pipe, where previously that was awkward and uncommon. Catching or ignoring
SIGPIPE would result in collateral changes affecting subshells and other
subprocesses, so any change would have to be thoroughly thought through,
planned, and tested. Previous similar changes have taken several iterations
to get right.)

The crux of the confusion is the difficulty in identifying when
subprocesses are created.

   - Brackets always ensure a subshell.
   - Components of a non-trivial pipeline each run in their own process,
   and (unless lastpipe is enabled) they're *all* separate from the main
   shell process.
   - External commands run as their own process, though not technically a
   subshell.
   - Backgrounded commands always run in a subshell, including compound
   commands and builtins.
   - Importantly, *foreground* built-in commands *don't* run in their own
   process; they're handled by the shell process itself.

This last point is why the main shell process gets killed when echo
attempts to write to a broken pipe.

You can show this difference by replacing "echo" with "/bin/echo":

$ for i in 1 2 3 4 5 ; do sleep 1 ; /bin/echo $i ; done > >(echo)

where the shell does not get killed because /bin/echo runs in a separate
process (and *that* gets killed).
Another way is by forcing the builtin echo into a subshell:

$ for i in 1 2 3 4 5 ; do sleep 1; ( echo $i ) ; done > >(echo)

Again, the subshell gets killed instead of the main script.

Later you suggested that the shell does not crash if you type:

$ (sleep ; echo) | echo

That would be misinterpreting the situation: the shell *inside the pipeline*
does indeed crash; it's just that you're not notified about it. If you were
to immediately follow up with

$ declare -p PIPESTATUS

you would see

 declare -a PIPESTATUS=([0]="141" [1]="0")

again showing 141 indicating that the subshell in the first half of the
pipeline was indeed killed by SIGPIPE.


If you never want an interactive shell to die when SIGPIPE is sent to it,
set a signal-handling trap:

$ trap : PIPE

However be aware that this will be inherited by subshells, and you probably
want to allow them to be terminated:

  function sigpipe_handler {
    ((BASHPID == $?)) && return  # do nothing in the main shell (not in a
subshell)

    # the following line is optional; it blocks ERR and EXIT traps, and
ensures the process dies as expected
    # unblock the signal, and then kill ourself.
    trap - PIPE ; kill -s PIPE "$BASHPID"

    # pick an exit code. I recommend using 128|SIGPIPE so as to emulate
what is seen outside the subshell,
    # so that we pretend that we've been killed.
    exit 141
  }

  trap sigpipe_handler PIPE

(You can of course use any exit code, but 141 approximates what would have
happened to the subshell without this trap.)

-Martin

PS: (mostly for the benefit of others in this thread) it's not that the
pipe "fills up". If it were simply filled up, the writing process would be
blocked until the pipe is drained, or until all its readers have closed,
whichever comes first.
The kernel raises SIGPIPE (signal #13 for Linux) because it lacks any
reader whatsoever, either when the write is initiated, or when the write
unblocks.

Reply via email to