Conditionals๐
Part of a deep dive: Bash Scripting
Consult the map
-
Bash Scripting โ step 4 of 6
โ Arguments and Exit Codes ยท you are here ยท 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 Might Have 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 | |
|---|---|
- Runs if
conditionis true. - Runs if the first condition was false and this one is true.
- Runs if no condition was true.
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.
flowchart TD
A["Script starts"] --> B{"Required vars set?"}
B -->|"No"| X1["exit 1"]
B -->|"Yes"| C{"Required tools installed?"}
C -->|"No"| X2["exit 1"]
C -->|"Yes"| D{"Connection works?"}
D -->|"No"| X3["exit 1"]
D -->|"Yes"| E["Main work runs"]
style A fill:#2d3748,stroke:#cbd5e0,stroke-width:2px,color:#fff
style B fill:#1a202c,stroke:#cbd5e0,stroke-width:2px,color:#fff
style C fill:#1a202c,stroke:#cbd5e0,stroke-width:2px,color:#fff
style D fill:#1a202c,stroke:#cbd5e0,stroke-width:2px,color:#fff
style X1 fill:#c53030,stroke:#cbd5e0,stroke-width:2px,color:#fff
style X2 fill:#c53030,stroke:#cbd5e0,stroke-width:2px,color:#fff
style X3 fill:#c53030,stroke:#cbd5e0,stroke-width:2px,color:#fff
style E fill:#2f855a,stroke:#cbd5e0,stroke-width:2px,color:#fff
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
What's Next?๐
Head to Loops โ for and while, iterating over files and command output, and the patterns that make repetition reliable.
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