14

I'm working with Bash 3, and I'm trying to form a conditional. In C/C++, its dead simple: ((A || B) && C). In Bash, its turning out not so (I think the Git authors must have contributed this code before they moved onto other endeavors).

This does not work. Note that <0 or 1> is not a string literal; it means a 0 or 1 (generally comes from grep -i).

A=<0 or 1>
B=<0 or 1>
C=<0 or 1>
if [ [ "$A" -eq "0" ] || [ "$B" -ne "0" ] ] && [ "$C" -eqe "0" ]; then ... fi

It results in:

line 322: syntax error near unexpected token `[['

I then tried:

A=<0 or 1>
B=<0 or 1>
C=<0 or 1>
if [ ([ "$A" -eq "0" ]) || ([ "$B" -ne "0" ]) ] && [ "$C" -eq "0" ]; then ... fi

it results in:

line 322: syntax error near unexpected token `[['

Part of the problem is search results are the trivial examples, and not the more complex examples with compound conditionals.

How do I perform a simple ((A || B) && C) in Bash?


I'm ready to just unroll it and repeat the same commands in multiple blocks:

A=<0 or 1>
B=<0 or 1>
C=<0 or 1>

if [ "$A" -eq "0" ] && [ "$C" -eq "0" ]; then
    ...
elif [ "$B" -ne "0" ] && [ "$C" -eq "0" ]; then
    ... 
fi
29

The syntax of bash is not C-like, even if a little part of it is inspired by C. You can't simply try to write C code and expect it to work.

The main point of a shell is to run commands. The open-bracket command [ is a command, which performs a single test¹. You can even write it as test (without the final closing bracket). The || and && operators are shell operators, they combine commands, not tests.

So when you write

[ [ "$A" -eq "0" ] || [ "$B" -ne "0" ] ] && [ "$C" -eq "0" ]

that's parsed as

[ [ "$A" -eq "0" ] ||
[ "$B" -ne "0" ] ] &&
[ "$C" -eq "0" ]

which is the same as

test [ "$A" -eq "0" ||
test "$B" -ne "0" ] &&
test "$C" -eq "0"

Notice the unbalanced brackets? Yeah, that's not good. Your attempt with parentheses has the same problem: spurious brackets.

The syntax to group commands together is braces. The way braces are parsed requires a complete command before them, so you'll need to terminate the command inside the braces with a newline or semicolon.

if { [ "$A" -eq "0" ] || [ "$B" -ne "0" ]; } && [ "$C" -eq "0" ]; then …

There's an alternative way which is to use double brackets. Unlike single brackets, double brackets are special shell syntax. They delimit conditional expressions. Inside double brackets, you can use parentheses and operators like && and ||. Since the double brackets are shell syntax, the shell knows that when these operators are inside brackets, they're part of the conditional expression syntax, not part of the ordinary shell command syntax.

if [[ ($A -eq 0 || $B -ne 0) && $C -eq 0 ]]; then …

If all of your tests are numerical, there's yet another way, which delimit artihmetic expressions. Arithmetic expressions perform integer computations with a very C-like syntax.

if (((A == 0 || B != 0) && C == 0)); then …

You may find my bash bracket primer useful.

[ can be used in plain sh. [[ and (( are specific to bash (and ksh and zsh).

¹ It can also combine multiple tests with boolean operators, but this is cumbersome to use and has subtle pitfalls so I won't explain it.

  • Thanks Giles. Does this hold for Bash 2 through Bash 4? I need something mildly portability, but Kusalananda mentioned some things I was not doing are portable (does that imply what I am doing is not portable?). Here, mildly means Bash 2 through Bash 4. – user56041 Jun 17 '16 at 0:38
  • @jww [[ … ]] was added in bash 2.02. ((…)) was added in bash 2.0. – Gilles Jun 17 '16 at 18:10
  • @Giles - thanks. Bash 2 is probably laughable to most. Its due to our governance, and unwillingness to abandon older platforms. Bash 2 and GCC 3 show up on our Fedora 1 testing. We will let an older platform go on occasion, but there has to be reasons for it. We don't subscribe to Apple, Microsoft, Mozilla, etc policy of abandonware. – user56041 Jun 17 '16 at 23:37
  • @jww Please understand that the precedence of || and && change from [ to [[ and ((. Equal precedence in [ (use parenthesis to control the order of evaluation), but && has higher precedence in [[ and (( than || (as in C). – user79743 Jun 18 '16 at 3:16
3

Use [[:

if [[ ( "$A" -eq "0" || "$B" -ne "0" ) && "$C" -eq "0" ]]; then ...

If you prefer [, the following works:

if [ \( "$A" -eq "0" -o "$B" -ne "0" \) -a "$C" -eq "0" ]; then ...
3

Use the -o operator instead of your nested ||. You can also make use of -a to replace && if needed in your other statements.

   EXPRESSION1 -a EXPRESSION2
          both EXPRESSION1 and EXPRESSION2 are true

   EXPRESSION1 -o EXPRESSION2
          either EXPRESSION1 or EXPRESSION2 is true


if [  "$A" -eq "0" -o "$B" -ne "0"  ] && [ "$C" -eq "0" ]; then echo success;fi
  • Thanks. I came across the -a and -o, but I did not experiment with them. Someone on Stack Overflow said to avoid it. I mostly agreed with them since the script is less readable using them. – user56041 Jun 16 '16 at 13:24
  • ... but more portable. – Kusalananda Jun 16 '16 at 15:15
  • @Kusalananda - Thanks for the heads-ups on portability. The script must run on systems with Bash 2 through Bash 4. Will [[ ( "$A" -eq "0" || "$B" -ne "0" ) && "$C" -eq "0" ]] have trouble on them? – user56041 Jun 16 '16 at 22:48
  • There will be no problems as long as you keep to bash or any other shell that understands [[ ... ]]. – Kusalananda Jun 17 '16 at 5:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy