🏠 首页 攻略 Shell脚本自动化:10个实战技巧

Shell脚本自动化:10个实战技巧

掌握这10个Shell脚本技巧,告别重复劳动。从错误处理到日志记录,从参数解析到定时任务,每个都是生产环境验证过的实用方案。

Shell脚本自动化:10个实战技巧

你有多少次在终端里重复执行同样的命令?

备份数据库、清理日志、部署代码、监控磁盘……这些活儿干多了,人就会累,而且容易出错。

写个脚本吧。但很多人写的脚本只能跑一次就废了——没有错误处理、没有日志、参数写死、换个环境就报错。

下面这10个技巧,是我这些年踩坑总结出来的。每个都来自真实生产场景,不是教科书废话。

技巧1:开头加上这些"保命符"

#!/bin/bash
set -euo pipefail
IFS=$'\n\t'

这三行是脚本的保险带。

set -e:任何命令报错,脚本立刻停止。别等它把错误链条拉得越来越长。

set -u:未定义的变量直接报错。很多脚本的诡异bug就是这么来的。

set -o pipefail:管道里只要有一个命令失败,整个管道就算失败。不然你以为 false | true 会返回成功,其实真的会。

IFS=$'\n\t':限制分隔符为换行和Tab,避免文件名里的空格搞事情。

有人觉得这些太严格了。但在生产环境里,宽松等于埋雷。

技巧2:函数化一切

别把所有逻辑堆在一个文件里。拆成函数,每个函数只做一件事。

log_info() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] INFO: $*"
}

log_error() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*" >&2
}

check_disk() {
    local usage
    usage=$(df / | tail -1 | awk '{print $5}' | tr -d '%')
    if (( usage > 90 )); then
        log_error "磁盘使用率过高:${usage}%"
        return 1
    fi
    log_info "磁盘使用率正常:${usage}%"
    return 0
}

函数的好处不只是好看。它让你能单独测试每个功能,出问题时能快速定位。

技巧3:用 getopts 解析参数

别自己手动解析 $1$2。用 getopts,标准、简洁、不容易出错。

while getopts "d:h:v" opt; do
    case $opt in
        d) directory="$OPTARG" ;;
        h) help_message; exit 0 ;;
        v) verbose=true ;;
        \?) echo "无效选项: -$OPTARG" >&2; exit 1 ;;
    esac
done

用的时候:

./backup.sh -d /data -v

清晰,可读,别人一看就知道怎么用。

技巧4:日志分级,别全往 stdout 打

LOG_LEVEL="${LOG_LEVEL:-INFO}"

log() {
    local level="$1"
    shift
    local timestamp
    timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[$timestamp] [$level] $*" >> "$LOG_FILE"
    
    if [[ "$LOG_LEVEL" == "DEBUG" ]]; then
        echo "[$timestamp] [$level] $*"
    fi
}

生产环境默认 INFO,调试时设 LOG_LEVEL=DEBUG。日志写到文件里,重要信息才输出到屏幕。

技巧5:临时文件要管理好

TMPDIR=$(mktemp -d /tmp/my_script.XXXXXX)
trap 'rm -rf "$TMPDIR"' EXIT

# 用 $TMPDIR 存临时文件
echo "临时数据" > "$TMPDIR/data.tmp"

# 脚本退出时自动清理,包括 Ctrl+C

mktemp -d 生成安全的临时目录,不会和其他脚本撞名。trap ... EXIT 保证不管正常退出还是被中断,临时文件都会被清理。

很多人嫌麻烦,直接用 /tmp/script_$$。进程挂了文件就留那儿了,几个月后 /tmp 塞满垃圾。

技巧6:用数组处理多文件

files=(
    "/data/logs/app.log"
    "/data/logs/error.log"
    "/data/logs/access.log"
)

for file in "${files[@]}"; do
    if [[ ! -f "$file" ]]; then
        log_error "文件不存在: $file"
        continue
    fi
    process_file "$file"
done

用数组比 ls | grep 安全得多。文件名有空格、有特殊字符都不会出问题。

