Search code examples
bashif-statementsyntaxgit-bashboolean-expression

What actually happens when we put conditions without [] (brackets) in if else block in a bash script?


final=''
if [ $final ]; then echo "final"; else echo "not final"; fi;

Look at this code above. How would it apply and why, if I remove the brackets in the if block?
How does it evaluate the condition in that situation?

Below is how I was trying to write the condition. I actually forgot the syntax at that point. It prints final in the console. At that time I was expecting it to print not final. (I guess I somewhere read that in bash conditions, empty strings evaluate to false). I knew there is something wrong in the syntax though there was no error.

if $final; then printf "final"; else printf "not final"; fi;

Then I wrapped it around brackets. Now it outputs the opposite. not final.

The not of these these statements also give output without any error. Now I can't understand anything.

 if [ $final ]; then printf "final"; else printf "not final"; fi;

Output: not final .

if ! $final; then printf "final"; else printf "not final"; fi;

Output: not final

if [ ! $final ]; then printf "final"; else printf "not final"; fi;

Output: final .
I am totally confused!

I am running these in git-bash in windows.


The answer from the question Checking the success of a command in a bash `if [ .. ]` statement is not a valid answer of my question because I was trying to understand what happens when I omit the [ brackets in an if statement, particularly when dealing with an empty string. The post said that if bracketts are removed then if block will run it as a command, other than testing it. But $final evaluates to an empty string. If I try to run empty strings in the commandline it gives error which it didn't give inside if block.

$ ''
-bash: : command not found

$ ! ''
-bash: : command not found

However, the answer did explain the role of [ in bash scripts and how it works as an alias for test. But, it did not directly clarify behavior of empty strings or direct command evaluation.

I read that answer, and my confusion was not cleared that is why I posted it here. My confusion only increased when I did some more research and read the post Why 0 is true but false is 1 in the shell?


Solution

  • In Bash, using if will simply run a command, and depending on the exit status of that command, it will run the code in the if block, otherwise the else block.

    An if statement in its most basic form:

    if some_command; then
        echo "some_command success: exit status = 0"
    else
        echo "some_command failure: exit status = $?"
    fi
    

    In Unix sh and Bash, [ is an actual command (a shell built-in).

    You can get help on it by using: help [

    The [ command is used for boolean comparisons, such as:

    if [ 4 -eq 4 ]; then echo "equal by math"; fi
    if [ 9 -ge 7 ]; then echo "greater-than-or-equal-to"; fi
    if [ "text-a" == "text-a" ]; then echo "equal by string comparison"; fi
    if [ "text-a" != "text-b" ]; then echo "not equal by string comparison"; fi
    

    Any command can have the exit status inverted using a ! before it, such as:

    if ! some_command; then echo "some_command failed"; fi
    if ! [ 3 -eq 4 ]; then echo "not (3 equals 4)"; fi
    

    In addition, the [ command lets you use ! inside the square brackets to invert the boolean result:

    if [ ! 3 -eq 4 ]; then echo "not (3 equals 4)"; fi
    

    The result will be the same whether it's inside the [, or to the left of the [, but the logic to arrive at the result is different.

    Please Note: There must be a space after the ! to avoid initiating history expansion. This searches your typed in commands looking for a previous command that starts with whatever is after the !. For example !rs would expand to your last entered command beginning with the letters rs, such as: rsync some options here.

    In your code:

    if [ $final ]; then
        echo "final"
    else
        echo "not final"
    fi
    

    You are not quoting your $final variable, which is very risky behaviour. If your variable is empty, or has special characters in it (such as spaces, ], -eq, etc.), this can break your if command. Just remember that variables will be expanded to their values before the command is executed. Always quote your variables inside [, like this:

    if [ "$final" ]; then
    

    Bash also has another extension, [[, which is not a part of Unix sh. It operates the same as [, except that within [[ you can safely use variables without quoting them, such as:

    if [[ $final -eq 7 ]]; then
    

    In your case of removing the [ from the if statement:

    if $final; then
        printf "final"
    else
        printf "not final"
    fi
    

    The variable $final will be expanded before the if statement runs, so it will actually run a command of whatever text is stored in the $final variable.

    For example:

    final="ls -Alh /var"
    if $final; then ...; fi
    
    final="doesnt_exist"
    if $final; then ...; fi
    

    Then it would expand to:

    if ls -Alh /var; then ...; fi
    if doesnt_exist; then ...; fi
    

    And finally, in regards to if [ $final ]; then when $final is empty, it would first expand to this:

    if [ ]; then
    

    On older versions of Bash this would produce an error for me, but it seems on my current v5.0.17 it no longer throws an error, it just silently fails.

    That covers all of your questions.

    But for a bonus, the if statement is actually not necessary in Bash. There are also && and || which tell the shell to run commands on success or failure of a command, such as:

    some_command && echo "success" || echo "failure"
    

    This only allows you to run a single command after && or ||, otherwise it wouldn't be clear which command the failure or success applied to.

    You can string together success chains like this:

    some_command && another_one && third && final || echo "one of the commands failed"
    

    Or you can use braces to include multiple commands on success or failure, such as:

    some_command && { echo "success"; command_2; } || { echo "failure"; command_3; }
    

    I often use this last method in my coding for printing out error messages and quitting, such as:

    My_Installer -o "$Install_Dir" || {
    
        printf "PROBLEM: Failed to copy files to %s.\n\n" "$Install_Dir"
        printf "Quitting.\n\n"
        exit 1
    
    }
    

    Have fun with Bash, hope this helps!