Linux运维必备:Shell脚本自动化实战与避坑指南
简介这份资源面向Linux运维工程师与Shell初学者聚焦日常运维场景的自动化脚本实践帮助读者用脚本替代重复手工操作提升排障与巡检效率。压缩包内共1个PDF文件约100KB内容以Shell脚本代码与命令示例为主涵盖日志过滤与错误统计、服务健康检查、旧文件清理、文件与目录备份压缩、多主机循环Ping、远程文件传输、用户home目录校验、日志实时监控、批量建用户、进程检查与kill、系统初始化配置等模块并附有脚本头部注释规范与变量、循环、条件判断等写法约定。目前已有1165人学习下载适合希望积累可复用脚本片段、对照实际运维任务查漏补缺的读者参考。1. 从一份 shell 脚本清单说起运维日常到底在自动化什么凌晨两点被告警叫醒登机器一看是某个分区使用率到了 92%日志文件把磁盘吃满了。手动du一圈、删几个大文件、再写条 crontab 做轮转——这套动作做过三遍以上就该考虑把它固化成脚本了。Linux运维必备工作常用shell脚本这个标题说的正是这件事把运维日常里那些重复、易错、需要记一堆参数的操作沉淀成一批能直接跑、能进定时任务、能交接给同事的 shell 脚本。它面向的是每天要登服务器的人——初级运维、后端兼运维、桌面运维转服务器运维的同行。这份清单不是让你背命令而是给你一套可复用的骨架磁盘巡检、日志清理、进程守护、批量分发、备份校验。下面按「先立住原理、再动手复现、最后讲坑」的顺序拆开讲每个脚本都给可抄的代码和参数说明。2. 磁盘与日志类脚本从 df 告警到自动清理的完整链路磁盘告警是运维最高频的告警之一也是最适合用脚本兜住的场景。核心逻辑就三步采集使用率、判断阈值、触发动作。听起来简单但真正上线要处理的问题不少——阈值按哪个挂载点算、清理时怎么避免误删、清理完怎么通知。这一章把这条链路走通。2.1 用 df 采集使用率并做阈值判断最朴素的写法是df -h加awk取第五列但-h输出带%和人类可读单位做数值比较反而麻烦。我一般用df -PPOSIX 输出格式保证一行一个文件系统不会因为长设备名换行再取百分比列。#!/bin/bash # disk_check.sh - 磁盘使用率巡检超过阈值输出告警 THRESHOLD85 # -P 保证输出格式稳定-x 排除临时文件系统 df -P -x tmpfs -x devtmpfs | awk -v t$THRESHOLD NR1 {next} # 跳过表头 { gsub(/%/,,$5) # 去掉百分号 if ($50 t) { printf WARN: %s 使用率 %s%%, 挂载点 %s\n, $1, $5, $6 } }逻辑说明df -P输出固定六列$5是使用率、$6是挂载点。gsub去掉百分号后做数值比较$50强制转数字避免字符串比较把9判成大于85。参数上THRESHOLD建议按分区类型区分——根分区可以设 85数据盘可以设 90/boot这种小分区反而要设低一点因为它满了会直接影响内核升级。提示df统计的是文件系统层面如果某个目录被大量小文件占满df和du的结果可能对不上这是 inode 耗尽或已删除但被进程持有的文件导致的排查时用lsof L1找。2.2 日志清理脚本按时间轮转而不是按大小硬删日志清理最容易翻车的地方是「一刀切删」。直接find /var/log -name *.log -mtime 7 -delete看着爽但可能把正在被进程写入的文件删掉进程句柄还在磁盘空间不释放等于白删。正确做法是先轮转再清理或者只清理明确不再写入的历史归档。#!/bin/bash # log_clean.sh - 清理指定目录下超过 N 天的归档日志 LOG_DIR/data/logs KEEP_DAYS7 # 只匹配 .log.1 .log.2.gz 这类归档不碰正在写的 .log find $LOG_DIR -type f \( -name *.log.[0-9]* -o -name *.log.*.gz \) \ -mtime $KEEP_DAYS -print -delete /var/log/log_clean.log 21 # 清理后统计释放空间 echo $(date %F %T) cleaned, current usage: $(df -P $LOG_DIR | awk NR2{print $5}) /var/log/log_clean.log逻辑说明-name *.log.[0-9]*精确匹配轮转后的文件-print -delete先打印再删除方便审计。-mtime 7表示修改时间超过 7 天。参数上KEEP_DAYS要结合业务日志保留要求定合规场景可能要求 30 天甚至更久别拍脑袋。把输出重定向到日志文件是为了出问题时能回溯「到底删了什么」。2.3 把脚本接进 crontab 并做幂等保护脚本写完不能靠手动跑要进定时任务。但定时任务有个经典坑上一次还没跑完下一次又起来了两个进程同时删同一批文件。加个锁。#!/bin/bash # 在脚本开头加锁防止重复执行 LOCK_FILE/tmp/log_clean.lock exec 200$LOCK_FILE flock -n 200 || { echo another instance running, exit; exit 1; } # ... 主逻辑 ...逻辑说明exec 200打开文件描述符 200 指向锁文件flock -n 200尝试非阻塞加锁拿不到就退出。这样即使 crontab 间隔设得比脚本执行时间短也不会并发。crontab 写法建议30 3 * * * /opt/scripts/log_clean.sh避开业务高峰同时把标准输出和错误都重定向到日志方便排查。3. 进程守护与批量操作脚本让服务挂了能自己起来磁盘和日志是「资源维度」的自动化进程守护是「可用性维度」。很多中小规模的服务没有上完整的编排系统靠一个 shell 守护脚本兜底是常见做法。这一章讲怎么写得可靠而不是写成一个死循环里ps一下的玩具。3.1 用 pgrep 判断进程存活并拉起判断进程在不在别用ps aux | grep xxx | grep -v grep这个写法在进程名有特殊字符时容易误判。用pgrep -f匹配完整命令行更稳。#!/bin/bash # guard.sh - 进程守护挂了就拉起 APP_NAMEmyapp APP_CMD/opt/myapp/bin/myapp --config /opt/myapp/conf.yml LOG/var/log/guard.log if ! pgrep -f $APP_CMD /dev/null; then echo $(date %F %T) $APP_NAME not running, restarting $LOG nohup $APP_CMD /var/log/${APP_NAME}.out 21 sleep 2 if pgrep -f $APP_CMD /dev/null; then echo $(date %F %T) restart success, pid$(pgrep -f $APP_CMD) $LOG else echo $(date %F %T) restart FAILED $LOG fi fi逻辑说明pgrep -f匹配的是完整命令行比匹配进程名精确。拉起后用sleep 2再确认一次避免「命令发出去了但进程秒退」被误判为成功。参数上APP_CMD要和实际启动命令完全一致否则pgrep -f匹配不到。这个脚本本身放进 crontab 每分钟跑一次即可。注意守护脚本只能兜「进程不在了」这一种情况。如果进程还在但假死比如卡在某个系统调用pgrep是查不出来的那种场景要靠健康检查接口别指望守护脚本包打天下。3.2 批量分发用 for 循环 ssh 在多台机器执行同一命令运维经常要在十几台机器上做同一件事比如统一改个配置、统一重启服务。shell脚本for循环在这里是主力。核心是把机器列表读进来循环里 ssh 过去执行。#!/bin/bash # batch_exec.sh - 批量在多台机器执行命令 HOSTS_FILE/opt/scripts/hosts.txt CMD$1 if [ -z $CMD ]; then echo usage: $0 command exit 1 fi while read -r host; do [ -z $host ] continue # 跳过空行 [[ $host ~ ^# ]] continue # 跳过注释行 echo $host ssh -o ConnectTimeout5 -o StrictHostKeyCheckingno $host $CMD done $HOSTS_FILE逻辑说明while read逐行读主机列表-r防止反斜杠被转义。ConnectTimeout5避免某台机器网络不通时整个脚本卡死。StrictHostKeyCheckingno在首次连接时跳过指纹确认适合内网批量场景但生产环境更推荐提前把主机密钥分发好。参数上hosts.txt一行一个主机名或 IP支持#注释。3.3 命令执行结果收集与失败重试批量执行最怕的是「有的成功有的失败但脚本不告诉你」。要在循环里收集退出码失败的单独记录必要时重试。#!/bin/bash HOSTS_FILE/opt/scripts/hosts.txt CMD$1 FAILED/tmp/batch_failed.txt : $FAILED # 清空失败记录 while read -r host; do [ -z $host ] continue for attempt in 1 2 3; do if ssh -o ConnectTimeout5 $host $CMD; then break fi echo retry $attempt on $host 2 [ $attempt -eq 3 ] echo $host $FAILED sleep 2 done done $HOSTS_FILE [ -s $FAILED ] echo failed hosts: cat $FAILED逻辑说明内层for attempt in 1 2 3做三次重试成功就break。三次都失败才写进失败清单。[ -s $FAILED ]判断文件非空才输出。参数上重试次数和间隔按网络质量调内网 3 次足够跨机房可以放宽到 5 次、间隔 5 秒。4. 备份与校验脚本备份不做校验等于没备份备份脚本谁都会写tar打包扔到备份盘就完事。但真正出过事的人都知道备份文件损坏、备份不完整、恢复时才发现少东西这些坑比不备份还致命。这一章讲怎么让备份「可验证」。4.1 tar 打包 md5 校验的备份脚本#!/bin/bash # backup.sh - 打包并生成校验值 SRC_DIR/data/app BACKUP_DIR/backup DATE$(date %F) ARCHIVE${BACKUP_DIR}/app_${DATE}.tar.gz tar -czf $ARCHIVE -C $(dirname $SRC_DIR) $(basename $SRC_DIR) if [ $? -ne 0 ]; then echo backup failed 2 exit 1 fi md5sum $ARCHIVE ${ARCHIVE}.md5 echo $(date %F %T) backup done: $ARCHIVE size$(du -h $ARCHIVE | cut -f1)逻辑说明-C切到源目录的父目录再打包这样归档里是相对路径恢复时不会把绝对路径铺得到处都是。md5sum生成校验文件恢复前先md5sum -c验证。参数上DATE精确到天如果一天备份多次要加时分秒否则会覆盖。4.2 备份完整性校验恢复演练脚本备份文件生成了不代表能用。我一般会写一个校验脚本定期从备份里抽一个文件解出来比对。#!/bin/bash # verify_backup.sh - 校验最新备份能否正常解压 LATEST$(ls -t /backup/app_*.tar.gz | head -1) [ -z $LATEST ] { echo no backup found; exit 1; } # 先校验 md5 md5sum -c ${LATEST}.md5 || { echo md5 mismatch; exit 1; } # 测试解压到临时目录只解不落地 if tar -tzf $LATEST /dev/null 21; then echo archive OK: $LATEST else echo archive CORRUPT: $LATEST 2 exit 1 fi逻辑说明tar -tzf只列出内容不解压能快速验证归档结构是否完整。md5sum -c验证文件本身没被篡改或截断。参数上ls -t取最新备份如果备份目录文件很多建议改用find -mtime -1限定范围避免ls参数过长。4.3 备份保留策略按代保留而不是无限堆积备份不能只增不减否则备份盘迟早也满。常见做法是保留最近 N 天 每月一份。#!/bin/bash BACKUP_DIR/backup # 删除 30 天前的日备份 find $BACKUP_DIR -name app_*.tar.gz -mtime 30 -delete find $BACKUP_DIR -name app_*.tar.gz.md5 -mtime 30 -delete # 每月 1 号的备份单独归档保留 if [ $(date %d) 01 ]; then cp $BACKUP_DIR/app_$(date %F).tar.gz $BACKUP_DIR/monthly/ 2/dev/null fi逻辑说明日备份保留 30 天月备份单独目录长期保留。-mtime 30按修改时间算注意备份文件的 mtime 是生成时间符合预期。参数上保留天数要结合备份盘容量和恢复需求定别照抄。5. 避坑与排查shell 脚本上线前必须过的几道坎脚本在测试机跑得好好的上生产就出问题这类血泪经验几乎每个运维都攒了一堆。这一章挑几个最高频的坑按「现象 → 原因 → 解决」写清楚。5.1 现象脚本手动跑正常crontab 里跑就报 command not found原因crontab 执行时的环境变量和登录 shell 不一样PATH通常只有/usr/bin:/bin你自己装的工具比如/usr/local/bin下的找不到。解决脚本里用绝对路径调用命令或者在脚本开头显式设置PATH。我一般两样都做#!/bin/bash export PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin另外 crontab 里建议用bash /path/to/script.sh而不是直接./script.sh避免执行权限或 shebang 问题。5.2 现象脚本里cd到某个目录失败但后续命令还是执行了删错了地方原因没加set -ecd失败后脚本继续往下跑后面的rm就在当前目录执行了。解决脚本开头加严格模式。set -euo pipefail-e命令失败即退出-u使用未定义变量报错-o pipefail管道中任一环节失败整体失败。加了之后cd /some/dir || exit 1这种写法可以省但要注意有些命令本来就允许失败比如grep没匹配到返回 1那种要显式|| true。5.3 现象变量里带空格或特殊字符脚本行为诡异原因变量没加引号shell 做了词分割和通配符展开。解决所有变量引用都加双引号$VAR。尤其是rm -rf $DIR/*这种DIR为空时如果不加引号会变成rm -rf /*这是经典翻车现场。加引号后空变量会变成rm -rf /*至少不会误删根目录。更稳妥的是先判断[ -n $DIR ]。5.4 现象for循环读文件时文件里的空格被当成分隔符原因for i in $(cat file)这种写法会按空白字符分割一行里的多个词被拆成多个元素。解决用while read逐行读配合IFS保留行首行尾空格。while IFS read -r line; do echo line: $line done file.txtIFS清空内部字段分隔符-r禁止反斜杠转义。这是处理配置文件、主机列表的标准写法。5.5 现象脚本执行到一半被中断留下锁文件或临时文件下次跑不了原因没做清理或者trap没设。解决用trap在退出时清理。TMP_FILE$(mktemp) trap rm -f $TMP_FILE EXITtrap ... EXIT保证无论脚本正常结束还是被中断清理都会执行。锁文件同理flock在进程退出时会自动释放但如果用的是自己创建的 pid 文件就要在trap里删。6. 进阶技巧把散装脚本收成一个可维护的工具集单个脚本写多了会面临「脚本散落各处、参数不统一、没人知道哪个是最新版」的问题。我踩过的坑是同一个功能写了三个版本分别放在/root、/opt、/home下出故障时自己都记不清该跑哪个。后来统一收成一个目录结构加一个入口脚本。6.1 目录结构与统一入口/opt/ops-scripts/ ├── bin/ │ └── ops # 统一入口 ├── lib/ │ ├── disk_check.sh │ ├── log_clean.sh │ ├── guard.sh │ └── backup.sh ├── conf/ │ └── ops.conf # 阈值、路径等配置 └── logs/入口脚本用case分发子命令#!/bin/bash # /opt/ops-scripts/bin/ops BASE_DIR/opt/ops-scripts source ${BASE_DIR}/conf/ops.conf case $1 in disk) bash ${BASE_DIR}/lib/disk_check.sh ;; log) bash ${BASE_DIR}/lib/log_clean.sh ;; guard) bash ${BASE_DIR}/lib/guard.sh ;; backup) bash ${BASE_DIR}/lib/backup.sh ;; *) echo usage: ops {disk|log|guard|backup} exit 1 ;; esac逻辑说明所有可调参数集中到ops.conf脚本里source进来。这样改阈值不用动脚本本身也方便不同环境用不同配置。case分发让调用方式统一成ops disk、ops backup比记一长串路径友好。6.2 用 shellcheck 做静态检查脚本写多了靠人眼 review 容易漏。shellcheck是 shell 脚本的静态检查工具能揪出引号缺失、变量未定义、无用命令等一堆问题。装好后直接shellcheck script.sh按提示改。我一般把它加进提交前的检查流程或者写个脚本批量扫。#!/bin/bash # check_all.sh - 批量检查脚本 for f in /opt/ops-scripts/lib/*.sh; do echo $f shellcheck $f || true done逻辑说明|| true保证某个脚本有问题时不中断整体检查。参数上shellcheck可以用-e排除特定规则比如某些场景确实需要-e之外的宽松处理但排除前先想清楚是不是真有必要。6.3 版本管理与变更记录脚本也是代码要进版本管理。我习惯在脚本头部写变更记录格式简单# CHANGELOG: # 2024-01-15 v1.2 增加 md5 校验 # 2024-01-10 v1.1 修复空变量误删问题 # 2024-01-05 v1.0 初始版本配合 git 管理整个/opt/ops-scripts目录每次改动提交一次。这样出问题能快速回滚也能看到谁在什么时候改了什么。别小看这一步我见过太多「脚本被同事改了一行导致备份失效三个月后才发现」的事故。6.4 一个具体技巧用set -x做局部调试脚本出问题时全量set -x输出太吵。可以在关键段落前后临时打开set -x # 这段逻辑有问题重点看 rm -f $TARGET_DIR/* set xset -x会把每条执行的命令和展开后的参数打印到 stderr能清楚看到变量到底展开成了什么。调试完记得关掉别留在生产脚本里。最后说个我自己的习惯任何脚本上线前先在测试机用bash -n script.sh做语法检查再手动跑一遍最后才进 crontab。进 crontab 后头几天盯着日志看确认行为符合预期再放手。脚本这东西写的时候多花十分钟加校验和日志出故障时就少熬一个通宵。希望帮到你。本文还有配套的精品资源点击获取