技巧7:网络请求加超时

curl --connect-timeout 10 --max-time 30 \
     -sSf "https://api.example.com/data" \
     -o /tmp/response.json

--connect-timeout 10:10秒连不上就放弃。

--max-time 30:总共最多跑30秒。

-sSf:静默模式,出错时显示错误信息,失败时返回非零状态码。

没有超时的网络请求是最常见的脚本卡死原因。

技巧8:锁文件防并发

同一个脚本被 cron 同时触发两次,后果可能很严重。加个锁:

LOCK_FILE="/var/run/my_script.lock"

if [[ -e "$LOCK_FILE" ]]; then
    pid=$(cat "$LOCK_FILE")
    if kill -0 "$pid" 2>/dev/null; then
        log_error "脚本已在运行(PID: $pid)"
        exit 1
    fi
    log_warning "发现过期锁文件,已清理"
fi

echo $$ > "$LOCK_FILE"
trap 'rm -f "$LOCK_FILE"' EXIT

检查锁文件是否存在,如果存在看对应的进程还在不在。进程没了说明是过期锁,清理掉。

技巧9:优雅的信号处理

cleanup() {
    log_info "收到退出信号,正在清理..."
    rm -f "$TMPDIR"/*
    rm -f "$LOCK_FILE"
    exit 0
}

trap cleanup INT TERM HUP

用户按 Ctrl+C、系统发 SIGTERM、SSH断开触发 SIGHUP——这些信号都能被捕获。

在清理函数里释放资源、删除临时文件、关闭连接。这样即使脚本被强制中断,也不会留下烂摊子。

技巧10:用 cron 配合日志轮转

# crontab -e
0 2 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1

简单任务用 cron 就够了。但别忘了日志轮转,不然 /var/log/backup.log 会越来越大。

配合 logrotate

# /etc/logrotate.d/backup
/var/log/backup.log {
    daily
    rotate 7
    compress
    missingok
    notifempty
}

每天轮转,保留7天,自动压缩旧日志。

一个完整示例

把这些技巧串起来,一个生产可用的备份脚本长这样:

#!/bin/bash
set -euo pipefail
IFS=$'\n\t'

SCRIPT_NAME="backup"
LOG_FILE="/var/log/${SCRIPT_NAME}.log"
LOCK_FILE="/var/run/${SCRIPT_NAME}.lock"
TMPDIR=$(mktemp -d /tmp/${SCRIPT_NAME}.XXXXXX)

log_info() { echo "[$(date '+%F %T')] INFO: $*" | tee -a "$LOG_FILE"; }
log_error() { echo "[$(date '+%F %T')] ERROR: $*" >&2 | tee -a "$LOG_FILE"; }

cleanup() {
    rm -rf "$TMPDIR"
    rm -f "$LOCK_FILE"
}
trap cleanup EXIT INT TERM

[[ -e "$LOCK_FILE" ]] && { log_error "已有实例运行"; exit 1; }
echo $$ > "$LOCK_FILE"

BACKUP_DIR="/backup/db"
mkdir -p "$BACKUP_DIR"

mysqldump -u user -p'pass' mydb > "$TMPDIR/db.sql"
gzip "$TMPDIR/db.sql"
mv "$TMPDIR/db.sql.gz" "$BACKUP_DIR/"

find "$BACKUP_DIR" -name "*.gz" -mtime +7 -delete
log_info "备份完成"

看着不复杂,但该有的都有了:错误处理、日志、临时文件管理、锁机制、信号处理、自动清理。

最后说两句

Shell脚本不是什么高深技术,但它真的是日常效率的放大器。

写好一个脚本,省下的时间够喝好几杯咖啡。写不好一个脚本,半夜被叫醒排查问题的时候,你会想砸键盘。

从今天开始,把你最常重复的那几个操作写成脚本。用上面对的错误处理和日志,你会发现——原来脚本可以这么省心。

你的日常重复操作是什么?写个脚本试试,跑通了就来分享一下。