funwithlinux guide

How to Automate Your Workflow with Bash Scripts

In today’s fast-paced digital world, repetitive tasks can drain your productivity. Whether you’re a developer, system administrator, or just a power user, automating these tasks can save hours of work, reduce human error, and ensure consistency. Enter **Bash scripting**—a lightweight, powerful tool built into every Linux, macOS, and Windows Subsystem for Linux (WSL) environment. Bash (Bourne Again SHell) is a command-line interpreter that allows you to run commands sequentially. A Bash script is a text file containing a series of these commands, enabling you to automate everything from file backups and log cleanup to system monitoring and batch processing. This guide will take you from Bash basics to writing robust, real-world scripts. By the end, you’ll be equipped to automate your own workflows and reclaim valuable time.

Table of Contents

  1. What is Bash Scripting?
  2. Setting Up Your Environment
  3. Your First Bash Script
  4. Variables in Bash
  5. Control Structures: Conditionals and Loops
  6. Handling Inputs: Command-Line Arguments
  7. Functions for Reusability
  8. Real-World Automation Examples
  9. Best Practices for Bash Scripts
  10. Troubleshooting Common Issues
  11. References

1. What is Bash Scripting?

Bash scripting is the process of writing a sequence of commands in a text file (called a “script”) that the Bash shell can execute. Unlike compiled languages (e.g., C++), Bash scripts are interpreted—meaning the shell reads and runs commands line by line without needing to be compiled first.

Why Bash?

  • Ubiquitous: Preinstalled on Linux, macOS, and WSL. No extra setup required.
  • Simplicity: Uses familiar command-line syntax (e.g., ls, cp, grep).
  • Powerful: Supports variables, loops, conditionals, and integration with other tools (e.g., awk, sed, git).

2. Setting Up Your Environment

Before writing scripts, ensure your environment is ready:

Prerequisites

  • A Bash-compatible terminal:
    • Linux: Default terminal (e.g., GNOME Terminal).
    • macOS: Terminal or iTerm2.
    • Windows: WSL (install via Microsoft Store) or Git Bash.
  • A text editor: Use VS Code, Vim, Nano, or Sublime Text.

Verify Bash Installation

Check if Bash is installed by running:

bash --version

You should see output like GNU bash, version 5.1.16(1)-release.

3. Your First Bash Script

Let’s start with a simple “Hello World” script to understand the basics.

Step 1: Create the Script File

Open your text editor and create a new file named hello_world.sh. The .sh extension is conventional for Bash scripts (though not required).

Step 2: Add the Shebang Line

