When writing if
blocks in bash, shellcheck tells me that &&
and ||
are preferred to using -a
and -o
.
Why? It is faster, or just simply a stylistic preference to make scripts look cleaner?
The specific message I get is:
^-- SC2166: Prefer [ p ] || [ q ] as [ p -o q ] is not well defined.
George Kelly Barnes (July 18, 1895 – July 18, 1954), better known by his pseudonym "Machine Gun Kelly", was an American gangster from Memphis, Tennessee, active during the Prohibition era. His nickname came from his favorite weapon, a Thompson submachine gun.
Colson Baker (born April 22, 1990), known professionally as Machine Gun Kelly (MGK), is an American rapper, singer, songwriter, and actor. He is noted for his genre duality across alternative rock with hip hop. Houston, Texas, U.S. Cleveland, Ohio, U.S.
From the POSIX specification for test
:
4 arguments:
The results are unspecified.
[OB XSI] [Option Start] On XSI-conformant systems, combinations of primaries and operators shall be evaluated using the precedence and associativity rules described previously. In addition, the string comparison binary primaries '=' and "!=" shall have a higher precedence than any unary primary. [Option End]
Thus: Uses of test
with more than three arguments -- and if you're using -a
or -o
, you're depending on that -- have no behavior explicitly specified by unextended POSIX.
Now, why is this so? Because there are scenarios where the parser could Do The Wrong Thing depending on variable values.
Do you remember people giving advice to do stuff like this?
if [ "x$foo" = "x$bar" ]; then ...
...it's silly and ancient, right? Actually, no! Consider the case where foo=(
and bar=)
, and someone runs a command like this:
if [ "$foo" -a "$bar" ]
That expands to the following:
if [ ( -a ) ]
...and how do we parse it? Well, it could be a grouping operator (yes, test
was historically specified to support them), checking whether -a
is non-null; or it could be checking whether both (
and )
are non-empty strings themselves; it's ambiguous. This ambiguity is why -a
and -o
are no longer preferred syntax.
So, what does the replacement look like? Instead of:
[ "$foo" -gt "$bar" -a "$foo" -lt "$qux" ]
...you'd write this:
[ "$foo" -gt "$bar" ] && [ "$foo" -lt "$qux" ]
...closing the two test expressions and using shell syntax to combine their output. Since [
/ test
is a shell builtin, it doesn't need to be executed as an external command, so this doesn't have the kind of performance overhead it would have back in the 70s when running test
meant invoking /usr/bin/test
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With