19 - 设计模式
19 - 设计模式
19.1 配置管理模式
多层配置文件
#!/bin/bash
# config_manager.sh —— 多层配置管理
declare -A CONFIG
# 默认值
load_defaults() {
CONFIG=(
[host]="localhost"
[port]="8080"
[log_level]="info"
[workers]="4"
[timeout]="30"
[debug]="false"
)
}
# 从 INI 文件加载
load_config_file() {
local file="$1"
[[ -f "$file" ]] || return 0
local section="global"
while IFS= read -r line || [[ -n "$line" ]]; do
line="${line#"${line%%[![:space:]]*}"}"
line="${line%"${line##*[![:space:]]}"}"
[[ -z "$line" || "$line" == \#* ]] && continue
if [[ "$line" =~ ^\[([^\]]+)\]$ ]]; then
section="${BASH_REMATCH[1]}"
continue
fi
if [[ "$line" =~ ^([^=]+)=(.*)$ ]]; then
local key="${BASH_REMATCH[1]}"
local value="${BASH_REMATCH[2]}"
key="${key#"${key%%[![:space:]]*}"}"
key="${key%"${key##*[![:space:]]}"}"
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
CONFIG["$key"]="$value"
fi
done < "$file"
}
# 从环境变量覆盖
load_env_overrides() {
local prefix="${1:-APP}"
local key value
for key in "${!CONFIG[@]}"; do
local env_name="${prefix}_${key^^}"
value="${!env_name:-}"
[[ -n "$value" ]] && CONFIG["$key"]="$value"
done
}
# 完整加载链
load_config() {
load_defaults
load_config_file "/etc/myapp/config.ini"
load_config_file "$HOME/.myapp/config.ini"
load_config_file "./config.ini"
load_env_overrides "MYAPP"
}
config_get() {
echo "${CONFIG[${1}]:-}"
}
# 使用
load_config
echo "Host: $(config_get host)"
echo "Port: $(config_get port)"
19.2 插件系统模式
#!/bin/bash
# plugin_system.sh —— 简易插件系统
readonly PLUGIN_DIR="${PLUGIN_DIR:-./plugins}"
declare -A PLUGINS=()
# 加载所有插件
load_plugins() {
local plugin_file
for plugin_file in "$PLUGIN_DIR"/*.sh; do
[[ -f "$plugin_file" ]] || continue
local plugin_name
plugin_name=$(basename "$plugin_file" .sh)
# 加载插件
source "$plugin_file"
# 检查插件是否定义了必需函数
if ! declare -f "plugin_${plugin_name}_init" &>/dev/null; then
echo "警告: 插件 $plugin_name 缺少 init 函数" >&2
continue
fi
PLUGINS["$plugin_name"]="$plugin_file"
echo "已加载插件: $plugin_name"
done
}
# 初始化所有插件
init_plugins() {
local name
for name in "${!PLUGINS[@]}"; do
"plugin_${name}_init"
done
}
# 调用插件的特定钩子
call_hook() {
local hook="$1"
shift
local name
for name in "${!PLUGINS[@]}"; do
local func="plugin_${name}_${hook}"
if declare -f "$func" &>/dev/null; then
"$func" "$@"
fi
done
}
插件文件示例:
#!/bin/bash
# plugins/notify.sh —— 通知插件
plugin_notify_init() {
echo "通知插件已初始化"
}
plugin_notify_on_deploy() {
local env="$1"
local version="$2"
echo "发送部署通知: $env -> $version"
# curl -X POST "https://hooks.slack.com/..." ...
}
plugin_notify_on_error() {
local message="$1"
echo "发送错误通知: $message"
}
# 主程序使用
source plugin_system.sh
load_plugins
init_plugins
# 触发钩子
call_hook "on_deploy" "production" "2.0.0"
19.3 日志框架
#!/bin/bash
# logger.sh —— 结构化日志框架
# 日志级别
readonly _LOG_DEBUG=0
readonly _LOG_INFO=1
readonly _LOG_WARN=2
readonly _LOG_ERROR=3
readonly _LOG_FATAL=4
# 配置
_LOG_LEVEL=${_LOG_LEVEL:-$_LOG_INFO}
_LOG_FILE=""
_LOG_FORMAT="text" # text | json
_LOG_COLOR=true
# 颜色
readonly _COLOR_RESET="\033[0m"
readonly _COLOR_GRAY="\033[0;37m"
readonly _COLOR_GREEN="\033[0;32m"
readonly _COLOR_YELLOW="\033[0;33m"
readonly _COLOR_RED="\033[0;31m"
readonly _COLOR_MAGENTA="\033[0;35m"
declare -a _LEVEL_NAMES=("DEBUG" "INFO" "WARN" "ERROR" "FATAL")
declare -a _LEVEL_COLORS=("$_COLOR_GRAY" "$_COLOR_GREEN" "$_COLOR_YELLOW" "$_COLOR_RED" "$_COLOR_MAGENTA")
_log() {
local level=$1
shift
local message="$*"
[[ $level -lt $_LOG_LEVEL ]] && return
local timestamp
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
local caller="${FUNCNAME[2]:-main}"
local source="${BASH_SOURCE[2]:-$0}"
local lineno="${BASH_LINENO[1]:-0}"
if [[ "$_LOG_FORMAT" == "json" ]]; then
# JSON 格式
local json="{\"time\":\"$timestamp\",\"level\":\"${_LEVEL_NAMES[$level]}\",\"caller\":\"$caller\",\"file\":\"$source:$lineno\",\"msg\":\"$message\"}"
echo "$json" >&2
[[ -n "$_LOG_FILE" ]] && echo "$json" >> "$_LOG_FILE"
else
# 文本格式
local level_name="${_LEVEL_NAMES[$level]}"
local color="${_LEVEL_COLORS[$level]}"
if [[ "$_LOG_COLOR" == true ]]; then
printf "${color}[%s] [%-5s] [%s:%d] %s${_COLOR_RESET}\n" \
"$timestamp" "$level_name" "$source" "$lineno" "$message" >&2
else
printf "[%s] [%-5s] [%s:%d] %s\n" \
"$timestamp" "$level_name" "$source" "$lineno" "$message" >&2
fi
[[ -n "$_LOG_FILE" ]] && printf "[%s] [%-5s] [%s:%d] %s\n" \
"$timestamp" "$level_name" "$source" "$lineno" "$message" >> "$_LOG_FILE"
fi
}
log::debug() { _log $_LOG_DEBUG "$@"; }
log::info() { _log $_LOG_INFO "$@"; }
log::warn() { _log $_LOG_WARN "$@"; }
log::error() { _log $_LOG_ERROR "$@"; }
log::fatal() { _log $_LOG_FATAL "$@"; }
# 日志配置
log::configure() {
while [[ $# -gt 0 ]]; do
case "$1" in
--level) _LOG_LEVEL="$2"; shift 2 ;;
--file) _LOG_FILE="$2"; shift 2 ;;
--format) _LOG_FORMAT="$2"; shift 2 ;;
--no-color) _LOG_COLOR=false; shift ;;
*) shift ;;
esac
done
}
# 性能计时
declare -A _TIMERS
log::timer_start() {
_TIMERS["$1"]=$(date +%s%N)
}
log::timer_end() {
local name="$1"
local start="${_TIMERS[$name]}"
local end=$(date +%s%N)
local duration=$(( (end - start) / 1000000 ))
log::info "计时器 [$name]: ${duration}ms"
unset "_TIMERS[$name]"
}
使用日志框架:
#!/bin/bash
source logger.sh
log::configure --level "$_LOG_DEBUG" --file "/var/log/app.log"
log::debug "调试信息"
log::info "应用启动"
log::warn "配置文件缺失,使用默认值"
log::error "连接数据库失败"
log::fatal "无法恢复的错误"
log::timer_start "operation"
# ... 业务逻辑 ...
sleep 1
log::timer_end "operation"
19.4 参数解析模式
getopts(POSIX 标准)
#!/bin/bash
# getopts 风格参数解析
usage() {
cat << 'EOF'
用法: script.sh [选项] <参数>
选项:
-e <env> 目标环境 (dev|prod)
-v 详细输出
-f 强制执行
-h 显示帮助
EOF
}
verbose=false
force=false
env="dev"
while getopts "e:vfh" opt; do
case "$opt" in
e) env="$OPTARG" ;;
v) verbose=true ;;
f) force=true ;;
h) usage; exit 0 ;;
?) usage; exit 1 ;;
esac
done
shift $((OPTIND - 1))
echo "环境: $env"
echo "详细: $verbose"
echo "强制: $force"
echo "剩余参数: $*"
长选项解析(手动模式)
#!/bin/bash
# 长选项参数解析
declare -A ARGS=()
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--env|-e)
ARGS[env]="$2"; shift 2 ;;
--version|-v)
ARGS[version]="$2"; shift 2 ;;
--output|-o)
ARGS[output]="$2"; shift 2 ;;
--force|-f)
ARGS[force]="true"; shift ;;
--dry-run|-n)
ARGS[dry_run]="true"; shift ;;
--help|-h)
usage; exit 0 ;;
--)
shift; break ;;
-*)
echo "未知选项: $1" >&2; usage >&2; exit 1 ;;
*)
break ;;
esac
done
ARGS[remaining]=("$@")
}
# 使用
parse_args "$@"
echo "环境: ${ARGS[env]:-dev}"
echo "版本: ${ARGS[version]:-latest}"
echo "输出: ${ARGS[output]:-/dev/stdout}"
echo "强制: ${ARGS[force]:-false}"
echo "模拟: ${ARGS[dry_run]:-false}"
19.5 锁文件模式
#!/bin/bash
# flock 文件锁
readonly LOCK_FILE="/var/lock/myapp.lock"
# 获取锁(非阻塞)
acquire_lock() {
exec 200>"$LOCK_FILE"
if ! flock -n 200; then
echo "另一个实例正在运行" >&2
return 1
fi
echo $$ >&200
}
# 释放锁
release_lock() {
flock -u 200 2>/dev/null
rm -f "$LOCK_FILE"
}
trap release_lock EXIT
acquire_lock || exit 1
# ... 独占执行 ...
19.6 重试模式
#!/bin/bash
# 通用重试函数
retry() {
local max_attempts="$1"
local delay="$2"
local backoff="${3:-1}" # 退避因子
shift 3
local attempt=1
local current_delay=$delay
while ((attempt <= max_attempts)); do
if "$@"; then
return 0
fi
local exit_code=$?
if ((attempt < max_attempts)); then
echo "尝试 $attempt/$max_attempts 失败 (退出码: $exit_code),${current_delay}s 后重试..." >&2
sleep "$current_delay"
current_delay=$((current_delay * backoff))
fi
((attempt++))
done
echo "所有 $max_attempts 次尝试均失败" >&2
return 1
}
# 使用
retry 3 5 2 curl -sf "https://api.example.com/health"
# 3次尝试,初始5秒,每次翻倍:5s → 10s → 20s
19.7 临时文件管理模式
#!/bin/bash
# 临时资源管理
declare -a _TEMP_RESOURCES=()
temp::dir() {
local tmpdir
tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/myapp.XXXXXX")
_TEMP_RESOURCES+=("dir:$tmpdir")
echo "$tmpdir"
}
temp::file() {
local tmpfile
tmpfile=$(mktemp "${TMPDIR:-/tmp}/myapp.XXXXXX")
_TEMP_RESOURCES+=("file:$tmpfile")
echo "$tmpfile"
}
temp::cleanup() {
for resource in "${_TEMP_RESOURCES[@]}"; do
local type="${resource%%:*}"
local path="${resource#*:}"
case "$type" in
file) [[ -f "$path" ]] && rm -f "$path" ;;
dir) [[ -d "$path" ]] && rm -rf "$path" ;;
esac
done
_TEMP_RESOURCES=()
}
trap temp::cleanup EXIT
# 使用
tmpdir=$(temp::dir)
tmpfile=$(temp::file)
echo "处理中..." > "$tmpfile"
# 脚本退出时自动清理
19.8 业务场景:完整 CLI 框架
#!/bin/bash
# myapp —— CLI 应用框架
set -euo pipefail
readonly VERSION="1.0.0"
readonly SCRIPT_NAME=$(basename "$0")
# ---- 模块加载 ----
source "$(dirname "$0")/lib/logger.sh"
source "$(dirname "$0")/lib/config.sh"
# ---- 全局配置 ----
declare -A CLI_ARGS=(
[command]=""
[env]="dev"
[verbose]="false"
[dry_run]="false"
)
# ---- 参数解析 ----
parse_args() {
[[ $# -eq 0 ]] && { usage; exit 1; }
CLI_ARGS[command]="$1"; shift
while [[ $# -gt 0 ]]; do
case "$1" in
--env|-e) CLI_ARGS[env]="$2"; shift 2 ;;
--verbose|-v) CLI_ARGS[verbose]="true"; shift ;;
--dry-run|-n) CLI_ARGS[dry_run]="true"; shift ;;
--version) echo "$VERSION"; exit 0 ;;
--help|-h) usage; exit 0 ;;
*) break ;;
esac
done
CLI_ARGS[remaining]=("$@")
}
usage() {
cat << EOF
用法: $SCRIPT_NAME <命令> [选项]
命令:
build 构建项目
test 运行测试
deploy 部署服务
status 查看状态
选项:
-e, --env <env> 环境 (dev|staging|prod)
-v, --verbose 详细输出
-n, --dry-run 模拟运行
--version 显示版本
-h, --help 显示帮助
示例:
$SCRIPT_NAME deploy --env production
$SCRIPT_NAME test --verbose
EOF
}
# ---- 命令实现 ----
cmd_build() {
log::info "开始构建..."
[[ "${CLI_ARGS[dry_run]}" == "true" ]] && { log::info "(模拟运行)"; return 0; }
# build logic
log::info "构建完成"
}
cmd_test() {
log::info "运行测试..."
# test logic
bats tests/ 2>/dev/null || log::warn "测试框架未安装"
log::info "测试完成"
}
cmd_deploy() {
local env="${CLI_ARGS[env]}"
log::info "部署到 $env 环境..."
if [[ "$env" == "prod" ]]; then
read -rp "确认部署到生产环境?(yes/no): " confirm
[[ "$confirm" != "yes" ]] && { log::info "已取消"; return 0; }
fi
[[ "${CLI_ARGS[dry_run]}" == "true" ]] && { log::info "(模拟运行)"; return 0; }
# deploy logic
log::info "部署完成"
}
cmd_status() {
log::info "查看状态..."
# status logic
}
# ---- 主入口 ----
main() {
parse_args "$@"
[[ "${CLI_ARGS[verbose]}" == "true" ]] && _LOG_LEVEL=$_LOG_DEBUG
case "${CLI_ARGS[command]}" in
build) cmd_build ;;
test) cmd_test ;;
deploy) cmd_deploy ;;
status) cmd_status ;;
*) log::error "未知命令: ${CLI_ARGS[command]}"; usage; exit 1 ;;
esac
}
main "$@"
19.9 注意事项
| 模式 | 适用场景 | 复杂度 |
|---|---|---|
| 配置管理 | 多环境部署 | 中 |
| 插件系统 | 可扩展工具 | 高 |
| 日志框架 | 生产级脚本 | 中 |
| 参数解析 | CLI 工具 | 低 |
| 重试模式 | 网络操作 | 低 |
| 临时文件管理 | 需要清理的资源 | 低 |