Table of Contents
- Functions: Reusable Code Blocks
- Arrays: Managing Collections of Data
- Process Substitution: Piping Without Temporary Files
- Traps: Handling Signals and Cleanup
- Regular Expressions: Advanced Text Processing
- Error Handling: Making Scripts Robust
- Parameter Expansion: Beyond $VAR
- Job Control: Parallel Execution & Background Tasks
- Real-World Automation Example
- Conclusion
- References
1. Functions: Reusable Code Blocks
Functions in Bash let you package logic into reusable blocks, making scripts modular and easier to maintain. Unlike basic scripts that repeat code, functions reduce redundancy and simplify debugging.
Defining a Function
# Syntax 1: Traditional
greet() {
echo "Hello, $1!" # $1 = first argument
}
# Syntax 2: Modern (more readable)
function welcome {
echo "Welcome to $1, $2!" # $2 = second argument
}
# Usage
greet "Alice" # Output: Hello, Alice!
welcome "Linux" "Bob" # Output: Welcome to Linux, Bob!
Key Features of Bash Functions
-
Return Values: Use
returnfor exit codes (0-255) orechoto return strings (capture with$(function)).add() { echo $(( $1 + $2 )) # Return sum as string } result=$(add 5 3) echo $result # Output: 8 -
Local Variables: Use
localto limit variable scope to the function (avoids polluting the global namespace).count_files() { local dir="$1" # Local variable if [ -d "$dir" ]; then echo $(ls -1 "$dir" | wc -l) else echo "Error: $dir is not a directory" >&2 # Send error to stderr return 1 # Non-zero exit code indicates failure fi } -
Recursion: Functions can call themselves (useful for tasks like directory traversal).
# Recursively count files in a directory and subdirectories recursive_count() { local dir="$1" local total=0 for item in "$dir"/*; do if [ -f "$item" ]; then total=$((total + 1)) elif [ -d "$item" ]; then # Recursively call for subdirectories subcount=$(recursive_count "$item") total=$((total + subcount)) fi done echo $total }
2. Arrays: Managing Collections of Data
Bash supports indexed arrays (ordered lists) and associative arrays (key-value pairs), enabling you to work with structured data—critical for tasks like managing lists of servers, filenames, or configuration options.
Indexed Arrays
Use indexed arrays for ordered data (e.g., lists of items).
# Declare and initialize
fruits=("apple" "banana" "cherry")
# Access elements (0-based index)
echo ${fruits[0]} # Output: apple
echo ${fruits[@]} # Output: apple banana cherry (all elements)
# Add elements
fruits+=("date") # Now: ("apple" "banana" "cherry" "date")
# Get length
echo ${#fruits[@]} # Output: 4
# Loop through elements
for fruit in "${fruits[@]}"; do
echo "I like $fruit"
done
Associative Arrays
Use associative arrays (introduced in Bash 4) for key-value data (e.g., configurations, mappings).
# Declare an associative array
declare -A user_roles
# Add key-value pairs
user_roles["alice"]="admin"
user_roles["bob"]="editor"
user_roles["charlie"]="viewer"
# Access values
echo "Alice's role: ${user_roles["alice"]}" # Output: Alice's role: admin
# Loop through keys/values
for user in "${!user_roles[@]}"; do
echo "$user: ${user_roles[$user]}"
done
Use Case: Managing server lists with properties (e.g., IP, port, status).
3. Process Substitution: Piping Without Temporary Files
Process substitution (<() and >()) lets you treat the output of a command as a temporary file, avoiding clunky temporary files in scripts. It’s ideal for comparing command outputs or feeding data to commands that expect file inputs.
Syntax
<(command): Runscommandand provides its output as a readable file.>(command): Runscommandand provides its input as a writable file.
Examples
-
Compare two command outputs (without saving to files):
# Compare "ls /tmp" and "ls /var/tmp" diff <(ls /tmp) <(ls /var/tmp) -
Feed multiple inputs to a command:
# Combine outputs of two commands and sort them sort <(echo -e "apple\ncherry") <(echo -e "banana\ndate") -
Log to a file and stdout simultaneously (using
teewith process substitution):# Run a script, log to "output.log", and show on screen ./my_script.sh | tee >(gzip > output.log.gz) # Compress log while displaying
4. Traps: Handling Signals and Cleanup
trap lets you execute code when the script receives a signal (e.g., Ctrl+C or exit), ensuring cleanup (e.g., deleting temp files, stopping background jobs) even if the script is interrupted.
Common Signals
EXIT: Triggered when the script exits (normal or error).SIGINT(2): Triggered byCtrl+C.SIGTERM(15): Triggered bykill(graceful termination).
Example: Cleanup Temp Files
#!/bin/bash
# Create a temporary directory
TMP_DIR=$(mktemp -d /tmp/my_script.XXXXXX)
echo "Temp dir: $TMP_DIR"
# Define cleanup function
cleanup() {
echo "Cleaning up temp dir..."
rm -rf "$TMP_DIR"
echo "Done."
}
# Trap EXIT, SIGINT, and SIGTERM to run cleanup
trap cleanup EXIT SIGINT SIGTERM
# Simulate work (e.g., write to temp file)
echo "Working in temp dir..."
sleep 10 # Press Ctrl+C here to test cleanup
Why It Matters: Prevents leftover files from cluttering the system if the script crashes.
5. Regular Expressions: Advanced Text Processing
Bash’s [[ ... =~ ... ]] construct supports regular expressions (regex) for pattern matching, enabling powerful text validation and extraction.
Syntax
if [[ "$string" =~ regex_pattern ]]; then
# Match found
fi
Examples
-
Validate email addresses (simplified regex):
email="[email protected]" regex='^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$' if [[ "$email" =~ $regex ]]; then echo "Valid email: $email" else echo "Invalid email: $email" fi -
Extract numbers from a string:
text="Order 12345 total: $99.99" regex='Order ([0-9]+) total: \$([0-9]+\.[0-9]+)' if [[ "$text" =~ $regex ]]; then order_id="${BASH_REMATCH[1]}" # First captured group total="${BASH_REMATCH[2]}" # Second captured group echo "Order ID: $order_id, Total: $total" # Output: Order ID: 12345, Total: 99.99 fi
Tip: Use BASH_REMATCH array to access captured groups (index 0 is the full match).
6. Error Handling: Making Scripts Robust
Advanced scripts need to handle errors gracefully. Use these techniques to avoid silent failures and debug issues.
1. set Options
set -e: Exit immediately if any command fails (avoids running invalid code).set -u: Treat undefined variables as errors (catches typos).set -o pipefail: Make a pipeline fail if any command in it fails (not just the last one).
#!/bin/bash
set -euo pipefail # "Strict mode"
# This will fail if "nonexistent_file.txt" doesn't exist (thanks to set -e)
cat nonexistent_file.txt
echo "This line won't run" # Script exits before here
2. Custom Error Messages
Combine trap with ERR signal to run code on errors:
#!/bin/bash
set -euo pipefail
error_handler() {
echo "Error at line $LINENO: $BASH_COMMAND" >&2
exit 1
}
trap error_handler ERR # Trigger on any command failure
# Example: This will trigger the error handler
cp file1.txt /invalid/dir/ # Fails (invalid dir), error_handler runs
7. Parameter Expansion: Beyond $VAR
Bash offers powerful parameter expansion syntax to manipulate strings without external tools like sed or awk.
Common Techniques
| Syntax | Purpose | Example | Output |
|---|---|---|---|
${var:-default} | Use default if var is unset/empty | var=""; echo ${var:-"empty"} | empty |
${var#prefix} | Remove shortest prefix from var | var="file.txt"; echo ${var#file.} | txt |
${var##prefix} | Remove longest prefix from var | var="/a/b/c.txt"; echo ${var##*/} | c.txt (basename) |
${var%suffix} | Remove shortest suffix from var | var="file.txt"; echo ${var%.txt} | file |
${var%%suffix} | Remove longest suffix from var | var="/a/b/c.txt"; echo ${var%%/*} | / (dirname) |
${var//search/repl} | Replace all search with repl | var="a b c"; echo ${var// /-} | a-b-c |
${#var} | Length of var | var="hello"; echo ${#var} | 5 |
Example: Sanitize Filenames
filename="My Document? 2023.txt"
# Replace spaces with underscores, remove special chars
sanitized=${filename// /_}
sanitized=${sanitized//[^a-zA-Z0-9_.-]/} # Keep only safe chars
echo "$sanitized" # Output: My_Document_2023.txt
8. Job Control: Parallel Execution & Background Tasks
Bash lets you run tasks in the background, manage them, and coordinate execution—critical for speeding up scripts with independent tasks.
Key Commands
command &: Runcommandin the background.jobs: List background jobs.fg %N: Bring jobNto the foreground.bg %N: Resume suspended jobNin the background.wait: Wait for all background jobs to finish.kill %N: Terminate jobN.
Example: Run Tasks in Parallel
#!/bin/bash
# Function to process a file (simulate work)
process_file() {
local file="$1"
echo "Processing $file..."
sleep 2 # Simulate work
echo "Done with $file"
}
# Run tasks in background
process_file "file1.txt" &
process_file "file2.txt" &
process_file "file3.txt" &
# Wait for all background jobs to finish
wait
echo "All files processed!"
Why It Matters: Reduces runtime from sequential (6 seconds) to parallel (2 seconds) for 3 tasks.
9. Real-World Automation Example
Let’s tie it all together with a deployment script that uses functions, arrays, error handling, traps, and parameter expansion.
#!/bin/bash
set -euo pipefail
# Configuration (associative array)
declare -A DEPLOY_CONFIG=(
["app_name"]="my_app"
["src_dir"]="./dist"
["servers"]="server1.example.com server2.example.com"
["deploy_dir"]="/var/www/my_app"
)
# Cleanup function (trap)
cleanup() {
if [ -n "${TMP_DIR:-}" ] && [ -d "$TMP_DIR" ]; then
echo "Cleaning up temp dir: $TMP_DIR"
rm -rf "$TMP_DIR"
fi
}
trap cleanup EXIT SIGINT SIGTERM
# Function to deploy to a single server
deploy_to_server() {
local server="$1"
echo "Deploying to $server..."
# Create temp dir (local to function)
local TMP_DIR=$(mktemp -d /tmp/deploy.XXXXXX)
# Copy source files to temp dir (with error handling)
cp -r "${DEPLOY_CONFIG["src_dir"]}/." "$TMP_DIR/" || {
echo "Failed to copy source files" >&2
return 1
}
# Use rsync to deploy (resumes on failure)
rsync -avz --delete "$TMP_DIR/" "$server:${DEPLOY_CONFIG["deploy_dir"]}"
# Restart app (example: systemd)
ssh "$server" "sudo systemctl restart ${DEPLOY_CONFIG["app_name"]}"
echo "Successfully deployed to $server"
}
# Main deployment logic
echo "Starting deployment of ${DEPLOY_CONFIG["app_name"]}..."
# Convert servers string to array
servers=(${DEPLOY_CONFIG["servers"]})
# Deploy to all servers in parallel
for server in "${servers[@]}"; do
deploy_to_server "$server" & # Run in background
done
# Wait for all deployments to finish
wait
echo "Deployment to all servers complete!"
Features Used:
- Associative array for config.
trapfor cleanup.- Functions with local variables.
- Error handling (
set -euo pipefail,|| { ... }). - Parallel job execution (
&andwait).
10. Conclusion
Advanced Bash automation transforms scripts from simple task runners into robust, maintainable tools. By mastering functions, arrays, traps, process substitution, and other techniques, you’ll write scripts that are efficient, resilient, and scalable.
The key to mastery is practice: experiment with the examples, modify them for your use case, and gradually integrate advanced features into your workflow. Over time, you’ll automate complex tasks with confidence.