Skip to main content

Command Palette

Search for a command to run...

Shell Scripts for Beginners: Chapter 2 — Conditional Logic & Loops (For)

Updated
18 min readView as Markdown

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 progress

  • success — everything went fine

  • failed — 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 then

  • The block ends with fi — which is simply "if" spelled backwards

  • This 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_status is "success", the condition $system_status = "failed" is false, so the debug block is skipped

  • If system_status is "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:

  1. The script checks the first condition (failed). If true → run that block, skip the rest.

  2. If the first condition was false, it checks the elif condition (launching). If true → run that block, skip the rest.

  3. If none of the conditions above were true, the else block 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 -gt and -lt avoids 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 iteration

  • in list_of_items — the list of values to loop through

  • do — marks the start of the commands to repeat

  • done — marks the end of the loop block (just like fi closes if)


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 1

  • i<=5 — continue while i is 5 or less

  • i++ — 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.txt exists before starting

  • Loops 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.