正在加载,请稍候…

Advanced Bash Scripting: Functions, Error Handling, and Automation

Write professional Bash scripts for automation. Learn functions, error handling, argument parsing, logging, parallel execution, and common scripting patterns.

Advanced Bash Scripting

Script Template

#!/usr/bin/env bash
# Script: deploy.sh
# Description: Automated deployment script
# Usage: ./deploy.sh [options] <environment>

set -euo pipefail  # Exit on error, undefined var, pipe failure
IFS=
#39; ' # Safer word splitting # Script directory SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" readonly SCRIPT_DIR # Defaults LOG_FILE="${SCRIPT_DIR}/deploy.log" DRY_RUN=false ENVIRONMENT="" # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # Logging log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; } info() { log "${GREEN}[INFO]${NC} $*"; } warn() { log "${YELLOW}[WARN]${NC} $*"; } error() { log "${RED}[ERROR]${NC} $*" >&2; } die() { error "$@"; exit 1; }

Argument Parsing

usage() {
  cat << EOF
Usage: $(basename "$0") [options] <environment>

Options:
  -h, --help     Show help
  -n, --dry-run  Dry run mode
  -v, --verbose  Verbose output
  -t, --tag TAG  Docker image tag

Environments:
  staging, production
EOF
  exit 0
}

parse_args() {
  while [[ $# -gt 0 ]]; do
    case "$1" in
      -h|--help)    usage ;;
      -n|--dry-run) DRY_RUN=true ;;
      -v|--verbose) set -x ;;
      -t|--tag)
        [[ -n "${2:-}" ]] || die "Option $1 requires an argument"
        TAG="$2"
        shift 2
        ;;
      --)  shift; break ;;
      -*)  die "Unknown option: $1" ;;
      *)   ENVIRONMENT="$1"; shift ;;
    esac
  done

  [[ -n "$ENVIRONMENT" ]] || die "Environment required"
  [[ "$ENVIRONMENT" =~ ^(staging|production)$ ]] || die "Invalid environment: $ENVIRONMENT"
}

parse_args "$@"

Error Handling and Cleanup

# Trap for cleanup on exit
cleanup() {
  local exit_code=$?
  info "Cleaning up..."
  rm -f /tmp/deploy-$-*
  if [[ $exit_code -ne 0 ]]; then
    error "Script failed with exit code $exit_code"
  fi
  exit $exit_code
}
trap cleanup EXIT INT TERM

# Retry function
retry() {
  local max=$1
  local delay=$2
  local cmd="${@:3}"
  local count=0

  until $cmd; do
    count=$((count + 1))
    if [[ $count -ge $max ]]; then
      error "Command failed after $max attempts: $cmd"
      return 1
    fi
    warn "Attempt $count failed. Retrying in ${delay}s..."
    sleep "$delay"
  done
}

retry 3 5 curl -sf https://api.example.com/health

Functions and Return Values

# Return multiple values via global variables or stdout
get_version() {
  local env="$1"
  local version
  version=$(git describe --tags --abbrev=0)
  echo "$version"  # Return via stdout
}

check_service() {
  local url="$1"
  local timeout="${2:-5}"
  curl --silent --fail --max-time "$timeout" "$url" > /dev/null 2>&1
  return $?  # Return via exit code
}

# Usage
VERSION=$(get_version "$ENVIRONMENT")
if check_service "https://api.example.com/health"; then
  info "Service is healthy"
else
  die "Service health check failed"
fi

Parallel Execution

# Run tasks in parallel
deploy_services() {
  local pids=()

  for service in api worker scheduler; do
    (
      info "Deploying $service..."
      kubectl rollout restart deployment/$service -n production
      kubectl rollout status deployment/$service -n production --timeout=300s
    ) &
    pids+=($!)
  done

  # Wait for all and check status
  local failed=0
  for pid in "${pids[@]}"; do
    if ! wait "$pid"; then
      failed=$((failed + 1))
    fi
  done

  return $failed
}

# GNU Parallel for more control
parallel --jobs 4 --halt now,fail=1   'echo "Processing {}" && process_item {}'   ::: item1 item2 item3 item4

Arrays and Associative Arrays

# Indexed arrays
services=("api" "worker" "scheduler" "cron")

# Loop
for service in "${services[@]}"; do
  echo "Service: $service"
done

# Slice
echo "${services[@]:1:2}"  # worker scheduler

# Associative arrays
declare -A config
config[host]="db.example.com"
config[port]="5432"
config[db]="myapp"

# Loop over keys
for key in "${!config[@]}"; do
  echo "$key=${config[$key]}"
done

Text Processing

# String manipulation
name="hello-world"
echo "${name^^}"           # HELLO-WORLD (uppercase)
echo "${name^}"            # Hello-world (capitalize)
echo "${name//-/_}"        # hello_world (replace)
echo "${name#hello-}"      # world (remove prefix)
echo "${name%%-*}"         # hello (remove suffix)
echo "${name:6:5}"         # world (substring)

# Process substitution
diff <(sort file1.txt) <(sort file2.txt)

# Here documents
read -r -d '' SQL << 'EOF'
SELECT id, name, email
FROM users
WHERE created_at > NOW() - INTERVAL '7 days'
EOF

# Efficient text processing
grep -r "TODO" --include="*.ts" --exclude-dir=node_modules | wc -l
find . -name "*.log" -mtime +30 -exec rm {} ;

Script Best Practices

Practice Why
set -euo pipefail Fail fast on errors
shellcheck Static analysis
Quote variables Prevent word splitting
readonly for constants Prevent accidental changes
Log with timestamps Easier debugging
Cleanup with traps No leftover temp files