Last updated:

A Shell script usually needs to test if a command succeeds or a condition is met. In Bash, this test can be done with a Bash if statement. As with any other programming language, Bash comes with conditional expressions that allow you to test for conditions and alter the control flow if the condition is satisfied or not.

This post covers the bash if statement and the related clauses then, else if (elif), and else.

Introduction to the Bash If Statement

In programming, an if statement is a conditional statement, also known as a conditional expression. An if statement will execute a portion of code if a given condition is met. It is often referenced as an If-Then, If-Else, or If-Then-Else statement. An if statement always tests for a boolean condition to evaluate to true or false. The then, else if (elif), and else are clauses to the if statement.

What is the syntax of a Bash If Statement?

In Bash, the if statement is part of the conditional constructs of the programming language. The if in a Bash script is a shell keyword that is used to test conditions based on the exit status of a test command. An exit status of zero, and only zero, is a success, i.e. a condition that is true. Any other exit status is a failure, i.e. a condition that is false.

The syntax of the if statement in Bash is:

if first-test-commands; then
  consequent-commands;
[elif more-test-commands; then
  more-consequents;]
[else alternate-consequents;]
fi

Tests commands in the bash if statement, and bash elif clauses, are executed in order until one test succeed. If no test succeeds, and a bash else clause is provided, then the code portion of the final else clause will be executed.

What are the double Parentheses ((…)), single […], and double [[..]] Square Brackets?

The ((...)), [...], and [[...]] constructs are often used to evaluate complex conditional expressions with comparison operators, and to return an exit status of 0 or 1 that can be used in a bash if statement.

The double parentheses ((...)) is used to test an arithmetic expression. You can read more about this construct in our post on bash arithmetic. It does support the && and || binary operators.

