Shell Scripts for Beginners: Chapter 2 — Conditional Logic & Loops (For)
Series: Shell Scripts for Beginners Chapter: Shell Script Introduction (Continued) Topics Covered: Conditional Logic (if / elif / else) · Comparison Operators · Pattern Matching · AND & OR Operators · File Operators · For Loops · Reading From Files · C-Style Loops · Real-World Loop Examples
Welcome Back — What This Blog Covers
In the previous chapter, we built the foundation of our Smart Home Automation scripts. We learned how to create and run shell scripts, use variables, accept command line arguments, read user inputs, and perform arithmetic operations.
Our current script can set up a single smart home device — powering on the hub, connecting devices to the network, running health checks, activating the daily schedule, and reporting the status. That's great for one device in one room.
But here's the reality of a smart home: there are dozens of devices, and things don't always go smoothly. A device might fail to connect. A health check might return a warning. And you definitely don't want to manually run the same setup script thirty times for thirty different devices.
That's exactly what this chapter tackles. We'll learn two of the most powerful concepts in scripting:
Conditional Logic — making your script smart enough to react differently based on what's happening (if this, do that; otherwise, do something else)
For Loops — making your script efficient by repeating a task automatically over a list of items
By the end of this post, your Smart Home scripts will be dramatically more intelligent and capable. Let's get into it.
8. Conditional Logic — Teaching Your Script to Make Decisions
The Problem: Scripts That Can't React
In our current setup script, the last thing we do is check the device status:
system_status=$(smarthome status)
echo "System Status for \(device_name: \)system_status"
The smarthome status command can return one of three values:
launching— the device setup is still in progresssuccess— everything went finefailed— something went wrong
Right now, our script just prints the status and exits. It doesn't care whether the device connected successfully or crashed. In the real world, that's a problem — if a device fails, we need to automatically run a debug command to find out why. If it succeeds, we just move on.
This is where conditional logic comes in.
The if Statement — The Core of Conditional Logic
The if statement is the backbone of decision-making in shell scripts. It works exactly the way you'd say it in plain English:
"If this condition is true, then do this."
Here's the basic structure:
if [ condition ]
then
# commands to run if condition is true
fi
A few things to notice:
The condition goes inside square brackets
[ ]There must be at least one space between the brackets and the condition
The block of commands to run is placed after
thenThe block ends with
fi— which is simply "if" spelled backwardsThis open/close pattern (
if...fi) is common in shell scripting — you'll see similar patterns with loops too
Applying This to Our Smart Home Script
Let's update our script so that if a device setup fails, we automatically run the debug command:
#!/bin/bash
device_name=$1
mkdir ${device_name}_session
smarthome add $device_name
smarthome start hub
smarthome connect network
smarthome check devices
smarthome activate schedule
system_status=$(smarthome status)
echo "System Status for \(device_name: \)system_status"
if [ $system_status = "failed" ]
then
echo "Device setup failed. Running diagnostics..."
smarthome debug $device_name
fi
When this script runs:
If
system_statusis"success", the condition$system_status = "failed"is false, so the debug block is skippedIf
system_statusis"failed", the condition is true, and the debug block runs automatically
Your script is now smart enough to respond to what actually happened.
Adding elif — Checking Multiple Conditions
What if you want to handle more than two outcomes? What if you want different messages for success vs launching vs failed?
That's where elif (short for "else if") comes in. It lets you chain multiple conditions:
if [ $system_status = "failed" ]
then
echo "❌ Device setup failed. Running diagnostics..."
smarthome debug $device_name
elif [ $system_status = "launching" ]
then
echo "⏳ Device is still initializing. Please wait..."
else
echo "✅ Device $device_name is online and active!"
fi
How the flow works:
The script checks the first condition (
failed). If true → run that block, skip the rest.If the first condition was false, it checks the
elifcondition (launching). If true → run that block, skip the rest.If none of the conditions above were true, the
elseblock runs as the default.
💡 Key rule: Only one block will ever run. Once a matching condition is found, the rest are skipped entirely.
Comparison Operators — The Language of Conditions
The real power of conditional logic comes from knowing which operator to use for which situation. Shell scripting has different operators for strings vs. numbers.
String Comparison Operators
| Operator | Meaning | Example |
|---|---|---|
= |
Equal to | [ $status = "failed" ] |
!= |
Not equal to | [ $status != "success" ] |
device_type="thermostat"
if [ $device_type = "thermostat" ]
then
echo "Setting temperature controls..."
fi
⚠️ Important: Use
=for strings only. Don't use it to compare numbers.
Numeric Comparison Operators
| Operator | Meaning | Example |
|---|---|---|
-eq |
Equal to | [ $count -eq 10 ] |
-ne |
Not equal to | [ $count -ne 0 ] |
-gt |
Greater than | [ $count -gt 5 ] |
-lt |
Less than | [ $count -lt 20 ] |
-ge |
Greater than or equal to | [ $count -ge 1 ] |
-le |
Less than or equal to | [ $count -le 100 ] |
active_devices=7
max_devices=10
if [ \(active_devices -gt \)max_devices ]
then
echo "Warning: Device limit exceeded!"
fi
💡 Why different operators for numbers? Because
>and<already have special meaning in the shell (they redirect input/output). Using-gtand-ltavoids that conflict.
Spacing Rules — The Gotcha That Trips Everyone Up
This is one of the most common mistakes for beginners. The spacing inside [ ] is mandatory:
# ✅ Correct — spaces around the operator and brackets
if [ $status = "failed" ]
# ❌ Wrong — no spaces around the = operator
if [ $status="failed" ]
# ❌ Wrong — no space after opening bracket
if [$status = "failed" ]
Miss a space, and the script throws a cryptic error. When in doubt, add spaces liberally.
Double Square Brackets [[ ]] — The Enhanced Version
Bash offers an upgraded form of the condition block: double square brackets [[ ]]. It supports everything single brackets do, plus additional pattern matching capabilities.
Pattern Matching With [[ ]]
Check if a string contains another string using wildcards (*):
device_name="living_room_lights"
if [[ $device_name == *"lights"* ]]
then
echo "This is a lighting device."
fi
The * wildcard means "anything can appear here." So *"lights"* means: the variable can have anything before or after the word "lights."
Character Class Matching
Check if a string ends with one of several specific characters:
device_id="device_A"
if [[ $device_id == device_[ABC] ]]
then
echo "Priority device detected."
fi
[ABC] means the last character must be exactly A, B, or C. device_D would not match.
When to Use [[ ]] vs [ ]
| Feature | [ ] |
[[ ]] |
|---|---|---|
| Basic comparisons | ✅ | ✅ |
| Works in all POSIX shells | ✅ | ❌ (Bash only) |
Pattern matching (*, ?) |
❌ | ✅ |
Character classes [ABC] |
❌ | ✅ |
For most scripts running in Bash (which is the default on most Linux systems), [[ ]] is the more powerful and forgiving option.
AND (&&) and OR (||) Operators — Combining Conditions
Often, a single condition isn't enough. You might want to check two things at once.
AND — Both conditions must be true
active_devices=8
max_devices=10
if [ \(active_devices -gt 5 ] && [ \)active_devices -lt $max_devices ]
then
echo "Device count is within normal operating range."
fi
Using [[ ]], you can combine them in a single block:
if [[ \(active_devices -gt 5 && \)active_devices -lt $max_devices ]]
then
echo "Device count is within normal operating range."
fi
OR — At least one condition must be true
device_type="camera"
if [ \(device_type = "camera" ] || [ \)device_type = "doorbell" ]
then
echo "Activating security monitoring..."
fi
File Operators — Checking Files and Directories
Shell scripts often need to check whether a file exists, is a directory, or is executable before doing something with it. These file operators make that easy:
| Operator | Checks For |
|---|---|
-e filename |
File exists (any type) |
-d filename |
Path is a directory |
-s filename |
File exists and has size > 0 |
-x filename |
File is executable |
-w filename |
File is writable |
Practical Smart Home Example
Before loading a device configuration file, check that it actually exists:
config_file="/etc/smarthome/devices.conf"
if [ -e $config_file ]
then
echo "Loading configuration from $config_file..."
smarthome load $config_file
else
echo "Error: Configuration file not found at $config_file"
exit 1
fi
Without this check, the script would try to load a non-existent file and fail with a confusing error. The -e operator catches this cleanly.
Another common use — only create a session directory if it doesn't already exist:
session_dir="${device_name}_session"
if [ ! -d $session_dir ]
then
mkdir $session_dir
echo "Created session directory: $session_dir"
fi
The ! negates the condition — so [ ! -d $session_dir ] means "if this is not a directory."
Full Updated Smart Home Script With Conditionals
#!/bin/bash
# Smart Home Device Setup Script with Status Handling
# Usage: ./setup_smarthome.sh <device_name>
device_name=$1
if [ -z "$device_name" ]
then
read -p "Enter the device name: " device_name
fi
session_dir="${device_name}_session"
# Only create the directory if it doesn't already exist
if [ ! -d $session_dir ]
then
mkdir $session_dir
fi
smarthome add $device_name
smarthome start hub
smarthome connect network
smarthome check devices
smarthome activate schedule
system_status=$(smarthome status)
echo "System Status for \(device_name: \)system_status"
if [ $system_status = "failed" ]
then
echo "❌ Setup failed. Running diagnostics for $device_name..."
smarthome debug $device_name
elif [ $system_status = "launching" ]
then
echo "⏳ $device_name is still initializing..."
else
echo "✅ $device_name is online and active!"
fi
Clean, readable, and reactive. This script now handles three different outcomes intelligently.
9. Loops — For: Doing the Same Thing Many Times, Automatically
The Problem: Repetition at Scale
Our script works great for setting up one device at a time. But a real smart home might have 30, 50, or even hundreds of devices:
Living room lights
Bedroom thermostat
Kitchen smart plug
Front door camera
Back door lock
Garage door sensor
...and so on
Running the setup script manually for each one is exactly the kind of tedious, error-prone work that shell scripting is supposed to eliminate. What we need is a way to tell the script: "Run this block of commands for every device in this list."
That's precisely what a for loop does.
The for Loop — Basic Structure
for variable in list_of_items
do
# commands to run for each item
done
Breaking it down:
variable— a temporary name that holds the current item in each iterationin list_of_items— the list of values to loop throughdo— marks the start of the commands to repeatdone— marks the end of the loop block (just likeficlosesif)
Simple Example — Setting Up Multiple Devices
#!/bin/bash
for device in living_room_lights bedroom_thermostat front_door_camera kitchen_plug
do
echo "Setting up device: $device"
smarthome add $device
smarthome activate schedule
echo "---"
done
What happens when this runs:
| Iteration | Value of $device |
What runs |
|---|---|---|
| 1st | living_room_lights |
Sets up living room lights |
| 2nd | bedroom_thermostat |
Sets up bedroom thermostat |
| 3rd | front_door_camera |
Sets up front door camera |
| 4th | kitchen_plug |
Sets up kitchen plug |
Four devices, one script run. No repetition. No manual effort.
Combining For Loops With Conditional Logic
The real magic happens when you combine loops with if statements. For every device, set it up and handle any failures automatically:
#!/bin/bash
for device in living_room_lights bedroom_thermostat front_door_camera kitchen_plug
do
echo "Setting up: $device"
smarthome add $device
smarthome activate schedule
status=$(smarthome status)
if [ $status = "failed" ]
then
echo "❌ $device failed. Running diagnostics..."
smarthome debug $device
else
echo "✅ $device is online."
fi
echo "---"
done
This script sets up every device, checks its status, and automatically debugs any that fail — all in one run.
Reading Device Names From a File
Typing device names directly into the script is fine when you have four devices. But what about forty? Or four hundred? And what if the list changes regularly?
The solution is to store the list in a separate text file and have the loop read from it.
Step 1: Create a file called devices.txt:
living_room_lights
bedroom_thermostat
front_door_camera
kitchen_plug
garage_door_sensor
back_door_lock
hallway_motion_sensor
bathroom_fan
Step 2: Update the script to read from this file:
#!/bin/bash
for device in $(cat devices.txt)
do
echo "Setting up: $device"
smarthome add $device
smarthome activate schedule
status=$(smarthome status)
if [ $status = "failed" ]
then
echo "❌ $device failed. Running diagnostics..."
smarthome debug $device
else
echo "✅ $device is online."
fi
echo "---"
done
Now, to add or remove a device, you simply edit devices.txt — the script itself never needs to change. That's exactly the kind of separation of configuration from code that makes scripts maintainable and reusable.
⚠️ Backticks vs
$(): You might see older scripts using backticks for command substitution:for device in `cat devices.txt`This works, but the modern and preferred way is
$():for device in $(cat devices.txt)The
$()form is easier to read, easier to nest, and less prone to visual confusion. Always prefer it.
Generating a Numeric Range — Sequences
Sometimes you want to loop a specific number of times — for example, to generate numbered device names or run health checks at set intervals.
Using a Manual List
for i in 1 2 3 4 5 6
do
echo "Setting up device_$i"
smarthome add "device_$i"
done
This would set up device_1 through device_6.
Using a Brace Expansion Range
For larger ranges, manually typing every number is painful. Use brace expansion instead:
for i in {1..10}
do
echo "Setting up device_$i"
smarthome add "device_$i"
done
{1..10} generates the numbers 1 through 10 automatically. You can go as high as you need — {1..100} for a hundred iterations.
C-Style For Loop — If You Come From Another Language
If you've used programming languages like C, Java, or JavaScript, you might be familiar with the classic for loop syntax with an initializer, condition, and step:
for (( i=1; i<=5; i++ ))
do
echo "Running health check #$i"
smarthome check devices
done
This is called a C-style for loop in Bash. It uses double parentheses (( )) and works exactly like you'd expect:
i=1— start at 1i<=5— continue while i is 5 or lessi++— increment i by 1 after each iteration
This is particularly useful when you need fine-grained control over the loop counter, or when you want to loop over an index rather than a list of values.
Real-World For Loop Examples
For loops are one of the most versatile tools in shell scripting. Here are some patterns directly applicable to system administration and smart home management:
1. Count Lines in Multiple Log Files
for log_file in $(ls /var/log/smarthome/)
do
echo "\(log_file: \)(wc -l < /var/log/smarthome/$log_file) lines"
done
2. Install a List of Packages From a File
# packages.txt contains: curl wget git vim htop
for package in $(cat packages.txt)
do
echo "Installing $package..."
apt-get install -y $package
done
3. Check Uptime on Multiple Servers
# servers.txt contains hostnames of your smart home hub servers
for server in $(cat servers.txt)
do
echo "Checking uptime on $server..."
ssh user@$server "uptime"
done
ℹ️ For the SSH example to work without manual password entry each time, set up passwordless SSH using public key authentication first.
Final Smart Home Script — Loops + Conditionals Together
Here's the complete, production-ready version of our Smart Home batch setup script:
#!/bin/bash
# Smart Home Batch Device Setup Script
# Reads device names from devices.txt and sets each one up
# Usage: ./batch_setup_smarthome.sh
devices_file="devices.txt"
# Check that the devices file exists before doing anything
if [ ! -e $devices_file ]
then
echo "Error: $devices_file not found. Please create it with one device name per line."
exit 1
fi
echo "Starting Smart Home batch setup..."
echo "======================================="
for device in \((cat \)devices_file)
do
echo ""
echo ">>> Setting up: $device"
session_dir="${device}_session"
if [ ! -d $session_dir ]
then
mkdir $session_dir
fi
smarthome add $device
smarthome start hub
smarthome connect network
smarthome check devices
smarthome activate schedule
status=$(smarthome status)
if [ $status = "failed" ]
then
echo "❌ $device — Setup FAILED. Running diagnostics..."
smarthome debug $device
elif [ $status = "launching" ]
then
echo "⏳ $device — Still initializing..."
else
echo "✅ $device — Online and active!"
fi
done
echo ""
echo "======================================="
echo "Batch setup complete."
This script:
Validates that
devices.txtexists before startingLoops through every device in the file
Only creates session directories that don't already exist
Handles three different status outcomes per device
Provides clear, readable output throughout
No hardcoded values. No manual intervention needed. Just update devices.txt and run the script.
Chapter 2 Summary
| Concept | Key Points |
|---|---|
if statement |
if [ condition ] / then / fi — runs commands only when condition is true |
elif / else |
Chain multiple conditions; else is the catch-all default |
| String operators | = (equal), != (not equal) |
| Numeric operators | -eq, -ne, -gt, -lt, -ge, -le |
| Spacing rule | Always leave spaces inside [ ] — both around brackets and operators |
[[ ]] |
Enhanced brackets supporting pattern matching and character classes (Bash only) |
| AND / OR | && (both must be true), ` |
| File operators | -e (exists), -d (directory), -s (non-empty), -x (executable), -w (writable) |
for loop |
Repeats a block of commands for each item in a list |
| List from file | for item in $(cat file.txt) — reads loop values from an external file |
| Brace expansion | {1..10} — generates a numeric sequence without typing each number |
| C-style loop | for (( i=1; i<=N; i++ )) — familiar syntax for programmers |
What's Next?
In the next chapter, we'll explore While Loops and Until Loops — another powerful loop type that keeps running as long as a condition is true, which is perfect for monitoring tasks, waiting for a device to come online, or retrying failed operations.
We'll also look at Functions — a way to organize and reuse chunks of your script without repeating the same code over and over.
Until then, practice combining loops and conditionals. Try writing a script that loops through your devices.txt, skips devices that are already online, and only sets up the ones that haven't been configured yet. You have all the tools to build that right now.
Happy scripting! 🏠⚙️
This blog is part of the Shell Scripts for Beginners series. Topics 8 and 9 of the Shell Script Introduction chapter. The project used throughout is a Smart Home Automation System.