Look, if you're poking around Linux or macOS terminals, or even wrangling servers somewhere, writing scripts is basically unavoidable. And the heart of making those scripts actually do something useful, make decisions like a human would? That's where the bash shell if then else construct comes crashing in. It's not glamorous, maybe a bit clunky at first glance, but man, is it essential. Trying to script without it is like trying to build a house with just a hammer – possible for a tiny shed, maybe, but good luck with anything complex. Let's break this down without the fluff.
No Fancy Talk: What is If Then Else in Bash Really?
At its absolute core, the bash shell if then else structure lets your script ask a simple question: "Is this thing true?" Based on the answer (yes or no, true or false), it takes one path or another. Think of it like those old Choose Your Own Adventure books.
- If the door is unlocked... then walk inside.
- Else (if it's locked)... try the window.
In Bash terms, you're checking conditions: if this file exists, if that number is bigger than 10, if the user typed 'yes'. Simple logic, powerful results. I remember messing this up early on, spending ages debugging a script because I forgot the spaces around the brackets! Frustrating, but a good lesson.
Getting Your Hands Dirty: The Basic Syntax
Alright, enough theory. Let's see the raw bones of it. Here's the simplest form:
if [ condition ] then # Commands to run if the condition is TRUE fi
See that [ condition ]
? That's the test. Those square brackets [ ]
are actually a command (seriously, try which [
sometime) that checks whatever you put inside. Crucial detail: you must have spaces inside the brackets around your condition. Bash is weirdly picky about whitespace. Forget them, and you get cryptic errors. Ask me how I know...
Adding the "Else" Branch
What about when the condition is false? That's where else
hops in:
if [ condition ] then # Commands for TRUE else # Commands for FALSE fi
Now you're covering both possibilities. Imagine checking if a backup directory exists: if it does, great, start the backup; else (it doesn't exist), create it first then backup. Makes sense, right?
Handling More Choices: Enter "Elif"
Life isn't always binary. Sometimes you have multiple options. For that, bash gives you elif
(short for "else if"):
if [ condition1 ] then # Action for condition1 TRUE elif [ condition2 ] then # Action for condition2 TRUE (and condition1 was FALSE) else # Action if NEITHER condition1 NOR condition2 were TRUE fi
You can chain as many elif
checks as you need, though it can get messy. I once saw a script with 15 elifs... not pretty and hard to debug. Sometimes a case
statement is cleaner for lots of options, but if
elif
else
handles the common 2-4 choices well.
Beyond Basic True/False: Decoding "Conditions"
This is where the real meat is. What can you actually test inside those [ ]
brackets? A whole lot. Here are the bread-and-butter checks you'll use constantly:
Checking Files and Directories (Super Common!)
Need to know if a file exists before you try reading it? Bash has your back.
Condition | What It Checks | Example | When You'd Use It |
---|---|---|---|
-e file |
File/directory exists (any type). | if [ -e "/backups/last_run.log" ] |
Before reading a config/log file, verifying input paths. |
-f file |
Is a regular file (not directory, symlink, etc.). | if [ -f "$HOME/.bashrc" ] |
Before modifying a known config file. |
-d file |
Is a directory. | if [ -d "/tmp/processing" ] |
Before creating files inside a dir, cleaning up temp folders. |
-s file |
File exists and has size > 0 (not empty). | if [ -s "output.csv" ] |
Before processing data files to ensure they contain data. |
-r file |
File exists and is readable. | if [ -r "/etc/shadow" ] (usually fails!) |
Before trying to read sensitive files you *might* have access to. |
-w file |
File exists and is writable. | if [ -w "report.txt" ] |
Before attempting to write or append to a file. |
-x file |
File exists and is executable. | if [ -x "/usr/bin/curl" ] |
Before running a command to verify it's available and runnable. |
These file tests saved my bacon more times than I can count, especially -f
and -d
. Accidentally treating a directory like a file? Script crashes. Check first.
Comparing Strings (Text Data)
Working with filenames, user input, configuration values? You'll be comparing strings.
Condition | What It Checks | Example | Critical Notes |
---|---|---|---|
"$str1" = "$str2" |
Strings are equal. | if [ "$USER" = "root" ] |
Quote variables! Prevents errors if they are empty or have spaces. Spaces around = or != are essential! |
"$str1" != "$str2" |
Strings are not equal. | if [ "$response" != "yes" ] |
Good for validating user input against expected values. |
-z "$str" |
String is empty (zero length). | if [ -z "$filename" ] |
Checking if a required variable wasn't set. Always quote! |
-n "$str" |
String is not empty (has length). | if [ -n "$error_message" ] |
Often used instead of ! -z . Quote it! |
That quoting variables tip? Non-negotiable. If $filename
is empty, [ -f $filename ]
becomes [ -f ]
, which is a syntax error. [ -f "$filename" ]
becomes [ -f "" ]
, which safely evaluates to false. Big difference. Got burned by that early on.
Comparing Numbers (Integers)
Loop counters, exit codes, user input numbers – time for numeric comparisons. Notice we use different operators than strings!
Condition | Meaning | Example | Math Equivalent |
---|---|---|---|
$num1 -eq $num2 |
Equal | if [ $count -eq 10 ] |
count == 10 |
$num1 -ne $num2 |
Not Equal | if [ $exit_code -ne 0 ] |
exit_code != 0 |
$num1 -gt $num2 |
Greater Than | if [ $disk_used -gt 90 ] |
disk_used > 90 |
$num1 -ge $num2 |
Greater Than or Equal | if [ $age -ge 18 ] |
age >= 18 |
$num1 -lt $num2 |
Less Than | if [ $temperature -lt 0 ] |
temperature < 0 |
$num1 -le $num2 |
Less Than or Equal | if [ $retries -le 3 ] |
retries <= 3 |
Mixing up -eq
(numbers) and =
(strings) is a classic beginner mistake. Your script might seem to work sometimes (if the numbers are single digits) and fail weirdly other times. Use the right tool!
Leveling Up: Combining Conditions
Real-world checks often need multiple conditions. Bash provides logical AND (-a
) and OR (-o
) within [ ]
, but honestly? They get messy fast, especially with quoting. The modern, cleaner way is using double brackets [[ ]]
and the operators &&
(AND) and ||
(OR).
Using Double Brackets [[ ]] - Less Quoting Headache
if [[ -f "$config_file" && -r "$config_file" ]]; then echo "Config file exists and is readable. Loading..." source "$config_file" elif [[ ! -e "$config_file" ]]; then echo "Config file missing! Using defaults." else echo "Config file exists but isn't readable! Check permissions." >&2 exit 1 fi
See the differences?
- No strict need for quotes around variables inside
[[ ]]
(though it doesn't hurt and can be safer in complex cases). - Use familiar
&&
and||
. - Pattern matching works with
==
and!=
(e.g.,[[ "$filename" == *.log ]]
). - Regular Expression matching with
=~
(e.g.,[[ "$email" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]
- basic email check).
I strongly recommend using [[ ]]
for anything beyond the simplest checks. It handles more complex logic and quoting much more gracefully.
Exit Codes: The Secret Sauce for Robust Scripts
Every command you run in Linux/Unix finishes with an exit code (or return status). It's a number between 0 and 255.
- 0 = Success
- Anything else (1-255) = Failure (or some specific non-success state defined by the command)
The if
statement inherently checks the exit code of the command it runs! This is incredibly powerful. Instead of [ ]
or [[ ]]
, you can run any command.
if grep -q "ERROR" /var/log/syslog; then echo "Found errors in syslog! Sending alert..." send_alert "Syslog Errors" fi
Here, the condition is the grep -q "ERROR" /var/log/syslog
command itself. grep -q
exits with:
- 0 if "ERROR" is found (success).
- 1 if "ERROR" is not found ("failure" to find it).
So the if
runs the echo
and send_alert
only if "ERROR" exists in the log. Clean and direct!
Why Exit Codes Matter with If Then Else
Understanding exit codes is fundamental to using bash shell if then else effectively beyond simple tests. Your script's logic often hinges on whether a critical step (cp
, mv
, curl
, make
) actually worked.
cp important_data.txt /backup/ if [ $? -eq 0 ]; then echo "Backup succeeded! Removing original." rm important_data.txt else echo "Backup FAILED! Exit code: $?. Original file kept." >&2 exit 1 fi
That cryptic $?
? That's a special variable holding the exit code of the *very last* command executed before it. So cp
runs, then [ $? -eq 0 ]
checks if it succeeded.
Here's a quick cheat sheet of common exit codes you might check:
Exit Code | General Meaning | Common Commands Using It |
---|---|---|
0 | Success | Most commands on success |
1 | General error | Catch-all for many commands (e.g., grep not found) |
2 | Misuse of shell builtin | bash itself (e.g., syntax error) |
126 | Command found but not executable | Permission denied when trying to run a program |
127 | Command not found | Typo in command name, or program not installed |
130 | Script terminated by Ctrl+C | SIGINT signal received |
137 | Process killed forcibly (SIGKILL) | kill -9 |
255* | Exit status out of range | Sometimes used for specific errors |
(*Note: 255 is the maximum due to being stored in 8 bits; returning a number greater than 255 usually wraps around). Checking specific exit codes lets you handle different failures differently.
Putting It All Together: Real-World Bash If Then Else Examples
Let's look at some practical chunks of scripts you might actually use. These combine file tests, string checks, exit codes, and logic.
Example 1: Safe File Processing
# Check if input file exists and is readable if [[ ! -f "$input_file" ]]; then echo "Error: Input file '$input_file' does not exist or is not a regular file." >&2 exit 1 fi if [[ ! -r "$input_file" ]]; then echo "Error: Cannot read input file '$input_file'. Check permissions." >&2 exit 1 fi # Check if output directory exists and is writable; create if needed output_dir="./processed" if [[ ! -d "$output_dir" ]]; then echo "Output directory '$output_dir' not found. Creating it..." mkdir -p "$output_dir" || { echo "Failed to create output dir! Aborting." >&2; exit 1; } fi if [[ ! -w "$output_dir" ]]; then echo "Error: Cannot write to output directory '$output_dir'. Check permissions." >&2 exit 1 fi # Process the file echo "Processing '$input_file'..." process_data "$input_file" > "$output_dir/result.txt" # Check if processing succeeded if [ $? -ne 0 ]; then echo "Error: Processing step failed! Check logs." >&2 exit 1 else echo "Processing completed successfully. Output in '$output_dir/result.txt'" fi
Example 2: User Input Validation
# Prompt user read -p "Do you want to continue? (yes/no): " user_response # Convert response to lowercase for case-insensitive check user_response_lower=${user_response,,} # Validate using bash shell if then else if [[ "$user_response_lower" == "yes" || "$user_response_lower" == "y" ]]; then echo "Proceeding with operation..." # ... perform actions ... elif [[ "$user_response_lower" == "no" || "$user_response_lower" == "n" ]]; then echo "Operation cancelled by user." exit 0 else echo "Invalid input: '$user_response'. Please enter 'yes' or 'no'." >&2 exit 1 fi
Frequently Stumbled-Upon Questions (The FAQ You Actually Need)
Do I really need spaces around [
, ]
, and operators like =
or -eq
?
Yes. Absolutely. Non-negotiable. if [ $var="value"]
will fail. if [ $var = "value" ]
(with spaces) is correct. This trips up everyone at least once. The [
is a command, and commands need spaces separating their arguments. Use [[ ]]
to reduce this pain slightly.
Why do my string comparisons fail when the variable is empty?
Because you didn't quote the variable! if [ $var = "hello" ]
. If $var
is empty, this becomes if [ = "hello" ]
- a syntax error. Always double-quote variables inside [ ]
or [[ ]]
: if [ "$var" = "hello" ]
. If $var
is empty, it becomes if [ "" = "hello" ]
, which correctly evaluates to false. Quoting is crucial.
What's the difference between [ ]
and [[ ]]
? Which should I use?
[ ]
(ortest
): The classic, POSIX-compatible test command. Works in most shells. Requires strict quoting and uses-a
/-o
for AND/OR.[[ ]]
: A Bash keyword (not a command). More powerful and forgiving. Better handling of variables (less quoting anxiety), supports&&
/||
, pattern matching (==
,!=
), regex matching (=~
). Generally safer and preferred for Bash scripts.
Recommendation: Use [[ ]]
exclusively in your Bash scripts unless you have a specific need for POSIX compatibility (like writing a /bin/sh
script). It prevents so many common quoting and logic headaches.
How do I check if a command succeeded in an if statement?
Put the command directly in the if
condition! The if
checks the exit code of the command immediately before the then
.
if curl -s -o /dev/null https://example.com; then echo "Website is reachable." else echo "Failed to reach website or curl error." >&2 fi
This runs curl
silently (-s
), dumping output to the void (-o /dev/null
), and the if
checks if curl exited with 0 (success). Much cleaner than running curl first and then checking $?
.
Can I put multiple commands inside an if-then-else block?
Absolutely! That's the whole point. Indent the lines under then
, else
, or elif
(or use semicolons).
if [[ "$debug_mode" == "true" ]]; then echo "DEBUG: Starting processing at $(date)" echo "DEBUG: Using config file: $config_path" set -x # Turn on command tracing fi
Do as much as you need within each branch.
Why do I sometimes see a semicolon before 'then'? Like if ...; then
It's a style thing, mainly. The semicolon ;
acts as a command separator. If you write your if
condition and the then
keyword on the same line, you need a semicolon to tell Bash where the condition ends and the then
keyword begins.
if [ -f file.txt ]; then # Semicolon required on same line echo "File exists" fi # Or, written differently (no semicolon needed): if [ -f file.txt ] then echo "File exists" fi
Both are valid. Putting the then
on a new line avoids the need for the semicolon and some folks find it cleaner, especially for longer conditions. I generally prefer putting then
on a new line unless the condition is very short.
What are common mistakes when using bash if then else?
- Missing Spaces: Around
[
,]
, operators (=
,-eq
,-lt
). - Unquoted Variables: Leading to syntax errors or unexpected behavior when variables are empty or contain spaces/special characters. Quote them!
- Using
=
for Numbers /-eq
for Strings: Mixing up string and numeric comparisons. - Forgetting the
fi
: Everyif
must be closed withfi
. Mismatchedif
/fi
is a syntax error. - Overcomplicating Logic: Deeply nested
if
/elif
/else
can be hard to read. Consider breaking code into functions or using acase
statement for many specific string matches. - Not Handling Failure Paths: Especially when checking exit codes. Always think, "What if this command fails?" and handle it gracefully with
else
or checking$?
.
Debugging tip: Add set -x
near the top of your script (or temporarily). It prints each command before it's executed, showing you exactly how variables expand and what commands are run. Helped me spot countless quoting and logic errors. Turn it off with set +x
.
Going Beyond the Basics (A Tiny Peek)
While bash shell if then else handles most conditional logic, Bash has other tools:
- Case Statements: Excellent for matching a variable against multiple specific patterns or strings. Often cleaner than a long chain of
elif [[ ... ]]
checks. - Conditional Execution with && and ||: You can chain simple actions:
command1 && command2
(run command2 ONLY if command1 succeeded).command1 || command2
(run command2 ONLY if command1 failed). Useful for one-liners but can become unreadable for complex logic – stick toif
then else for clarity there.
Mastering if
then
else
is the foundation. Build that solidly first.
Look, the bash shell if then else structure might seem like a small cog, but it's the core decision-making engine of your scripts. Get comfortable with file tests (-f
, -d
), string comparisons (=
, !=
- quoted!), numeric comparisons (-eq
, -lt
), and checking exit codes directly in the if
. Embrace [[ ]]
for its quoting sanity and extra features. Remember those spaces, quote your vars, and close your if
s with fi
. It's not rocket science, but getting these fundamentals wrong will make scripting feel like pulling teeth. Get them right, and suddenly automating tasks feels powerful. Go write some scripts that actually make smart choices!
Leave a Comments