Table of Contents
- What is Bash?
- Why Automate with Bash?
- Bash Basics
- Intermediate Bash Scripting
- Advanced Bash Techniques
- Real-World Automation Projects
- Conclusion
- References
What is Bash?
Bash is a Unix shell and command language developed by Brian Fox in 1989 as a free replacement for the Bourne Shell (sh). It’s the default shell on most Linux distributions, macOS (until macOS Catalina, which switched to Zsh, but Bash remains available), and BSD systems.
- Interactive Mode: When you open a terminal, Bash runs in interactive mode, allowing you to type commands and see results immediately (e.g.,
ls,cd,echo). - Script Mode: Bash can execute scripts—text files containing a sequence of commands—enabling automation of multi-step tasks.
Why Automate with Bash?
Automation with Bash offers several advantages:
- Efficiency: Replace manual, repetitive tasks (e.g., backups, log rotation) with scripts that run in seconds.
- Consistency: Eliminate human error by standardizing workflows (e.g., deploying code the same way every time).
- Scalability: Manage hundreds of systems or files with a single script (e.g., bulk user creation).
- Accessibility: Bash is pre-installed on most Unix-like systems, requiring no additional setup.
Bash Basics
Variables and Data Types
Variables store data for later use. Bash is dynamically typed (no need to declare types), and variable names are case-sensitive.
Declaring Variables
Use = to assign values (no spaces around =!):
name="Alice" # String variable
age=30 # Numeric variable
is_active=true # Boolean (treated as string in Bash)
Accessing Variables
Prefix the variable name with $:
echo "Name: $name" # Output: Name: Alice
echo "Age: $age" # Output: Age: 30
Environment Variables
Global variables available to all processes (e.g., PATH, HOME). Use export to make a local variable global:
export PATH="$PATH:/new/directory" # Add a directory to PATH
echo "Home directory: $HOME" # Output: /home/alice (varies by user)
Command Substitution
Capture the output of a command and store it in a variable using $(command) or backticks `command` (preferred: $() for readability).
Example: Get the current date and store it in a variable:
current_date=$(date +"%Y-%m-%d") # Format: YYYY-MM-DD
echo "Today is $current_date" # Output: Today is 2024-05-20
Example: Count files in the current directory:
file_count=$(ls | wc -l)
echo "Number of files: $file_count"
Control Structures
Control structures (conditionals, loops) dictate the flow of execution.
Conditional Statements (if-else)
Check conditions (e.g., file existence, numeric comparisons) with if, elif, and else.
Syntax:
if [ condition ]; then
# Code to run if condition is true
elif [ another_condition ]; then
# Code if first condition fails, second succeeds
else
# Code if all conditions fail
fi
Common Conditions:
-f file: True iffileexists and is a regular file.-d dir: True ifdirexists and is a directory.-z string: True ifstringis empty.$a -eq $b: Numeric equality (use-nefor not equal,-gtfor greater than, etc.).
Example: Check if a file exists:
filename="data.txt"
if [ -f "$filename" ]; then
echo "$filename exists."
else
echo "$filename not found. Creating it..."
touch "$filename" # Create the file if missing
fi
Case Statements
Use case for multi-condition checks (cleaner than nested if-else):
read -p "Enter a number (1-3): " num
case $num in
1) echo "One" ;;
2) echo "Two" ;;
3) echo "Three" ;;
*) echo "Invalid number" ;; # Default case
esac
Loops
Repeat commands until a condition is met.
For Loops: Iterate over a list (files, numbers, strings):
# Iterate over strings
for fruit in apple banana cherry; do
echo "Fruit: $fruit"
done
# Iterate over numbers (1 to 5)
for i in {1..5}; do
echo "Count: $i"
done
# Iterate over files in a directory
for file in /home/alice/documents/*.txt; do
echo "Processing $file"
done
While Loops: Run until a condition is false:
count=1
while [ $count -le 5 ]; do
echo "Count: $count"
count=$((count + 1)) # Increment count (Bash arithmetic)
done
Until Loops: Run until a condition is true (opposite of while):
count=5
until [ $count -lt 1 ]; do
echo "Countdown: $count"
count=$((count - 1))
done
Functions
Functions group reusable code into modular blocks.
Defining Functions
greet() {
local name=$1 # Local variable (only accessible in the function)
echo "Hello, $name!"
}
$1,$2, etc., are positional parameters (arguments passed to the function).localrestricts the variable to the function (avoids global scope pollution).
Calling Functions
greet "Bob" # Output: Hello, Bob!
Return Values
Bash functions return exit codes (0 = success, non-zero = error), not values. Use return to set the exit code:
add() {
local sum=$(( $1 + $2 ))
echo $sum # "Return" value via output
}
result=$(add 5 3) # Capture output with command substitution
echo "5 + 3 = $result" # Output: 5 + 3 = 8
Intermediate Bash Scripting
Input/Output Redirection
Control where command input comes from and output goes (files, pipes, etc.).
Redirect Output (>, >>)
>: Overwrite a file with output.>>: Append output to a file.
Example: Save ls output to files.txt:
ls -l > files.txt # Overwrite existing content
echo "New line" >> files.txt # Append to files.txt
Redirect Input (<)
Read input from a file instead of the terminal:
sort < unsorted.txt > sorted.txt # Sort unsorted.txt and save to sorted.txt
Pipes (|)
Send the output of one command as input to another:
ps aux | grep "bash" # List processes and filter for "bash"
cat access.log | grep "ERROR" | wc -l # Count ERROR entries in a log
Process Management
Control running processes (background jobs, killing tasks, etc.).
Run in Background (&)
Add & to run a command in the background (free up the terminal):
long_running_script.sh & # Run script in background
Manage Jobs
jobs: List background jobs.fg %1: Bring job 1 to the foreground.bg %1: Resume job 1 in the background.kill %1: Terminate job 1.
Check and Kill Processes
Use ps to list processes and kill to terminate them:
ps aux | grep "python" # Find Python processes
kill 1234 # Kill process with ID 1234 (use -9 for force kill: kill -9 1234)
Error Handling
Prevent scripts from failing silently with error-checking tools.
set Options
Add these at the top of scripts to enforce strictness:
set -e # Exit on any command failure
set -u # Treat unset variables as errors
set -o pipefail # Exit if any command in a pipe fails
trap for Cleanup
Run commands on script exit (e.g., delete temporary files):
temp_file=$(mktemp) # Create a temporary file
# Clean up temp_file when script exits (success or failure)
trap 'rm -f "$temp_file"' EXIT
# ... script logic that uses $temp_file ...
Advanced Bash Techniques
Regular Expressions
Bash works with tools like grep, sed, and awk to match patterns in text.
grep: Search for Patterns
Use -E for extended regex (supports +, ?, ()):
# Find lines with "ERROR" in a log file
grep "ERROR" app.log
# Find valid email addresses (simplified regex)
grep -E '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$' emails.txt
sed: Stream Editor
Modify text in-place (use -i for editing files):
# Replace "old" with "new" in a file
sed -i 's/old/new/g' document.txt # g = global replace (all occurrences)
Arrays
Store multiple values in a single variable (Bash 4+ supports associative arrays for key-value pairs).
Indexed Arrays
fruits=("apple" "banana" "cherry") # Declare array
echo ${fruits[0]} # Access first element (0-based index): apple
echo ${fruits[@]} # All elements: apple banana cherry
echo ${#fruits[@]} # Number of elements: 3
Loop Through Arrays
for fruit in "${fruits[@]}"; do
echo "I like $fruit"
done
Debugging Bash Scripts
Debugging tools help identify issues in scripts:
set -x: Print commands and their arguments as they execute (trace mode).set -v: Print input lines as they are read (verbose mode).echo: Add debug messages to track variables:name="Alice" echo "Debug: name is $name" # Output: Debug: name is Alice
Real-World Automation Projects
Project 1: Automated Backup Script
A script to back up a directory to a compressed file (e.g., tar.gz).
Step 1: Define Variables
source_dir="/home/alice/documents"
backup_dir="/mnt/backup"
timestamp=$(date +"%Y%m%d_%H%M%S") # Unique filename
backup_file="$backup_dir/backup_$timestamp.tar.gz"
Step 2: Check Dependencies
Ensure tar is installed and backup_dir exists:
if ! command -v tar &> /dev/null; then
echo "Error: tar is not installed."
exit 1
fi
if [ ! -d "$backup_dir" ]; then
echo "Creating backup directory: $backup_dir"
mkdir -p "$backup_dir"
fi
Step 3: Run Backup
echo "Backing up $source_dir to $backup_file..."
tar -czf "$backup_file" "$source_dir"
# 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
Project 2: Log File Parser
Parse a web server log (e.g., Nginx) to count 404 errors and list top IPs.
Sample Log Format
192.168.1.1 - - [20/May/2024:12:00:00 +0000] "GET /page HTTP/1.1" 404 123
10.0.0.2 - - [20/May/2024:12:01:00 +0000] "GET /home HTTP/1.1" 200 456
Script Code
log_file="/var/log/nginx/access.log"
# Count 404 errors
echo "404 Errors: $(grep " 404 " "$log_file" | wc -l)"
# Top 5 IPs with most requests
echo -e "\nTop 5 IPs:"
awk '{print $1}' "$log_file" | sort | uniq -c | sort -nr | head -5
Project 3: System Monitoring Tool
Monitor CPU, memory, and disk usage; alert if thresholds are exceeded.
Script Code
#!/bin/bash
set -euo pipefail
# Thresholds (adjust as needed)
CPU_THRESHOLD=80 # %
MEM_THRESHOLD=80 # %
DISK_THRESHOLD=85 # %
# Check CPU usage
cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2 + $4}') # User + System CPU
if (( $(echo "$cpu_usage > $CPU_THRESHOLD" | bc -l) )); then
echo "ALERT: High CPU usage - $cpu_usage%"
fi
# Check memory usage
mem_usage=$(free | grep Mem | awk '{print $3/$2 * 100.0}')
if (( $(echo "$mem_usage > $MEM_THRESHOLD" | bc -l) )); then
echo "ALERT: High Memory usage - $mem_usage%"
fi
# Check disk usage
disk_usage=$(df -h / | grep / | awk '{print $5}' | sed 's/%//')
if [ "$disk_usage" -gt "$DISK_THRESHOLD" ]; then
echo "ALERT: High Disk usage - $disk_usage%"
fi
Conclusion
From variables and loops to advanced regex and system monitoring, this tutorial has covered the full spectrum of Bash automation. The key to mastery is practice: experiment with scripts, break them, and fix them. Start small (e.g., a file organizer) and gradually tackle complex projects (e.g., deployment pipelines).
Bash is a timeless tool—investing in it will pay dividends in efficiency and versatility across your technical career.
References
- GNU Bash Manual
- Bash Hackers Wiki
- Advanced Bash-Scripting Guide
- Book: Learning the Bash Shell by Cameron Newham (O’Reilly)
- ShellCheck (static analysis tool for Bash scripts)