nathan
nathan

Reputation: 854

exclamation mark to test variable is true or not in bash shell

I know in shell the exclamation mark can invert the outcome of condition. Here I wanna use it to test variable is true or false .

#! /bin/bash
bat=false
if [ ! $bar ]; then
    echo 'bar is false'
else
   echo 'bar is true'
fi

I was expecting 'bar is false'. However it turned out the other way. Then I use "$bar" == "false". It is right.

So what is the tips for using exclamation mark. Would it only invert outcome when we test about file ?

Upvotes: 8

Views: 13026

Answers (3)

mklement0
mklement0

Reputation: 439247

tl;dr

[ ! $bar ] treats $bar as a string, and any nonempty string is considered "true" - including literal false; in other words: [ ! 'true' ] && [ ! 'false' ] both evaluate to "false", because the operands are nonempty strings in both cases and ! negates the outcome.

Therefore, you must use string comparison:

bar=false
if [ ! "$bar" = 'true' ]; then  # same as: [ "$bar" != 'true' ]
    echo 'bar is false'
else
    echo 'bar is true'
fi

In Bash, you can also use [[ ! $bar == 'true' ]] or [[ $bar != 'true' ]]; unless you need to remain POSIX-compliant, I suggest using [[ ... ]].


In the context of [ ... ] and [[ ... ]] (Bash-specific), variable values are strings by default, and, generally, POSIX-like shells have no explicit Boolean data type.

Unary operator ! interprets its operand implicitly as a Boolean, and a string in a Boolean context is interpreted as follows:

  • only an empty string is considered "false" (exit code 1(!))
  • any nonempty string - including literal false - is considered "true" (exit code 0(!))

Thus, ! $bar evaluates to "false", because $bar - containing nonempty string 'false' - evaluates to "true", and ! inverts that.


! can also be used outside of conditionals to directly negate the success status of commands (including [).
Since false and true also exist as command names (typically, as shell builtins), you could do the following, but do note that it only works as intended with variable values that are either the literals false or true:

bar=false
if ! "$bar"; then
   echo 'bar is false'
else
   echo 'bar is true'
fi

Background information

POSIX-like shells only have 2 basic (scalar) data types:

  • strings (by default):

    • In [ ... ] and [[ ... ]] conditionals, operators = (== in Bash), <, <=, >, >= perform string comparison.
  • integers:

    • In [ ... ] and [[ ... ]] conditionals, distinct arithmetic operators (-eq, lt, le, gt, ge) must be used to perform numerical comparison

    • Alternatively, in an arithmetic expansion ($(( ... ))), ==, <, <=, >, >= have their usual, numeric meaning.
      In Bash, you can also use (( ... )) as an arithmetic conditional.

Note: Per POSIX, you cannot type a variable as an integer (because there is no way to declare a shell variable), so it is only implicitly treated as one in comparisons using arithmetic operators / in arithmetic contexts.
In Bash, however, you can declare integer variables with declare -i / local -i, but in conditionals / arithmetic contexts it is still the choice of operator / arithmetic context that determines whether the value is treated as a string or as an integer.


Booleans in shells are expressed implicitly as exit codes, in a reversal of the usual Boolean-to-integer mapping:

  • "true" maps to exit code 0(!)
  • "false" is expressed as exit code 1(!) or any other nonzero exit code.

Upvotes: 16

Jonathan Leffler
Jonathan Leffler

Reputation: 754500

See the POSIX specification of test for details of the portable use of the [ or test utility.

If you use [ ! $bar ] you are playing with fire.

  • If $bar is an empty string, it degenerates to the one-argument form of test, with that argument being !, and exits successfully because ! is not an empty string.

  • If $bar is not an empty string, then the arguments are ! and the value of $bar, which establishes that the non-empty string is non-empty, and then inverts the condition, so it exits with a failure status.

You should normally write [ ! "$bar" ] which will then work correctly if $bar is empty. You could (arguably should) use the explicit tests:

  • [ -z "$bar" ] to test for an empty (zero-length) string, and
  • [ -n "$bar" ] to test for a non-empty string

but the quotes are mandatory to avoid confusion. The non-empty and zero-length are mnemonic terms.

$ bar=
$ [ ! $bar ] || echo Test failed
$ bar=xyz
$ [ ! $bar ] || echo Test failed
Test failed
$ [ ! "$bar" ] || echo Test failed
Test failed
$ bar=
$ [ ! "$bar" ] || echo Test failed
$ [ -n "$bar" ] || echo Test failed
Test failed
$ [ -z "$bar" ] || echo Test failed
$ 

Some people prefer the Bash (Korn shell, …) operator [[ which behaves differently. You could experiment with variations on the theme of this code:

set -x
bar=
[ ! $bar ] || echo Test failed
bar=xyz
[ ! $bar ] || echo Test failed
[ ! "$bar" ] || echo Test failed
bar=
[ ! "$bar" ] || echo Test failed
[ -n "$bar" ] || echo Test failed
[ -z "$bar" ] || echo Test failed
bar=xyz
[ -n "$bar" ] || echo Test failed
[ -z "$bar" ] || echo Test failed

bar=
[[ ! $bar ]] || echo Test failed
bar=xyz
[[ ! $bar ]] || echo Test failed
[[ ! "$bar" ]] || echo Test failed
bar=
[[ ! "$bar" ]] || echo Test failed
[[ -n "$bar" ]] || echo Test failed
[[ -z "$bar" ]] || echo Test failed
bar=xyz
[[ -n "$bar" ]] || echo Test failed
[[ -z "$bar" ]] || echo Test failed
bar=
[[ -n $bar ]] || echo Test failed
[[ -z $bar ]] || echo Test failed
bar=xyz
[[ -n $bar ]] || echo Test failed
[[ -z $bar ]] || echo Test failed
set +x  # Unnecessary if you save this in a script file and don't dot (source) it

It will (probably) help you understand what's going on. The output is quite verbose, though.

Upvotes: 3

Greg Nisbet
Greg Nisbet

Reputation: 6994

if tests the results of commands and can't directly test the values of variables. In this case you're using it in conjunction with the test/[ command, which is an ordinary command and can be used anywhere.

[ -f ./foo.txt ] && echo "foo.txt exists"

or equivalently

test -f ./foo.txt && echo "foo.txt exists"

both test whether a file exists at the path ./foo.txt.

Depending on what you mean by "true", you can use the test or [ shell builtin to determine whether a variable is equal to another, and for some other functions like testing whether a directory or file exists.

You can use if [ -z "$variable" ]; then ...; fi to check whether a variable is the empty string.

If you want to check whether the variable is set at all, even to an empty value, you can use parameter expansion [ -z "${var+x}" ]. It only evaluates to true (meaning has exit status zero) when var is undefined.

If you want to compare a variable against a fixed string you can use [ "$var" = "some-string" ] or use case e.g.

case "$var" in
   some-string)
     dostuff
   ;;
esac

Note that you can negate that the result of test/[ using !.

if ! [ -z "$var" ]; then
    do-something
fi

Does negate the condition.

However, ! is also valid as an argument to test/[

if [ ! -z "$var" ]; then
    do-something
fi

means the same thing.

Upvotes: 2

Related Questions