CodeRigor

LESSON

Make decisions with if, elif and else

Test values with [[ ]], compare numbers with -eq and -lt and text with ==, choose between blocks with if, elif and else, and combine conditions.

Every script so far ran all of its statements, top to bottom. Real programs choose: free entry for small children, a warning when a number is too large. To choose, a script asks a question about its values and runs some statements only when the answer is yes.

A condition is a yes-or-no question

A condition is a question that is either true or false. In Bash you write one between [[ and ]], and if runs a block, a group of statements that belong together, only when the condition is true.

Bash
age=20
if [[ "$age" -ge 18 ]]; then
  echo "You may vote."
fi
echo "Done."

This prints You may vote. and then Done.. Change 20 to 15 and run it again: it prints only Done..

Read the pieces in order:

  • [[ "$age" -ge 18 ]] is the condition. -ge means "is greater than or equal to", so this asks: is the age at least 18?
  • ; then ends the condition and starts the block.
  • The lines down to fi are the block. They run only when the condition is true.
  • fi ("if" backwards) ends the block. The statements after it run in every case.

The two spaces at the start of the line inside the block are called indentation. Bash does not need them; people do. Indent every block so that a reader can see at a glance which lines belong to the if.

The spaces inside the double square brackets are required: one after [[, one before ]], and one on each side of -ge.

Comparing numbers

Six operators compare whole numbers. Each is a dash and two letters:

Operator Meaning True example
-eq equal [[ 5 -eq 5 ]]
-ne not equal [[ 5 -ne 3 ]]
-lt less than [[ 3 -lt 5 ]]
-le less than or equal [[ 5 -le 5 ]]
-gt greater than [[ 5 -gt 3 ]]
-ge greater than or equal [[ 5 -ge 5 ]]

The difference between -lt and -le, and between -gt and -ge, is what happens when the two numbers are equal. "Under 18" is -lt 18; "18 or under" is -le 18. Decide which one the task means, every time.

Comparing text

Text is compared with == (the same) and != (different):

Bash
answer="yes"
if [[ "$answer" == "yes" ]]; then
  echo "Saving."
fi
if [[ "$answer" != "no" ]]; then
  echo "The answer was not no."
fi

This prints Saving. and The answer was not no..

== compares character by character, and capital letters count: "Yes" and "yes" are different. Note the two equals signs. One = assigns; two compare.

Why there are two sets of operators

To Bash every value is text, so the operator has to say how to compare. -eq and its relatives read both sides as numbers. == compares the characters.

Bash
if [[ "07" -eq "7" ]]; then
  echo "-eq: the same number"
fi
if [[ "07" == "7" ]]; then
  echo "==: the same text"
else
  echo "==: different text"
fi

This prints -eq: the same number and ==: different text. Use the dash operators for numbers and == for words.

Two traps follow from this, and neither gives an error message:

  • < and > do not compare numbers inside [[ ]]. They compare text in dictionary order, where "9" comes after "10" because the character 9 comes after the character 1. Always use -lt and -gt for numbers.
  • -eq does not compare words. [[ "yes" -eq "no" ]] is true: a word that is not a number counts as 0, and 0 equals 0. Always use == for text.

One of two blocks: else

else adds a block that runs when the condition is false. Exactly one of the two blocks runs.

Bash
temperature=12
if [[ "$temperature" -ge 20 ]]; then
  echo "Warm enough for a picnic."
else
  echo "Bring a coat."
fi

This prints Bring a coat..

A condition chooses one of two paths
start / 3
The condition is tested once. When it is true the first block runs; when it is false the else block runs. Both paths meet again after fi.
A condition chooses one of two paths
The condition is tested once. When it is true the first block runs; when it is false the else block runs. Both paths meet again after fi.

More than two paths: elif

elif is short for "else if". It adds another condition, tested only when everything above it was false. Bash goes down the list, runs the block of the first condition that is true, and skips the rest.

Bash
score=72
if [[ "$score" -ge 80 ]]; then
  echo "Distinction"
elif [[ "$score" -ge 50 ]]; then
  echo "Pass"
