高级脚本技巧
掌握高级 Shell 脚本技巧,编写更强大、更可靠的自动化脚本。
脚本模板
专业脚本框架
#!/bin/bash
#
# Script Name: example.sh
# Description: 脚本功能描述
# Author: Your Name
# Created: 2024-01-01
# Version: 1.0
#
set -euo pipefail # 严格模式
IFS=$'\n\t' # 字段分隔符
# 颜色定义
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly NC='\033[0m' # No Color
# 全局变量
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_NAME="$(basename "$0")"
readonly LOG_FILE="/var/log/${SCRIPT_NAME%.sh}.log"
# 日志函数
log() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}
info() {
echo -e "${GREEN}[INFO]${NC} $*" | tee -a "$LOG_FILE"
}
warn() {
echo -e "${YELLOW}[WARN]${NC} $*" | tee -a "$LOG_FILE"
}
error() {
echo -e "${RED}[ERROR]${NC} $*" | tee -a "$LOG_FILE"
}
die() {
error "$*"
exit 1
}
# 清理函数
cleanup() {
local exit_code=$?
log "脚本退出,退出码: $exit_code"
# 清理临时文件等
rm -f /tmp/temp_$$_*
}
trap cleanup EXIT
# 显示帮助
usage() {
cat << EOF
Usage: $SCRIPT_NAME [OPTIONS]
Description:
脚本功能描述
Options:
-h, --help 显示帮助信息
-v, --verbose 详细输出
-d, --debug 调试模式
-c, --config FILE 配置文件
Examples:
$SCRIPT_NAME -v
$SCRIPT_NAME -c /etc/config.conf
EOF
exit 0
}
# 参数解析
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
usage
;;
-v|--verbose)
VERBOSE=1
shift
;;
-d|--debug)
set -x
shift
;;
-c|--config)
CONFIG_FILE="$2"
shift 2
;;
*)
die "未知选项: $1"
;;
esac
done
}
# 主函数
main() {
info "脚本开始执行"
# 检查权限
[[ $EUID -eq 0 ]] || die "此脚本需要 root 权限"
# 检查依赖
command -v jq >/dev/null 2>&1 || die "需要安装 jq"
# 主要逻辑
info "执行主要任务..."
info "脚本执行完成"
}
# 脚本入口
parse_args "$@"
main
错误处理
严格模式
#!/bin/bash
# set -e: 遇到错误立即退出
# set -u: 使用未定义变量时报错
# set -o pipefail: 管道中任一命令失败则失败
# set -x: 打印执行的命令(调试)
set -euo pipefail
# 示例:严格模式的效果
echo "开始执行"
# 这会导致脚本退出(因为 set -e)
# false
# 这不会执行
echo "这行不会打印"
错误捕获和处理
#!/bin/bash
# 捕获错误信息
error_handler() {
local line_no=$1
local bash_lineno=$2
local last_cmd=$3
local code=$4
echo "错误发生在:"
echo " 文件: $0"
echo " 行号: $line_no"
echo " 命令: $last_cmd"
echo " 退出码: $code"
# 发送告警
# send_alert "脚本错误: $0:$line_no"
}
trap 'error_handler ${LINENO} ${BASH_LINENO} "$BASH_COMMAND" $?' ERR
# 重试机制
retry() {
local max_attempts=$1
shift
local cmd="$@"
local attempt=1
until $cmd; do
if (( attempt >= max_attempts )); then
echo "命令失败,已重试 $max_attempts 次"
return 1
fi
echo "命令失败,等待重试... (尝试 $attempt/$max_attempts)"
sleep 5
((attempt++))
done
return 0
}
# 使用示例
retry 3 curl -f https://example.com/api
并发处理
后台任务管理
#!/bin/bash
# 并发执行任务
parallel_tasks() {
local max_jobs=4
local job_count=0
for item in item1 item2 item3 item4 item5 item6; do
# 等待直到有空闲槽位
while (( $(jobs -r | wc -l) >= max_jobs )); do
sleep 0.1
done
# 后台执行任务
{
echo "处理 $item"
sleep 2
echo "$item 完成"
} &
done
# 等待所有任务完成
wait
echo "所有任务完成"
}
parallel_tasks
GNU Parallel
# 安装
sudo apt install parallel
# 基本使用
seq 10 | parallel echo "处理任务 {}"
# 并行运行命令
parallel -j 4 ::: command1 command2 command3 command4
# 处理文件列表
find . -name "*.log" | parallel gzip {}
# 从文件读取任务
parallel -a tasks.txt -j 8 ./process.sh
# 进度显示
seq 100 | parallel --progress sleep 0.1
# 结果收集
parallel -j 4 "echo {}; date" ::: task{1..10} > results.txt
xargs 并发
# 并发执行(4 个进程)
find . -name "*.txt" | xargs -P 4 -I {} gzip {}
# 批量处理
seq 100 | xargs -n 10 -P 4 ./batch_process.sh
# 空格处理
find . -print0 | xargs -0 -P 4 -I {} echo "处理: {}"
进程间通信
命名管道(FIFO)
#!/bin/bash
# 创建命名管道
FIFO="/tmp/my_fifo"
mkfifo "$FIFO"
# 清理函数
cleanup() {
rm -f "$FIFO"
}
trap cleanup EXIT
# 写入进程
{
for i in {1..10}; do
echo "消息 $i"
sleep 1
done
} > "$FIFO" &
# 读取进程
while read line; do
echo "收到: $line"
done < "$FIFO"
信号处理
#!/bin/bash
# 信号处理函数
handle_sigint() {
echo "收到 SIGINT (Ctrl+C)"
exit 0
}
handle_sigterm() {
echo "收到 SIGTERM"
cleanup
exit 0
}
handle_sigusr1() {
echo "收到 SIGUSR1,重新加载配置"
load_config
}
# 注册信号处理
trap handle_sigint SIGINT
trap handle_sigterm SIGTERM
trap handle_sigusr1 SIGUSR1
# 主循环
while true; do
echo "运行中... PID: $$"
sleep 5
done
进程同步
#!/bin/bash
# 使用文件锁
LOCK_FILE="/var/lock/myscript.lock"
acquire_lock() {
exec 200>"$LOCK_FILE"
flock -n 200 || {
echo "无法获取锁,其他实例正在运行"
exit 1
}
}
release_lock() {
flock -u 200
}
trap release_lock EXIT
acquire_lock
echo "获取锁成功,执行任务..."
sleep 10
配置文件处理
读取 INI 配置
#!/bin/bash
# config.ini
# [database]
# host=localhost
# port=3306
# user=admin
# password=secret
read_ini() {
local file=$1
local section=$2
local key=$3
awk -F= -v section="$section" -v key="$key" '
/^\[.*\]$/ {
current_section = substr($0, 2, length($0)-2)
}
current_section == section && $1 == key {
gsub(/^[ \t]+|[ \t]+$/, "", $2)
print $2
}
' "$file"
}
# 使用
DB_HOST=$(read_ini config.ini database host)
DB_PORT=$(read_ini config.ini database port)
echo "数据库: $DB_HOST:$DB_PORT"
JSON 处理
#!/bin/bash
# 使用 jq 处理 JSON
config='{"server": {"host": "localhost", "port": 8080}}'
# 读取值
host=$(echo "$config" | jq -r '.server.host')
port=$(echo "$config" | jq -r '.server.port')
# 修改值
new_config=$(echo "$config" | jq '.server.port = 9090')
# 从文件读取
jq '.servers[] | select(.active == true)' config.json
# 生成 JSON
jq -n \
--arg name "test" \
--argjson port 8080 \
'{name: $name, port: $port}'
YAML 处理
#!/bin/bash
# 使用 yq 处理 YAML(需要安装 yq)
# pip install yq
# 读取值
host=$(yq -r '.database.host' config.yaml)
# 修改值
yq -y '.database.port = 5432' config.yaml > config_new.yaml
# 合并 YAML
yq -s '.[0] * .[1]' base.yaml override.yaml
数组和关联数组
高级数组操作
#!/bin/bash
# 数组定义
servers=("web1" "web2" "web3")
# 数组长度
echo "服务器数量: ${#servers[@]}"
# 数组切片
echo "前两个: ${servers[@]:0:2}"
# 追加元素
servers+=("web4" "web5")
# 删除元素
unset servers[1]
# 数组遍历
for server in "${servers[@]}"; do
echo "服务器: $server"
done
# 数组索引遍历
for i in "${!servers[@]}"; do
echo "[$i]: ${servers[$i]}"
done
# 数组排序
IFS=$'\n' sorted=($(sort <<<"${servers[*]}"))
unset IFS
# 数组去重
unique=($(printf "%s\n" "${servers[@]}" | sort -u))
关联数组应用
#!/bin/bash
# 声明关联数组
declare -A server_status
declare -A server_config
# 设置值
server_status[web1]="running"
server_status[web2]="stopped"
server_status[db1]="running"
# 配置映射
server_config[web1]="192.168.1.10:80"
server_config[web2]="192.168.1.11:80"
server_config[db1]="192.168.1.20:3306"
# 遍历关联数组
for server in "${!server_status[@]}"; do
echo "$server: ${server_status[$server]} @ ${server_config[$server]}"
done
# 检查键是否存在
if [[ -v server_status[web1] ]]; then
echo "web1 存在"
fi
# 统计
declare -A count
for item in a b c a b a; do
((count[$item]++))
done
for key in "${!count[@]}"; do
echo "$key: ${count[$key]}"
done
文本处理
sed 高级用法
# 多行替换
sed '/start/,/end/s/old/new/g' file.txt
# 使用变量
var="new_value"
sed "s/old_value/$var/g" file.txt
# 备份并修改
sed -i.bak 's/old/new/g' file.txt
# 删除空行
sed '/^$/d' file.txt
# 提取匹配行
sed -n '/pattern/p' file.txt
# 在匹配行前后插入
sed '/pattern/i\新行内容' file.txt
sed '/pattern/a\新行内容' file.txt
awk 高级用法
# 统计
awk '{sum+=$1; count++} END {print sum/count}' data.txt
# 条件处理
awk '$3 > 100 {print $1, $2}' data.txt
# 数组
awk '{count[$1]++} END {for(i in count) print i, count[i]}' file.txt
# 多文件处理
awk 'FNR==1{print FILENAME} {print}' file1.txt file2.txt
# 格式化输出
awk '{printf "%-10s %5d %8.2f\n", $1, $2, $3}' data.txt
# 函数
awk 'function abs(x){return x<0?-x:x} {print abs($1)}' data.txt
性能优化
避免子进程
# 慢速(每次迭代创建子进程)
for i in {1..1000}; do
result=$(expr $i + 1)
done
# 快速(使用内置算术)
for i in {1..1000}; do
result=$((i + 1))
done
# 慢速(使用外部命令)
if [ $(grep -c "pattern" file.txt) -gt 0 ]; then
echo "找到"
fi
# 快速(使用内置功能)
if grep -q "pattern" file.txt; then
echo "找到"
fi
批量操作
# 慢速(逐个文件处理)
for file in *.log; do
cat "$file" | process
done
# 快速(批量处理)
cat *.log | process
# 慢速(多次管道)
cat file.txt | grep pattern | sort | uniq
# 快速(减少管道)
grep pattern file.txt | sort -u
实用脚本示例
系统监控脚本
#!/bin/bash
# monitor.sh - 系统资源监控
THRESHOLD_CPU=80
THRESHOLD_MEM=90
THRESHOLD_DISK=85
EMAIL="admin@example.com"
check_cpu() {
local cpu=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d% -f1)
if (( $(echo "$cpu > $THRESHOLD_CPU" | bc -l) )); then
echo "CPU 使用率过高: ${cpu}%"
return 1
fi
return 0
}
check_memory() {
local mem=$(free | grep Mem | awk '{printf("%.0f", $3/$2 * 100)}')
if (( mem > THRESHOLD_MEM )); then
echo "内存使用率过高: ${mem}%"
return 1
fi
return 0
}
check_disk() {
df -h | awk 'NR>1 {gsub(/%/,"",$5); if($5>threshold) print $0}' threshold=$THRESHOLD_DISK
}
# 主循环
while true; do
alert=""
check_cpu || alert+="CPU过高\n"
check_memory || alert+="内存过高\n"
disk_alert=$(check_disk)
[[ -n "$disk_alert" ]] && alert+="磁盘空间不足:\n$disk_alert\n"
if [[ -n "$alert" ]]; then
echo -e "$alert" | mail -s "系统告警" "$EMAIL"
fi
sleep 300
done
日志分析脚本
#!/bin/bash
# log_analyzer.sh - 日志分析工具
LOG_FILE="/var/log/nginx/access.log"
REPORT_FILE="/tmp/log_report_$(date +%Y%m%d).txt"
{
echo "=== 日志分析报告 ==="
echo "时间: $(date)"
echo ""
echo "Top 10 访问IP:"
awk '{print $1}' "$LOG_FILE" | sort | uniq -c | sort -rn | head -10
echo ""
echo "Top 10 访问页面:"
awk '{print $7}' "$LOG_FILE" | sort | uniq -c | sort -rn | head -10
echo ""
echo "HTTP 状态码统计:"
awk '{print $9}' "$LOG_FILE" | sort | uniq -c | sort -rn
echo ""
echo "流量统计 (MB):"
awk '{sum+=$10} END {printf "%.2f\n", sum/1024/1024}' "$LOG_FILE"
} > "$REPORT_FILE"
echo "报告已生成: $REPORT_FILE"
下一步:学习 系统性能调优 章节。