Conditionals
Part of Essentials — Bash Scripting
Fourth in the Essentials Bash series. Assumes you understand Arguments and Exit Codes. Next up: Loops.
Every real script makes decisions: does this file exist? Did that command succeed? Is this variable set? Bash conditionals are the logic layer that turns a list of commands into a real program.
Where You've Seen This
The if/elif/else structure is the same as every other scripting language. The one mental shift: Bash conditions are exit codes, not booleans. A command that exits 0 is "true"; non-zero is "false." Once that clicks, the rest follows.
if / elif / else
The syntax is straightforward. What matters more is the pattern: in real scripts, conditionals are mostly guard clauses — check what could go wrong at the top and exit immediately if it does. The rest of the script then runs with confidence that its preconditions are met.
| Basic if Structure | |
|---|---|
fi closes every if. The then keyword requires either a ; before it on the same line, or a newline — both are valid, the single-line form is more common in scripts.
The [[ ]] Test Operator
[[ ]] is Bash's built-in test expression. It evaluates a condition and exits 0 (true) or 1 (false) — which means it works directly inside if, while, and &&/|| chains.
Before operating on a file, confirm it exists and is what you expect. The -f and -d tests are the most common in production scripts:
| File Existence Tests | |
|---|---|
- Exists — file or directory.
- Exists and is a regular file.
- Exists and is a directory.
- Exists and is a symlink.
- Exists and is non-empty (size > 0).
Validate your script has the access it needs before attempting an operation. A permission failure mid-script is harder to debug than a clear error at the start — see File Permissions:
| Permission Tests | |
|---|---|
- Readable by the current user.
- Writable by the current user.
- Executable by the current user.
Use string tests to validate variables before using them. An empty variable used in a destructive command is the most common source of production incidents:
| String Tests | |
|---|---|
- True if
VARis empty or unset. - True if
VARis non-empty. - String equality.
- String inequality.
- Contains substring —
==with a glob pattern. - Matches regex — the pattern is unquoted on the right-hand side.
Use -eq, -lt, -gt for numeric comparisons. == does string comparison — [[ 10 == 9 ]] is false but [[ 10 > 9 ]] does a string sort, not a numeric one:
| Numeric Tests | |
|---|---|
- Equal.
- Not equal.
- Less than.
- Greater than.
- Less than or equal.
- Greater than or equal.
Combining Conditions
Use && and || inside [[ ]] to express complex guards in a single test rather than nesting multiple if blocks:
| Logical Operators | |
|---|---|
- Both conditions must be true — file exists AND is readable.
- Either condition satisfies —
productionORstaging. !inverts the test — runs the body when the directory does NOT exist.
[[ ]] vs [ ]
You'll see both in existing scripts:
| Feature | [[ ]] |
[ ] |
|---|---|---|
| Word splitting on variables | ❌ No — safer | ✅ Yes — requires careful quoting |
Pattern matching with == |
✅ Glob patterns | ❌ No |
Regex with =~ |
✅ Yes | ❌ No |
&& and \|\| inside the test |
✅ Yes | ❌ No |
| Portability | Bash only | POSIX sh compatible |
Use [[ ]] in Bash scripts. Use [ ] only when you need POSIX sh portability (scripts starting with #!/bin/sh).
Testing Command Success
This is where Bash conditionals become uniquely powerful. Because if evaluates exit codes, any command works as a condition — not just [[ ]] tests. You can branch directly on whether a command succeeded or failed, without capturing its output or checking $?:
grep -qruns silently — the exit code is the condition. 0 if the pattern was found, 1 if not.!inverts the exit code — this block runs whennginxis NOT active.command -vis the portable way to check whether a program exists in PATH.
Real-World Patterns
Any script that reads a config file or writes to a directory should validate its paths before doing any work. Fail with a clear message rather than letting the script crash mid-way:
- Guard against a missing file before trying to read it.
- Guard against a permission problem separately — the error message tells the user exactly what's wrong.
Validate string arguments as soon as you receive them — before any logic that depends on their value. An invalid value caught early gives a clear error; one caught late produces confusing behaviour:
- Check for empty before checking the value — an empty variable would pass most value checks silently.
- Allowlist validation — reject anything not explicitly permitted.
Consolidate everything that must be true before a script's main work begins. A preflight block at the top means the script either starts with confidence or fails immediately with a clear reason:
- Check required environment variables first — cheapest check, most common failure.
- Check required tools before trying to use them.
- Verify the actual connection — the most expensive check, so it comes last.
Practice Exercises
Exercise 1: File Validation Script
Write a script that accepts a file path as its argument and:
- Exits with code 1 if no argument is provided
- Exits with code 2 if the path exists but is a directory, not a file
- Exits with code 3 if the file doesn't exist
- Prints
"File OK: {path}"and exits with code 0 if the file exists and is readable
Solution
| validate-file.sh | |
|---|---|
Exercise 2: OS Family Detector
Write a script that detects the Linux distribution family:
- If
/etc/debian_versionexists, print "Debian-based system" - If
/etc/redhat-releaseexists, print "Red Hat-based system" - If
/etc/arch-releaseexists, print "Arch Linux" - Otherwise, print "Unknown distribution"
Quick Recap
- Guard-first: check what can go wrong at the top, exit early, then run the main logic with confidence
if [[ condition ]]; then ... elif ... else ... fi—ficloses everyif- Use
[[ ]]not[ ]in Bash scripts — no word splitting, supports globs and regex - File tests:
-f(regular file),-d(directory),-e(exists),-r/-w/-x(permissions),-s(non-empty) - String tests:
-z(empty),-n(non-empty),==,!=,=~(regex),==with*(glob) - Numeric tests:
-eq,-ne,-lt,-gt,-le,-ge - Any command works as a condition — its exit code is the test
&&and||combine conditions;!inverts them
Further Reading
Command References
man bash— the "Conditional Expressions" section lists every test operatorhelp test— Bash built-in help for[ ]expressions
Deep Dives
- Bash FAQ: test, [, and [[ — Wooledge wiki's complete comparison of all three test constructs
Official Documentation
Exploring Python
- Is It Still Up? — When
if [[ ]]checks grow into full health-check loops with retries and structured failure reporting: the Python approach to service monitoring
What's Next?
Head to Loops — for and while, iterating over files and command output, and the patterns that make repetition reliable.