The single square brackets [...] is the command [ which is a shell builtin and an alias to the test command. The test command and the alias [ are used to evaluate conditional expressions. This is part of the POSIX standard.

There are some notable differences between the double brackets and single bracket notation:

The double square brackets [[...]] is a shell keyword. It is similar in behavior to the single square bracket and is used to evaluate conditional expressions and is a Bash, Zsh, and Korn shell specific. This construct can handle more complex conditions and is less error-prone, see the FAQ on some examples of incorrect use of the single bracket command.

Note that the ((...)) and [[...]] constructs are Bash compound commands.

What are the Bash Conditional Expressions?

Conditional expressions are used by the [[ compound command and the test and [ builtin commands. Conditional Expressions can be unary (one operand) or binary (two operands). Unary operators are often used to test the status of a file, a variable, a shell option (optname), or a string.

Unary and Binary expressions are formed with the following primaries.

Conditional ExpressionMeaning
-a fileTrue if file exists.
-b fileTrue if file exists and is a block special file.
-c fileTrue if file exists and is a character special file.
-d fileTrue if file exists and is a directory.
-e fileTrue if file exists.
-f fileTrue if file exists and is a regular file.
-g fileTrue if file exists and its set-group-id bit is set.
-h fileTrue if file exists and is a symbolic link.
-k fileTrue if file exists and its “sticky” bit is set.
-p fileTrue if file exists and is a named pipe (FIFO).
-r fileTrue if file exists and is readable.
-s fileTrue if file exists and has a size greater than zero.
-t fdTrue if file descriptor fd is open and refers to a terminal.
-u fileTrue if file exists and its set-user-id bit is set.
-w fileTrue if file exists and is writable.
-x fileTrue if file exists and is executable.
-G fileTrue if file exists and is owned by the effective group id.
-L fileTrue if file exists and is a symbolic link.
-N fileTrue if file exists and has been modified since it was last read.
-O fileTrue if file exists and is owned by the effective user id.
-S fileTrue if file exists and is a socket.
file1 -ef file2True if file1 and file2 refer to the same device and inode numbers.
file1 -nt file2True if file1 is newer (according to modification date) than file2, or if file1 exists and file2 does not.
file1 -ot file2True if file1 is older than file2, or if file2 exists and file1 does not.
-o optnameTrue if the shell option optname is enabled (see set -o for a list of options).
-v varnameTrue if the shell variable varname is set (has been assigned a value, even an empty value).
-R varnameTrue if the shell variable varname is set and is a name reference.
-z stringTrue if the length of string is zero.
-n stringTrue if the length of string is non-zero.
string1 == string2
string1 = string2
True if the strings are equal. It will perform pattern matching when used with the [[ command. The = notation should be used with the test command for POSIX conformance.
string1 != string2True if the strings are not equal.
string1 =~ regexTrue if the strings match the Bash regular expression regex. Captured groups are stored in the BASH_REMATCH array variable.
string1 < string2True if string1 sorts before string2 lexicographically.
string1 > string2True if string1 sorts after string2 lexicographically.

The Conditional Expressions also support arithmetic binary operators as follows and where arg1 and arg2 are either positive or negative integers. When used with the [[ command, arg1 and arg2 are evaluated as arithmetic expressions, hence the (( compound command should be preferred.

Conditional ExpressionMeaning
arg1 -eq arg2True if arg1 equal to arg2
arg1 -ne arg2True if arg1 not equal to arg2
arg1 -lt arg2True if arg1 less than arg2
arg1 -le arg2True if arg1 less than or equal to arg2
arg1 -gt arg2True if arg1 greater than arg2
arg1 -ge arg2True if arg1 greater than or equal to arg2

How to use an If Statement with Then, Else, Else If (elif) clauses?

As we mentioned earlier, a If Statement must have a then clause and optionally can have an else if clause with the keyword elif followed by then, and/or an else clause. The If Statement always ends with the fi keyword.

The if, then, else, elif and fi keywords must be the last keyword of a line or they need to be terminated with a semi-colon ; before any other keyword is being used.

if false; then
  echo 'This command will never run since condition is always false.';
elif ((RANDOM%2)); then
  echo 'This command will execute only when $RANDOM % 2 equal to 0.';
else
  echo 'This command will execute if no other condition is met.';
fi

Using a Bash If Statement with Conditional Expressions

Note that a condition doesn’t need any special enclosing characters like parentheses, though they may be used to override the precedence of other operators. Depending on the test to be performed, a command can be used directly, or the use of the [[ compound command, or the test and [ builtin commands. The (( compound command is reserved for Arithmetic Expansion. Blank spaces between keywords and commands matters.

For example, if we want to test whether a file exists and is a regular file (not a symlink), we could use the -f primary with any of the following notation.

[me@linux ~]$ touch myfile
[me@linux ~]$ if [[ -f myfile ]]; then echo "myfile exists. If Statement Condition equal $?."; fi
myfile exists. If Statement Condition equal 0.
[me@linux ~]$ if [ -f myfile ]; then echo "myfile exists. If Statement Condition equal $?."; fi
myfile exists. If Statement Condition equal 0.
[me@linux ~]$ if test -f myfile; then echo "myfile exists. If Statement Condition equal $?."; fi
myfile exists. If Statement Condition equal 0.

You can negate a condition using the ! keyword.

[me@linux ~]$ rm myfile
[me@linux ~]$ if [[ ! -f myfile ]]; then echo "myfile does not exist. If Statement Condition equal $?."; fi
myfile does not exist. If Statement Condition equal 0.

Using a Bash If Statement with multiple conditions

As we mentioned above, you can use the binary operators && (and) and || (or) in the double brackets [[ compound notation. This is similar to using the -a (and) and -o (or) in a single bracket.

[me@linux ~]$ touch myfile
[me@linux ~]$ if [[ -f myfile &&  -r myfile ]]; then echo "File exists and is Readable."; fi
File exists and is Readable.
[me@linux ~]$ if [ -f myfile -a  -r myfile ]; then echo "File exists and is Readable."; fi
File exists and is Readable.

Note that if you use the binary operators in a single bracket notation you will end up with an error bash: [: missing ``]'. This is because [ is a command and expect ] as the last argument. Similarly, when using test, the command would fail with bash: -r: command not found as && terminate the previous command and expect a new command right after. When using && or || with single brackets, you will need to use them outside of the brackets or test command.

[me@linux ~]$ if [ -f myfile &&  -r myfile ]; then echo "File exists and is Readable."; fi
bash: [: missing `]'
[me@linux ~]$ if [ -f myfile ] && [ -r myfile ]; then echo "File exists and is Readable."; fi
File exists and is Readable.
[me@linux ~]$ if test -f myfile &&  -r myfile; then echo "File exists and is Readable."; fi
bash: -r: command not found
[me@linux ~]$ if test -f myfile && test -r myfile; then echo "File exists and is Readable."; fi
File exists and is Readable.

Using Nested If Statements

A nested if statement is an if statement inside a clause of another if statement. Nothing prevents multiple levels of if statement in a shell script and in Bash.

if first-test-commands; then
  if second-level-test-commands; then
    consequent-commands;
  else second-level-alternate-consequents;
[elif more-test-commands; then
  more-consequents;]
[else alternate-consequents;]
fi

Common Pitfalls and Errors

Incorrect usage of the single bracket command [

One of the most common mistakes with the shell command [ is to incorrectly use quotes in a conditional expression. String literals don’t need to be quoted in a [ or test condition, unless it contains wildcards characters.

The first argument of a condition should be quoted when it is a variable. When incorrectly used you will face the bash error bash: [: too many arguments.

[me@linux ~]$ myVar="a b"

# Wrong
[me@linux ~]$ [ $myVar = "a b" ]; echo $?
bash: [: too many arguments
2

# Correct
[me@linux ~]$ [ "$myVar" = "a b" ]; echo $?
0

Another mistake is to not properly use whitespaces with the [ command. As we discussed earlier in this post, the [ construct is a shell builtin command that is similar to the test command. As such, after the command name should be a space before the first argument and each argument, including comparison operators, should have whitespaces.

[me@linux ~]$ var="Example String"

# WRONG: Missing whitespaces around the command `[` would lead to a bash error "command not found"
[me@linux ~]$ if [string="$var"]; then echo ${var}; fi
bash: [string=]: command not found

# WRONG: Missing whitespaces around the operator would wrongly return the expression as true
[me@linux ~]$ if [ string="$var" ]; then echo ${var}; fi
Example String

# CORRECT use of whitespaces with the [ command
[me@linux ~]$ if [ string = "$var" ]; then echo ${var}; fi

Lastly, a frequent mistake with the [ command is to use the binary operator && or || inside the brackets which is incorrect for the same reason as above. [ is a command where the last argument must be ]. The && and || operators break that condition and will lead to the bash error bash: [: missing ``]'.

[me@linux ~]$ a=b; c=d;

# INCORRECT use of &&
[me@linux ~]$ if [ $a = b && $c = d ]; then echo "Success"; fi
bash: [: missing `]'

# CORRECT use of &&
[me@linux ~]$ if [ $a = b ] && [ $c = d ]; then echo "Success"; fi
Success

How to solve the Binary Operator Expected Error?

The reason for the Bash error binary operator expected is generally due to a variable being expanded to multiple words and not being properly quoted when used with the test or [ command. If you don’t require your script to be 100% POSIX compliant, a better alternative is to use the [[ bash builtin command which will not be impacted by word splitting or glob expansion.

[me@linux ~]$ string="word1 word2"

# INCORRECT: Missing quotes
[me@linux ~]$ if [ -n $string ]; then echo "\$string length >0"; fi
bash: [: word1: binary operator expected

# CORRECT
[me@linux ~]$ if [ -n "$string" ]; then echo "\$string length >0"; fi
$string length >0

# CORRECT and BETTER in bash
[me@linux ~]$ if [[ -n $string ]]; then echo "\$string length >0"; fi
$string length >0

Why you should not use the || and && operators instead of a Bash If Statement?

Trying to emulate a ternary operator with the || (or) and && (and) binary operators can be error-prone and lead to unexpected results. Prefer the a regular if statement constructs when possible. The main problem is that the command && will also generate an exit status and may lead to the command after the || to be executed.

# Incorrect / Not Recommended
[[ ! -f myFile ]] && myCommandOnSuccess || myCommandOnFailure

# Correct / Best Practice
if [[ -f myFile ]]; then
  myCommandOnSuccess
else
  myCommandOnFailure
fi

# WRONG: All commands are executed
[me@linux ~]$ [[ ! -f myFile ]] && { echo "File not found"; false; } || { echo "File exist"; true; }
File not found
File exist

# WRONG: All arithmetic expansions are executed and return incorrect z value
[me@linux ~]$ z=0; [[ -v z ]] && ((z++)) || ((z--)); echo $z
0
# CORRECT bash if statement usage
[me@linux ~]$ z=0; if [[ -v z ]]; then ((z++)); else ((z--)); fi; echo $z
1

# CORRECT for arithmetic expansions only and can't use -v
[me@linux ~]$ z=0; (( z==0 ? z++ : z-- )); echo $z
0

Detailed Examples & FAQ

Below are some common examples and use cases of if statement and conditional expressions in Bash.

How to check if a variable exists or is “null”?

Three conditional expression primaries can be used in Bash to test if a variable exists or is null: -v, -n, and -z.

👉 In the Bash shell, there is no definition of a null variable. We will either talk about a variable being set or not set. Note that a variable can be set with an empty string (zero-length string using double quotes "") or no value, which may be mentioned as a null value. This terminology should not be confused with the Bash Null Command which has a completely different purpose.

The -v primary can be used to test if a shell variable is set. Note that it takes a variable name as parameter, i.e. without the $ sign. If the variable is set with an empty or zero-length value, the condition will return true (exit code 0).

# Variable doesn't exist
[me@linux ~]$ [[ -v myVar ]] ; echo "Condition returned $?"
Condition returned 1

# Variable is set and exist
[me@linux ~]$ myVar="test"
[me@linux ~]$ [[ -v myVar ]] ; echo "Condition returned $?"
Condition returned 0

# Variable is set to a zero length string (null/empty) and exist
[me@linux ~]$ myVar=
[me@linux ~]$ [[ -v myVar ]] ; echo "Condition returned $?"
Condition returned 0

# Variable is unset and doesn't exist
[me@linux ~]$ unset myVar
[me@linux ~]$ [[ -v myVar ]] ; echo "Condition returned $?"
Condition returned 1

The -n and -z will also check for a string length. The -n option check for a non-zero length and the -z option check for a zero-length string. Those primaries may be useful if you intend is to check if a variable is empty or not. Though be careful if your shell script runs with the set -u option and your variable does not exist, then your script will fail with an error like bash: myVar: unbound variable.

To work around this, you can test with a parameter expansion by using ${parameter:+word} to ensure that the variable is set. You can also combine the use of -v and -z such as [[ -v varName && -z $varName ]]. I find the latter structure more clear as it translate into “if my variable exists, and my variable length is zero (-z) / non-zero (-n), then…”, though this is a slightly different behavior than just using the parameter expansion solution.

[me@linux ~]$ unset myVar

# Test for variable length greater than zero with myVar unset
[me@linux ~]$ [[ -z $myVar ]] ; echo "Condition returned $?"
Condition returned 0

# Test for variable length equal to zero with myVar unset
[me@linux ~]$ [[ -n $myVar ]] ; echo "Condition returned $?"
Condition returned 1

# Test for variable length with myVar set
[me@linux ~]$ myVar="test"
[me@linux ~]$ [[ -z $myVar ]] ; echo "Condition returned $?"
Condition returned 1
[me@linux ~]$ [[ -n $myVar ]] ; echo "Condition returned $?"
Condition returned 0

# INCORRECT test for a variable length with set -u option and parameter expansion
[me@linux ~]$ unset myVar ; set -u
[me@linux ~]$ [[ -z $myVar ]] ; echo "Condition returned $?"
bash: myVar: unbound variable

# CORRECT tests with a variable not set
[me@linux ~]$ unset myVar ; set -u
[me@linux ~]$ [[ -z ${myVar:+x} ]] ; echo "Condition returned $?"
Condition returned 0
[me@linux ~]$ [[ -n ${myVar:+x} ]] ; echo "Condition returned $?"
Condition returned 1

# CORRECT if you consider an unset variable not the same as a zero-length variable
# Condition return "False' in both cases
[me@linux ~]$ [[ -v myVar && -z $myVar ]] ; echo "Condition returned $?"
Condition returned 1
[me@linux ~]$ [[ -v myVar && -n $myVar ]] ; echo "Condition returned $?"
Condition returned 1

# CORRECT tests with an empty variable
[me@linux ~]$ myVar="" ; set -u
[me@linux ~]$ [[ -v myVar && -z $myVar ]] ; echo "Condition returned $?"
Condition returned 0
[me@linux ~]$ [[ -z ${myVar:+x} ]] ; echo "Condition returned $?"
Condition returned 1
[me@linux ~]$ [[ -v myVar && -n $myVar ]] ; echo "Condition returned $?"
Condition returned 1
[me@linux ~]$ [[ -n ${myVar:+x} ]] ; echo "Condition returned $?"
Condition returned 1

# CORRECT tests with an non empty variable
[me@linux ~]$ myVar="test"
[me@linux ~]$ [[ -v myVar && -z $myVar ]] ; echo "Condition returned $?"
1
[me@linux ~]$ [[ -z ${myVar:+x} ]] ; echo "Condition returned $?"
Condition returned 1
[me@linux ~]$ [[ -v myVar && -n $myVar ]] ; echo "Condition returned $?"
Condition returned 0
[me@linux ~]$ [[ -n ${myVar:+x} ]] ; echo "Condition returned $?"
Condition returned 0

You can use the single bracket expression with those primaries but you need to make sure to quote the variable when testing for a string length with -z and -n to prevent word-splitting or glob expansion. The quotes are not necessary with the double bracket since this is the default behavior.

How to check if a file exists?

The -f primary can be used to test whether a regular file exists or not.

[me@linux ~]$ touch myfile
[me@linux ~]$ if [[ -f myfile ]]; then echo "myfile exists. If Statement Condition equal $?."; fi
myfile exists. If Statement Condition equal 0.
[me@linux ~]$ if [ -f myfile ]; then echo "myfile exists. If Statement Condition equal $?."; fi
myfile exists. If Statement Condition equal 0.
[me@linux ~]$ if test -f myfile; then echo "myfile exists. If Statement Condition equal $?."; fi
myfile exists. If Statement Condition equal 0.

Remember that conditional expressions will follow symlinks when testing files and will operate the test on the target of the link. Hence, the test will return 1 (false)) when a symlinks point to a non-existent file, or if you don’t have the proper access permission to access the target. To test whether a regular file exists or the corresponding symlinks, one would test with the -f and -L primaries combined.

[me@linux ~]$ touch myfile ; ln -s broken_target broken_link

# test with -f on a regular file and a broken symlink
[me@linux ~]$ [[ -f myfile ]]; echo $?
0
[me@linux ~]$ [[ -f broken_link ]]; echo $?
1

# test with -L on a regular file and a broken symlink
[me@linux ~]$ [[ -L myfile ]]; echo $?
1
[me@linux ~]$ [[ -L broken_link ]]; echo $?
0

# Combined test whether a file or symlink exist
[me@linux ~]$ [[ -f broken_link || -L broken_link ]]; echo $?
0

How to check if a directory exists?

The -d primary can be used to test whether a directory exists or not.

[me@linux ~]$ mkdir myDir
[me@linux ~]$ if [[ -d myDir ]]; then echo "myDir exists. If Statement Condition equal $?."; fi
myDir exists. If Statement Condition equal 0.
[me@linux ~]$ if [ -d myDir ]; then echo "myDir exists. If Statement Condition equal $?."; fi
myDir exists. If Statement Condition equal 0.
[me@linux ~]$ if test -d myDir; then echo "myDir exists. If Statement Condition equal $?."; fi
myDir exists. If Statement Condition equal 0.

How to check if a command succeeds or failed?

A Bash If Statement takes a command and will test the exit code of that command, using the syntax if <command>; then <code>; fi.

Unless you expect to use the value of the exit code of your command, do not use the exit status code using $?, it is unnecessary and can be error-prone when used with set -e.

# CORRECT
[me@linux ~]$ if grep -q RegPattern "$myfile"; then
  echo "Found RegPattern in $myfile"
fi

# NOT RECOMMENDED
[me@linux ~]$ grep -q RegPattern "$myfile"
[me@linux ~]$ if [ $? -eq 0 ]; then echo "Found RegPattern in $myfile"; fi

How to do string comparison and check if a string equals to a value?

When using [[, the == operator can be used to test strings equality in Bash. Remember that the [[...]] compound command will perform pattern matching where the right-hand side can be a glob pattern. Hence, to prevent globbing and test for equality of strings, make sure to quote the right-hand side of the conditional expression.

👉 Read more about globbing and glob patterns with my post on How To Use Bash Wildcards for Globbing.

[me@linux ~]$ myString1=abc ; myString2=ab*

# Without quotes, Bash will perform glob pattern and fail to test for equality properly
[me@linux ~]$ [[ $myString1 == $myString2 ]]; echo $?
0
[me@linux ~]$ [[ $myString1 == "$myString2" ]]; echo $?
1

# Correct string equality test with a bash if statement
[me@linux ~]$ if [[ $myString1 == "$myString2" ]]; then
  echo "\$myString1 equals to \$myString2 with the string: $myString1";
else
  echo "\$myString1 and \$myString2 are different with \$myString1=$myString1 and \$myString2=$myString2";
fi

If your shell script requires POSIX compliance, you would need to test using the test or [ commands and use the = operator. Quotes becomes irrelevant in those cases as the test and [ don’t perform globbing.

If you want to test the strings against a regular expression, you will need to use the =~ operator and define the regex first since using quotes would cause your regex to be handled as a string.

[me@linux ~]$ myString="ab c" ; myRegex='[[:alpha:]]+.*'
# Test the string against the regex pattern
[me@linux ~]$ [[ $myString =~ $myRegex ]]; echo $?
0

# This would fail as it test against the string value of the regex
[me@linux ~]$ [[ $myString =~ "$myRegex" ]]; echo $?
1

How to check if a string is in an array?

In order to look for an exact match, your regex pattern needs to add extra space before and after the value like (^|[[:space:]])"VALUE"($|[[:space:]]).

[me@linux ~]$ declare -A myAssociativeArray; myAssociativeArray=([a]=123 [b]=456)
[me@linux ~]$ [[ ${myAssociativeArray[*]} =~ (^|[[:space:]])"12"($|[[:space:]]) ]] ; echo $?
1
[me@linux ~]$ [[ ${myAssociativeArray[*]} =~ (^|[[:space:]])"123"($|[[:space:]]) ]] ; echo $?
0

See the detailed examples in my post on Bash Array.

How to use the Bash ternary operator?

Bash does not have a ternary operator, though when using Arithmetic Expansion, the double parentheses ((...)) construct support the question mark ? as an element of a C-style ternary (or trinary) operator, for example (( condition ? result-if-true : result-if-false )). It has a limited use case in my opinion as most of the time it would be more appropriate to just test for the condition by using the standard returned exit code of 0 or 1.

[me@linux ~]$ myVar=12 ; echo $((myVar>10?true:false)) ; echo $((myVar>10?17:3))
0
17

Using the && and || operators to emulate a ternary operator in a shell script is not recommended as it is prone to error, see below section on why you should not use the || and && operators instead of a Bash If Statement.

How to negate an if condition in a Bash if statement? (if not command or if not equal)

To negate any condition, use the ! operator, for example: if ! <test-command>; then <command-on-failure>; fi. Note that the space between the ! and the following command is important, otherwise, it would perform the bash history expansion and most-likely will return a bash error event not found.

For string comparison the not equal operator != can be used in a conditional expression, for example, string1 != string2.

Below is an example of a negative condition on a grep command.

if ! grep -q lookupWord "$myFile"; then
  echo 'Failed to grep';
fi

How to use the BASH_REMATCH variable with the Regular Expression Operator =~?

The Regular Expression conditional operator =~ takes a string value on the left side and a Bash extended regular expression on the right side of the operator. Syntax: *string1* =~ *regex*. The operator return an exit code 0 (True) if the strings match the regular expression regex.

The $BASH_REMATCH environment variable is a read-only Bash Array containing the values matched by the extended regular expression at the right side of the =~ binary operator in a double-bracket [[ conditional expression. Captured groups are stored in the BASH_REMATCH array variable. The string matching the entire regular expression is assigned the first index (0) of the array. The following elements in the array, at index n, correspond to the string matching the n^th parenthesized subexpression.

[me@linux ~]$ shopt -s extglob
[me@linux ~]$ var="This is An Example of Bash Extended Regular Expression"
[me@linux ~]$ [[ "$var" =~ Regular ]] && echo "Word 'Regular' found. BASH_REMATCH=${BASH_REMATCH[@]}"
Word 'Regular' found. BASH_REMATCH=Regular
[me@linux ~]$ [[ "$var" =~ (a) ]] && echo "Letter 'a' found. BASH_REMATCH=${BASH_REMATCH[@]}"
GET UNIQUE TIPS AND THE LATEST NEWS BY SUBSCRIBING TO MY NEWSLETTER.
AND FOLLOW ME ON