Only if it doesn't matter that the script fails non-gracefully. Some scripts are better to either have explicit error handling code, or simply never fail. In particular, scripts you source into your shell should not use set options to change the shell's default behavior.
"Prefer to use set -o nounset."
ALWAYS use this option. You can test for a variable that might not be set with "${FOO:-}". There is no real downside.
"Use set -o pipefail."
Waste of time. You will spend so much time debugging your app from random pipe failures that actually didn't matter. Dont use this option; just check the output of the pipe for sane values.
"Use [[ ]] for conditions"
No!!! Only use that for bashisms where there's no POSIX alternative and try to avoid them wherever possible. YAGNI!
"Use cd "$(dirname "$0")""
Use either "$(dirname "${BASH_SOURCE[0]}")" or grab a POSIX readfile-f implementation.
"Use shellcheck."
This should have been Best Practice #1. You will learn more about scripting from shellcheck than 10 years worth of blog posts. Always use shellcheck. Always.
Also, don't use set -o nounset when set -u will do. Always avoid doing something "fancy" with a Bashism if there's a simpler POSIX way. The whole point of scripts is for them to be dead simple.
For most people, YAGNI means using convenient Bash-isms, because their scripts won't ever be run on environments that don't have Bash.
Edit: Admittedly, someone in this thread pointed out the flaw in my argument, there are plenty of cases where you can't assume you have Bash. I still hold that proofing something for all possible environments is itself a YAGNI.
> Only use that for bashisms where there's no POSIX alternative
This seems like really bad advice because the number of people writing bash massively massively outnumbers the people writing sh. Regex matching, glob matching, proper parsing, &&/||, no need to quote.
I would say the opposite, enjoy all the bashisms like (( )) [[ ]], numeric for loops, extended globs, brace expansion, ranges, OH GOD YES VARIABLE REFERENCES, and only rewrite when you absolutely have to make it work on sh.
Almost nobody knows all those bashisms. Nobody on the team will be able to understand it, edit it. Avoid being fancy if there's an uglier, simpler thing.
> Some scripts are better to either have explicit error handling code, or simply never fail.
Silently ignoring sub-commands that exit with a non-zero code is not the same thing as "never failing". Your script might return 0, but that doesn't mean it did what you expect it to.
> Also, don't use set -o nounset when set -u will do.
`set -o nounset` is a lot easier to understand for the next person to read the script. Yes, you can always open the manpage if you don't remember, but that is certainly less convenient than having the option explained for you.
What shell are you using that doesn't support `set -o nounset`? Even my router (using OpenWRT+ash) understands the long-form version.
> Only use that for bashisms where there's no POSIX alternative
I totally disagree. You expect people to know the difference between `[[ ... ]]` and `[ ... ]` well enough to know what the bash version is required? You expect the next person to edit the script will know that if they change the condition, then they might need to switch from `[` to `[[`?
How do you even expect people to test which of the two that they need? On most systems, `/bin/sh` is a link to `/bin/bash`, and the sh-compatibility mode of bash is hardly perfect. It's not necessarily going to catch a test that will fail in `ash` or `dash`.
I think the "YAGNI" applies to trying to support some hypothetical non-bash shell that more than 99% of scripts will never be run with. Just set your shebang to `#!/bin/bash` and be done with it.
I totally agree about `pipefail`, though. I got burned by this with condition like below:
```
if (foo | grep -Eq '...'); then
```
Since `-q` causes grep to exit after the first match, the first command exited with an error code since the `stdout` pipe was broken.
> Silently ignoring sub-commands that exit with a non-zero code is not the same thing as "never failing".
Well I meant the former. Very useful for things like init scripts where you would prefer the script do as much as it can to get something online.
> What shell are you using that doesn't support `set -o nounset`?
You're right, this does appear to be in POSIX, so I guess it's fine. But it is unusual to see in my experience.
> You expect people to know the difference between `[[ ... ]]` and `[ ... ]` well enough to know what the bash version is required?
No, I want them to use POSIX semantics until they have to do something Bash-y. Simplicity when it doesn't cost anything extra is best practice.
> Just set your shebang to `#!/bin/bash` and be done with it.
Homebrew, Jenkins, Asdf, etc may provide their own version of Bash that is required rather than the system default, and some systems have no /bin/bash at all. So you should use #!/usr/bin/env bash for Bash scripts and #!/usr/bin/env sh for POSIX Shell scripts. This lets the user override the PATH with their required version of Bash for this script. (and the script itself can check for versions of Bash, and even re-exec itself)
> Only if it doesn't matter that the script fails non-gracefully. Some scripts are better to either have explicit error handling code, or simply never fail.
Then handle those errors explicitly. The above will catch those error that you did not think about.
Oh how I hate the double square bracket. It is the source of many head scratching bugs and time wasted. "The script works in my machine!" It doesn't work in production where we only have sh. It won't exit due to an error, the if statement will gobble the error. You only find the bug after enough bug reports hit that particular condition.
After a couple shots to the foot I avoid double square brackets at all cost.
This should be fixed with a shebang and shellcheck. If your shebang is #!/bin/sh, shellcheck will complain loudly about bash-isms. If production is sh and doesn't have bash, there's quite a few other bash-ism you want to check for. You can run shellcheck in CI to check your scripts and return non-zero if they aren't clean, and you can force off warnings for lines that are ok.
EDIT: I should have said, "could be fixed once and for all", "should" is just my opinion.
A common containerization philosophy is to use a bare-minimum base image and add only what you need. Something like an Alpine container doesn't come with Bash.
Alpine docker container only comes with ash shell by default. If you don't use any Bash-isms, you can just write a POSIX shell script. Otherwise if you have many containers from many different sources, you might have to bake all new containers just to add Bash to them.
Only if it doesn't matter that the script fails non-gracefully. Some scripts are better to either have explicit error handling code, or simply never fail. In particular, scripts you source into your shell should not use set options to change the shell's default behavior.
"Prefer to use set -o nounset."
ALWAYS use this option. You can test for a variable that might not be set with "${FOO:-}". There is no real downside.
"Use set -o pipefail."
Waste of time. You will spend so much time debugging your app from random pipe failures that actually didn't matter. Dont use this option; just check the output of the pipe for sane values.
"Use [[ ]] for conditions"
No!!! Only use that for bashisms where there's no POSIX alternative and try to avoid them wherever possible. YAGNI!
"Use cd "$(dirname "$0")""
Use either "$(dirname "${BASH_SOURCE[0]}")" or grab a POSIX readfile-f implementation.
"Use shellcheck."
This should have been Best Practice #1. You will learn more about scripting from shellcheck than 10 years worth of blog posts. Always use shellcheck. Always.
Also, don't use set -o nounset when set -u will do. Always avoid doing something "fancy" with a Bashism if there's a simpler POSIX way. The whole point of scripts is for them to be dead simple.