Table of Contents
- What is Bash?
- Why Automate with Bash?
- Bash Fundamentals
- Control Structures
- Functions
- Practical Automation Examples
- Advanced Tips for Robust Scripts
- Best Practices
- Conclusion
- References
What is Bash?
Bash is a command-line shell and scripting language developed as a successor to the original Bourne Shell (sh). It’s the default shell on most Linux distributions (e.g., Ubuntu, Fedora) and macOS (though macOS now uses Zsh by default, Bash remains widely compatible).
- 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). - Script Mode: Bash can execute a series of commands stored in a text file (called a “Bash script”), enabling automation of repetitive tasks.
Why Automate with Bash?
Bash automation offers compelling benefits:
- Simplicity: No need for complex languages (Python, Java). Bash uses plain text and familiar terminal commands.
- Speed: Scripts run directly in the shell, with minimal overhead.
- Integration: Bash seamlessly works with Linux tools (e.g.,
grep,awk,tar) and system utilities. - Scalability: Automate tasks across hundreds of servers with tools like
sshand Bash scripts. - Free & Open Source: Bash is pre-installed on Linux/macOS, requiring no additional setup.
Bash Fundamentals
Let’s start with the building blocks of Bash scripting.
Variables
Variables store data (text, numbers) for later use. They’re defined with =, and accessed with $.
Syntax:
VARIABLE_NAME="value" # No spaces around =
echo $VARIABLE_NAME # Access with $
Example:
NAME="Alice"
AGE=30
echo "Hello, I'm $NAME, and I'm $AGE years old." # Output: Hello, I'm Alice, and I'm 30 years old.
- Quoting: Use double quotes (
" ") to allow variable expansion (e.g.,"Hello $NAME"). Use single quotes (' ') to treat text literally (e.g.,'Hello $NAME'outputsHello $NAME).
Basic Commands in Scripts
Bash scripts use the same commands as the terminal. Here are key ones for automation:
| Command | Purpose | Example in Script |
|---|---|---|
ls | List directory contents | ls -l /home/user/documents |
cd | Change directory | cd /tmp |
cp | Copy files/directories | cp report.txt /backup/ |
mv | Move/rename files/directories | mv oldname.txt newname.txt |
rm | Delete files/directories | rm -f temp.log (force delete) |
mkdir | Create directory | mkdir -p /new/directory/path (parent dirs) |
tar | Archive/compress files | tar -czf backup.tar.gz /data (gzip) |
Input/Output Redirection
Bash lets you redirect command output to files or read input from files.
>: Overwrite a file with output (e.g.,echo "Hi" > greetings.txt).>>: Append output to a file (e.g.,echo "Hello" >> greetings.txt).<: Read input from a file (e.g.,sort < names.txt).2>: Redirect errors (stderr) to a file (e.g.,rm badfile.txt 2> errors.log).
Example:
# Save list of .txt files to a log
ls -l *.txt > txt_files.log
# Append errors to a log
cp important.txt /backup/ 2>> backup_errors.log
Pipes
Pipes (|) chain commands, passing the output of one command as input to the next.
Example:
# Find .txt files modified in the last 7 days and count them
find /docs -name "*.txt" -mtime -7 | wc -l
Here, find outputs paths to .txt files, and wc -l counts the lines (i.e., the number of files).
Control Structures
Control structures let you add logic to scripts (e.g., “if X happens, do Y”; “repeat Z 5 times”).
Conditionals (if-else)
Use if statements to run code based on conditions.
Syntax:
if [ condition ]; then
# Code to run if condition is true
elif [ another_condition ]; then
# Code if first condition is false, second is true
else
# Code if all conditions are false
fi # Close the if block
Conditions (use [ ] for tests):
| Condition | Meaning |
|---|---|
$a -eq $b | a equals b (numbers) |
$a -ne $b | a not equal to b |
$a -lt $b | a less than b |
$a -gt $b | a greater than b |
-f "file" | File exists |
-d "dir" | Directory exists |
"$str1" = "$str2" | Strings equal |
Example: Check if a file exists
FILE="data.csv"
if [ -f "$FILE" ]; then
echo "$FILE exists. Let's process it!"
else
echo "$FILE not found. Exiting."
exit 1 # Exit with error code
fi
Loops (for, while)
Loops repeat code until a condition is met.
For Loops
Iterate over a list (files, numbers, strings).
Syntax:
for item in list; do
# Code to run for each item
done
Examples:
# Iterate over numbers
for i in {1..5}; do
echo "Count: $i"
done
# Iterate over files
for file in *.txt; do
echo "Processing $file"
cat "$file" >> combined.txt
done
While Loops
Run code as long as a condition is true.
Syntax:
while [ condition ]; do
# Code to run
done
Example: Countdown from 5
count=5
while [ $count -gt 0 ]; do
echo $count
count=$((count - 1)) # Decrement count
sleep 1 # Wait 1 second
done
echo "Blast off!"
Functions
Functions group code into reusable blocks, like mini-scripts.
Syntax:
function_name() {
# Code here
# Access parameters with $1, $2, ... (first, second argument)
}
Example:
greet() {
echo "Hello, $1! Today is $(date +%A)." # $1 = first argument; date +%A = day of week
}
greet "Bob" # Output: Hello, Bob! Today is Tuesday.
- Return Values: Bash functions don’t return values directly, but you can use
echoto output a value and capture it with$(function_name).
Practical Automation Examples
Let’s apply what we’ve learned with real-world scripts.
Example 1: Automated Backup Script
This script backs up a directory to a compressed archive, adds a timestamp, and deletes old backups.
#!/bin/bash
# Backup Script: Compress /home/user/docs and clean old backups
# Configuration
SOURCE_DIR="/home/user/docs"
BACKUP_DIR="/backup"
RETENTION_DAYS=7 # Keep backups for 7 days
# Create backup filename with timestamp (e.g., backup_20240520_1430.tar.gz)
TIMESTAMP=$(date +%Y%m%d_%H%M)
BACKUP_FILE="$BACKUP_DIR/backup_$TIMESTAMP.tar.gz"
# Check if source directory exists
if [ ! -d "$SOURCE_DIR" ]; then
echo "Error: Source directory $SOURCE_DIR not found."
exit 1
fi
# Create backup
echo "Creating backup: $BACKUP_FILE"
tar -czf "$BACKUP_FILE" "$SOURCE_DIR"
# Check if backup succeeded
if [ $? -ne 0 ]; then # $? = exit code of last command (0 = success)
echo "Error: Backup failed!"
exit 1
fi
# Delete backups older than RETENTION_DAYS
echo "Deleting backups older than $RETENTION_DAYS days..."
find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +$RETENTION_DAYS -delete
echo "Backup completed successfully!"
How to Use:
- Save as
backup.sh. - Make executable:
chmod +x backup.sh. - Run:
./backup.sh.
Example 2: Log File Error Parser
This script extracts “ERROR” lines from a log file, counts them, and sends a report via email.
#!/bin/bash
# Log Error Parser: Analyze /var/log/syslog for errors
LOG_FILE="/var/log/syslog"
ERROR_LOG="error_report_$(date +%Y%m%d).txt"
RECIPIENT="[email protected]"
# Check if log file exists
if [ ! -f "$LOG_FILE" ]; then
echo "Error: Log file $LOG_FILE not found."
exit 1
fi
# Extract ERROR lines and count
echo "Error Report for $(date):" > "$ERROR_LOG"
echo "======================" >> "$ERROR_LOG"
grep "ERROR" "$LOG_FILE" >> "$ERROR_LOG"
ERROR_COUNT=$(grep -c "ERROR" "$LOG_FILE")
echo -e "\nTotal Errors: $ERROR_COUNT" >> "$ERROR_LOG"
# Send email (requires 'mailutils' package)
if [ $ERROR_COUNT -gt 0 ]; then
echo "Sending error report to $RECIPIENT..."
mail -s "Syslog Error Report ($ERROR_COUNT errors)" "$RECIPIENT" < "$ERROR_LOG"
else
echo "No errors found. No email sent."
fi
Example 3: System Monitoring Alert
This script checks CPU usage and sends an alert if it exceeds 80%.
#!/bin/bash
# CPU Monitor: Alert if CPU usage > 80%
THRESHOLD=80
ALERT_EMAIL="[email protected]"
# Get CPU usage (idle percentage) using top
CPU_IDLE=$(top -bn1 | grep "Cpu(s)" | awk '{print $8}')
CPU_USAGE=$(echo "100 - $CPU_IDLE" | bc) # Calculate usage
# Compare with threshold
if (( $(echo "$CPU_USAGE > $THRESHOLD" | bc -l) )); then
echo "ALERT: High CPU Usage - $CPU_USAGE%"
echo "CPU Usage is above $THRESHOLD%: $CPU_USAGE%" | mail -s "High CPU Alert" "$ALERT_EMAIL"
else
echo "CPU Usage: $CPU_USAGE% (OK)"
fi
Advanced Tips for Robust Scripts
- Error Handling: Use
set -eto exit the script if any command fails. Addset -uto catch undefined variables:# At the top of your script set -euo pipefail # Exit on error, undefined var, or pipe failure - Debugging: Run scripts with
bash -x script.shto see each command as it executes. - Shebang Line: Always start scripts with
#!/bin/bashto specify the shell. - Arguments: Access script arguments with
$1,$2, etc. Example:./script.sh arg1 arg2→$1=arg1.
Best Practices
- Comment Generously: Explain why (not just what) the code does.
- Use Descriptive Names: Variables like
BACKUP_DIRare clearer thanbdir. - Test in Staging: Avoid running untested scripts on production systems.
- Avoid
rm -rf: Use with extreme caution (e.g.,rm -rf "$DIR/"—note the trailing/to avoid deleting$DIRif empty). - Version Control: Store scripts in Git for tracking changes.
Conclusion
Bash is a versatile tool that transforms manual drudgery into efficient automation. By mastering variables, control structures, and practical scripting patterns, you can automate backups, monitoring, log analysis, and more. Start small—write a simple script to organize your downloads folder, then build up to complex workflows. With Bash, the power to streamline your Linux experience is at your fingertips.
References
- GNU Bash Manual
- Bash Guide for Beginners (TLDP)
- Advanced Bash-Scripting Guide (TLDP)
- Bash Cookbook by Carl Albing & JP Vossen