thelinuxvault guide

Scripting with Bash: Automating Repetitive Linux Tasks

In the world of Linux, repetitive tasks—like backing up files, rotating logs, monitoring system resources, or processing batches of data—can eat up hours of your day. Manually executing these tasks not only wastes time but also increases the risk of human error. Enter **Bash scripting**: a powerful, lightweight tool built into every Linux system that lets you automate these tasks with minimal effort. Bash (Bourne Again SHell) is the default command-line interpreter for most Linux distributions, and its scripting capabilities allow you to chain commands, add logic (like conditionals and loops), and create reusable "programs" to handle repetitive work. Whether you’re a system administrator, developer, or casual Linux user, learning Bash scripting will supercharge your productivity. This blog will guide you from the basics of Bash scripting to advanced automation techniques, with practical examples you can implement today. Let’s dive in!

Table of Contents

  1. What is Bash Scripting?
  2. Setting Up Your First Bash Script
    • 2.1 The Shebang Line
    • 2.2 Making Scripts Executable
    • 2.3 Your First Script: “Hello World” (and Beyond)
  3. Core Concepts for Effective Scripting
    • 3.1 Variables: Storing and Using Data
    • 3.2 User Input: Interacting with the User
    • 3.3 Conditionals: Making Decisions (if-else)
    • 3.4 Loops: Repeating Actions (for, while)
    • 3.5 Functions: Reusing Code
  4. Practical Examples of Task Automation
    • 4.1 Automated File Backup
    • 4.2 Log Rotation
    • 4.3 System Resource Monitoring
    • 4.4 Batch File Renaming
  5. Advanced Tips and Best Practices
    • 5.1 Error Handling
    • 5.2 Debugging Techniques
    • 5.3 Writing Readable Scripts
    • 5.4 Portability
  6. Tools to Enhance Bash Scripting
    • 6.1 Scheduling with Cron
    • 6.2 Text Processing with sed/awk
  7. Conclusion
  8. References

What is Bash Scripting?

Bash scripting is the practice of writing sequences of Bash commands into a text file (called a “script”) that the Bash interpreter can execute as a single program. Instead of typing commands one-by-one in the terminal, you write them once in a script and run the script to automate the entire workflow.

Why Bash?

  • Ubiquitous: Bash is preinstalled on all Linux/macOS systems (and available for Windows via WSL). No extra setup required!
  • Lightweight: Scripts are plain text files—no compilation needed.
  • Powerful: Combine simple commands (e.g., ls, cp, grep) with logic (conditionals, loops) to build complex workflows.

Setting Up Your First Bash Script

Let’s start with the basics: creating and running a simple Bash script.

2.1 The Shebang Line

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

#!/bin/bash  

This line must be the first line of the script. Without it, the system may default to a different shell (like sh), which lacks some Bash-specific features.

2.2 Making Scripts Executable

Scripts are just text files—you need to mark them as executable to run them directly. Here’s how:

  1. Create a new file (e.g., my_script.sh) with a text editor like nano or vim.
  2. Add the shebang line and your commands.
  3. Make it executable with chmod:
    chmod +x my_script.sh  
  4. Run the script:
    ./my_script.sh  # Runs the script in the current directory  
    # Or: bash my_script.sh  # Explicitly uses Bash (useful if permissions aren’t set)  

2.3 Your First Script: “Hello World” (and Beyond)

Let’s write a simple script to print a greeting. Open greet.sh and add:

#!/bin/bash  

# This is a comment (ignored by Bash)  
echo "Hello, World!"  # Print "Hello, World!" to the terminal  

# Get the current user’s name  
username=$(whoami)  # Store output of `whoami` in a variable  

# Greet the user  
echo "Welcome, $username! Today is $(date +%A)."  # Use command substitution  

How it works:

  • #! specifies the interpreter.
  • # starts a comment (use these to explain your code!).
  • echo prints text to the terminal.
  • $(command) is command substitution: runs command and inserts its output into the script. Here, $(whoami) gets the username, and $(date +%A) gets the current day of the week.

