Shell – What’s the difference between ‘test’ and evaluating with /dev/null

shell-scripttest

I noticed different .sh scripts for homebrew which check whether it's installed.

One uses this syntax:

if test ! $(which brew); then

The other uses this syntax:

if ! which brew > /dev/null; then

I get that they are both checking the exit code for which brew, but I'm curious as to whether there are other differences between them. To me, the former is clearer, and I would assume (maybe incorrectly) more efficient since it doesn't have to redirect any output.

Also, why does the second actually execute which brew, regardless of whether the output is sent to /dev/null or not? Just how shell works?

Best Answer

The first is a clumsy hack, the second is a common mistake.

These two tests do something completely different, which happens to produce the same outcome.

if test ! $(which brew); then

This tests whether the output of which brew is empty.

  • If brew is on the search path, then which brew produces a word of output, so the test command receives two arguments: ! and the path to brew. When test has two arguments, the first of which is !, it returns true if the second argument is empty (which isn't the case here) and false otherwise.
  • If brew isn't on the search path, then the output of which brew is empty, so the test command receives a single argument which is !, so test returns true.

Note that this command will produce an error message and a failure status if the path to brew contains whitespace, because that's what an unquoted command substitution means. It so happens that a failure status was the desired outcome here, so this works in a roundabout way.

This command does not test the exit code of which brew. The exit code is ignored.

if ! which brew > /dev/null; then

This is the straightforward way to test if which brew succeeds. It doesn't depend on anything brittle except which itself.

which brew is always called in both cases. Why would it matter that the output is redirected to /dev/null? “Hide this command's output” does not mean “don't run this command”.

The proper way to test if brew is not available on the command search path is

if ! type brew >/dev/null 2>/dev/null; then

See Why not use "which"? What to use then?

Related Question