In bash, writing ${var?} instead of just ${var} or $var means if var isn't defined then bash will throw an error and _not_ execute your command, instead of expanding it to "" and carrying on.
mv file1 file2 $subdir # oops, I overwrote file2
mv file1 file2 ${subdir?} # error message instead of disaster
My favourite use of this is for example commands in documentation, with placeholders for the user to fill in. Then it's OK if a user accidentally copy-pastes it _without_ filling them in!
I sometimes use `set -e` at the top of a script but it does so many unexpected things.
This tip is a more practical option in a lot of cases, thanks!
@Rob_Russell `set -e` is errexit, which is also nice but something else. To have ${var} error on undefined, you need `set -u`.
I reflexively start all of my scripts with ol' reliable `set -euo pipefail`.
@muvlon @Rob_Russell thank _you_, I hadn't been aware of 'set -u' to make this behaviour the default!
(@hendric, I think that's what you were just asking for!)
@muvlon @Rob_Russell @hendric if y'all didn't know, `set -o pipefail` is also very handy - it means that earlier command's exit codes won't be overridden by later commands that have been piped. That is $? is non-zero if any command in the pipeline is.
So `fail | grep blah` still results in $? being 1 (or whatever else)
@nogweii @Rob_Russell @hendric Yep! There are some pitfalls though, which is why I sometimes do opt out of pipefail.
Example: `if foo | grep -q "bar"; then ... ` is something you might like to use. The -q makes grep quiet, i.e. it won't print matches. However! It will also make grep exit (successfully) on the first match. That by itself is fine, but it also closes grep's stdin, so if `foo` checks for errors, it might fail now. And with pipefail, your `if` is now never taken!
@muvlon @nogweii @Rob_Russell @hendric yes, that is awkward. You'd almost like 'died of SIGPIPE' to be an exception, not counting as failure for pipefail purposes. Because it doesn't reflect badly on the pipe writer – it only means the reader lost interest, and it's up to the reader whether that was a bad thing.
(Similarly, why does Python default to printing a huge stack-trace on SIGPIPE, bah)
But one problem: SIGPIPE _not on stdout_ might need to be an exception to the exception.
I basically use the unofficial "bash strict mode" for this.
http://redsymbol.net/articles/unofficial-bash-strict-mode/
Though I prefer the flavour at the bottom of this blog post.
https://olivergondza.github.io/2019/10/01/bash-strict-mode.html
It has been a while since I have stumbled upon a great post of Aaron Maxwell introducing what he refers to as “Unofficial Bash Strict Mode” and started taking advantage of its benefits on everyday bases.olivergondza (PoorlyWritten)
That's nice
I additionally recommend to use
mv file1 file2 "$subdir"
or better (TIL!)
mv file1 file2 "${subdir?}"
as even if $subdir expands to an empty string the surrounding "…" ensure the result is a third argument to mv.
And you can add an error message after the "?", too 😀
mv "{$1?No sourcefile given}" "{$2?No destination given}"
Edit: See the section on parameter substitution in the advanced bash scripting guide.
@ovrim yes, so said many other people in this thread. But in the context I mentioned, this doesn't help: if you're writing an example command in documentation, your reader may not be putting it in a script at all (it's at least as likely they'll run it interactively), and if they do, _they_, not you, make the choice of whether to put 'set -u' at the top of the script!
So it's still valuable to have a way to write a statement that's safe even when you don't control its context.
@oldherl well, the last thing anyone would accuse bash of (or any other POSIX-derived shell) is being modern!
But bash's use of ? here doesn't seem 180° away from the Rust ? operator, say. Both mean "if there's an error, propagate it to some far-away caller and do not continue running the code that would otherwise follow this statement". Every detail of the error representation is different, but the effect on control flow is identical.
Uff, in my head I read this directly the other way around, with the ? version being okay with undefined.