Run it:

chmod +x greet.sh  
./greet.sh  

Output:

Hello, World!  
Welcome, alice! Today is Wednesday.  

Core Concepts for Effective Scripting

To build useful scripts, you need to master these foundational concepts.

3.1 Variables: Storing and Using Data

Variables let you store data (text, numbers, command output) for later use.

Syntax:

  • Declare: variable_name="value" (no spaces around =!).
  • Use: $variable_name or ${variable_name} (curly braces help avoid ambiguity).

Example:

#!/bin/bash  

# Assign variables  
greeting="Hello"  
name="Bob"  
age=30  

# Use variables  
echo "$greeting, $name! You are $age years old."  

# Command substitution (store output of a command)  
current_dir=$(pwd)  
echo "You are in: $current_dir"  

Output:

Hello, Bob! You are 30 years old.  
You are in: /home/alice/scripts  

3.2 User Input: Interacting with the User

Use the read command to get input from the user.

Syntax:

read -p "Prompt message: " variable_name  # -p adds a prompt  

Example:

#!/bin/bash  

read -p "Enter your name: " name  
read -p "Enter your favorite color: " color  

echo "Hello, $name! Your favorite color is $color."  

Run it:

./input.sh  
Enter your name: Charlie  
Enter your favorite color: Blue  
Hello, Charlie! Your favorite color is Blue.  

3.3 Conditionals: Making Decisions (if-else)

Conditionals let your script execute different commands based on whether a condition is true or false. Use if, elif (else if), and fi (ends the if block).

Syntax:

if [ condition ]; then  
  # Commands to run if condition is true  
elif [ another_condition ]; then  
  # Commands if first condition is false, second is true  
else  
  # Commands if all conditions are false  
fi  

Common Conditions:

  • -f "file.txt": True if file.txt exists and is a regular file.
  • -d "dir": True if dir exists and is a directory.
  • $var -eq 5: True if var is equal to 5 (numeric comparison).
  • "$var" == "text": True if var equals “text” (string comparison).
  • -z "$var": True if var is empty.

Example: Check if a file exists

#!/bin/bash  

read -p "Enter a filename: " filename  

if [ -f "$filename" ]; then  
  echo "$filename exists. Contents:"  
  cat "$filename"  # Print file contents  
elif [ -d "$filename" ]; then  
  echo "$filename is a directory, not a file."  
else  
  echo "$filename does NOT exist."  
fi  

3.4 Loops: Repeating Actions (for, while)

Loops automate repetitive tasks, like processing multiple files or lines of text.

For Loops: Iterate Over a List

Syntax:

for item in list; do  
  # Commands to run for each item  
done  

Example: Process all .txt files

#!/bin/bash  

echo "Processing .txt files in current directory:"  
for file in *.txt; do  
  echo "- $file has $(wc -l < "$file") lines."  # Count lines in each file  
done  

While Loops: Repeat Until a Condition Fails

Syntax:

while [ condition ]; do  
  # Commands to repeat  
done  

Example: Countdown from 5

#!/bin/bash  

count=5  
while [ $count -gt 0 ]; do  
  echo $count  
  count=$((count - 1))  # Decrement count by 1  
  sleep 1  # Wait 1 second  
done  
echo "Blast off!"  

3.5 Functions: Reusing Code

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

Syntax:

function_name() {  
  # Commands here  
  # Use $1, $2, etc., to access parameters passed to the function  
}  

Example: Greeting Function

#!/bin/bash  

# Define function  
greet() {  
  local name="$1"  # Local variable (only exists inside the function)  
  echo "Hello, $name! 🎉"  
}  

# Call the function with different names  
greet "Alice"  
greet "Bob"  
greet "Charlie"  

Output:

Hello, Alice! 🎉  
Hello, Bob! 🎉  
Hello, Charlie! 🎉  

Practical Examples of Task Automation

Now that you know the basics, let’s build scripts to solve real-world problems.

4.1 Automated File Backup

