61

In the below simple example, if CONDITION is not set in the script, running the script prints "True". However, one would expect it to print "False". What is the reason for this behavior?

# Set CONDITION by using true or false:
# CONDITION=true or false

if $CONDITION; then
    echo "True"
else
    echo "False"
fi
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Jeff
  • 817
  • 6
  • 12
  • 1
    The Bash has it's own way of checking for an undefined or empty variable .. Your syntax, simply put is thought out wrong. Bash will look at that statement and say OK nothing equals nothing .. So true .. The way Bash handles checking for empty/non-existent variables is `if [ -z ${CONDITION} ]; then` – Zak Jul 06 '22 at 21:14
  • @Zak There's no "equals" here. `if` uses the return code of the condition. – wjandrea Jul 06 '22 at 21:15
  • @wjandrea -- Correct .. But a better way to put it is .. Nothing is something .. so exit 0 .. "true" -- The fact that there are no operators means it will return successful – Zak Jul 06 '22 at 21:18
  • 4
    More details here: https://stackoverflow.com/a/47876317/5303092 – Jeff Jul 06 '22 at 21:18
  • 2
    `$CONDITION` should probably be quoted here. Then you'll get an error message, which I'd consider better behaviour. Also, it should probably be lowercase too, unless it's an environment variable. Then [ShellCheck](https://www.shellcheck.net/) will warn you that it's not assigned. – wjandrea Jul 06 '22 at 21:42
  • [tag:dash] works the same – glenn jackman Jul 06 '22 at 21:57
  • 4
    Note that what you're doing here is storing a command in `$CONDITION`, and running it when entering the `if`. That's kinda icky in that if some part of the script accidentally stores something unexpected there (or if the value comes from the outside environment!), the script would run that as a command. Best case: an error message, worst case: running a command with consequences. It might be better to explicitly use `[ "$CONDITION" = true ]` to test for `true` vs. any other value (or unset variable); or `[ "$CONDITION" ]` to test for any non-empty value vs. an empty value (or unset variable). – ilkkachu Jul 07 '22 at 12:09
  • 4
    As a rule of thumb: if a variable isn't quoted there is a 99.9% chance that your script is wrong or broken anyway. There are only very specific circumstances in which non-quoting a variable expansion is what you really want. – GACy20 Jul 07 '22 at 15:28
  • A nice way to do bool variables in bash is to use 0 and 1, and evaluate them with `if ((foo))` arithmetic expansion, which is true for a non-zero numeric result. [How can I declare and use Boolean variables in a shell script?](https://stackoverflow.com/a/26920580) / [How to perform boolean operations in BASH (outside conditions)](https://unix.stackexchange.com/q/592967). Then you don't have to expand a variable as a command name like `/bin/true` or `true`. – Peter Cordes Jul 08 '22 at 03:22

1 Answers1

80

The argument to if is a statement to execute, and its exit status is tested. If the exit status is 0 the condition is true and the statements in then are executed.

When $CONDITION isn't set, the statement is an empty statement, and empty statements always have a zero exit status, meaning success. So the if condition succeeds.

This behavior is explained in the POSIX shell specification:

If there is no command name, but the command contained a command substitution, the command shall complete with the exit status of the last command substitution performed. Otherwise, the command shall complete with a zero exit status.

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • Then why does `if ""; then echo yes; fi` fail on a command not found? – Vercingatorix Jul 06 '22 at 21:32
  • 4
    Because that's not an empty statement. It's trying to find a program whose name is the empty string. – Barmar Jul 06 '22 at 21:33
  • 5
    You'd get the same error if you did `if "$CONDITION"` – Barmar Jul 06 '22 at 21:34
  • 22
    This really is a weird edge case, because `if ; then echo yes; fi` is a syntax error, but `if $unset; then echo yes; fi` is OK. I suppose the difference is that the former is _not_ "given a statement to execute"; the latter is given one but it just happens to be null. – glenn jackman Jul 06 '22 at 21:55
  • 11
    Right. The difference is that the first error is detected during parsing, while the second executes after the statement is parsed and the variable substituted. – Barmar Jul 06 '22 at 21:56
  • 3
    [2.9.1 Simple Commands](https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/utilities/V3_chap02.html#tag_18_09_01), last part: _"If there is no command name, but the command contained a command substitution, the command shall complete with the exit status of the last command substitution performed. Otherwise, the command shall complete with a zero exit status."_ You could also have something like `if "$@"; then ...` which would result in an empty command if there were no positional parameters. The grammar does require at least something there, but a simple redirection would also do... – ilkkachu Jul 07 '22 at 12:05
  • @glennjackman FYI ':' can represent the empty command; thus `while :; do ... done` is valid, as is `if :; then...` – Dave Jul 13 '22 at 01:47
  • 1
    @Dave, but `:` is not an _empty_ command, it's an _actual_ non-empty command. It's a "no op" but it's not actually _no operation_. – glenn jackman Jul 13 '22 at 02:27