4

I realize this is a simple question, but I am finding a hard time getting an answer due to the finnicky syntax requirements in bash. I have the following script:

if ! [ [ -z "$1" ] || [ -z "$2" ] ]; then
  echo "both arguments are set!"
fi

I get the following output when I run it without arguments:

./test: line 3: [: -z: binary operator expected 
both arguments are set!

I am not expecting any output - neither argument is set. What am I doing wrong?

Mateusz Piotrowski
  • 8,029
  • 10
  • 53
  • 79
Jake
  • 7,565
  • 6
  • 55
  • 68
  • Try to avoid negating with the `-n` test option. – Walter A Oct 08 '15 at 08:33
  • @999999999999999999999999999999 quite the opposite. See the answer from Barmar – Zloj Oct 08 '15 at 08:36
  • From boolean logic: `¬(A v B) => ¬A ^ ¬B`. – Phylogenesis Oct 08 '15 at 08:42
  • @Zloj Not really, `[[]]` is clearly a superior construct. Just because you can do it the way barmar has doesn't make it better... – 123 Oct 08 '15 at 09:00
  • @999999999999999999999999999999 but single brackets ensure portability, are more readable and less confusing. How can double brackets be better? Can you elaborate **why** exactly the `[[ ]]` construct is better? – Zloj Oct 08 '15 at 09:14
  • @Zloj, they are portable, that's it. They are in no way more readable, they have less functionality and need everything to be quoted. – 123 Oct 08 '15 at 09:19

3 Answers3

11

Square brackets are not a grouping construct in bash. [ is just an alias for the test command, the arguments are parsed normally. And || is a shell operator that separates commands.

If you want to group multiple commands, you can use the () subshell syntax.

if ! ([ -z "$1" ] || [ -z "$2" ]);

or the { } grouping syntax:

if ! { [ -z "$1" ] || [ -z "$2" ]; };

Or you could use a single call to test, with its built-in -o operator:

if ! [ -z "$1" -o -z "$2" ]
Barmar
  • 741,623
  • 53
  • 500
  • 612
  • I made a mistake with my first run - this doesn't actually work for me. The expression always returns true, and the statement that follows always executes. Perhaps I'm missing part of your intended script - could you provide a working example? – Jake Oct 08 '15 at 08:51
  • [`!` has higher precedence than `||`](http://tldp.org/LDP/abs/html/opprecedence.html), so you need to use a group (or better yet, rewrite the logic in a more sensible way). – Tom Fenech Oct 08 '15 at 09:13
8

The test shell builtin [ supports the arguments -a and -o which are logical AND and OR respectively.

#!/bin/sh 

if [ -n "$1" -a -n "$2" ]
then echo "both arguments are set!"
fi

Here I use -n to check that the strings are non-zero length instead of -z which shows that they are zero length and therefore had to be negated with a !.

" -n string True if the length of string is nonzero."

"-z string True if the length of string is zero."

If you are using bash it also supports a more powerful type of test [[]] which can use the boolean operators || and &&:

#!/bin/bash 

if [[ -n "$1" && -n "$2" ]]
then echo "both arguments are set!"
fi

In comments it was addressed that none of these samples show how to negate multiple tests in shell, here is an example which does:

#!/bin/sh 

if [ ! -z "$1" -a ! -z "$2" ]
then echo "both arguments are set!"
fi
  • In my original question I have the two tests in separate pairs of brackets. The purpose of this is that I might have more complex expressions with their own operators inside and it would be nice to be able to specify precedence with brackets or parentheses. Bash seems finicky about these as well. Can you explain how to group multiple subconditions together in the manner of the original question, so that a single logical operator can be applied to the compound expression? – Jake Oct 08 '15 at 08:59
  • In your penultimate example, I believe that `[[ -n "$1" || -n "$2" ]]` should be `[[ -n "$1" && -n "$2" ]]` (may as well remove the quotes too). – Tom Fenech Oct 08 '15 at 09:28
  • Updated to reflect your advice Dr. Fenech. –  Oct 08 '15 at 09:32
6

Remember (or learn!) that [ is not part of shell syntax, it is a command.

If you want to group together multiple commands, then you should use { }

if ! { [ -z "$1" ] || [ -z "$2" ]; }

Note that the parser expects to see a line ending or semicolon before the closing }.

For the record, I'd avoid the negation and use && here:

if [ -n "$1" ] && [ -n "$2" ]

...and since you specified , you may as well take advantage of the enhanced test construct:

if [[ -n $1 && -n $2 ]]
Tom Fenech
  • 72,334
  • 12
  • 107
  • 141
  • You can put the and in the middle of the construct instead of making two instances. – 123 Oct 08 '15 at 09:28