8

I want strict mode in my scripts. I would also appreciate portability.

set -o pipefail seems compulsory. Yet shellcheck(a static linter) is unhappy that "In POSIX sh, set option pipefail is undefined".

Is it correct? If so, is this a bash solely feature or is it rather prolific?

1

3 Answers 3

22

The pipefail option comes from ksh93 and is also present in a few other shells. It was added:

A request to add it to the POSIX standard was submitted in 2013 and a resolution accepted in 2019. So it will be in the next major revision of the standard due soon.

It will still probably take some more time for remaining implementations (mostly dash, bosh and ksh88-based shells) to add support for it. After which the "EOL" clock will start ticking and 10 years later you'll be able to assume all sh implementations on currently supported systems will support it.

1
  • 1
    It is included in the current POSIX draft P1003.1™-202x/D2.1 (August 2021, at page 2383): pipefail Derive the exit status of a pipeline from the exit statuses of all of the commands in the pipeline, not just the last (rightmost) command, as described in Section 2.9.2. Commented Oct 26, 2022 at 13:29
8

The pipefail shell option is specific to a number of shells that shellcheck claims to support1. You can use this shell option and be portable, if by "portability" you assume that any target system has a shell that supports it (and any other constructs that you may be using). This is the same type of "portability" that you get with any other specific scripting language.

The shellcheck linter will complain if it finds set -o pipefail in a shell script that is a sh script, since it's currently not supported by POSIX sh.

To ensure that your script is a script interpreted by the bash shell (or any specific shell that you are coding for), the script should have a #!-line pointing to the correct shell interpreter, e.g.,

#!/bin/bash

or possibly

#!/usr/bin/env bash

or something similar.

With a proper #!-line that additionally indicates that the script will be interpreted by a particular shell that is not sh, the shellcheck linter will not complain about you setting the pipefail shell option in your script.

If you don't use a #!-line in your scripts, then you should consider doing so (or always run your scripts with an explicit interpreter on the command line). Meanwhile, the shellcheck command line tool can be told to switch to mode using its -s (or --shell=) option:

shellcheck --shell=bash myscript

1I suspect that shellcheck has a "POSIX sh mode" and an "other mode" to support bash, dash, and ksh (shellcheck does not claim to support zsh). The pipefail shell option is documented to work with bash, and ksh. The zsh shell has a PIPE_FAIL shell option that can be set in the same way. The dash shell does not support the option, but if the #!-line mentions dash, shellcheck won't complain about pipefail.

4
  • It does not complain if using ksh or dash only sh. For sh it is on from v0.7. (If I read this correctly) On my system set -o pipefail has effect on bash, ksh, zsh albeit strange error code in ksh (93u+) - fails (as expected) with sh, dash, tcsh ...
    – ibuprofen
    Commented Jun 19, 2021 at 11:46
  • @ibuprofen I suspect that shellcheck has a "POSIX sh mode" and an "other mode". The pipefail shell option is documented to work with bash, and ksh. The zsh shell has a PIPE_FAIL shell option that can be set in the same way. Dash does not support the option.
    – Kusalananda
    Commented Jun 19, 2021 at 13:20
  • 1
    In zsh, case and underscores are ignored in option names, so set -o pipefail works as well. In yash, that's case, underscores or hyphens, so set -o PIPE_FAIL, set -o PIPE-FAIL or set -o --pipefail (but also set --pipe-fail) would also work. In bash, case is significant and underscore/hyphens are not ignored. bash also has 2 sets of options, one set with set -o, the other with shopt -s. Some of the ones set with set -o can also be passed as bash --option, but not pipefail. Commented Jun 19, 2021 at 20:59
  • Such a simple solution. Such a comprehensive answer. Thanks!
    – Vorac
    Commented Jun 20, 2021 at 1:14
0

What do you want to happen when script runs on shell that doesn't support pipefail?

Turns out order matters on some shells but is not the whole story. bash cares about order even within single set command — it continues on set -o NoSuchOption -e vs. exits on set -e -o NoSuchOption.

  • 👉 If you want to exit if pipefail is not supported, set -e; set -o pipefail looks enough. (the shells I tried print an error message in such case)

But it gets worse: bash --posix (== /bin/sh on many systems) and dash exit immediately on set -o NoSuchOption even without set -e anywhere!
bash can be appeased by set -o NoSuchOption || true (not sure why that works) but not dash, it really crashes on the spot.

  • 👉 If you want to continue running on all shells, even ones that don't support pipefail, it looks like you must first probe whether it's supported. This is best I came up with for bash, bash --posix and dash, no idea if it's portable yet more other shells:
set -e
trap '
  s=$? p=("${PIPESTATUS[@]}")
  echo "$0: Error ${p[*]} on line $LINENO: $BASH_COMMAND" 1>&2
  exit $s
' ERR 2>/dev/null || true

set -o | grep -q pipefail && set -o pipefail
echo cont1

true | sh -c 'exit 2' | false | true
echo cont2

true | sh -c 'exit 3'
echo cont3

Here bash (whether posix mode or not) stops on the mid-pipeline errors:

cont1
t.sh: Error 0 2 1 0 on line 13: true

while dash doesn't support pipefail, only aborted where right-most command exited non-0:

cont1
cont2

dash also doesn't support ERR traps (https://stackoverflow.com/a/58297505/239657 tries to approximate it but not well enough imho)

  • ❓ but is this a good idea? You risk writing code that assumes pipefail is in effect but it might not be; and you can't explicitly check $PIPESTATUS array, e.g. dash doesn't have it either.

    So this was an interesting exercise, but I'd recommend to exit where pipefail is unsupported. And IMHO once you care about such things you want to require bash specifically — it gives you other goodies too like ERR, printing stack traces with $BASH_SOURCE, $BASH_LINENO, $FUNCNAME arrays etc...

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .