Shell 脚本编程技巧
基础语法
Shebang 与环境
#!/bin/bash
# 推荐使用 /usr/bin/env 提高可移植性
#!/usr/bin/env bash
set -e # 遇到错误立即退出
set -u # 使用未定义变量时报错
set -o pipefail # 管道中任意命令失败都返回非零
set -x # 调试模式,打印执行的命令
变量
# 定义变量(等号两侧无空格)
name="World"
readonly API_URL="https://api.example.com"
# 使用变量
echo "Hello, ${name}!"
# 默认值
echo "${USER:-default_user}"
echo "${API_KEY:=default_key}"
# 字符串替换
str="hello world"
echo "${str/hello/hi}" # hi world
echo "${str//o/O}" # hellO wOrld
# 子串
echo "${str:0:5}" # hello
数组
# 索引数组
fruits=("apple" "banana" "orange")
echo "${fruits[0]}" # apple
echo "${fruits[@]}" # 全部元素
echo "${#fruits[@]}" # 数组长度
# 关联数组(Bash 4+)
declare -A user
user[name]="Alice"
user[role]="admin"
echo "${user[name]}"
流程控制
条件判断
# if 语句
if [[ -f "$file" ]]; then
echo "文件存在"
elif [[ -d "$file" ]]; then
echo "目录存在"
else
echo "不存在"
fi
# 常用文件测试
[[ -f "$file" ]] # 是否是文件
[[ -d "$dir" ]] # 是否是目录
[[ -x "$bin" ]] # 是否可执行
[[ -n "$str" ]] # 字符串非空
[[ -z "$str" ]] # 字符串为空
[[ "$a" == "$b" ]] # 字符串相等
[[ "$a" -gt "$b" ]] # 数值大于(lt:小于, ge:大于等于, le:小于等于)
# case 语句
case "$1" in
start)
echo "Starting..."
;;
stop|restart)
echo "Stopping..."
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
;;
esac
循环
# for 循环
for i in {1..5}; do
echo "Number: $i"
done
# 遍历文件
for file in /var/log/*.log; do
[[ -f "$file" ]] || continue
echo "Processing: $file"
done
# while 循环
while IFS= read -r line; do
echo "$line"
done < input.txt
# 计数循环
count=0
while [[ $count -lt 5 ]]; do
echo $((count++))
done
函数
# 定义函数
log_info() {
local msg="$1"
echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') - $msg"
}
log_error() {
local msg="$1"
echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') - $msg" >&2
}
# 函数返回值
is_file_exists() {
[[ -f "$1" ]]
}
# 使用函数
log_info "开始部署"
if is_file_exists "config.yml"; then
log_info "配置文件存在"
else
log_error "配置文件缺失"
exit 1
fi
错误处理
# 陷阱信号
cleanup() {
echo "清理临时文件..."
rm -rf /tmp/deploy_*
}
trap cleanup EXIT
# 忽略特定错误
set +e
some_command_that_might_fail
set -e
# 超时控制
timeout 30 curl -s https://example.com || {
echo "请求超时"
exit 1
}
# 重试逻辑
retry() {
local n=0
local max=3
local delay=5
until [[ $n -ge $max ]]; do
"$@" && return
n=$((n+1))
echo "重试 $n/$max..."
sleep $delay
done
return 1
}
retry curl -s https://api.example.com
最佳实践
参数解析
#!/bin/bash
usage() {
echo "Usage: $0 [-h] [-v] [-o output] input"
exit 1
}
verbose=false
output=""
while getopts "hvo:" opt; do
case $opt in
h) usage ;;
v) verbose=true ;;
o) output="$OPTARG" ;;
*) usage ;;
esac
done
shift $((OPTIND - 1))
[[ $# -lt 1 ]] && usage
安全检查
# 检查必要命令
for cmd in curl jq docker; do
if ! command -v "$cmd" &>/dev/null; then
echo "错误:未找到命令 $cmd"
exit 1
fi
done
# 检查 root 权限
if [[ $EUID -ne 0 ]]; then
echo "请以 root 用户运行"
exit 1
fi
输出处理
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'
echo -e "${GREEN}成功${NC}"
echo -e "${RED}失败${NC}"
# 进度显示
total=${#files[@]}
for i in "${!files[@]}"; do
printf "\r处理中: %d/%d" $((i+1)) $total
# ... 处理逻辑
done
echo
总结
编写 Shell 脚本时始终使用 set -euo pipefail 增强安全性,善用函数提高代码复用性,使用 trap 确保资源清理。关注可读性和错误处理,让脚本不光能跑,还要跑得稳。测试建议使用 ShellCheck 做静态分析。