LinuxII · Shell and Command-Line OperationsControl flow
Exit codes, conditionals, and tests
What you'll learn
- Drive conditional execution with $?, &&, ||, and if
- Use test, [, and [[ for string, integer, and file predicates
- Read command output into shell variables safely
- Distinguish success from "no matches" correctly
Prerequisites
Verified against Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL 9.x · Rocky Linux 9.x · AlmaLinux 9.x · Linux kernel 6.1 LTS / 6.6 LTS · systemd 255+ · OpenSSH 8.7p1 (RHEL 9) / 9.6p1 (Ubuntu 24.04) · nftables 1.0.x · chrony 4.x · Pacemaker 2.1.x · Corosync 3.1.x · 2026-08-09
The exit code is the universal decision signal in shell. This lesson shows the constructs that read it.
Conditional operators
The two shortest conditional constructs:
| Operator | Meaning |
|---|---|
&& | Run the right-hand command only if the left-hand exited 0 |
|| | Run the right-hand command only if the left-hand exited non-zero |
$ mkdir -p /tmp/example && cd /tmp/example && pwd/tmp/exampleIllustrative output
$ test -f /etc/shadow && echo exists || echo missingexistsIllustrative output
The if statement
if condition; then
: # success branch
elif other_condition; then
: # second branch
else
: # failure branch
fi
The : is bash’s null command — it does nothing and returns 0.
It is here because a branch containing only a comment is a
syntax error: bash requires at least one command per branch,
and shellcheck reports SC1073: Couldn't parse this then clause. : is the idiomatic placeholder, and it is also how you
write a deliberately empty branch in real code.
The condition is any command. Its exit code is what if reads.
$ if grep -q '^root:' /etc/passwd; then echo found; else echo missing; fifoundIllustrative output
Tests: [, [[, and test
Bash has three ways to write a test expression:
test expression— the POSIX-portable form.[ expression ]— the same form, with the brackets as syntax sugar. Note the spaces inside the brackets —[ foo = bar ]is a test;[foo=bar]is something else.[[ expression ]]— the bash extension. Adds pattern matching, regex matching (=~), and removes some word-splitting hazards.
$ x=10; if [[ "$x" -ge 5 ]]; then echo big; else echo small; fibigIllustrative output
Common test operators
| Category | POSIX [ | Bash [[ |
|---|---|---|
| String equality | = , != | = , == , != |
| Integer equality | -eq, -ne, -lt, -le, -gt, -ge | -eq, -ne, -lt, -le, -gt, -ge |
| File existence | -e file | -e file |
| File is regular | -f file | -f file |
| File is directory | -d file | -d file |
| File is readable | -r file | -r file |
| File is non-empty | -s file | -s file |
| String is non-empty | -n string | -n string |
| String is empty | -z string | -z string |
| Logical AND | expr1 -a expr2 | expr1 && expr2 |
| Logical OR | expr1 -o expr2 | expr1 || expr2 |
| Negation | ! expr | ! expr |
| Pattern match | – | string == pattern |
| Regex match | – | string =~ regex |
Reading command output
The right way to capture command output in a variable:
$ kernel=$(uname -r); echo "running kernel: $kernel"running kernel: 6.6.31-linuxkitIllustrative output
The wrong way:
$ kernel=`uname -r`; echo "running kernel: $kernel"running kernel: 6.6.31-linuxkitIllustrative output
The “no matches” trap
grep, find -print, test -f, and many other commands return
non-zero when they find nothing. This is not an error — it is the
documented way to signal “no results”. Scripts that treat it as an
error need explicit handling:
$ if grep -q 'definitely-not-present' /etc/passwd; then echo found; else echo "exit=$? — no matches is exit 1, not an error"; fiexit=1 — no matches is exit 1, not an errorIllustrative output
Knowledge check
Knowledge check · 3 questions
Q1. Given `make && make install || echo failed`, in which scenario does the script print 'failed'?
Q2. grep exits 1 when the pattern does not match any lines.
Q3. Which of the following test expressions are correct POSIX `[` syntax? Select all that apply.
Passing score: 75%. Answers are checked in this browser.