6

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.
4
  • If shellcheck reports an issue, it will print a link to the description next to the issue.
    – hek2mgl
    Commented Jan 12, 2016 at 23:39
  • @hek2mgl I'm using the local version which only gives me an issue code, but I haven't found anything useful looking for that code Commented Jan 12, 2016 at 23:43
  • 2
    @MatthewHerbst Check this: github.com/koalaman/shellcheck/wiki/SC2166
    – hek2mgl
    Commented Jan 12, 2016 at 23:48
  • @hek2mgl not sure why I couldn't find that, thanks! Commented Jan 12, 2016 at 23:53

1 Answer 1

10

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.

3
  • So, for my basic understanding of POSIX, what that basically means is that the code && and || is more compatible with other machines that might not have extended POSIX? Commented Jan 12, 2016 at 23:46
  • @MatthewHerbst, it's not just a question of whether it's extended or not -- the POSIX spec itself doesn't describe how -a and -o need to behave in sufficient detail for all POSIX-compliant implementations to behave precisely the same way in ambiguous cases. Commented Jan 12, 2016 at 23:54
  • Please note that the outcome of [ affects the $PIPESTATUS array after the first evaluation. Use the internal [[ like in if [[ "${PIPESTATUS[0]}" -ne 0 && "${PIPESTATUS[1]}" -ne 0 ]]; then..., at least for Bash.
    – Halfgaar
    Commented Apr 22, 2020 at 9:11

Not the answer you're looking for? Browse other questions tagged or ask your own question.