Simple Bash scripts use variables and loops. Advanced Bash scripts use arrays to manage lists of data, associative arrays for key-value storage, and select menus for interactive user choices. These patterns separate novice scripts from professional automation tools. In this lesson, you'll level up your Bash skills with the techniques used in production deployment scripts.
1. Learning Objectives
By the end of this lesson, you will be able to:
- Create and manipulate indexed arrays
- Use associative arrays for key-value data
- Build interactive
selectmenus - Use here documents for multi-line text
- Understand process substitution
- Create advanced interactive scripts
2. Why This Matters
Real-world scenario: You need to deploy an application to 20 servers. You have a list of server names, their IP addresses, and their roles (web, db, cache). Without arrays, you'd have 20 variables. With arrays, you manage everything in one data structure.
When to use each type:
- Indexed arrays: Lists of items (servers, files, users)
- Associative arrays: Key-value pairs (server->IP, name->role)
- Select menus: Interactive user choices
3. Core Concepts
Indexed Arrays (Lists)
#!/bin/bash
# Indexed arrays - ordered lists
# Create arrays (multiple ways)
servers=("web-01" "web-02" "db-01" "cache-01")
files=(*.txt) # Create from glob pattern
empty=() # Empty array
# Access elements (zero-indexed)
echo "First server: ${servers[0]}" # web-01
echo "Second server: ${servers[1]}" # web-02
echo "All servers: ${servers[@]}" # web-01 web-02 db-01 cache-01
echo "Array length: ${#servers[@]}" # 4
# Modify arrays
servers[4]="cache-02" # Add at index 4
servers+=("monitor-01") # Append to end
unset servers[2] # Remove index 2 (db-01)
# Slice arrays (start:length)
echo "First two: ${servers[@]:0:2}"
# Loop through arrays
echo "=== Server List ==="
for server in "${servers[@]}"; do
echo "Server: $server"
done
# Loop with index
for i in "${!servers[@]}"; do
echo "Index $i: ${servers[$i]}"
done
Associative Arrays (Dictionaries/Maps)
#!/bin/bash
# Associative arrays - key-value pairs (Bash 4.0+)
# Declare associative array
declare -A server_ip
declare -A config
# Add key-value pairs
server_ip["web-01"]="10.0.1.10"
server_ip["web-02"]="10.0.1.11"
server_ip["db-01"]="10.0.2.10"
# Or declare all at once
declare -A server_role=(
["web-01"]="webserver"
["web-02"]="webserver"
["db-01"]="database"
)
# Access values
echo "web-01 IP: ${server_ip["web-01"]}"
echo "db-01 role: ${server_role["db-01"]}"
# Get all keys or values
echo "All servers: ${!server_ip[@]}"
echo "All IPs: ${server_ip[@]}"
# Loop through associative array
echo "=== Server Configuration ==="
for server in "${!server_ip[@]}"; do
echo "$server -> ${server_ip[$server]} (${server_role[$server]})"
done
# Check if key exists
if [[ -v server_ip["web-03"] ]]; then
echo "web-03 exists"
else
echo "web-03 not found"
fi
# Remove entry
unset server_ip["web-02"]
Select Menus (Interactive User Choice)
#!/bin/bash
# Select menu - interactive choice menu
# Simple select menu
options=("Start Service" "Stop Service" "Restart Service" "View Status" "Exit")
echo "Choose an option:"
select choice in "${options[@]}"; do
case $choice in
"Start Service")
echo "Starting service..."
# systemctl start myservice
;;
"Stop Service")
echo "Stopping service..."
;;
"Restart Service")
echo "Restarting service..."
;;
"View Status")
echo "Service is running"
;;
"Exit")
echo "Goodbye!"
break
;;
*)
echo "Invalid option $REPLY"
;;
esac
done
# Select with server selection
declare -A servers=(
["web-01"]="10.0.1.10"
["web-02"]="10.0.1.11"
["db-01"]="10.0.2.10"
)
PS3="Select a server to ping: " # Prompt
echo "Available servers:"
select server in "${!servers[@]}" "Quit"; do
if [[ "$server" == "Quit" ]]; then
echo "Exiting..."
break
elif [[ -n "$server" ]]; then
echo "Pinging ${server} at ${servers[$server]}..."
ping -c 2 "${servers[$server]}"
else
echo "Invalid selection"
fi
done
Here Documents (Multi-line Text)
#!/bin/bash
# Here documents - multi-line text blocks
# Basic here document
cat << EOF
This is a multi-line
block of text
that preserves formatting
and line breaks.
EOF
# Here document with variables
name="John"
cat << EOF
Hello $name,
This is your report.
Today is $(date).
EOF
# Here document with command substitution
cat << EOF
Current directory: $(pwd)
User: $(whoami)
Files: $(ls | wc -l)
EOF
# Here document with tab suppression (<<-)
cat <<- EOF
This line has a leading tab
This line is indented
But the output won't show the tabs
EOF
# Create file from here document
cat > config.yml << 'EOF'
# This is a YAML configuration file
database:
host: localhost
port: 5432
user: admin
password: ${PASSWORD} # This won't expand (quoted EOF)
app:
name: myapp
version: 1.0
EOF
# Multi-line variable using here document
read -r -d '' help_text << 'HELP'
Usage: myscript [OPTIONS]
Options:
-h, --help Show this help
-v, --verbose Enable verbose output
-f, --file Specify input file
Examples:
myscript -f data.txt
myscript --verbose
HELP
echo "$help_text"
Process Substitution
#!/bin/bash
# Process substitution - treat command output as a file
# Compare two directories
diff <(ls /etc) <(ls /usr/local/etc)
# Count lines in command output
wc -l <(grep -r "ERROR" /var/log/)
# Read from process substitution
while IFS= read -r line; do
echo "Processing: $line"
done < <(ps aux | grep nginx)
# Combine multiple command outputs
paste <(ls -1 dir1/) <(ls -1 dir2/)
# Real-world example: Compare server configs
diff <(ssh user@server1 'cat /etc/nginx/nginx.conf') \
<(ssh user@server2 'cat /etc/nginx/nginx.conf')
# Another useful pattern: Join files
join <(sort file1.txt) <(sort file2.txt)
4. Complete Project: Server Management CLI
#!/bin/bash
# server_manager.sh - Advanced server management CLI
declare -A SERVERS=(
["web-01"]="10.0.1.10"
["web-02"]="10.0.1.11"
["web-03"]="10.0.1.12"
["db-01"]="10.0.2.10"
["db-02"]="10.0.2.11"
["cache-01"]="10.0.3.10"
)
declare -A SERVER_ROLES=(
["web-01"]="web"
["web-02"]="web"
["web-03"]="web"
["db-01"]="database"
["db-02"]="database"
["cache-01"]="cache"
)
# Color codes
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Function: Show help
show_help() {
cat << 'HELP'
Server Management CLI
COMMANDS:
list List all servers
ping <server> Ping a server
ssh <server> SSH into a server
status <server> Check server status
roles Show servers by role
health Run health check on all servers
exec <server> <cmd> Execute command on server
help Show this help
exit Exit program
EXAMPLES:
server_manager.sh list
server_manager.sh ping web-01
server_manager.sh exec web-01 "df -h"
HELP
}
# Function: List all servers
list_servers() {
echo -e "${BLUE}Available Servers:${NC}"
echo "----------------------------------------"
printf "%-15s %-15s %-10s\n" "SERVER" "IP ADDRESS" "ROLE"
echo "----------------------------------------"
for server in "${!SERVERS[@]}"; do
printf "%-15s %-15s %-10s\n" "$server" "${SERVERS[$server]}" "${SERVER_ROLES[$server]}"
done
}
# Function: Ping server
ping_server() {
local server="$1"
local ip="${SERVERS[$server]}"
if [[ -z "$ip" ]]; then
echo -e "${RED}Error: Server '$server' not found${NC}"
return 1
fi
echo -e "${YELLOW}Pinging $server ($ip)...${NC}"
ping -c 3 "$ip"
}
# Function: SSH to server
ssh_server() {
local server="$1"
local ip="${SERVERS[$server]}"
if [[ -z "$ip" ]]; then
echo -e "${RED}Error: Server '$server' not found${NC}"
return 1
fi
echo -e "${GREEN}Connecting to $server ($ip)...${NC}"
ssh "$USER@$ip"
}
# Function: Check server status
check_status() {
local server="$1"
local ip="${SERVERS[$server]}"
if [[ -z "$ip" ]]; then
echo -e "${RED}Error: Server '$server' not found${NC}"
return 1
fi
if ping -c 1 -W 2 "$ip" > /dev/null 2>&1; then
echo -e "${GREEN}✓ $server is ONLINE${NC}"
return 0
else
echo -e "${RED}✗ $server is OFFLINE${NC}"
return 1
fi
}
# Function: Show servers by role
show_by_role() {
local role
echo -e "${BLUE}Select a role:${NC}"
select role in "web" "database" "cache" "all"; do
case $role in
web|database|cache)
echo -e "\n${YELLOW}Servers with role '$role':${NC}"
for server in "${!SERVERS[@]}"; do
if [[ "${SERVER_ROLES[$server]}" == "$role" ]]; then
echo " - $server (${SERVERS[$server]})"
fi
done
break
;;
all)
list_servers
break
;;
*)
echo -e "${RED}Invalid option${NC}"
;;
esac
done
}
# Function: Run health check on all servers
health_check() {
echo -e "${BLUE}Running health check on all servers...${NC}\n"
local online=0
local offline=0
for server in "${!SERVERS[@]}"; do
if check_status "$server" > /dev/null 2>&1; then
echo -e "${GREEN}✓ $server${NC}"
((online++))
else
echo -e "${RED}✗ $server${NC}"
((offline++))
fi
done
echo -e "\n${BLUE}Summary:${NC} $online online, $offline offline"
}
# Function: Execute remote command
exec_command() {
local server="$1"
local cmd="$2"
local ip="${SERVERS[$server]}"
if [[ -z "$ip" ]]; then
echo -e "${RED}Error: Server '$server' not found${NC}"
return 1
fi
if [[ -z "$cmd" ]]; then
echo -e "${RED}Error: No command specified${NC}"
return 1
fi
echo -e "${YELLOW}Executing on $server ($ip): $cmd${NC}"
ssh "$USER@$ip" "$cmd"
}
# Main menu
main() {
# If arguments provided, run in command-line mode
if [[ $# -gt 0 ]]; then
case "$1" in
list) list_servers ;;
ping) ping_server "$2" ;;
ssh) ssh_server "$2" ;;
status) check_status "$2" ;;
roles) show_by_role ;;
health) health_check ;;
exec) exec_command "$2" "$3" ;;
help) show_help ;;
*) echo -e "${RED}Unknown command: $1${NC}"; show_help ;;
esac
exit 0
fi
# Interactive mode
echo -e "${GREEN}=== Server Management CLI ===${NC}"
echo -e "Type 'help' for commands, 'exit' to quit\n"
while true; do
read -p "server-manager> " cmd args
case "$cmd" in
list) list_servers ;;
ping) ping_server "$args" ;;
ssh) ssh_server "$args" ;;
status) check_status "$args" ;;
roles) show_by_role ;;
health) health_check ;;
exec)
read -r server command <<< "$args"
exec_command "$server" "$command"
;;
help) show_help ;;
exit|quit) echo "Goodbye!"; break ;;
"") continue ;;
*) echo -e "${RED}Unknown command: $cmd${NC}";;
esac
echo ""
done
}
# Run main function with all arguments
main "$@"
5. Common Errors & Solutions
6. Summary Checklist
7. Next Steps
Next lesson: Bash Lesson 2.4: Error Handling & Debugging
You'll learn to:
- Use
set -e,set -x,set -u - Implement
trapfor cleanup - Debug with
shellcheck - Add proper error handling
Comments (0)
This is exactly what I needed! The initContainer approach solved our migration issues completely. Thanks for the detailed guide!
ReplyLeave a Comment