53

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 Answers 3

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.

8
  • 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
    Commented Jun 17, 2016 at 0:38
  • @jww [[ … ]] was added in bash 2.02. ((…)) was added in bash 2.0. Commented Jun 17, 2016 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
    Commented Jun 17, 2016 at 23:37
  • 1
    Do parentheses inside double brackets start a subshell?
    – Roland
    Commented Mar 4, 2020 at 12:24
  • 1
    @Roland No, parentheses inside [[ … ]] or $((…)) are just for grouping. Commented Mar 4, 2020 at 17:23
15

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 ...
1
  • 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 Commented Jul 18, 2020 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
5
  • 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.
    – user56041
    Commented Jun 16, 2016 at 13:24
  • 1
    ... but more portable.
    – Kusalananda
    Commented Jun 16, 2016 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
    Commented Jun 16, 2016 at 22:48
  • 1
    There will be no problems as long as you keep to bash or any other shell that understands [[ ... ]].
    – Kusalananda
    Commented Jun 17, 2016 at 5: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: pastebin.com/zM77TXW1
    – KFL
    Commented Aug 16, 2020 at 4:49

You must log in to answer this question.