logical OR in bash with two function calls in sub conditions

94 views Asked by At

I have a bash function returning bool

_on_host() { [[ $(hostname) =~ ^$1.* ]]; }

It works well. How to to write a function which will answer if I'm on host A OR on host B ?

My failed attempts:

$ juju() { [[ _on_host aaa || _on_host bbb ]];
-bash: conditional binary operator expected
-bash: syntax error near `aaa'

or

$ juju() { [ _on_host x100 -o _on_host borealis ]; }
$ juju
-bash: [: too many arguments

or

juju() { [[ _on_host aaa ]] || [[ _on_host bbb ]]; }
-bash: conditional binary operator expected
-bash: syntax error near `aaa'
2

There are 2 answers

0
ruohola On BEST ANSWER

You can simply use:

juju() { _on_host aaa || _on_host bbb; }
0
Weijun Zhou On

As I have earlier commented and the other answer states, the solution is simply use

juju() { _on_host aaa || _on_host bbb; }

The following are some explanations.

TLDR: || outside [[ .. ]] and [ .. ] is used to form a command list (and hence is used to combine commands) while || inside [[ .. ]] and [ .. ] are used to combine expressions, which can be of one of several forms but none of those forms would run a command and test it's return status for you.


In bash, command1 || command2 forms a OR-list of commands. Quote from the bash manual

An OR list has the form

command1 || command2

command2 is executed if, and only if, command1 returns a non-zero exit status.

The return status of AND and OR lists is the exit status of the last command executed in the list.

Since _on_host aaa is already a complete command, you just need to form a OR-list to obtain the final return status.

On the other hand, in _on_host(), if you just write $(hostname) =~ ^$1.*, it is interpreted as a command and will take $(hostname) as the command name (assuming no spaces in the variable) and =~ and ^$1.* as the two command arguments (which is subject to parameter expansion and globbing), that's why you need to use a condition construct ([[ .. ]]) to do the testing.

The following explains why your attempts don't work.

The first one

juju() { [[ _on_host aaa || _on_host bbb ]];

does not work because [[ .. ]] is a conditional construct, and the || operator inside a conditional construct takes two expressions (instead of two commands).

expression1 || expression2

True if either expression1 or expression2 is true.

Each of the expressions must be in the form of a conditional expression. _on_host aaa does not match any of those forms and hence Bash is confused and gives you the error. The important thing is that as you can see from the list, there is no form of condition expression that tests the return value of a command for you. All the tests listed are about strings, files, shell options and variable names.

The second attempt

[ _on_host x100 -o _on_host borealis ]

invokes an external command [, which is an alias for test. The manual page for test can be seen here. The syntax is quite similar to the conditional construct in bash and again we can see that -o requires two expressions,

   EXPRESSION1 -o EXPRESSION2
          either EXPRESSION1 or EXPRESSION2 is true

If we interpret _on_host aaa as an expression we once again find that there is no conditional expression that _on_host aaa can possibly match.

The third form

[[ _on_host aaa ]] || [[ _on_host bbb ]];

is forming a OR-list of commands where each of these commands is itself a conditional. The construct [[ .. ]] || [[ .. ]] itself is valid, but there is hardly a reason to use it over [[ .. || .. ]].

The lesson is that don't attempt to run a command and somehow check it's return status inside a [[ .. ]] or [ .. ]. It just won't work.