🖥️ Automating the Basics: A Hands-On Intro to Bash Scripting
This guide walks you through what Bash scripting is, how it’s used, and when to graduate to a programming language like Python.

Whether you're a homelabber, sysadmin, or just love the command line—you’re running Debian, Ubuntu, Fedora, Arch, or something minimal and headless—Bash scripting gives you the power to automate the command line and simplify daily tasks. And there's no better way to learn than firing up your box and getting your hands dirty. For this, I will be using a Raspberry Pi with Raspberry Pi OS, which is just based on the Debian Linux distribution (Ubuntu, Mint, Kali) for theRaspberry Pi.
💡 What You’ll Learn
- What Bash scripting is (and what it isn’t)
- Why it's used in real-world environments
- How to write and run your first script
- Key Bash concepts: variables, loops, conditionals
- Real-life examples for system admins and homelabbers
- When to use Bash vs. a real programming language
🔍 What Is Bash Scripting?
Bash scripting is the practice of writing a sequence of shell commands into a plain-text file and executing them like a mini-program.
Instead of typing the same commands over and over, a Bash script lets you write them once and run them as often as you need—with zero extra keystrokes.
🧠 When Is Bash Scripting Actually Used?
Use Case | Example |
---|---|
🛠️ System Maintenance | Auto-updating, cleaning /tmp , checking disk usage |
📡 Network Automation | Running NMAP scans, rotating logs, restarting services |
🔁 Repetitive Tasks | Backing up configs, converting files, syncing directories |
🧪 DevOps/CI Pipelines | Prepping build environments, starting containers |
🧰 Getting Started (No GUI Needed)
All you need is:
- Any Unix-like OS (Linux, Raspberry Pi, macOS, WSL)
- Bash shell (default in most distros)
- A terminal with
vi
(or your preferred text editor) - Curiosity + a goal
🛠️ Step 1: Create Your First Script File (We’re Using vim
— But You Do You)
If you're more comfortable with something else like nano
, or even emacs
, go for it — the content stays the same.
Let’s make our first Bash script:
vim hello.sh
In vim
:
- Press
i
to enter INSERT mode - Type or paste the following:
#!/bin/bash
echo "Hello, world! This is your first script."
- Press
Esc
, then type:wq
and hitEnter
to save and exit
🧠 Tip: If you’re using nano
, just type:
nano hello.sh
And save with CTRL + O
, Enter
, CTRL + X
.
🔐 Step 2: Make It Executable
chmod +x hello.sh
Now your script has execute permission — just like a program.
🚀 Step 3: Run It
./hello.sh
You should see:
Hello, world! This is your first script.
If you’re using sudo
, always run with ./
:
sudo ./hello.sh
Otherwise, you’ll get a command not found
error — because sudo
doesn’t check the current directory by default.

🔎 Bash Breakdown: What’s Happening?
#!/bin/bash
— Shebang line tells the system to use Bashecho
— Prints to the terminalchmod +x
— Gives the script execution rights./script.sh
— Runs the script from the current directory
🧠 Understanding Core Bash Scripting Concepts
These three building blocks—variables, conditionals, and loops—make your scripts flexible, smart, and powerful. Let’s walk through each with examples and real-life logic.
🌀 1. Variables: Store and Reuse Values
A variable in Bash is used to store data like text, numbers, or file paths — and reuse them later in your script.
📦 Syntax:
name="Bryan"
echo "Welcome, $name!"
✅ Output: Welcome, Bryan!
🔍 Real Example:
Let’s say you regularly back up a folder. Instead of hardcoding the path each time, you can store it as a variable:
backup_path="/etc"
tar -czf backup.tar.gz $backup_path
Variables make your script:
- Easier to read
- Easier to maintain
- More portable
🧪 2. Conditionals: Make Decisions
Conditionals allow your script to take different paths depending on whether a test passes or fails (true or false).
📦 Syntax:
if [ condition ]; then
# commands if true
else
# commands if false
fi
✅ Example: Check if a file exists
if [ -f /etc/passwd ]; then
echo "The file exists!"
else
echo "No such file found."
fi


