Practical Bash Scripting: Automating Linux from Scratch

Linux tutorial - IT technology blog
Linux tutorial - IT technology blog

Bash Scripting: Why Just Typing Commands Isn’t Enough?

Three years ago, I spent an entire night manually typing backup commands for 10 production VPS instances. Back then, I only knew how to use history and copy-paste. Disaster struck when I accidentally typed a space after rm -rf /. Luckily, I stopped in time, but that moment was enough for me to realize: if you want to advance in your Linux career, automation is mandatory.

Which Tool Should You Choose for Automation?

When a task repeats more than three times, you should stop manual typing. Generally, we have a few options:

  • Manual Commands: Fast for one-time tasks. However, it’s a “nightmare” if you need to execute them on 20 servers simultaneously.
  • Using Aliases: Great for short commands like alias ll='ls -la'. But Aliases cannot handle complex logic or if-else conditions.
  • Bash Script: The most balanced choice. It runs natively on every Linux distro without needing extra installations like Python or Node.js.
  • Ansible/Terraform: Extremely powerful for large infrastructure. But if you just want to quickly check disk space, they are way too bulky.

Why You Should (and Shouldn’t) Use Bash?

Pros:

  • 100% available on Ubuntu, CentOS, or even the ultra-lightweight Alpine.
  • Interacts with files and system processes with near-zero latency.
  • Every error you encounter has likely had an answer on StackOverflow from 10 years ago.

Cons:

  • Bash syntax is full of “pitfalls” regarding whitespace.
  • Debugging can sometimes be more frustrating than in high-level languages.
  • Easy to turn into a mess of spaghetti code if you’re too lazy to use functions.

4 Steps to Build “Production-Ready” Scripts

To avoid scripts running wild and deleting the wrong data, I always apply the following 4-step process.

1. Always Use Shebang and Variables

The #! /bin/bash line at the top of the file is mandatory. It tells the system exactly which interpreter to use. Additionally, always use clear variable names instead of hard-coding values.

#!/bin/bash

# Always capitalize environment variable names for easier management
BACKUP_DIR="/var/backups/nginx_logs"
CURRENT_USER=$(whoami)

echo "Hello $CURRENT_USER, system starting backup into $BACKUP_DIR"

2. Controlling Conditions (Conditionals)

In Bash, double square brackets [[ ]] are more flexible than the single [ ] variety. One ironclad rule: always leave whitespace around the brackets. Without it, your script will stop working immediately.

if [[ ! -d "$BACKUP_DIR" ]]; then
    echo "Directory does not exist. Initializing..."
    mkdir -p "$BACKUP_DIR"
else
    echo "Directory already exists, continuing process."
fi

3. Optimization with Loops

Suppose you need to compress 50 old log files to free up 80% of disk space. A for loop will handle this in less than 2 seconds.

# Compress all .log files in the current directory
for log_file in *.log; do
    echo "Compressing: $log_file"
    tar -czf "${log_file}.tar.gz" "$log_file"
done

4. Encapsulate Code into Functions

If you find yourself writing echo too many times to notify status, create a separate log function. This keeps your code cleaner and much easier to maintain.

notify() {
    local status=$1
    local msg=$2
    echo "[$(date +'%H:%M:%S')] [$status] $msg"
}

notify "INFO" "Script is starting..."
notify "ERROR" "Database not responding!"

Error Handling Techniques to “Survive” on Production

A great script differs from a beginner script in how it handles failure. A small mistake in a backup script can result in total data loss if you don’t check for errors.

The Power Trio: set -euo pipefail

I always add this line right after the Shebang. It’s “insurance” for every script:

  • -e: Stop the script immediately if any command fails.
  • -u: Report an error if you call an undeclared variable.
  • -o pipefail: Catch errors even when they occur behind a pipe |.

Checking the Exit Code ($?)

After critical commands like rsync or mysqldump, check the $? variable. A value of 0 means success; otherwise, there’s a problem.

rsync -avz /src/ /dest/
if [[ $? -ne 0 ]]; then
    echo "Sync error! Check your network connection."
    exit 1
fi

Closing Thoughts from Real-World Experience

Writing Bash isn’t hard; writing it safely is. After years of operation, I’ve come to one rule: Always test your scripts in a Staging environment. Never fully trust what you’ve just typed.

If a script exceeds 500 lines, it’s time to switch to Python. But for daily sysadmin tasks, Bash remains “king.” Start with small scripts, and you’ll see your productivity increase significantly.

Share: