简介:面向Linux运维工程师及shell初学者,这份PDF系统整理了日常高频使用的自动化脚本,覆盖日志错误过滤、服务连通性检查、旧文件清理、目录备份压缩、批量Ping多主机、SCP远程传输、用户home目录校验、日志实时监控等典型场景,并扩展了自动化建用户、进程查找与kill、时区同步等进阶命令,可直接参考或改造后用于生产环境。资源包体为1个PDF文档,约100KB,体积小巧便于离线查阅。目前已有1164人学习下载,适合希望提升脚本效率、减少重复操作的运维人员。文档以实际笔记形式呈现,通过grep -i ERROR、find -mtime +90、for循环、tail -fn0等具体示例讲解语法,每段脚本附注释和判断结果,既能帮助初学者理解shell执行逻辑,也能让有经验者快速提取可用代码段,实现日志监控、批量巡检和备份清理的自动化。
1. 为什么 Linux 运维都该有一份属于自己的 shell 脚本库
每天登录服务器查磁盘、翻日志、盯进程,是 Linux 运维最日常的工作;把这些操作固化成 shell 脚本,才是从敲命令走向自动化运维的分水岭。以 PDF 形式流传的这类运维脚本合集,整理的并不是高深框架,而是把 Linux 常用命令、判断逻辑和文本处理组合成能直接跑、能交接、能复用的脚本集。新手能照着它完成 shell 脚本入门,熟手则适合拿来做查漏补缺。
Shell 脚本的价值不在语法多花哨,而在下一次、下一个人、凌晨三点,都能用同一套逻辑把问题处理掉。下面这套写法是我日常维护脚本库时一直在用的,从脚本骨架讲起,逐步落到巡检、日志、进程守护和高并发的批量任务,最后给出上线前的自检手段。
2. 给 shell 脚本打好地基:执行环境、退出码与参数校验
2.1 用 set -euo pipefail 让脚本在出错时立刻停下
见过太多"跑完了但每一步都在报错"的脚本,根源都是没设置执行环境。脚本头部建议固定这样写:
#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' readonly SCRIPT_NAME="$(basename "$0")" readonly RUN_TIME="$(date '+%Y-%m-%d %H:%M:%S')"set -e:任意一条命令返回非零退出码就立即退出,避免错误被后续命令掩盖。set -u:引用未定义变量直接报错,能提前暴露拼错的变量名。set -o pipefail:管道中任何一段失败,整条管道都算失败,配合set -e才能拦住cmd1 | grep x里 grep 无匹配的情况。IFS=$'\n\t':把 for 循环的默认分隔符从空格改成换行和 Tab,处理带空格的文件名时不会莫名其妙裂成多段。
set -e有两个例外要注意:放在if条件、while条件或||右侧的命令,失败不会触发退出,这正好是判断类命令需要的语义。真正的坑是grep -q无匹配返回 1,如果单独成行会被-e直接干掉。老手互查脚本,第一眼基本都看头部这三行,写错了后面再花哨也没用。
2.2 统一的日志函数与输出规范
一个脚本库里的输出格式如果不统一,排障时一半时间在猜这条日志是哪个脚本打的。我一般在每个脚本里放一个 log 函数:
log() { local level="$1"; shift local ts ts="$(date '+%Y-%m-%d %H:%M:%S')" echo "[$ts] [$level] $*" >> "${LOG_FILE:-/tmp/ops.log}" }调用级别约定如下表:
| 级别 | 使用场景 | 示例 |
|---|---|---|
| INFO | 正常流程节点 | log INFO "disk check started" |
| WARN | 超阈值但不阻断 | log WARN "load > 2x cores for 5min" |
| ERROR | 命令失败、任务中断 | log ERROR "log dir not exists" |
| FATAL | 参数非法、环境不满足 | log FATAL "must run as root" |
日志路径用${LOG_FILE:-/tmp/ops.log}这样的变量兜底,生产环境把LOG_FILE指到/var/log/ops/下按脚本名分文件,开发环境不动脚本就能切到/tmp。时间戳统一用%Y-%m-%d %H:%M:%S,后面做文本分析时sort、awk直接按字符串排序即可,省掉一次格式转换。
2.3 getopts 与默认参数:让脚本能被别人安全调用
运维脚本一旦要在多台机器上复跑,参数解析就不能靠$1硬编码位置。常见做法是getopts加默认值:
usage() { echo "usage: $0 -d <dir> [-t <threshold>]" >&2 exit 2 } dir="/var/log" threshold=85 while getopts "d:t:h" opt; do case "$opt" in d) dir="$OPTARG" ;; t) threshold="$OPTARG" ;; h) usage ;; *) usage ;; esac done shift $((OPTIND - 1)) [ -d "$dir" ] || usageshift $((OPTIND - 1))把选项消费掉,让$1变成剩余的位置参数。这里新手常犯一个错:选项字符串必须写成"d:t:h",冒号表示该选项需要参数,漏了冒号,-d /var/log里的路径会被当成普通参数,后续所有判断全乱。参数合法性校验放在脚本开头,宁可报错退出 2,也不要带着脏参数往下跑。
3. 服务器运维最高频的三种脚本:巡检、日志清理与进程守护
3.1 一键巡检 CPU、内存、磁盘与负载
巡检脚本是脚本库里最常被翻出来改的,因为阈值要跟着业务规模调。核心逻辑就四步:采集指标、比对阈值、输出结果、异常时记录:
#!/usr/bin/env bash set -euo pipefail threshold_disk="${1:-85}" # 磁盘使用率阈值 load_cores="$(nproc)" disk_usage() { df -P / | awk 'NR==2 {print $5}' | tr -d '%' } load_avg=$(cut -d' ' -f1 /proc/loadavg) if [ "$load_avg" -gt "$((load_cores * 2))" ]; then echo "ALERT: loadavg ${load_avg} > cores*2" fi usage=$(disk_usage) if [ "$usage" -ge "$threshold_disk" ]; then echo "ALERT: disk usage ${usage}% >= ${threshold_disk}%" fidf -P的-P是 POSIX 输出格式,避免行被折行导致awk NR==2取错行;负载直接读/proc/loadavg第一列,比uptime再切分少一次文本处理。阈值经验值可以参考下表:
| 指标 | 读取来源 | 常用告警阈值 | 备注 |
|---|---|---|---|
| CPU | top//proc/stat | 持续超过 90% | 按核数看更准 |
| 内存 | free -m | available 低于 20% | 别只看 used |
| 磁盘 | df -P | 使用率超过 85% | 根分区和业务分区分设 |
| 负载 | /proc/loadavg | 超过核数 2 倍持续 5 分钟 | 短时高负载勿误报 |
巡检结果的文案要机器可读,固定ALERT:前缀,后面接告警函数或日志采集管道时,用 grep 就能一次收敛,不用在正则上再做文章。
3.2 日志切割与过期清理:别等磁盘写满再动手
日志不清理,巡检迟早报 ERROR。线上环境一般用 logrotate 管,但脚本库里常见的场景是"目标目录不固定、策略临时调整",这种时候一个带保留天数的清理脚本更顺手:
#!/usr/bin/env bash set -euo pipefail log_dir="${1:-/var/log/app}" keep_days="${2:-14}" find "$log_dir" -type f -name "*.log" -mtime +"$keep_days" -delete find "$log_dir" -type f -name "*.log" -size +200M \ -exec gzip -9 {} \;-mtime +14表示修改时间超过 14 天,+不能丢,裸的 14 表示"正好 14 天"。执行顺序是"先删过期、再压大文件",保证磁盘紧张时不会因为先压缩而雪上加霜;真正上线前,把-delete换成-print先看一遍清单是标准的灰度手段。
提示:nginx 这类自己持有文件句柄的进程,切割或压缩后必须
kill -USR1 <pid>让它重新打开日志文件,否则磁盘空间不会真正释放。这个细节在运维面试里也常被拿出来问。
3.3 进程守护脚本与重启次数限制
tomcat、jar、node 服务在 systemd 管不到的环境(容器内、客户现场)里,需要一个"挂了就拉起,但不无限重启"的守护脚本:
#!/usr/bin/env bash set -u proc_name="app-server" max_restarts=3 restart_file="/tmp/${proc_name}.restarts" if ! pgrep -f "$proc_name" > /dev/null; then count=$(cat "$restart_file" 2>/dev/null || echo 0) if [ "$count" -ge "$max_restarts" ]; then echo "FATAL: exceed max restarts" >> /var/log/ops/watchdog.log exit 1 fi nohup /opt/app/start.sh >> /var/log/ops/app.log 2>&1 & echo $((count + 1)) > "$restart_file" else rm -f "$restart_file" # 进程活着就清零计数 fipgrep -f匹配完整命令行,它会连脚本自身的命令行一起匹配,所以脚本文件名最好和业务进程名错开。重启计数写在/tmp下的文件里,是为了脚本被 cron 重复拉起时状态仍能共享。这里用set -u而不用set -e,是因为pgrep无匹配返回 1 是正常判断路径,不能被-e掐断。nohup ... &启动后进程要确认不会收到 SIGHUP,标准做法是把调度入口放到 cron 里,让 init 进程接管子进程。
4. 让批量脚本更可靠:超时控制、并发执行与告警通知
4.1 命令超时处理:防脚本卡死在等待上
批量跑脚本最怕某台机器网络不通,一条ssh或curl卡住,后面全部排队。GNU coreutils 自带的timeout是最直接的方案:
set +e timeout 10 ssh -o ConnectTimeout=3 user@host "uptime" rc=$? set -e if [ "$rc" -eq 124 ]; then echo "host timeout" >> /tmp/ops_ssh_fail.log elif [ "$rc" -ne 0 ]; then echo "ssh failed with ${rc}" >> /tmp/ops_ssh_fail.log fitimeout的退出码 124 专门表示"命令因超时被终止",用它可以区分执行失败和卡死。为什么外层要包set +e再set -e?因为set -e存在时,timeout返回非零会直接中断脚本,rc=$?根本执行不到。ssh自带的ConnectTimeout只控制 TCP 建连,不控制连接后远端命令的执行时长,所以两层超时必须都设;curl同理,--connect-timeout管建连、--max-time管总时长,缺一不可。
4.2 多主机并发巡检:for 循环、wait 与 xargs -P 的取舍
shell 脚本的 for 循环串行跑几十台机器不可接受,常见改法是后台任务加wait:
hosts=(web-01 web-02 db-01) out_dir="/tmp/ops_check" mkdir -p "$out_dir" for host in "${hosts[@]}"; do ( ssh -o ConnectTimeout=3 "$host" \ "df -P / | awk 'NR==2 {print \$5}'" > "$out_dir/$host.disk" 2>&1 echo "$host done" ) & done waitwait是并发脚本的灵魂:不加它,脚本主体结束,后台任务被 shell 带着退出,结果文件可能还没写完。&加wait的组合等于简易线程池,但控制不了并发上限。要精确限流时换xargs -P:
| 方案 | 并发控制 | 结果收集 | 适用规模 |
|---|---|---|---|
for+&+wait | 无限制 | 按文件写 | 十几台以内 |
xargs -P 10 | 精确控制 | 按行输出 | 几十到上百台 |
parallel | 最灵活 | 持久化、重试 | 需要额外安装 |
xargs -P的标准写法是seq 1 50 | xargs -P 10 -I{} sh -c 'check_one {}',主机列表从管道喂入,天然限制并发数。注意sh -c里引号层级很容易出错,复杂逻辑建议先写成函数文件再用export -f导出。
提示:ssh 批量执行前先跑一轮
ssh -o BatchMode=yes user@host true确认免密,BatchMode 会直接拒绝密码交互,避免脚本在密码提示上集体卡死。
4.3 告警函数:把异常直接推到通知渠道
巡检发现问题只写日志不够,要能直接触达人员。不同团队的通知渠道不统一,脚本库里通用做法是封装一个send_alert:
send_alert() { local subject="$1" body="$2" local webhook="${ALERT_WEBHOOK:-}" [ -n "$webhook" ] || { echo "no webhook, skip"; return 0; } curl -fsS --connect-timeout 3 --max-time 10 \ -H 'Content-Type: application/json' \ -d "{\"msgtype\":\"text\",\"text\":{\"content\":\"${subject} ${body}\"}}" \ "$webhook" || echo "alert send failed" >> /tmp/ops_alert.log }ALERT_WEBHOOK从环境变量取而不写死在脚本里,脚本库才能跨环境复用,提交到代码仓库时也不会泄露 token。curl失败不阻断主流程,告警丢了要保证巡检本身不挂;-f让 HTTP 4xx/5xx 按失败退出,加上两个超时参数防止 curl 也卡住。批量任务里更推荐"攒批":全部机器跑完,按主机名收敛成一条消息发出,避免告警轰炸。通知渠道可以是钉钉机器人、企业微信机器人或邮件的 SMTP 封装,函数接口保持一致,换渠道只改函数体不动调用方。
5. 脚本上线前自检与成果沉淀:从可用到可交接
5.1 shellcheck、语法检查与灰度验证
脚本写完别直接上生产,先过三道检查:
bash -n check_disk.sh # 1. 只查语法,不执行 shellcheck -x check_disk.sh # 2. 静态检查,标出未引用变量、路径问题 bash -x check_disk.sh -d /tmp 2>&1 | head -50 # 3. 跟踪每行展开后的真实命令shellcheck会标出 SC2086(变量未加引号)、cd后不校验目录是否存在等典型问题,比人眼靠谱得多。灰度验证要养成一个习惯:给变更类脚本加--dry-run开关,删除、压缩、重启都只打印将要执行的命令而不真执行,灰度机跑通后再开全量。
5.2 把脚本库整理成手册:注释规范与 PDF 输出
标题里的 PDF 提醒了一件事:脚本库必须能交接。我的做法是给每个脚本头部写固定注释块,包含用途、依赖命令、参数表、退出码和最近变更,再用 awk 把所有脚本的头部注释抽出来合成手册:
for f in /opt/ops/scripts/*.sh; do echo "## $f" awk '/^#!/{next} /^#/{gsub(/^# ?/,""); print; next} {exit}' "$f" done > /tmp/ops_manual.mdawk 的规则是:跳过 shebang 行,连续读取头部#注释行并剥掉井号和前导空格,遇到第一个非注释行就退出。生成的 Markdown 用 pandoc 转 PDF 时,加--highlight-style=tango保留代码高亮、--listings控制分页,避免代码行被页边距截断。这份文档不需要详尽到讲解每个函数,能让人照着参数表独立调用、看懂退出码含义,交接就已经完成。
本文还有配套的精品资源,点击获取