251

Why does the following output True?

#!/bin/sh

if [ false ]; then
    echo "True"
else
    echo "False"
fi

This will always output True even though the condition would seem to indicate otherwise. If I remove the brackets [] then it works, but I do not understand why.

peter.babic
  • 3,214
  • 3
  • 18
  • 31
tenmiles
  • 2,521
  • 3
  • 18
  • 20
  • 4
    [Here's](http://stackoverflow.com/a/8934070/645270) something to get you started. – keyser Oct 29 '13 at 22:09
  • 16
    BTW, a script starting with `#!/bin/sh` is not a bash script -- it's a POSIX sh script. Even if the POSIX sh interpreter on your system is provided by bash, it turns off a bunch of extensions. If you're wanting to write bash scripts, use `#!/bin/bash` or its locally-appropriate equivalent (`#!/usr/bin/env bash` to use the first bash interpreter in the PATH). – Charles Duffy Feb 18 '15 at 16:54
  • This affects `[[ false ]]` too. – CMCDragonkai Aug 16 '15 at 12:02

6 Answers6

299

You are running the [ (aka test) command with the argument "false", not running the command false. Since "false" is a non-empty string, the test command always succeeds. To actually run the command, drop the [ command.

if false; then
   echo "True"
else
   echo "False"
fi
chepner
  • 497,756
  • 71
  • 530
  • 681
  • 5
    The idea of false being a command, or even a string, seems odd to me, but I guess it works. Thanks. – tenmiles Oct 29 '13 at 22:20
  • 61
    `bash` has no Boolean data type, and so no keywords representing true and false. The `if` statement merely checks if the command you give it succeeds or fails. The `test` command takes an expression and succeeds if the expression is true; a non-empty string is an expression that evaluates as true, just as in most other programming languages. `false` is a command which always fails. (By analogy, `true` is a command that always succeeds.) – chepner Oct 29 '13 at 22:24
  • 4
    `if [[ false ]];` returns true, too. As does `if [[ 0 ]];`. And `if [[ /usr/bin/false ]];` does not skip the block, either. How can false or 0 evaluate to non-zero? Damn this is frustrating. If it matters, OS X 10.8; Bash 3. – jww Jun 17 '16 at 08:42
  • 9
    Don't mistake `false` for a Boolean constant or `0` for an integer literal; both are just ordinary strings. `[[ x ]]` is equivalent to `[[ -n x ]]` for any string `x` that doesn't start with a hyphen. `bash` doesn't have *any* Boolean constants in any context. Strings that look like integers are treated as such inside `(( ... ))` or with operators such as `-eq` or `-lt` inside `[[ ... ]]`. – chepner Jun 17 '16 at 13:01
  • The commands `true` and `false` are in UNIX for purposes like this. Today I saw this style in someone's code: `predicate=false;if ! $predicate;then echo ok;else echo ko;fi` which prints ok. I have always set shell variables to use in predicates. I think the style of using the commands true and false reads better. – tbc0 Jul 26 '16 at 21:54
  • 2
    @tbc0, there are more concerns at hand than just how something reads. If variables are set by code not entirely under your control (or which can be manipulated externally, be it by unusual filenames or otherwise), `if $predicate` can easily be abused to execute hostile code in a manner that `if [[ $predicate ]]` cannot. – Charles Duffy Feb 18 '17 at 05:25
  • You said "`bash` has no Boolean data type". Then how is `if false; then` processed. Isn't that written with `bash`? Also I think you should move your top comment into the question. Comments may get deleted. And it's contains critical information for answering the question – mfaani Mar 30 '20 at 16:13
  • 3
    `false` is a command that unconditionally produces an exit status of 1. – chepner Mar 30 '20 at 16:32
  • ahh I didn't know. just tried `man false` and it man page showed up – mfaani Mar 30 '20 at 16:37
  • Thanks. Then I go back to `man test`, it says `STRING equivalent to -n STRING`. – Kai Jul 23 '20 at 01:51
  • @jww Again, you are checking whether those tokens are empty strings, and they are not. Fundamentally your mistake is probably that you assume that `[[` is part of the `if` statement's syntax. It is not; it is simply `if command`; and `[[` is a command which (without further options) checks if its argument is a non-empty string. – tripleee Sep 26 '20 at 11:13
  • Riiiiiiiiiiiiight. – Ben Racicot Mar 09 '21 at 20:02
121

A Quick Boolean Primer for Bash

The if statement takes a command as an argument (as do &&, ||, etc.). The integer result code of the command is interpreted as a boolean (0/null=true, 1/else=false).

The test statement takes operators and operands as arguments and returns a result code in the same format as if. An alias of the test statement is [, which is often used with if to perform more complex comparisons.

The true and false statements do nothing and return a result code (0 and 1, respectively). So they can be used as boolean literals in Bash. But if you put the statements in a place where they're interpreted as strings, you'll run into issues. In your case:

if [ foo ]; then ... # "if the string 'foo' is non-empty, return true"
if foo; then ...     # "if the command foo succeeds, return true"

So:

if [ true  ] ; then echo "This text will always appear." ; fi;
if [ false ] ; then echo "This text will always appear." ; fi;
if true      ; then echo "This text will always appear." ; fi;
if false     ; then echo "This text will never appear."  ; fi;

This is similar to doing something like echo '$foo' vs. echo "$foo".

When using the test statement, the result depends on the operators used.

if [ "$foo" = "$bar" ]   # true if the string values of $foo and $bar are equal
if [ "$foo" -eq "$bar" ] # true if the integer values of $foo and $bar are equal
if [ -f "$foo" ]         # true if $foo is a file that exists (by path)
if [ "$foo" ]            # true if $foo evaluates to a non-empty string
if foo                   # true if foo, as a command/subroutine,
                         # evaluates to true/success (returns 0 or null)

In short, if you just want to test something as pass/fail (aka "true"/"false"), then pass a command to your if or && etc. statement, without brackets. For complex comparisons, use brackets with the proper operators.

And yes, I'm aware there's no such thing as a native boolean type in Bash, and that if and [ and true are technically "commands" and not "statements"; this is just a very basic, functional explanation.

Community
  • 1
  • 1
Beejor
  • 8,606
  • 1
  • 41
  • 31
  • 4
    FYI, if you want to use 1/0 as True/False in your code, this `if (( $x )); then echo "Hello"; fi` shows the message for `x=1` but not for `x=0` or `x=` (undefined). – Jonathan H Apr 10 '18 at 22:26
7

I found that I can do some basic logic by running something like:

A=true
B=true
if ($A && $B); then
    C=true
else
    C=false
fi
echo $C
Rodrigo
  • 131
  • 2
  • 6
  • 9
    One *can*, but it's a really bad idea. `( $A && $B )` is a surprisingly inefficient operation -- you're spawning a subprocess via calling `fork()`, then string-splitting the contents of `$A` to generate a list of elements (in this case of size one), and evaluating each element as a glob (in this case one that evaluates to itself), then running the result as a command (in this case, a builtin command). – Charles Duffy Feb 18 '17 at 05:20
  • 3
    Moreover, if your `true` and `false` strings are coming from somewhere else, there's a risk that they could actually contain contents other than `true` or `false`, and you could get unexpected behavior up to and including arbitrary command execution. – Charles Duffy Feb 18 '17 at 05:21
  • 9
    It's much, **much** safer to write `A=1; B=1` and `if (( A && B ))` -- treating them as numeric -- or `[[ $A && $B ]]`, which treats an empty string as false and a non-empty string as true. – Charles Duffy Feb 18 '17 at 05:22
  • 5
    (note that I'm only using all-caps variable names above to follow the conventions established by the answer -- POSIX actually [explicitly specifies](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html) all-caps names to be used for environment variables and shell builtins, and reserves lowercase names for application use; to avoid conflicts with future OS environments, particularly given that setting a shell variable overwrites any like-named environment variable, it's always ideal to honor that convention in one's own scripts). – Charles Duffy Feb 18 '17 at 05:24
4

Using true/false removes some bracket clutter...

#! /bin/bash    
#  true_or_false.bash

[ "$(basename $0)" == "bash" ] && sourced=true || sourced=false

$sourced && echo "SOURCED"
$sourced || echo "CALLED"

# Just an alternate way:
! $sourced  &&  echo "CALLED " ||  echo "SOURCED"

$sourced && return || exit
psh
  • 41
  • 1
  • I found this helpful but in ubuntu 18.04 $0 was "-bash" and the basename command threw an error. Changing that test to `[[ "$0" =~ "bash" ]] ` made the script work for me. – WiringHarness Nov 22 '19 at 12:37
4

as noted by @tripleee, this is tangential, at best.

still, in case you arrived here searching for something like that (as i did), here is my solution

having to deal with user acessible configuration files, i use this function :

function isTrue() {
    if [[ "${@^^}" =~ ^(TRUE|OUI|Y|O$|ON$|[1-9]) ]]; then return 0;fi
    return 1
    }

wich can be used like that

if isTrue "$whatever"; then..

You can alter the "truth list" in the regexp, the one in this sample is french compatible and considers strings like "Yeah, yup, on,1, Oui,y,true to be "True".

note that the '^^' provides case insensivity

1

Adding context to hopefully help provide a bit of additional clarity on this subject. To a BaSH newbie, it's sense of true/false statements is rather odd. Take the following simple examples and their results.

This statement will return "true":

foo=" "; if [ "$foo" ]; then echo "true"; else echo "false"; fi

But this will return "false":

foo=" "; if [ $foo ]; then echo "true"; else echo "false"; fi

Do you see why? The first example has a quoted "" string. This causes BaSH to treat it literally. So, in a literal sense, a space is not null. While in a non-literal sense (the 2nd example above), a space is viewed by BaSH (as a value in $foo) as 'nothing' and therefore it equates to null (interpreted here as 'false').

These statements will all return a text string of "false":

foo=; if [ $foo ]; then echo "true"; else echo "false"; fi
foo=; if [ "$foo" ]; then echo "true"; else echo "false"; fi
foo=""; if [ $foo ]; then echo "true"; else echo "false"; fi
foo=""; if [ "$foo" ]; then echo "true"; else echo "false"; fi

Interestingly, this type of conditional will always return true:

These statements will all return a result of "true":

foo=""; if [ foo ]; then echo "true"; else echo "false"; fi

Notice the difference; the $ symbol has been omitted from preceding the variable name in the conditional. It doesn't matter what word you insert between the brackets. BaSH will always see this statement as true, even if you use a word that has never been associated with a variable in the same shell before.

if [ sooperduper ]; then echo "true"; else echo "false"; fi

Likewise, defining it as an undeclared variable ensures it will always return false:

if [ $sooperduper ]; then echo "true"; else echo "false"; fi

As to BaSH it's the same as writing:

sooperduper="";if [ $sooperduper ]; then echo "true"; else echo "false"; fi

One more tip....

Brackets vs No Brackets

Making matters more confusing, these variations on the IF/THEN conditional both work, but return opposite results.

These return false:

if [ $foo ]; then echo "true"; else echo "false"; fi
if [ ! foo ]; then echo "true"; else echo "false"; fi

However, these will return a result of true:

if $foo; then echo "true"; else echo "false"; fi
if [ foo ]; then echo "true"; else echo "false"; fi
if [ ! $foo ]; then echo "true"; else echo "false"; fi

And, of course this returns a syntax error (along with a result of 'false'):

if foo; then echo "true"; else echo "false"; fi

Confused yet? It can be quite challenging to keep it straight in your head in the beginning, especially if you're used to other, higher level programming languages.

MrPotatoHead
  • 1,035
  • 14
  • 11