bash - What do the brackets in if-statements do?

10,914

Solution 1

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:

Solution 2

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).

Share:
10,914

Related videos on Youtube

dkv
Author by

dkv

Updated on September 18, 2022

Comments

  • dkv
    dkv almost 2 years

    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?).

    • LittleSmurfie
      LittleSmurfie about 7 years
    • JayJay
      JayJay about 7 years
      This could be a good reference for you to read over. linuxacademy.com/blog/linux/…
    • ilkkachu
      ilkkachu about 7 years
      flag=False; if ! $flag ; then ... should give you an error, unless you really have a command called False. (false would be standard, however)
    • Michael D.
      Michael D. about 7 years
      ... for me if [ !$flag ]; then works (no space between ! and $flag)
    • Krzysztof Stasiak
      Krzysztof Stasiak about 7 years
      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
    • Kusalananda
      Kusalananda over 5 years
      @ilkkachu They may be running macOS on a case-insensitive filesystem.
    • ilkkachu
      ilkkachu over 5 years
      @Kusalananda, yeah, thanks for reminding me of that horror, I was happy to have forgotten that. (The joke here is that it's a Mac I have on my lap right now, and False and "True" work just fine. rolleyes)
  • Kusalananda
    Kusalananda over 5 years
    @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.
  • Stéphane Chazelas
    Stéphane Chazelas over 5 years
    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.
  • Kusalananda
    Kusalananda over 5 years
    @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?
  • Stéphane Chazelas
    Stéphane Chazelas over 5 years
    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?