2015-06-23
A Bash test
limitation and the brute force way around it
Suppose that you are writing a Bash script (specifically Bash) and
that you want to get a number from a command that may fail and might
also return output '0' (which is a bad value here).
No problem, you say, you can write this like so:
sz=$(zpool list -Hp -o size $pool) if [ $? -ne 0 -o "$sz" -eq 0 ]; then echo failure .... fi
Because you are a smart person, you test that this does the right thing when it fails. Lo and behold:
$ ./scrpt badarg ./scrpt: line 3: [: : integer expression expected
At one level, this is a 'well of course'; -eq
specifically requires
numbers on both sides, and when the command fails it does not output
a number (in fact $sz
winds up empty). At another level it is very
annoying, because what we want here is the common short-circuiting
logical operators.
The reason we're not getting the behavior we want is that test
(in the built in form in Bash) is parsing and validating its entire
set of arguments before it starts determining the boolean values
of the overall expression. This is not necessarily a bad idea (and
test
has a bunch of smart argument processing), but it is inconvenient.
(Note that Bash doesn't claim that test
's -a
and -o
operators
are short-circuiting operators. In fact the idea is relatively
meaningless in the context of test
, since there's relatively
little to short-circuit. A quick test suggests that at least some
versions of Bash check every condition, eg stat()
files, even
when they could skip some.)
My brute force way around this was:
if [ $? -ne 0 -o -z "$sz" ] || [ "$sz" -eq 0 ]; then .... fi
After all, [
is sort of just another program, so it's perfectly
valid to chain [
invocations together with the shell's actual
short circuiting logical operators. This way the second [
doesn't
even get run if $sz
looks bad, so it can't complain about 'integer
expression expected'.
(This may not be the right way to do it. I just felt like using brute force at the time.)
PS: Given that Bash emits this error message whether you like it
or not, it would be nice if it had a test
operator for 'this
thing is actually a number'. My current check here is a bit of
a hack, as it assumes zpool
emits either a number or nothing.
(Updated: minor wording clarification due to reddit, because they're right, 'return 0' is the wrong way to phrase that; I knew what I meant but I can't expect other people to.)