脚本规范的"四行宣言"
每个脚本头部建议放这四行:
#!/bin/bash
set -euo pipefail
IFS=$'\n\t'
意义:
#!/bin/bash— 用 bash 跑(不是 sh)set -e— 任何命令失败立即退出set -u— 引用未定义变量报错set -o pipefail— 管道里任何一段失败就算失败IFS=$'\n\t'— 默认分隔符改成"换行 / Tab"(避免空格分词问题)
合起来叫"unofficial strict mode"——能挡掉 80% 常见 bug。
调试技巧
1. set -x:每行执行前打印
#!/bin/bash
set -x # 开
echo "hello"
ls /tmp
set +x # 关
输出:
+ echo hello
hello
+ ls /tmp
...
也可以临时跑:
bash -x script.sh
2. set -v:打印每行(不展开变量)
跟 -x 类似但更原始。
3. trap:错误时的清理
#!/bin/bash
set -euo pipefail
tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"; echo "已清理 $tmpdir"' EXIT
# 后续无论怎么退出,trap 都会跑
cp big-files "$tmpdir/"
process "$tmpdir"
可捕获的信号:
| 信号 | 时机 |
|---|---|
EXIT |
任何退出 |
ERR |
出错(配合 set -e) |
INT |
Ctrl+C |
TERM |
kill 默认信号 |
4. 调试错误时回溯
trap 'echo "Error in line $LINENO: $BASH_COMMAND"' ERR
出错时打印行号和具体命令。
shellcheck:静态分析(必装)
sudo apt install shellcheck
shellcheck script.sh
shellcheck 检查脚本里所有潜在 bug 和不良习惯——quotes 没加、变量名拼错、用了已废弃的语法等。写脚本前必跑。
VS Code 装 shellcheck 插件,边写边提示。
错误处理模板
#!/bin/bash
set -euo pipefail
# 日志
log() { printf "[%s] %s\n" "$(date +%T)" "$*"; }
err() { printf "[%s] ERROR: %s\n" "$(date +%T)" "$*" >&2; }
die() { err "$@"; exit 1; }
# 清理
tmpdir=$(mktemp -d)
cleanup() {
log "清理 $tmpdir"
rm -rf "$tmpdir"
}
trap cleanup EXIT
# 出错时附加信息
trap 'err "脚本在 line $LINENO 失败: $BASH_COMMAND"' ERR
# 主流程
main() {
log "开始"
# 前置检查
[[ "$EUID" -eq 0 ]] || die "需要 root 权限"
command -v jq >/dev/null || die "缺少 jq"
# 业务
do_work
log "完成"
}
do_work() {
# ...
:
}
main "$@"
常见性能优化
1. 避免循环里调外部命令
# ❌ 慢:每次循环 fork 一个 awk
for i in {1..1000}; do
echo "$i" | awk '{print $1*2}'
done
# ✓ 一次性
seq 1 1000 | awk '{print $1*2}'
2. 用内置而不是外部命令
# ❌ 慢:fork basename
basename "$file"
# ✓ 快:参数展开
"${file##*/}"
3. 大文件用 awk / sed 一次过
# ❌ 逐行处理 100 万行
while read line; do
[[ "$line" == *ERROR* ]] && ((count++))
done < big.log
# ✓ awk 一次扫
count=$(awk '/ERROR/{c++} END{print c}' big.log)
命名与风格规范
# 变量:小写 + 下划线
backup_dir="/backups"
# 常量 / 环境:大写
readonly MAX_RETRIES=3
export DEBUG=1
# 函数:小写 + 下划线
do_backup() { ... }
# 缩进:4 空格(或 2 空格,统一就好)
# 长选项更可读
rsync --archive --verbose --delete # 比 -avz --delete 易读
一个真实的最小生产脚本骨架
#!/bin/bash
#
# backup.sh - 每日数据库 + 文件备份
#
# 用法:backup.sh [--dry-run]
#
set -euo pipefail
IFS=$'\n\t'
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly LOG_FILE="/var/log/backup.log"
readonly BACKUP_DIR="/backups/$(date +%F)"
# 日志(同时屏幕和文件)
exec > >(tee -a "$LOG_FILE") 2>&1
log() { printf "[%s] %s\n" "$(date +'%F %T')" "$*"; }
die() { log "ERROR: $*"; exit 1; }
dry_run=false
[[ "${1:-}" == "--dry-run" ]] && dry_run=true
main() {
log "开始备份(dry-run=$dry_run)"
mkdir -p "$BACKUP_DIR"
backup_db
backup_files
upload_to_cloud
cleanup_old
log "完成"
}
backup_db() {
log "备份数据库..."
$dry_run && return
mysqldump -uroot mydb | gzip > "$BACKUP_DIR/db.sql.gz"
}
backup_files() {
log "备份文件..."
$dry_run && return
tar -czf "$BACKUP_DIR/data.tar.gz" /srv/data
}
upload_to_cloud() {
log "上传到对象存储..."
$dry_run && return
rclone copy "$BACKUP_DIR" "remote:backups/$(basename "$BACKUP_DIR")"
}
cleanup_old() {
log "清理 30 天前..."
$dry_run && return
find /backups -mindepth 1 -maxdepth 1 -type d -mtime +30 -exec rm -rf {} \;
}
main "$@"
下一篇起进入模块七:磁盘与存储管理。