A common task: backing up a directory (e.g., Documents) to an external drive or cloud folder. This script will create a timestamped backup zip file.

Script: backup.sh

#!/bin/bash  

# Configuration  
source_dir="/home/alice/Documents"  # Directory to back up  
dest_dir="/mnt/external_drive/backups"  # Where to store backups  
timestamp=$(date +%Y%m%d_%H%M%S)  # e.g., 20240520_143022  
backup_file="$dest_dir/documents_backup_$timestamp.zip"  

# Check if source and destination exist  
if [ ! -d "$source_dir" ]; then  
  echo "Error: Source directory $source_dir does not exist."  
  exit 1  # Exit with error code 1  
fi  

if [ ! -d "$dest_dir" ]; then  
  echo "Error: Destination directory $dest_dir does not exist."  
  exit 1  
fi  

# Create backup  
echo "Creating backup: $backup_file"  
zip -r "$backup_file" "$source_dir"  # -r = recursive (include subfolders)  

# Check if backup succeeded  
if [ $? -eq 0 ]; then  # $? = exit code of last command (0 = success)  
  echo "Backup completed successfully!"  
else  
  echo "Backup FAILED!"  
  exit 1  
fi  

4.2 Log Rotation

Logs (e.g., /var/log/syslog) grow indefinitely. This script compresses old logs, deletes very old ones, and keeps only the last 5 backups.

Script: rotate_logs.sh

#!/bin/bash  

log_file="/var/log/syslog"  # Log file to rotate  
backup_dir="/var/log/old_logs"  # Store compressed logs here  
max_backups=5  # Keep only last 5 backups  

# Create backup dir if it doesn’t exist  
mkdir -p "$backup_dir"  

# Compress the current log file (add timestamp)  
timestamp=$(date +%Y%m%d)  
compressed_log="$backup_dir/syslog_$timestamp.gz"  
gzip -c "$log_file" > "$compressed_log"  # -c = write to stdout (redirect to file)  

# Truncate the original log file (so new logs can be written)  
> "$log_file"  # Overwrites the file with empty  

# Delete oldest backups if we have more than max_backups  
num_backups=$(ls -1 "$backup_dir"/syslog_*.gz | wc -l)  
if [ $num_backups -gt $max_backups ]; then  
  echo "Deleting oldest backups (keeping last $max_backups)..."  
  # List backups by name (sorted), take the oldest ones, delete them  
  ls -1tr "$backup_dir"/syslog_*.gz | head -n -$max_backups | xargs rm -f  
fi  

echo "Log rotation completed. Compressed log: $compressed_log"  

4.3 System Resource Monitoring

This script checks CPU usage, disk space, and memory, and sends an alert if any exceed thresholds (e.g., disk > 90% full).

Script: monitor_system.sh

#!/bin/bash  

# Thresholds (adjust as needed)  
cpu_threshold=80  # % CPU usage  
disk_threshold=90  # % disk usage (root partition)  
mem_threshold=85   # % memory usage  
alert_email="[email protected]"  # Send alerts here  

# Check CPU usage (using top, extract idle %, subtract from 100)  
cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print 100 - $8 "%"}' | cut -d% -f1)  

# Check disk usage (root partition)  
disk_usage=$(df -h / | awk 'NR==2 {print $5}' | cut -d% -f1)  

# Check memory usage (using free, extract used %)  
mem_usage=$(free | awk '/Mem/ {printf "%.0f", $3/$2 * 100}')  

# Build alert message  
alert_msg=""  
if [ $cpu_usage -gt $cpu_threshold ]; then  
  alert_msg+="⚠️ High CPU usage: $cpu_usage% (threshold: $cpu_threshold%)\n"  
fi  
if [ $disk_usage -gt $disk_threshold ]; then  
  alert_msg+="⚠️ High disk usage: $disk_usage% (threshold: $disk_threshold%)\n"  
fi  
if [ $mem_usage -gt $mem_threshold ]; then  
  alert_msg+="⚠️ High memory usage: $mem_usage% (threshold: $mem_threshold%)\n"  
