3

This question already has an answer here:

Perhaps this applies to more than just bash, but I am confused about the role of brackets in if-statements. Most examples seem to have the following format

if [ expression ]; then
    #do stuff
fi

But this doesn't always work for me. For example I have the following script test.sh:

#!/bin/bash

flag=False
if ! $flag; then
    echo "No brackets: Flag is $flag"
fi
if [ ! $flag ]; then
    echo "With brackets: Flag is $flag"
fi
echo "The end."

Which prints

$ ./test.sh
No brackets: Flag is False
The end.

So the statement using brackets is either ignored or it evaluates to False. Why does this happen? What do the brackets do?

I've also seen double brackets. How are those used?

--

Edit: the questions claimed as duplicates (this and this) answer only a small part of this question. I'm still unclear why the syntax with brackets would fail (it seems to me that test ! false should evaluate to true) and why the syntax without brackets succeeds (although, as @ilkkachu mentions in the comment, it seems like it should actually fail as well?).

marked as duplicate by Sergiy Kolodyazhnyy, elbarna, Thomas, RalfFriedl, Romeo Ninov Nov 16 '18 at 7:09

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • 1
    This could be a good reference for you to read over. linuxacademy.com/blog/linux/… – JayJay Jun 7 '17 at 21:58
  • 1
    flag=False; if ! $flag ; then ... should give you an error, unless you really have a command called False. (false would be standard, however) – ilkkachu Jun 7 '17 at 22:33
  • ... for me if [ !$flag ]; then works (no space between ! and $flag) – Michael D. Jun 12 '17 at 19:48
  • 1
    I have googled that if test have 2 arguments and If the first argument is ! (exclamation mark), the expression is true if, and only if, the second argument is null. i found it here – Krzysztof Stasiak Jun 12 '17 at 20:11
2

On any Unix other than those that have case-insensitive filesystems (like macOS), the if statement

if ! $flag; then
    echo "No brackets: Flag is $flag"
fi

would generate the output

bash: False: command not found
No brackets: Flag is False

On macOS, the error would not be generate as the False command is the same as the standard (external) false command due to the case-insensitivity of the filesystem.

In either case, the test on ! $flag is true (but for different reasons), so the text is printed. On most Unices, ! $flag is true since it runs the False command, fails, and the failure is negated. On macOS, it runs the external false utility, which returns false, which is negated.

The second if statement,

if [ ! $flag ]; then
    echo "With brackets: Flag is $flag"
fi

works the same way on any Unix. It calls the [ utility, which is the same as the test utility (but [ requires a matching ] as its last operand), with the two arguments ! and False. By default, test will return a true value if the string argument has non-zero length, which it has, but it's negated by the ! so test returns false and the echo is never executed.

For clarity, it is better to use the -n test to test for a non-empty string:

if [ -n "$flag" ]; then ...

or -z to test for an empty string:

if [ -z "$flag" ]; then ...

The quoting of the variable expansions is not important in this particular piece of code as $flag expands to a single word that is not a filename globbing pattern. In general though, variable expansions should be double quoted to avoid word splitting and filename globbing.

For a discussion about [ vs. [[, see

Related:

  • @Christopher Hmm... My main disk is still case-insensitive with APFS, but that may be because it's been converted from case-insensitive HFS+ with the Mojave update. BTW, HFS+ had a case-sensitive option too, but the default was case-insensitive. – Kusalananda Nov 15 '18 at 19:02
  • Whether $flag expand to one or more words and which depends on the value of $IFS. It still doesn't make sense to leave it unquoted when you know $flag contains False. – Stéphane Chazelas Nov 16 '18 at 0:16
  • @StéphaneChazelas Bash resets IFS for new shells, and the variable is not set in the script. How could $flag be split in multiple words here? – Kusalananda Nov 16 '18 at 6:36
  • Yes, it may be OK if that script in its entirety is not modified or that code is not copy pasted elsewhere (but now it's on a Q&A website, so it's too late) and that script is not sourced. Those are the conditions we'd need to state to say it may be acceptable not to quote. Why would you not quote though? – Stéphane Chazelas Nov 16 '18 at 7:13
1

The exact answer is a bit complex. It boils down to understanding that the word false is both a command and an string (depending on context).

A longer description:

Lets start with the string A_Non_Existing_Command.

str=A_Non_Existing_Command

So, a (In general: please quote variable expansions.)

$ if $str; then echo yes; else echo no; fi
bash: A_Non_Existing_Command: command not found
no

Will print no, as the command executed fails (it doesn't exist) and the else part gets executed. Of course, there is an error message as well (more later).

However, the test command, either test or [ (read help test inside bash) will test if:

the length of the string between brackets is non-zero

And, so:

$ if [ "$str" ]; then echo yes; else echo no; fi
yes

Prints a yes (as the string has a non-zero amount of characters (usually bytes)). It doesn't matter that the string is also the name of a non-existing command. In this case the value of the variable is interpreted (evaluated) as an string.

And, of course, the negative test will print no:

$ if [ ! "$str" ]; then echo yes; else echo no; fi
no

Which is exactly equivalent to [ -z "$str" ].

Now, changing the str content to False and executing the same commands:

 $ str=false
 $ if     "$str"  ; then echo yes; else echo no; fi
 no

 $ if [   "$str" ]; then echo yes; else echo no; fi
 yes

 $ if [ ! "$str" ]; then echo yes; else echo no; fi
 no

If you get the same with str=False (without errors) is probably because you are in a system with a case-insensitive filesystem (where searching for False will find false without any error).

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