Every Bash script should start with a shebang line (#!), which tells the system which interpreter to use. For Bash, this is:

#!/bin/bash

Step 3: Add Commands

Below the shebang, add a command to print text to the terminal using echo:

#!/bin/bash

# This is a comment (ignored by the shell)
echo "Hello, World!"  # Print a message

Step 4: Make the Script Executable

By default, the file won’t be executable. Use chmod to grant execute permissions:

chmod +x hello_world.sh

Step 5: Run the Script

Execute the script with:

./hello_world.sh

Output:

Hello, World!

Pro Tip: To run scripts from any directory, add their location to your $PATH (e.g., export PATH="$HOME/scripts:$PATH" in ~/.bashrc).

4. Variables in Bash

Variables store data for reuse. They make scripts dynamic and easier to maintain.

Defining Variables

Use VAR_NAME=value (no spaces around =):

name="Alice"
age=30

Accessing Variables

Prefix the variable name with $:

echo "Name: $name"  # Output: Name: Alice
echo "Age: $age"    # Output: Age: 30

Environment Variables

Bash provides built-in variables for system info (e.g., $HOME for your home directory, $PATH for executable paths):

echo "Home: $HOME"  # Output: Home: /home/alice
echo "Path: $PATH"  # Output: Path: /usr/local/sbin:/usr/local/bin:...

User Input with read

Use read to capture input from the user:

#!/bin/bash

echo "Enter your name:"
read username  # Stores input in $username
echo "Hello, $username!"

Run it:

./greet.sh
Enter your name: Bob
Hello, Bob!

5. Control Structures: Conditionals and Loops

Bash supports logic to make scripts decision-driven and iterative.

Conditionals: if Statements

Use if to run commands only if a condition is true.

Syntax:

if [ condition ]; then
  # Commands if true
elif [ another_condition ]; then
  # Commands if first false, second true
else
  # Commands if all false
fi

Common Conditions:

  • File tests:
    • -f file: True if file exists and is a regular file.
    • -d dir: True if dir exists and is a directory.
    • -e path: True if path exists.
  • String comparisons:
    • string1 == string2: True if equal.
    • string1 != string2: True if not equal.
  • Numeric comparisons:
    • num1 -eq num2: Equal.
    • num1 -ne num2: Not equal.
    • num1 -lt num2: Less than.
    • num1 -gt num2: Greater than.

Example: Check if a File Exists

#!/bin/bash

file="data.txt"

if [ -f "$file" ]; then
  echo "$file exists."
else
  echo "$file does NOT exist."
fi

Loops: for and while

for Loop: Iterate Over a List

#!/bin/bash

# Loop over fruits
fruits=("apple" "banana" "cherry")
for fruit in "${fruits[@]}"; do
  echo "I like $fruit"
done

Output:

I like apple
I like banana
I like cherry

while Loop: Run Until a Condition Fails

#!/bin/bash

count=1
while [ $count -le 5 ]; do
  echo "Count: $count"
  count=$((count + 1))  # Increment count
done

Output:

Count: 1
Count: 2
Count: 3
Count: 4
Count: 5

6. Handling Inputs: Command-Line Arguments

Scripts often need input when run (e.g., a filename or directory). Use command-line arguments for this.

Key Variables for Arguments

  • $1, $2, …: The first, second, etc., argument.
  • $@: All arguments as a list.
  • $#: Number of arguments.
  • $0: The script’s filename.

Example: Backup Script with Arguments

#!/bin/bash

# Usage: ./backup.sh <source_dir> <dest_dir>

source_dir="$1"
dest_dir="$2"

# Check if arguments are provided
if [ $# -ne 2 ]; then
  echo "Usage: $0 <source_dir> <dest_dir>"
  exit 1  # Exit with error code 1
fi

# Check if source exists
if [ ! -d "$source_dir" ]; then
  echo "Error: Source directory $source_dir does not exist."
  exit 1
fi

# Create destination if it doesn't exist
mkdir -p "$dest_dir"

# Copy files (add -v for verbose)
cp -r "$source_dir"/* "$dest_dir/"
echo "Backup from $source_dir to $dest_dir completed!"

Run it:

./backup.sh ~/Documents ~/Backups/Docs
Backup from /home/alice/Documents to /home/alice/Backups/Docs completed!

7. Functions for Reusability

Functions let you group commands into reusable blocks, making scripts cleaner and easier to maintain.

Syntax:

function_name() {
  # Commands here
  # Access parameters with $1, $2, etc.
}

Example: A File Backup Function

#!/bin/bash

# Function to backup a file
backup_file() {
  local source="$1"  # Local variable (only in function)
  local dest="$2"

  if [ ! -f "$source" ]; then
    echo "Error: $source not found."
    return 1  # Return error code
  fi

  cp "$source" "$dest"
  echo "Backed up $source to $dest"
}

# Use the function
backup_file "notes.txt" "notes_backup.txt"
backup_file "missing.txt" "nowhere.txt"  # Will error

Output:

Backed up notes.txt to notes_backup.txt
Error: missing.txt not found.

8. Real-World Automation Examples

Let’s apply what we’ve learned to solve common problems.

Example 1: Log Cleanup Script

Delete log files older than 7 days to free up space.

#!/bin/bash

LOG_DIR="/var/log/myapp"
DAYS=7

# Check if log directory exists
if [ ! -d "$LOG_DIR" ]; then
  echo "Error: $LOG_DIR does not exist."
  exit 1
fi

# Delete files older than 7 days (add -v for verbose)
find "$LOG_DIR" -name "*.log" -type f -mtime +$DAYS -delete

echo "Deleted logs older than $DAYS days in $LOG_DIR."

How to Use:

  • Save as clean_logs.sh.
  • Make executable: chmod +x clean_logs.sh.
  • Run with sudo if logs require permissions: sudo ./clean_logs.sh.

Example 2: System Monitoring Script

Check CPU usage, disk space, and memory, and alert if thresholds are exceeded.

#!/bin/bash

# Thresholds (adjust as needed)
CPU_THRESHOLD=80  # %
DISK_THRESHOLD=85 # %
MEM_THRESHOLD=80  # %

# Check CPU usage (using top for single value)
cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2 + $4}')
cpu_usage=${cpu_usage%.*}  # Remove decimal

# Check disk space (root partition)
disk_usage=$(df -h / | awk 'NR==2 {print $5}' | sed 's/%//')

# Check memory usage (used %)
mem_usage=$(free | awk '/Mem/ {print $3/$2 * 100}' | awk '{print int($1)}')

# Alert if thresholds exceeded
if [ "$cpu_usage" -gt "$CPU_THRESHOLD" ]; then
  echo "ALERT: CPU usage is $cpu_usage% (threshold: $CPU_THRESHOLD%)"
fi

if [ "$disk_usage" -gt "$DISK_THRESHOLD" ]; then
  echo "ALERT: Disk usage is $disk_usage% (threshold: $DISK_THRESHOLD%)"
fi

if [ "$mem_usage" -gt "$MEM_THRESHOLD" ]; then
  echo "ALERT: Memory usage is $mem_usage% (threshold: $MEM_THRESHOLD%)"
fi

How to Use:

Example 3: Batch Rename Files

Add a prefix (e.g., “vacation_”) to all .jpg files in a directory.

#!/bin/bash

# Usage: ./rename_jpgs.sh <prefix>
prefix="$1"

if [ $# -ne 1 ]; then
  echo "Usage: $0 <prefix>"
  exit 1
fi

count=1
for file in *.jpg; do
  if [ -f "$file" ]; then  # Ensure it's a file
    new_name="${prefix}_${count}.jpg"
    mv "$file" "$new_name"
    echo "Renamed $file to $new_name"
    count=$((count + 1))
  fi
done

How to Use:

./rename_jpgs.sh vacation
Renamed img1.jpg to vacation_1.jpg
Renamed img2.jpg to vacation_2.jpg

Bonus: Scheduling with Cron

To run scripts automatically (e.g., daily), use cron, a time-based job scheduler.

  1. Open the crontab editor:

    crontab -e
  2. Add a line to schedule your script. For example, run clean_logs.sh daily at 2 AM:

    0 2 * * * /path/to/clean_logs.sh

    Cron Syntax: min hour day month weekday command

    • * = every (e.g., * * * * * = every minute).

9. Best Practices for Bash Scripts

Follow these tips to write reliable, maintainable scripts:

  1. Use the Shebang Line: Always start with #!/bin/bash (not #!/bin/sh for portability).
  2. Comment Liberally: Explain why (not just what) the code does.
  3. Quote Variables: Use "$VAR" instead of $VAR to handle spaces in filenames (e.g., "My File.txt").
  4. Check for Errors: Use set -e to exit on errors, or set -eu to exit on errors/unset variables:
    #!/bin/bash
    set -eu  # Strict mode: exit on error or undefined variable
  5. Test Thoroughly: Run scripts with bash -n script.sh to check for syntax errors before execution.
  6. Use Absolute Paths: Avoid relative paths (e.g., /home/user/scripts instead of ./scripts).
  7. Make Scripts Idempotent: Ensure running the script multiple times has the same effect (e.g., mkdir -p instead of mkdir).

10. Troubleshooting Common Issues

  • “Permission denied”: Use chmod +x script.sh to make it executable.
  • Syntax errors: Check for missing then, fi, or done; ensure spaces after [ and before ] in conditions (e.g., [ -f "$file" ] not [-f"$file"]).
  • Variables not updating: Ensure no spaces around = when defining variables (e.g., name="Alice" not name = "Alice").
  • Debugging: Add set -x at the top of the script to print commands as they run (remove when done).

11. References

Conclusion

Bash scripting is a superpower for automating repetitive tasks. With variables, conditionals, loops, and functions, you can build scripts to handle backups, log cleanup, system monitoring, and more. Start small, test often, and gradually tackle more complex workflows. Your future self (and your productivity) will thank you!

Happy scripting! 🚀