Advanced Bash Scripting for Sysadmins
Why Master Bash?
Bash scripting is essential for:
- Automating repetitive tasks
- System administration at scale
- Infrastructure as code
- DevOps pipelines
- Linux proficiency
Shebang and Basics
#!/bin/bash
# Always include shebang as first line
set -euo pipefail # Exit on error, undefined vars, pipe failures
Script Permissions
chmod +x script.sh
./script.sh
Variables and Parameters
#!/bin/bash
# Variable assignment (no spaces!)
NAME="John"
AGE=30
# Using variables
echo "Hello, $NAME"
echo "Age: ${AGE}"
# Default values
${VAR:-default} # Use default if VAR unset
${VAR:=default} # Set and use default
${VAR:?Error} # Exit with error if unset
# String operations
${VAR#prefix} # Remove prefix
${VAR%suffix} # Remove suffix
${VAR/old/new} # Replace first occurrence
${VAR//old/new} # Replace all occurrences
${VAR:0:5} # Substring (first 5 chars)
# Script arguments
$0 # Script name
$1, $2, ... # Positional arguments
$@ # All arguments (array)
$# # Number of arguments
Functions
#!/bin/bash
# Function definition
backup_files() {
local source=$1
local dest=$2
local timestamp=$(date +%Y%m%d_%H%M%S)
echo "Backing up $source to $dest"
tar -czf "$dest/backup_$timestamp.tar.gz" "$source"
return 0
}
# Function call
backup_files "/home/user/documents" "/backups"
# Capture return value
if backup_files "/var/www" "/backups"; then
echo "Backup successful"
else
echo "Backup failed"
fi
Conditionals
#!/bin/bash
# Test conditions
if [[ -f /etc/passwd ]]; then
echo "File exists"
fi
# Multiple conditions
if [[ $AGE -gt 18 ]] && [[ $AGE -lt 65 ]]; then
echo "Working age"
elif [[ $AGE -ge 65 ]]; then
echo "Retirement age"
else
echo "Young age"
fi
# String comparison
if [[ "$OS" == "Linux" ]]; then
echo "Running on Linux"
fi
# File tests
[[ -e $file ]] # Exists
[[ -f $file ]] # Regular file
[[ -d $file ]] # Directory
[[ -r $file ]] # Readable
[[ -w $file ]] # Writable
[[ -x $file ]] # Executable
[[ -s $file ]] # Non-empty
# Arithmetic
[[ $x -eq $y ]] # Equal
[[ $x -ne $y ]] # Not equal
[[ $x -lt $y ]] # Less than
[[ $x -gt $y ]] # Greater than
Loops
#!/bin/bash
# For loop
for i in {1..10}; do
echo "Iteration $i"
done
# Array iteration
servers=("web1" "web2" "web3")
for server in "${servers[@]}"; do
echo "Pinging $server"
ping -c 1 "$server"
done
# While loop
counter=0
while [[ $counter -lt 5 ]]; do
echo "Count: $counter"
((counter++))
done
# Until loop
until [[ $counter -eq 0 ]]; do
echo "Countdown: $counter"
((counter--))
done
# Break and continue
for i in {1..10}; do
if [[ $i -eq 5 ]]; then
continue # Skip this iteration
fi
if [[ $i -eq 8 ]]; then
break # Exit loop
fi
echo $i
done
Arrays
#!/bin/bash
# Array declaration
arr=(1 2 3 4 5)
arr=([0]=10 [1]=20 [2]=30)
# Access elements
echo ${arr[0]} # First element
echo ${arr[@]} # All elements
echo ${#arr[@]} # Array length
# Add element
arr+=(6)
# Iterate
for item in "${arr[@]}"; do
echo "$item"
done
# Find index
for i in "${!arr[@]}"; do
echo "Index $i: ${arr[$i]}"
done
String Processing
#!/bin/bash
# String length
STR="Hello World"
echo ${#STR}
# Substring
echo ${STR:0:5} # "Hello"
echo ${STR:6} # "World"
# Pattern matching
if [[ $STR =~ ^Hello ]]; then
echo "Starts with Hello"
fi
# Case conversion
echo ${STR^^} # Uppercase
echo ${STR,,} # Lowercase
# String splitting
IFS=',' read -ra PARTS <<< "one,two,three"
for part in "${PARTS[@]}"; do
echo "$part"
done
Error Handling
#!/bin/bash
set -euo pipefail # Exit on any error
# Trap errors
trap 'echo "Error on line $LINENO"' ERR
trap 'cleanup' EXIT
cleanup() {
echo "Cleaning up..."
rm -f /tmp/tempfile
}
# Command substitution with error checking
output=$(command 2>&1) || {
echo "Command failed: $output"
exit 1
}
# Try-catch pattern
run_command() {
if output=$(command 2>&1); then
echo "Success: $output"
else
echo "Error: $output"
return 1
fi
}
Practical Examples
System Monitoring Script
#!/bin/bash
check_disk_space() {
local threshold=$1
df -h | awk 'NR>1 {print $5}' | while read usage; do
usage_percent=${usage%\%}
if [[ $usage_percent -gt $threshold ]]; then
echo "WARNING: Disk usage at $usage_percent%"
fi
done
}
check_memory() {
local mem_available=$(free -h | awk '/^Mem:/ {print $7}')
echo "Available memory: $mem_available"
}
# Run checks
check_disk_space 80
check_memory
Log Rotation Script
#!/bin/bash
rotate_logs() {
local logdir=$1
local keep_days=$2
find "$logdir" -name "*.log" -type f | while read logfile; do
gzip "$logfile"
done
# Remove old logs
find "$logdir" -name "*.log.gz" -mtime +$keep_days -delete
}
# Usage
rotate_logs "/var/log/myapp" 30
Backup with Encryption
#!/bin/bash
backup_encrypted() {
local source=$1
local dest=$2
local password=$3
tar -czf - "$source" | \
openssl enc -aes-256-cbc -salt -pass pass:"$password" \
-out "$dest/backup_$(date +%s).tar.gz.enc"
}
# Usage (read password securely)
read -sp "Backup password: " PASSWORD
backup_encrypted "/home/user/data" "/backups" "$PASSWORD"
Parallel Processing
#!/bin/bash
process_files() {
local max_jobs=4
local job_count=0
for file in *.txt; do
# Wait if max jobs reached
while [[ $(jobs -r -p | wc -l) -ge $max_jobs ]]; do
sleep 1
done
# Process file in background
process_file "$file" &
done
# Wait for all background jobs
wait
}
Debugging
#!/bin/bash
# Enable debugging
bash -x script.sh
# Or in script
set -x # Enable debug
set +x # Disable debug
# Debug specific sections
PS4='${BASH_SOURCE}:${LINENO}: '
set -x
complex_operation
set +x
# Use trap for debugging
trap 'echo "DEBUG: $BASH_COMMAND"' DEBUG
Best Practices
-
Use shellcheck: Lint your scripts
apt install shellcheck shellcheck script.sh -
Quote variables: Prevents word splitting
rm -rf $dir # DANGEROUS! rm -rf "$dir" # Safe -
Use local variables: In functions
local var="value" -
Handle signals: Cleanup on exit
trap cleanup EXIT INT TERM -
Document code: Use comments
# Process files and backup for file in "${files[@]}"; do -
Test thoroughly: Use shellcheck and test cases
bash -n script.sh # Syntax check
Performance Tips
# Avoid subshells (slow)
cat file | while read line; do # Subshell!
process "$line"
done
# Better: redirection
while read line; do
process "$line"
done < file
# Batch commands
echo {1..1000} | xargs command # Parallel execution
Conclusion
Bash scripting is powerful when you understand its features. Start simple, test thoroughly, and gradually increase complexity.