52

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

3 Answers3

88

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. –  Jun 17 '16 at 00:38
  • @jww [[ … ]] was added in bash 2.02. ((…)) was added in bash 2.0. – Gilles 'SO- stop being evil' 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. –  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). –  Jun 18 '16 at 03:16
  • 1
    Do parentheses inside double brackets start a subshell? – Roland Mar 04 '20 at 12:24
  • 1
    @Roland No, parentheses inside [[ … ]] or $((…)) are just for grouping. – Gilles 'SO- stop being evil' Mar 04 '20 at 17:23
  • if ([ $A -eq 0 ] || [ $B -ne 0 ]) && [ $C -ne 0 ]; : What is wrong with using () instead of {}, so we don't need the semi-colon. How is using a subshell a problem ? – bob dylan Mar 07 '21 at 10:21
  • @bobdylan Using a subshell is not a problem in this particular case. But there's no advantage to using a subshell here, and it's a more complicated concept than grouping. – Gilles 'SO- stop being evil' Mar 07 '21 at 13:04
14

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 ...
Stephen Kitt
  • 434,908
  • Thank you, Stephen. In my case, I used your first example thusly...

    regex='^[0-9]+$' length=${#datearg} if ! [[ $datearg =~ $regex && $length -eq 8 ]] ; then echo "error: Not a number of lenth of 8"; fi

    – Michael Lueck Jul 18 '20 at 20:46
5

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
  • 1
    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. –  Jun 16 '16 at 13:24
  • 1
    ... 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? –  Jun 16 '16 at 22:48
  • 1
    There will be no problems as long as you keep to bash or any other shell that understands [[ ... ]]. – Kusalananda Jun 17 '16 at 05:16
  • 2
    The down side of using -a is it doesn't short circuit if the first expression is falsy. So if you rely on short circuiting to prevent a potential fatal 2nd expression from running, then you cannot use -a. Example: https://pastebin.com/zM77TXW1 – KFL Aug 16 '20 at 04:49