Using [email protected]
inside a bash test
expression gives odd results.
Here's a minimal test script to reproduce the issue (among the alternative formulations for the same test which do pass without error):
#! /bin/bash
# file path: ./bash_weirdness.sh
echo "args: '[email protected]'"
echo "--- empty ---"
if test "" ; then echo "1.A1.TEST - not empty?"; fi
if test -z "" ; then echo "1.A2.EMPTY - empty?"; fi
if test -n "" ; then echo "1.A3.NE - not empty?"; fi
if ! test "" ; then echo "1.B1.NOT.TEST - empty?"; fi
if ! test -z "" ; then echo "1.B2.NOT.EMPTY - not empty?"; fi
if ! test -n "" ; then echo "1.B3.NOT.NE - empty?"; fi
echo "--- space ---"
if test " " ; then echo "2.A1.TEST - not empty?"; fi
if test -z " " ; then echo "2.A2.EMPTY - empty?"; fi
if test -n " " ; then echo "2.A3.NE - not empty?"; fi
if ! test " " ; then echo "2.B1.NOT.TEST - empty?"; fi
if ! test -z " " ; then echo "2.B2.NOT.EMPTY - not empty?"; fi
if ! test -n " " ; then echo "2.B3.NOT.NE - empty?"; fi
echo "--- \[email protected] ---"
if test "[email protected]" ; then echo "3.A1.TEST - not empty?"; fi
if test -z "[email protected]" ; then echo "3.A2.EMPTY - empty?"; fi
if test -n "[email protected]" ; then echo "3.A3.NE - not empty?"; fi
if ! test "[email protected]" ; then echo "3.B1.NOT.TEST - empty?"; fi
if ! test -z "[email protected]" ; then echo "3.B2.NOT.EMPTY - not empty?"; fi
if ! test -n "[email protected]" ; then echo "3.B3.NOT.NE - empty?"; fi
echo "--- \$* ---"
if test "$*" ; then echo "4.A1.TEST - not empty?"; fi
if test -z "$*" ; then echo "4.A2.EMPTY - empty?"; fi
if test -n "$*" ; then echo "4.A3.NE - not empty?"; fi
if ! test "$*" ; then echo "4.B1.NOT.TEST - empty?"; fi
if ! test -z "$*" ; then echo "4.B2.NOT.EMPTY - not empty?"; fi
if ! test -n "$*" ; then echo "4.B3.NOT.NE - empty?"; fi
The first two sections ('empty' and 'space') are in there to verify that test
, test -n
and test -z
work fine; that's how bad the confusion is.
The problem occurs when [email protected]
represents 'no arguments', i.e. when $#
would be zero(0).
Three test runs show what's wrong: notice the '3.A3.NE - not empty?' report in the third run:
$ ./bash_weirdness.sh x
->
args: 'x'
--- empty ---
1.A2.EMPTY - empty?
1.B1.NOT.TEST - empty?
1.B3.NOT.NE - empty?
--- space ---
2.A1.TEST - not empty?
2.A3.NE - not empty?
2.B2.NOT.EMPTY - not empty?
--- [email protected] ---
3.A1.TEST - not empty?
3.A3.NE - not empty?
3.B2.NOT.EMPTY - not empty?
--- $* ---
4.A1.TEST - not empty?
4.A3.NE - not empty?
4.B2.NOT.EMPTY - not empty?
Which is fine.
$ ./bash_weirdness.sh ""
->
args: ''
--- empty ---
1.A2.EMPTY - empty?
1.B1.NOT.TEST - empty?
1.B3.NOT.NE - empty?
--- space ---
2.A1.TEST - not empty?
2.A3.NE - not empty?
2.B2.NOT.EMPTY - not empty?
--- [email protected] ---
3.A2.EMPTY - empty?
3.B1.NOT.TEST - empty?
3.B3.NOT.NE - empty?
--- $* ---
4.A2.EMPTY - empty?
4.B1.NOT.TEST - empty?
4.B3.NOT.NE - empty?
Which is fine too.
$ ./bash_weirdness.sh
->
args: ''
--- empty ---
1.A2.EMPTY - empty?
1.B1.NOT.TEST - empty?
1.B3.NOT.NE - empty?
--- space ---
2.A1.TEST - not empty?
2.A3.NE - not empty?
2.B2.NOT.EMPTY - not empty?
--- [email protected] ---
3.A2.EMPTY - empty?
3.A3.NE - not empty?
3.B1.NOT.TEST - empty?
--- $* ---
4.A2.EMPTY - empty?
4.B1.NOT.TEST - empty?
4.B3.NOT.NE - empty?
Which is not fine at all: notice the incorrect '3.A3.NE' result.
"[email protected]" and test
don't mix
test -n "[email protected]"
This test isn't properly written. The -n
test expects a single argument and tells you if that argument is an empty string or not. "[email protected]"
will expand into one word, multiple words, or even no words at all. It is therefore inappropriate to write test -n "[email protected]"
.
If you want to check that arguments were passed, use one of these:
test $# -gt 0
[[ $# -gt 0 ]]
(($# > 0))
(($#))
If you want to check that arguments were passed and also are non-empty, then use "$*"
instead. $*
always expands to a single string, so it's compatible with test -n
.
test -n "$*"
[[ -n $* ]]
The confusing case of test -n
$ test -n; echo $?
0
So why the heck does this test pass? Well, if you don't pass an argument to -n
then bash doesn't see the -n
as an operator. Instead, it understands the test as a form of
test STRING
If you write test STRING
without an operator, it's implicitly equivalent to test -n STRING
. test STRING
is a shorthand way of testing if STRING
is non-empty.
In other words, test -n
is equivalent to
test -n '-n'
And this test passes since '-n'
is a non-empty string.
"[email protected]" is awesome
By the way, the way "[email protected]"
behaves is in fact a great thing. "[email protected]"
is the best way to access the full argument list while handling whitespace correctly and avoiding issues with word splitting and globbing. When in doubt, use "[email protected]"
.
And if you want to store "[email protected]"
in a variable, use an array.
args=("[email protected]")
for arg in "${args[@]}"; do
echo "$arg"
done
That will preserve the multi-wordedness of "[email protected]"
.