✔️-f
checks for a file
✔️-d
checks for a directory
✔️-z
checks if a string is empty
✔️-n
checks if a string is not empty
🔍 Real Example:
Let’s warn the user if disk space is getting low:
space_left=$(df / | grep / | awk '{print $5}' | sed 's/%//')
if [ "$space_left" -gt 80 ]; then
echo "⚠️ Disk usage is high: ${space_left}%"
else
echo "✅ Disk space is healthy."
fi

Conditionals let your script react to real-world states.
🔁 3. Loops: Do It Again and Again
A loop repeats a command or group of commands. Perfect when you want to do something for every file, every user, every log, etc.
📦 for
Loop Syntax:
for item in list; do
# commands
done
✅ Example: Loop over files
for file in *.log; do
echo "Found file: $file"
done
This would echo the name of every .log
file in the current directory.
🔍 Real Example: Loop through users
Want to print every user account on your system?
for user in $(cut -d: -f1 /etc/passwd); do
echo "User account: $user"
done
✅ This script scans your system’s user database and prints each account. You’ll see built-in system users likedaemon
,www-data
,ssh
, and finally your own user at the end (likesteeletekk
).

🧠 This is a great way to:
- Audit accounts
- Prepare for adding/removing users
- Learn how your Linux system manages identity
🔐 What’s /etc/passwd
?
It’s not just for passwords (despite the name). It’s the user account database for the system.
When you run:
cat /etc/passwd

You see a whole wall of user accounts, even though you probably only created one for yourself (like steeletekk
). So what are the rest?
🛠️ These are system and service accounts
Linux creates a bunch of special-purpose user accounts during installation — each one is used to run a particular service, process, or daemon. They are not meant for login. They often have:
- No password
- No real home directory
- A “nologin” shell (to prevent login access)
- System users are usually UID
0–999
(while Human users usually start at UID1000+)
So, the first field before the:
is always the username — that’s why wecut
-f1
.
You can use loops to:
- Process multiple files
- Iterate over command output
- Automate batch operations
🧰 Bringing It All Together: A Practical Script Example
Let’s wrap variables, conditionals, and loops into a real script that cleans up old log files. This version includes sudo
, where needed for real-world usage.
Let’s say we want to scan a directory for log files (*.log
) and delete the ones older than a certain number of days. We'll define that as a variable, check if files exist (conditionals), and loop through them all.
📄 Script: cleanup_logs.sh
#!/bin/bash
# 🔁 VARIABLE: Define target directory and age threshold
log_dir="/var/log"
days_old=7
echo "Looking for log files older than $days_old days in $log_dir..."
# ✅ CONDITIONAL: Check if directory exists
if [ ! -d "$log_dir" ]; then
echo "Directory $log_dir does not exist. Exiting."
exit 1
fi
# 🔁 LOOP: Iterate over matching old files
for file in $(find "$log_dir" -name "*.log" -type f -mtime +"$days_old"); do
echo "Deleting: $file"
rm -f "$file"
done
echo "✅ Log cleanup complete."
Pop that into a file named cleanup_logs.sh
, make it executable and try it out