else
  echo "Try again"
fi

This prints Pass. 72 is not at least 80, so the first block is skipped. 72 is at least 50, so the second block runs, and Bash goes straight to fi without looking at the else.

Bash tests the conditions in order and stops at the first true one
start / 4 ⇄ code
With a score of 72 the first condition is false and the second is true, so Pass is printed. The else block is never reached.
Bash tests the conditions in order and stops at the first true one
With a score of 72 the first condition is false and the second is true, so Pass is printed. The else block is never reached.

Because the first true condition wins, order matters. If the two conditions were swapped, a score of 95 would print Pass: it is at least 50, and Bash would never reach the test for 80. Put the narrowest condition first.

Combining conditions

Inside [[ ]] three more operators join or reverse conditions:

  • && means and: true only when both sides are true.
  • || means or: true when at least one side is true.
  • ! means not: it turns true into false and false into true.
Bash
age=30
day="Sunday"
if [[ "$age" -ge 18 && "$age" -lt 65 ]]; then
  echo "Standard ticket"
fi
if [[ "$day" == "Saturday" || "$day" == "Sunday" ]]; then
  echo "Weekend opening hours"
fi
if [[ ! "$day" == "Monday" ]]; then
  echo "Open today"
fi

This prints all three lines. 30 is at least 18 and also under 65. The day is Sunday, which makes one side of the || true. And the day is not Monday.

Each side of && and || must be a complete comparison. "Saturday or Sunday" is written with the variable on both sides, as above.

A decision about something the user typed

Decisions become useful when the value comes from the user. This script reads a number and responds to it:

Bash
echo "How many tickets?"
read -r tickets
if [[ "$tickets" -gt 10 ]]; then
  echo "Sorry, 10 is the most we can sell."
else
  echo "That costs $(( tickets * 5 )) pounds."
fi

With 4 typed it prints the question and That costs 20 pounds.. With 12 it prints the question and Sorry, 10 is the most we can sell.. Try both in the Bash playground, with the number in the Standard input box.

Error messages you will meet

A missing space after [[.

Bash
age=20
if [[$age -ge 18 ]]; then
  echo "You may vote."
fi
echo "Done."
Text
solution.sh: line 2: [[20: command not found
Done.

You know this message from lesson 1. Without the space, Bash joined [[ and the value 20 into one word, [[20, and looked for a command with that name. Nothing was tested, the block was skipped, and the script carried on.

A missing fi.

Bash
age=20
if [[ "$age" -ge 18 ]]; then
  echo "You may vote."
echo "Done."
Text
solution.sh: line 5: syntax error: unexpected end of file from `if' command on line 2

This is a syntax error, as in lesson 1: the text breaks the writing rules of the language, and this site runs none of the script. Here Bash reached the end of the file while still inside an if. The message gives two line numbers. The first, line 5, is where Bash gave up (one past the last line). The second, line 2, is the useful one: the if that was never closed.

A missing then.

Bash
age=20
if [[ "$age" -ge 18 ]]
  echo "You may vote."
fi
Text
solution.sh: line 4: syntax error near unexpected token `fi'
solution.sh: line 4: `fi'

"Unexpected token" means Bash met a word it did not expect at that point. It found fi while it was still waiting for then. The mistake is above the line that is named, which is common with syntax errors: when the named line looks correct, read upwards.

What you can do now

You can compare numbers and text, choose between two or more blocks, and combine conditions with and, or and not. In the exercises you will predict which block runs, then write a script that works out a ticket price.

Next: repeat steps with for and while, so that a script can do something many times.

Watch & read

Free lectures, talks and readings that cover this skill from another angle. All resources →

Starts at the chapter 'Test operators' (23:23) and continues into 'If/Elif/Else' (25:19), about five minutes in all. Stop when 'Case statements' begins (28:37); this unit does not use them.

A slower walk through a first if statement (1:42) and if/else (7:19). The first twelve minutes match this lesson; the later chapters test for files and install software, which this unit does not cover. If a test in the video is written with single square brackets, that is an older form of the [[ ]] used here.