What does "test $2 &&" mean in this bash script?
That's an incorrect way to write:
#!/bin/sh -
set -e # Exit if any command fails
# If multiple args given run this script once for each arg
if [ "$#" -gt 1 ]; then
for arg do
"$0" "$arg"
done
exit
fi
I think what the author intended to do was check if the second argument was a non-empty string (which is not the same thing as checking whether there are more than 1 argument, as the second argument could be passed but be the empty string).
test somestring
A short form of
test -n somestring
Returns true if somestring
is not the empty string. (test ''
returns false, test anythingelse
returns true (but beware of test -t
in some shells that checks whether stdout is a terminal instead)).
However, the author forgot the quotes around the variable (on all the variables actually). What that means is that the content of $2
is subject to the split+glob operator. So if $2
contains characters of $IFS
(space, tab and newline by default) or glob characters (*
, ?
, [...]
), that won't work properly.
And if $2
is empty (like when less than 2 arguments are passed or the second argument is empty), test $2
becomes test
, not test ''
. test
does not receive any argument at all (empty or otherwise).
Thankfully in that case, test
without arguments returns false. It's slightly better than test -n $2
which would have returned true instead (as that would become test -n
, same as test -n -n
), so that code would appear to work in some cases.
To sum up:
to test if 2 or more arguments are passed:
[ "$#" -gt 1 ]
or
[ "$#" -ge 2 ]
to test if a variable is non-empty:
[ -n "$var" ] [ "$var" != '' ] [ "$var" ]
all of which are reliable in POSIX implementations of
[
, but if you have to deal with very old systems, you may have to use[ '' != "$var" ]
instead for implementations of
[
that choke on values of$var
like=
,-t
,(
...to test if a variable is defined (that could be used to test if the script is passed a second argument, but using
$#
is a lot easier to read and more idiomatic):[ "${var+defined}" = defined ]
(or the equivalent with the test
form. Using the [
alias for the test
command is more common-place).
Now on the difference between cmd1 && cmd2
and if cmd1; then cmd2; fi
.
Both run cmd2
only if cmd1
is successful. The difference in that case is that the exit status of the overall command list will be that of the last command that is run in the &&
case (so a failure code if cmd1
doesn't return true (though that does not trip set -e
here)) while in the if
case, that will be that of cmd2
or 0 (success) if cmd2
is not run.
So in cases where cmd1
is to be used as a condition (when its failure is not to be regarded as a problem), it's generally better to use if
especially if it's the last thing you do in a script as that will define your script's exit status. It also makes for more legible code.
The cmd1 && cmd2
form is more commonly used as conditions themselves like in:
if cmd1 && cmd2; then...
while cmd1 && cmd2; do...
That is in contexts where we care for the exit status of both those commands.
test $2 && some_command
is composed of two commands:
test $2
is checking if the string after expansion of the second argument ($2
) is of non-zero length i.e. it is necessarily checkingtest -n $2
([ -n $2 ]
). Note that, as you have not used quotes around$2
,test
will choke on values with whitespaces. You should usetest "$2"
here.&&
is a short-circuit evaluation operator, which indicates that the command after&&
will only be run if the command before it is successful i.e. has exit code 0. So in the above example,some_command
will only be run iftest $2
succeeds i.e. if the string is of non-zero length, thensome_command
will be run.