Since you're cleaning up files in/var/log
, many of them are owned byroot
, so you'll needsudo
to delete them.
💡 What’s Happening?
Feature | Usage in Script |
---|---|
Variable | log_dir and days_old store configurable values |
Conditional | Checks if $log_dir exists before running cleanup |
Loop | Goes through each file returned by find |
🔐 Real Use Cases for This Script
- Run via cron weekly to manage disk usage
- Adapt for
.gz
,.tmp
,.bak
, etc. - Use in Pi-hole or Docker volume logs cleanup
- Extend to email you a summary if disk space is low
⚙️ Another Simple Real-World Script Example: System Update
I run regular updates using sudo apt update && sudo apt upgrade -y
What I could do is, add it to a file named update.sh
:
#!/bin/bash
echo "Updating system..."
sudo apt update && sudo apt upgrade -y
echo "All done!"
Save it as update.sh
, make it executable, and run it when needed. Or schedule it in a cron job to keep systems up to date.
steeletekk@LabPi:~ $ cat update.sh
#!/bin/bash
echo "Updating system..."
sudo apt update && sudo apt upgrade -y
echo "All done!"
steeletekk@LabPi:~ $ chmod +x update.sh
steeletekk@LabPi:~ $ ./update.sh
Updating system...
Hit:1 http://deb.debian.org/debian bookworm InRelease
Get:2 http://deb.debian.org/debian-security bookworm-security InRelease [48.0 kB]
Hit:3 http://deb.debian.org/debian bookworm-updates InRelease
Hit:4 http://archive.raspberrypi.com/debian bookworm InRelease
Fetched 48.0 kB in 11s (4,477 B/s)
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
All packages are up to date.
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
Calculating upgrade... Done
The following package was automatically installed and is no longer required:
libcamera0.3
Use 'sudo apt autoremove' to remove it.
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
All done!
steeletekk@LabPi:~ $
🧠 Bash Scripting vs. Programming Languages
Here’s where many get confused — Bash feels like programming, but it’s not the same as writing in Python or Go.
🧰 Bash Scripting: Command-Line Automation
Feature | Bash Scripting |
---|---|
Built For | Automating terminal tasks |
Great At | System maintenance, scripting CLI workflows |
Weak At | Complex logic, large applications |
Setup | Minimal — just a terminal and bash |
Real Use | Backups, monitoring, file manipulation |
Use Bash when:
- Automating system commands
- Managing configs or logs
- Writing quick CLI glue logic
🧠 Programming Languages: Application Development
Feature | Python / Go / Rust / JS |
---|---|
Built For | Complex logic, apps, and services |
Great At | APIs, file parsing, data handling |
Weak At | CLI-only workflows (can be overkill) |
Setup | Requires interpreters or compiled binaries |
Real Use | Building tools, services, dashboards |
Use a full language when:
- You need structured logic
- You’re working with APIs or data
- Your Bash script starts to hit 100+ lines
⚔️ Real-World Comparison: Bash vs. Python
Task | Bash | Python |
---|---|---|
Backing up /etc | ✅ tar command | ✅ shutil , os modules |
Renaming files in a folder | ✅ for file in *; do mv... | ✅ os.rename() |
Parsing JSON | 😬 Use jq or external tools | ✅ Built-in |
Error handling & retries | ☠️ Messy if/else | ✅ Try/except blocks |
Building CLI apps | ⚠️ Hacky menus | ✅ Use argparse , click , etc. |
🔐 Best Practices
- Use
#
comments to document scripts - Test scripts in a sandbox before production
- Always use
"quoted variables"
to handle spaces - Exit intentionally (
exit 0
orexit 1
) - Modularize when possible (break into functions)
📚 Where to Go From Here
- Build a disk usage checker
- Monitor open ports with
ss
ornetstat
- Script log cleanup (
find /var/log -name "*.log" -mtime +7 -delete
) - Backup
Pi-hole
, Unbound, or config files with a nightly cron script
📘 Final Thoughts: A Little Scripting Goes a Long Way
Bash scripting isn’t about perfection — it’s about progress. You don’t need to write elegant, complex code. You just need to solve a problem and let the system handle the repetition.
With just a few core concepts — variables, conditionals, and loops — you can automate:
- Cleanup jobs
- System updates
- Monitoring checks
- Backups
- And just about anything else you do more than once
You’ve seen real-world examples. You’ve learned what /etc/passwd
is actually for. And you've built something that could run silently in the background of your system every day, doing your dirty work.
🚦What’s Next?
Maybe that’s it — maybe not.
If there’s interest (or if I just feel like it), we’ll explore Phase 2:
- Passing arguments to your scripts
- Logging actions
- Scheduling tasks with
cron
- Adding interactivity and safety nets
But for now… just keep writing small scripts, running them, breaking them, and learning by doing.
Until next time — script responsibly.
☮️ OUT!
— Bryan