4

I want to check that of two variables both or neither are set. I've tried multiple options, and this is the most clean solution I've come up with:

if [ ! -z $A -a -z $B ] || [ -z $A -a ! -z $B ]; then
  #error
fi
  #success

When I run the script with both A and B set - it runs fine. But when I run it with A missing I get:

./test.sh: line 3: [: too many arguments
./test.sh: line 3: [: too many arguments

line 3 being the condition statement.

When I run it with B missing, I get:

./test.sh: line 3: [: argument expected
./test.sh: line 3: [: argument expected

Is it my condition that has wrong syntax or am I missing smth else?

Milkncookiez
  • 6,817
  • 10
  • 57
  • 96

3 Answers3

7

You should try to avoid -a; it's non-standard and considered obsolete by the POSIX standard. Since || and && have equal precedence, you need to use { ... } to properly group the individual tests.

(This is in addition to the immediate need to quote your parameter expansions.)

if { [ ! -z "$A" ] && [ -z "$B" ]; } || { [ -z "$A" ] && [ ! -z "$B" ]; }; then

However, a simpler expression might be

if [ -z "$A$B" ] || { [ "$A" ] && [ "$B" ]; }; then
  1. The concatenation of two strings is empty if and only if both strings are also empty.
  2. [ "$A" ] is short for [ -n "$A" ], which is equivalent to [ ! -z "$A" ].

Using bash's [[ ... ]] command, you can write the more natural

if [[ -z $A && -n $B || -n $A && -z $B ]];

Quotes are optional in this case, and || and && are usable inside [[ ... ]] with the precedence you expect.

chepner
  • 497,756
  • 71
  • 530
  • 681
3

Quote your variables:

if [ ! -z "$A" -a -z "$B" ] || [ -z "$A" -a ! -z "$B" ]; then

If the variables are unquoted and unset, they are replaced with nothing, meaning that the command essentially becomes:

if [ ! -z -a -z ] || [ -z -a ! -z ]; then

resulting in the error you see.

user000001
  • 32,226
  • 12
  • 81
  • 108
1

You forgot to use quotation marks around your vars:

if [ ! -z "$A" -a -z "$B" ] || [ -z "$A" -a ! -z "$B" ]; then
    echo "error"
fi

Bash will replace your vars in your script with the values, so when A=5 and B is unset, your version will read:

if [ ! -z 5 -a -z  ] || [ -z 5 -a ! -z ]; then

You see that the syntax is wrong, as -z expects an argument. When using quotes, is reads:

if [ ! -z "5" -a -z "" ] || [ -z "5" -a ! -z "" ]; then

AS you can see, now the argument for B is an empty string, which is valid.

Also your version would have failed when setting A="string with spaces" when unquoted.

Nidhoegger
  • 4,973
  • 4
  • 36
  • 81