4 ms·
The biggest footgun in `[` and `test` is the single argument behavior. For example, you might attempt to check if a variable is nonempty like so: [ -n $FO
by ridiculous_fish 3y ago
The biggest footgun in `[` and `test` is the single argument behavior. For example, you might attempt to check if a variable is nonempty like so:
[ -n $FOO ]
but if FOO is unset, it expands to nothing (as opposed to the empty string), so this is equvalent to:
[ -n ]
and POSIX requires that the one-argument form of `[` succeed if that argument (here, "-n") is non-empty. So this will falsely report that $FOO is non-empty.
Remember to quote your variables!
- whateveracct 3y agoShellCheck your scripts!
- mr_toad 3y agoOr use set -u to fail early. Or use [ -n ${FOO-} ] which will replace the unset variable with an empty string.
- yjftsjthsd-h 3y agoPreferably you'd use set -u to avoid certain problems, and also keep using shellcheck for all the other things it can catch.
- LegibleCrimson 3y agoThat bottom one will also fail. The empty string is not treated as a shell word. You need double quotes.
- kazinator 3y agoThat is false. There is no difference between ${FOO} and ${FOO-}. Both disappear if unquoted, and FOO is unset or blank: $ printf "<%s>\n" alpha ${beta} omega <alpha> <omega> $ printf "<%s>\n" alpha ${beta-} omega <alpha> <omega> The form ${var-} form is useful for safely evaluating a variable that might be unset, when "set -u" mode is in effect, and for whatever reason we cannot just fix the script so that the variable is set.
- js2 3y agoThey meant "${FOO:-}" which should still be quoted. The general form is "${FOO:-default}" where default can itself be another variable or whatever string you want. I usually prefer to set default values at the top of a script though using this idiom: : "${FOO:=bar}" And when creating a local variable I'll immediately set it to an empty string if there's a chance it won't be assigned later: local foo=""
- kazinator 3y agoThat colon makes no difference if the replacement is blank.
- js2 3y agoHoly crap, 25 years of writing shell scripts and I just learned the difference: With colon, tests variable for unset or empty. Without the colon tests only for unset. It's POSIX too: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02 https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V...
- kazinator 3y agoSo ${foo-bar} will not expand to bar if foo exists, but is empty. The empty value will prevail. ${foo:-bar} will expand to bar. If we have nothing in place of bar, they are effectively same.
- ninkendo 3y ago> and for whatever reason we cannot just fix the script so that the variable is set I use this pattern all the time for variables that should be overridable by whoever is calling the script, ie. `FORCE="${FORCE-no}"`, overridable with `FORCE=yes foo.sh`. I'm not sure of any other way to do this. (Yes, using getopt or other option parsing is better, but for my own scripts, env vars are just such a simpler way to pass options, and after 20 years of using bash/unix/linux I still can't write a getopt stanza from memory.)
- Xiol32 3y agoset -euo pipefail Effectively strict mode for shell scripts.
- sweeter 3y ago"set -x" is a life saver for debugging. Also "set -euo pipefail" so a script just exits on errors or unexpected behavior. If done well you can really reign in squirrely behavior. Bash has some odd behaviors and footguns but it can also be surprisingly reliable and versatile. Ive caught some seriously messed up stuff that I couldn't figure out otherwise, using set -x. Also shellcheck will forcefully hammer good practice into your head and catch a ton of hangups that are hard to know before making the mistakes. also setting backup variables is a good idea like: pictures="${pics_dir:-$HOME/Pictures}"
- dixie_land 3y ago[ x"$FOO" != x"" ]
- andrewshadura 3y agoNo. This hasn't been necessary for decades.
- overtomanu 3y agowhoa, lot of history in this trick https://news.ycombinator.com/item?id=26776956 https://news.ycombinator.com/item?id=26776956
- stouset 3y agoPlease stop with tricks like this and just quote your variables. Everywhere.
- deleted 3y ago[deleted]
- mattrighetti 3y agoIn this case I would use [ -n "${FOO?}" ] so that the script will immediately stop if $FOO is null or unset
- 8n4vidtmkvmk 3y agoSet -xufo pipefail Or whatever the magic string is. Enable all the errors at the start of the script
- jstimpfle 3y agoI think your last sentence needs to go first. Quote your variables! There's no actual footgun in the specification of the test builtin -- the footgun is shell itself. The behaviour that you mention makes sense because [ "$FOO" ] is always the non-empty check regardless what it contains (could be "-n").
- gpvos 3y agoAlso, if $FOO contains a space, it will expand into multiple arguments. Just quote your variables, always.