# 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,](https://lnkd.in/grgWN5vB) 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:

```bash
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:

```bash
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:

```bash
#!/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:

```bash
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" ]` |

```bash
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 ]` |

```bash
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**:

```bash
# ✅ 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 (`*`):

```bash
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:

```bash
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

```bash
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:

```bash
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

```bash
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:

```bash
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:

```bash
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

```bash
#!/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

```bash
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

```bash
#!/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:

```bash
#!/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`:

```plaintext
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:

```bash
#!/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:
> 
> ```bash
> for device in `cat devices.txt`
> ```
> 
> This works, but the modern and preferred way is `$()`:
> 
> ```bash
> 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

```bash
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:

```bash
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:

```bash
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

```bash
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

```bash
# 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

```bash
# 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:

```bash
#!/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), `||` (at least one 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.*