fi  

# Send alert if needed  
if [ -n "$alert_msg" ]; then  # -n = non-empty string  
  echo -e "System Alert:\n$alert_msg" | mail -s "⚠️ High Resource Usage" "$alert_email"  
  echo "Alert sent to $alert_email"  
else  
  echo "All resources are within safe thresholds."  
fi  

4.4 Batch File Renaming

Rename all .jpg files in a folder to vacation_001.jpg, vacation_002.jpg, etc.

Script: rename_photos.sh

#!/bin/bash  

read -p "Enter directory with .jpg files: " dir  
prefix="vacation"  # New filename prefix  
counter=1  # Start numbering at 1  

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

# Move to the directory (to avoid full paths in filenames)  
cd "$dir" || exit 1  

# Rename .jpg files (sorted alphabetically)  
for file in *.jpg; do  
  # Skip if no .jpg files exist  
  if [ ! -f "$file" ]; then  
    echo "No .jpg files found in $dir."  
    exit 0  
  fi  

  # Pad counter with leading zeros (e.g., 1 → 001)  
  new_name="${prefix}_$(printf "%03d" $counter).jpg"  

  # Rename the file  
  mv -v "$file" "$new_name"  # -v = verbose (show rename action)  

  counter=$((counter + 1))  
done  

echo "Renamed $((counter - 1)) files."  

Advanced Tips and Best Practices

5.1 Error Handling

  • set -e: Exit the script immediately if any command fails (avoids running broken code).
  • set -u: Treat undefined variables as errors (prevents silent bugs).
  • trap: Run cleanup commands (e.g., delete temp files) if the script is interrupted (e.g., Ctrl+C).

Example:

#!/bin/bash  
set -euo pipefail  # -e: exit on error, -u: error on undefined var, -o pipefail: exit if any command in a pipe fails  

# Cleanup function  
cleanup() {  
  echo "Cleaning up temp files..."  
  rm -f /tmp/my_temp_file.txt  
}  
trap cleanup EXIT  # Run cleanup when script exits (success or failure)  

# Rest of script...  

5.2 Debugging Techniques

  • bash -x script.sh: Run the script in debug mode (prints each command before executing it).
  • echo "Debug: variable=$var": Add debug messages to track variables.
  • set -x: Enable debug mode inside the script (use set +x to disable).

5.3 Writing Readable Scripts

  • Comments: Explain why (not just what) the code does.
  • Meaningful names: Use backup_dir instead of b.
  • Indentation: Use 2–4 spaces to group code blocks (e.g., inside if/for).

5.4 Portability

  • Use #!/bin/sh instead of #!/bin/bash if targeting systems without Bash (e.g., BSD).
  • Avoid Bash-specific features (e.g., arrays, [[ ]] conditionals) if writing POSIX-compliant scripts.

Tools to Enhance Bash Scripting

5.1 Scheduling with Cron

Use cron to run scripts automatically (e.g., daily backups at 2 AM).

How to set up a cron job:

  1. Run crontab -e to edit your cron table.
  2. Add a line with the schedule and script path:
    # Format: minute hour day month weekday command  
    0 2 * * * /home/alice/scripts/backup.sh  # Run daily at 2:00 AM  
  3. Save and exit. Cron will run the script automatically.

5.2 Text Processing with sed/awk

  • sed: Edit text in-place (e.g., replace “old” with “new” in a file: sed -i 's/old/new/g' file.txt).
  • awk: Process structured text (e.g., sum the 3rd column of a CSV: awk -F ',' '{sum += $3} END {print sum}' data.csv).

Conclusion

Bash scripting is a cornerstone of Linux automation, empowering you to turn tedious tasks into one-click (or scheduled) workflows. By mastering variables, conditionals, loops, and functions, and following best practices like error handling and readability, you can build robust scripts to handle backups, monitoring, log rotation, and more.

Start small (e.g., a script to rename photos), then tackle bigger projects. With cron for scheduling and tools like sed/awk for text processing, the possibilities are endless.

References