RED HAT ENTERPRISE LINUX
Conditional Execution
in Bash Scripts
Conditionally execute code (use of: if, test, [], etc.)
CIS126RH | RHEL System Administration 1
Mesa Community College
Scripts that do the same thing every time regardless of circumstances are fragile.
Conditional logic lets scripts check whether a file exists before editing it,
whether a service is running before restarting it, whether the current user is root
before attempting a privileged operation. This module covers the if statement,
the test command and its bracket equivalents, and the full range of
conditions used in RHEL administration scripts. These constructs are tested on the RHCSA exam.
Learning Objectives
-
Explain exit codes and how bash uses them —
Describe how every command returns an exit code and how
ifuses that code to decide what to execute -
Write if/then/else statements —
Construct single-branch, two-branch, and multi-branch conditional blocks
using
if,then,elif,else, andfi - Use test, [ ], and [[ ]] to evaluate conditions — Apply file tests, string comparisons, and integer comparisons
-
Use conditional operators on the command line —
Apply
&&and||for inline conditional execution
Exit Codes: The Foundation
Every command that runs in bash exits with a numeric exit code.
This code is how the shell knows whether a command succeeded or failed —
and it is what if evaluates.
| Exit code | Meaning | if evaluates as |
|---|---|---|
0 | Success — command completed without error | True — execute the then block |
1–255 | Failure — non-zero means something went wrong | False — skip the then block |
# Check the exit code of the last command with $?
$ grep root /etc/passwd
$ echo $?
0 # match found — success
$ grep nobody99 /etc/passwd
$ echo $?
1 # no match — failure
$ ls /nonexistent
$ echo $?
2 # error — file not found
This is the opposite of most programming languages where 0 is false.
In bash, exit code 0 means success, which if treats
as true. Any non-zero exit code means failure, treated as false.
The if Statement Structure
The if statement runs a command and executes one block of code
if the command exits with 0 (success) and another block if it does not.
# Full structure with if, then, else, fi
if COMMAND; then
# commands here run when COMMAND exits 0 (success)
else
# commands here run when COMMAND exits non-zero (failure)
fi
# Single-branch — no else needed
if COMMAND; then
# runs only on success
fi
# Multi-branch with elif
if COMMAND1; then
# runs if COMMAND1 succeeds
elif COMMAND2; then
# runs if COMMAND1 failed and COMMAND2 succeeds
else
# runs if both commands failed
fi
Every if must end with fi (if spelled backwards).
Missing fi is one of the most common syntax errors in bash scripts —
the shell waits for more input or reports a syntax error on the next line.
A First Real Example
Any command that returns an exit code can be used directly in an if
statement — no test brackets required.
#!/bin/bash
# Check if a user exists before taking action
if grep -q "^student:" /etc/passwd; then
echo "User student exists"
else
echo "User student does not exist"
fi
# Check whether a service is active
if systemctl is-active --quiet sshd; then
echo "sshd is running"
else
echo "sshd is not running — starting it"
systemctl start sshd
fi
The -q (quiet) flag on grep and --quiet on
systemctl suppress output — the command still returns the correct exit
code, but nothing is printed. This keeps script output clean.
The test Command
test evaluates an expression and exits with 0 if the expression is
true or 1 if it is false. It is the primary tool for testing file attributes,
string values, and numbers in scripts.
# test returns 0 (true) or 1 (false)
$ test -f /etc/hosts
$ echo $?
0 # /etc/hosts exists and is a regular file
$ test -f /etc/nofile
$ echo $?
1 # does not exist — false
# Used inside an if statement
if test -f /etc/hosts; then
echo "/etc/hosts exists"
fi
# Negation with ! — true when the test is false
if test ! -f /tmp/lockfile; then
echo "No lockfile — safe to proceed"
fi
Single Brackets: [ ]
[ ] is a synonym for test — it evaluates the same
expressions and returns the same exit codes. It is the most common form in
POSIX-compatible scripts.
# [ ] is identical to test — choose whichever is clearer
if [ -f /etc/hosts ]; then
echo "/etc/hosts is a regular file"
fi
# Check if a variable is non-empty
NAME="student"
if [ -n "$NAME" ]; then
echo "NAME is set to: $NAME"
fi
# Compare two strings
if [ "$USER" = "root" ]; then
echo "Running as root"
fi
The brackets must have a space after [ and before ].
[-f /etc/hosts] is a syntax error.
Variables must be quoted: "$VAR" not $VAR — an unquoted
empty variable causes a syntax error inside brackets.
Double Brackets: [[ ]]
[[ ]] is a bash built-in that extends [ ] with safer
handling of variables and additional operators. It is the preferred form in
bash-specific scripts.
# [[ ]] does not require quoting for empty variables
if [[ $NAME == "student" ]]; then
echo "Hello, student"
fi
# Pattern matching with == (glob patterns, not regex)
if [[ $FILE == *.conf ]]; then
echo "$FILE is a config file"
fi
# Regex matching with =~ operator
if [[ $IPADDR =~ ^[0-9]{1,3}\.[0-9]{1,3} ]]; then
echo "Looks like an IP address"
fi
# Logical AND inside [[ ]] — no need for -a flag
if [[ -f /etc/hosts && -r /etc/hosts ]]; then
echo "File exists and is readable"
fi
Use [ ] when writing POSIX shell scripts that must run on any Unix system.
Use [[ ]] when writing bash scripts on RHEL — it is safer and supports
pattern and regex matching.
File Test Operators
File tests are the most common conditions in administration scripts.
All work with both test, [ ], and [[ ]].
| Operator | True if… | Example |
|---|---|---|
-e file | File exists (any type) | [ -e /etc/hosts ] |
-f file | File exists and is a regular file | [ -f /etc/ssh/sshd_config ] |
-d dir | Path exists and is a directory | [ -d /var/log ] |
-r file | File exists and is readable | [ -r /etc/shadow ] |
-w file | File exists and is writable | [ -w /tmp/output.txt ] |
-x file | File exists and is executable | [ -x /usr/bin/python3 ] |
-s file | File exists and is not empty (size > 0) | [ -s /var/log/messages ] |
-L file | File exists and is a symbolic link | [ -L /etc/localtime ] |
-b file | File is a block device | [ -b /dev/sda ] |
-z file | File exists and is empty (size = 0) | [ -z /tmp/lockfile ] |
String Test Operators
String tests compare variable values or check whether they are set and non-empty.
| Operator | True if… | Example |
|---|---|---|
-z "$str" | String is empty (zero length) | [ -z "$NAME" ] |
-n "$str" | String is not empty (non-zero length) | [ -n "$NAME" ] |
"$a" = "$b" | Strings are equal (POSIX) | [ "$USER" = "root" ] |
"$a" == "$b" | Strings are equal (bash extended) | [[ "$USER" == "root" ]] |
"$a" != "$b" | Strings are not equal | [ "$USER" != "root" ] |
"$a" < "$b" | a comes before b alphabetically | [[ "$a" < "$b" ]] |
# Check if the script is run as root
if [ "$(id -u)" -ne 0 ]; then
echo "Error: this script must be run as root"
exit 1
fi
# Guard: check that an argument was provided
if [ -z "$1" ]; then
echo "Usage: $0 username"
exit 1
fi
Integer Comparison Operators
Integer comparisons use letter-based operators inside [ ] and
[[ ]], or standard symbols inside arithmetic (( )).
| [ ] operator | Meaning | (( )) equivalent |
|---|---|---|
-eq | Equal to | == |
-ne | Not equal to | != |
-lt | Less than | < |
-le | Less than or equal to | <= |
-gt | Greater than | > |
-ge | Greater than or equal to | >= |
# Count lines in a log and warn if too many
LINES=$(wc -l < /var/log/messages)
if [ "$LINES" -gt 10000 ]; then
echo "Warning: /var/log/messages has $LINES lines"
fi
# Arithmetic form — natural symbols, no quotes needed
if (( LINES > 10000 )); then
echo "Warning: $LINES lines"
fi
Compound Conditions
Multiple conditions can be combined with logical AND and OR operators.
| Inside [ ] | Inside [[ ]] | Meaning |
|---|---|---|
-a | && | AND — both conditions must be true |
-o | || | OR — at least one condition must be true |
! | ! | NOT — negates the condition |
# Both conditions must be true — file exists AND is not empty
if [[ -f /var/log/messages && -s /var/log/messages ]]; then
echo "Log file exists and has content"
fi
# Either condition may be true — missing arg OR bad arg
if [[ -z "$1" || "$1" == "--help" ]]; then
echo "Usage: $0 username"
exit 0
fi
# NOT — do something when directory does NOT exist
if [[ ! -d /srv/project ]]; then
mkdir -p /srv/project
echo "Created /srv/project"
fi
Inline Conditionals: && and ||
The && and || operators between commands provide
a compact way to write simple conditional logic without a full if block.
# && — run second command only if first succeeds
mkdir -p /srv/data && echo "Directory created"
# || — run second command only if first fails
id student &> /dev/null || useradd student
# Chain: create dir, enter it, confirm — stop on any failure
mkdir -p /tmp/work && cd /tmp/work && echo "Ready"
# Guard pattern: exit if not root
[ "$(id -u)" -eq 0 ] || { echo "Must be root"; exit 1; }
Use && and || for simple one-action conditions.
Use a full if/then/else block when the condition has multiple commands
or when the logic is complex enough to benefit from clear structure.
Real Administration Script Examples
Backup a config file before editing
#!/bin/bash
CONFIG="/etc/ssh/sshd_config"
if [[ -f "$CONFIG" ]]; then
cp "$CONFIG" "${CONFIG}.bak"
echo "Backed up $CONFIG"
else
echo "Error: $CONFIG not found"
exit 1
fi
Ensure a directory exists before writing to it
#!/bin/bash
LOGDIR="/var/log/myscript"
[[ -d "$LOGDIR" ]] || mkdir -p "$LOGDIR"
echo "$(date): started" >> "$LOGDIR/run.log"
Script Structure Best Practices
A well-structured bash script with conditionals follows a consistent pattern.
#!/bin/bash
#
# Purpose: add a user and set up their home directory
# Usage: ./setup-user.sh USERNAME
# 1. Check prerequisites
if [ "$(id -u)" -ne 0 ]; then
echo "Error: must be run as root"; exit 1
fi
if [ -z "$1" ]; then
echo "Usage: $0 USERNAME"; exit 1
fi
USERNAME="$1"
# 2. Main logic
if id "$USERNAME" &> /dev/null; then
echo "User $USERNAME already exists — skipping"
else
useradd -m "$USERNAME"
echo "Created user $USERNAME"
fi
Put prerequisite checks — root check, argument validation, dependency checks — at the top of the script before any actions. This fails fast with a clear message rather than failing halfway through.
Common Mistakes
| Mistake | What goes wrong | Correct form |
|---|---|---|
Missing spaces inside [ ] |
[-f /etc/hosts] — bash error: command not found |
[ -f /etc/hosts ] — space after [ and before ] |
Unquoted variables in [ ] |
[ $VAR = "x" ] fails if VAR is empty or has spaces |
[ "$VAR" = "x" ] — always quote variables |
Missing fi |
Syntax error on the next line or unexpected end of file | Every if must end with fi |
Using = for numeric comparison |
[ "$COUNT" = "10" ] — string comparison, not numeric |
[ "$COUNT" -eq 10 ] — use -eq for numbers |
Using == inside POSIX [ ] |
Some shells reject == inside single brackets |
Use = inside [ ] and == inside [[ ]] |
No semicolon between condition and then on same line |
if [ -f /etc/hosts ] then — syntax error |
if [ -f /etc/hosts ]; then — semicolon before then |
Knowledge Check
Answer these before moving to the next slide.
- What exit code does a command return on success? What does
ifdo with that code? - Write an
ifstatement that prints "File found" if/etc/resolv.confexists as a regular file, and "File missing" if it does not. - What is wrong with this line?
if [-d /tmp/work]; then - Write a condition that is true only when the variable
$USERis equal torootAND the file/etc/shadowexists. - A script receives a username as
$1. Write a guard clause that exits with a usage message if no argument was provided. - What is the difference between
[ ]and[[ ]]? Name one feature available in[[ ]]that is not in[ ].
Knowledge Check — Answers
- A successful command returns exit code
0. Theifstatement treats exit code 0 as true and executes thethenblock. Any non-zero exit code is treated as false and thethenblock is skipped. -
if [ -f /etc/resolv.conf ]; then echo "File found" else echo "File missing" fi - Missing spaces inside the brackets. The correct form is
if [ -d /tmp/work ]; then— a space is required after[and before]. [[ "$USER" == "root" && -f /etc/shadow ]]— both conditions joined with&&inside double brackets. Alternatively:[ "$USER" = "root" ] && [ -f /etc/shadow ]-
if [ -z "$1" ]; then echo "Usage: $0 username" exit 1 fi [ ]is a POSIX-compatible synonym fortest— it works in any shell but requires strict quoting of variables.[[ ]]is a bash built-in that handles unquoted variables safely and adds features such as pattern matching with==, regex matching with=~, and the&&and||logical operators inside the brackets.
Key Takeaways
-
Exit code 0 means success (true); non-zero means failure (false).
The
ifstatement evaluates the exit code of any command.$?holds the exit code of the last command. -
The if block structure is: if COMMAND; then ... fi
Every
ifends withfi. Useeliffor additional conditions. Useelsefor the fallback block. -
Use
[ ]for POSIX compatibility; use[[ ]]for bash scripts. File tests (-f,-d,-e), string tests (-z,-n,=), and integer comparisons (-eq,-ne,-gt) work in both. Always quote variables inside[ ]. -
Start scripts with guard clauses.
Check for root (
[ "$(id -u)" -ne 0 ]) and required arguments ([ -z "$1" ]) at the top. Fail fast with a clear message rather than failing partway through.
Graded Lab
- Write a script that checks whether
/etc/shadowis readable by the current user. Print an appropriate message for each outcome. - Write a script that accepts a filename as
$1, exits with a usage message if no argument is given, and prints whether the file exists, is a regular file, is a directory, or is something else. - Write a script that checks whether the
sshdservice is active. If it is not, start it and print a message. If it is already running, say so. - Write a script with a root guard. If not root, print an error and exit 1.
If root, create
/srv/labonly if it does not already exist. - At the command line (not in a script), use
&&and||to create a user namedlabuseronly if they do not already exist. - Run
bash -n scriptname.shon each script to check for syntax errors without executing it.
"Conditionally execute code (use of: if, test, [], etc.)" Script tasks on the exam require if statements for safety checks, file tests, and service state validation. Guard clauses and file existence tests appear in nearly every script-writing task.
Next: Use looping constructs to process file, command line input