Linux运维常用脚本实战:从环境检查到日志清理的避坑指南

发布时间:2026/10/14 20:58:21
Linux运维常用脚本实战:从环境检查到日志清理的避坑指南
简介一份面向Linux运维人员及初学者的常用自动化脚本合集围绕服务器日常管理中常见的效率痛点给出可直接参考的Shell实现。脚本覆盖MySQL数据库备份与主从同步状态监控、批量创建用户、批量远程执行命令、网站异常检测、LNMP一键部署、Java项目自动发布、Dos攻击自动屏蔽IP、nginx日志按天切割与分析、服务器资源利用率查看等高频场景。资源包共20个文件包含19个Shell脚本与1个nginx.conf配置文件整体仅12KB轻量实用便于按需挑选或二次改造。目前已有82人学习下载。这些脚本不是零散命令的堆砌而是具有明确运维意图的实用工具集既能帮助新手理解自动化运维的思路也能让有经验的工程师直接复用提升日常巡检、备份与故障处理效率。1. Linux运维常用脚本先弄清楚这堆脚本到底在解决什么问题Linux运维常用脚本听起来像是一堆现成命令的堆叠实际上它是把运维里最频繁、最容易出错的重复动作固化成可复用、可审计、可交接的代码。比如凌晨两点磁盘告警、新机器上线要装一堆基础组件、几十台机器批量改配置这些场景靠人工敲命令不是不行但会累、会漏、会因人而异脚本就是把这些动作从「靠记忆」变成「靠代码」。这篇文章不谈花哨的框架只讲一线运维真正每天在用的那几类脚本环境判断、危险命令保护、监控告警、日志清理、批量执行以及每个脚本里最容易踩的坑。适合刚接手服务器、还没建立自己脚本库的新手也适合写了几年脚本但偶尔被 rm 和 find 坑一把的熟手。看完你能直接照着抄也能知道自己以前为什么翻车。2. 动手前的三条铁规矩环境判断、rm 后悔药、远程执行护栏写运维脚本和写业务代码最大的区别是业务代码跑挂了可以回滚运维脚本跑挂了可能直接让服务起不来。所以动手写任何功能之前先把护栏做好。我一般会先做三件事判断当前机器身份、给危险的删除命令加后悔药、给远程执行加超时和回显。2.1 环境判断脚本动任何命令之前先搞清楚「我是谁」运维脚本经常要在不同发行版、不同用户身份的机器上跑。同一个脚本root 下能删的文件普通用户下可能直接 Permission deniedCentOS 的 yum 和 Ubuntu 的 apt 命令完全不一样。所以脚本开头第一件事不是写功能而是写「我是谁、我在哪」。#!/bin/bash # env_check.sh执行正式操作前先输出环境信息 set -uo pipefail # 判断当前用户是否为 root1 表示是0 表示否 if [ $(id -u) -eq 0 ]; then IS_ROOT1 else IS_ROOT0 fi # 读取发行版信息兼容 CentOS / Ubuntu / Debian 等 if [ -f /etc/os-release ]; then . /etc/os-release OS_FAMILY$ID else OS_FAMILYunknown fi # 输出内核版本和关键架构信息 KERNEL$(uname -r) ARCH$(uname -m) echo uid$(id -u) root${IS_ROOT} 发行版${OS_FAMILY} 内核${KERNEL} 架构${ARCH} # 检查关键目录是否存在不存在则提前告警 for d in /etc /var/log /opt; do [ -d $d ] || echo 警告目录 $d 不存在 done这段脚本里有几个点需要说明。id -u拿到的用户 IDroot 固定是 0这是判断权限最可靠的方式比whoami匹配字符串更稳。/etc/os-release是现代发行版都带的标准文件source 之后直接就能拿到ID这个变量比去解析/etc/redhat-release兼容性好得多。开头我用set -uo pipefail而不是set -e因为这是检查脚本某个检查项失败不应该让整个脚本退出而是要跑完所有检查、把问题全部暴露出来。uname -r和uname -m在某些脚本里判断是否需要装特定内核模块时非常有用提前打印出来能省掉后面排查的时间。实际使用中这个脚本通常会作为其他脚本的「前置片段」被 source 进去而不是单独运行。常见做法是把它抽成一个lib_env.sh其他脚本开头写source /opt/scripts/lib_env.sh这样所有脚本对环境的判断逻辑只有一个地方维护不会出现 A 脚本认为自己是 CentOS、B 脚本认为自己是 Ubuntu 的割裂情况。2.2 给 rm 加一道后悔药回收站脚本与自动清理rm -rf是运维界最贵的命令没有之一。我见过不止一次有人把变量写错rm -rf ${DIR}/变成rm -rf /或者清理脚本里路径变量没赋值直接变成rm -rf。人肉防线不可靠得靠机制。最实用的做法是给 rm 建一个回收站删除变成移动后悔了还能捞回来。#!/bin/bash # install_trash.sh给当前用户安装回收站机制 TRASH_DIR${HOME}/.trash mkdir -p $TRASH_DIR # 向 ~/.bashrc 追加回收站函数和别名 cat ~/.bashrc EOF # 回收站函数把文件移动到 ~/.trash保留时间戳后缀 trash() { local ts ts$(date %Y%m%d_%H%M%S) for f in $; do [ -e $f ] || [ -L $f ] mv $f ${HOME}/.trash/${f##*/}_${ts} done } # 用 trash 覆盖 rm避免手滑 alias rmtrash # 给习惯了 del 的人一个入口 alias deltrash EOF echo 回收站已安装到 ${TRASH_DIR} echo 执行 source ~/.bashrc 后生效这里有个细节值得注意函数里用了[ -e $f ] || [ -L $f ]因为-e对损坏的符号链接会返回假但符号链接本身也是要删的对象加上-L判断才完整。${f##*/}是 bash 参数扩展取路径的最后一段防止/var/log/a.log被移动后名字变成/var/log/a.log_时间戳这种带斜杠的怪名字。光有回收站不够还得有自动清理策略。不然.trash目录会无限膨胀。我一般会在 crontab 里加一行# 每天凌晨 3 点清理回收站里超过 7 天的文件 0 3 * * * find ${HOME}/.trash -type f -mtime 7 -delete这套方案的成本很低但它能救命的场景非常多。注意它是用户级的root 的~/.bashrc也要装一份因为 root 的 rm 手滑代价最大。装完之后建议先source ~/.bashrc再手动rm一个测试文件验证一下别等真的删错了才发现别名没生效。2.3 远程执行护栏超时、回显、退出码三板斧运维脚本里大量动作都是通过 ssh 在远程机器上执行的。ssh 有个让人头疼的问题网络抖动或远端卡死时命令可能挂在那里几分钟不动看起来像脚本在正常执行实际上早就卡死了。所以远程执行必须带超时、带退出码检查。#!/bin/bash # run_remote.sh封装安全的远程执行函数 SSH_TIMEOUT${SSH_TIMEOUT:-15} run_remote() { local host$1 shift local cmd$* echo [$(date %H:%M:%S)] 执行: ${host} - ${cmd} # 用 timeout 包裹 ssh超时返回 124 timeout $SSH_TIMEOUT ssh -o ConnectTimeout5 \ -o StrictHostKeyCheckingaccept-new \ -o BatchModeyes \ $host $cmd local rc$? if [ $rc -eq 124 ]; then echo [${host}] 命令执行超时${SSH_TIMEOUT}s请检查远端状态 2 elif [ $rc -ne 0 ]; then echo [${host}] 命令失败退出码 ${rc} 2 fi return $rc } # 用法示例 # run_remote web-01 systemctl restart nginx # run_remote db-01 df -h /datatimeout是外部命令它发送的是 SIGTERM对 ssh 进程有效但要注意远端命令不会自动被杀掉这是它的一个局限。StrictHostKeyCheckingaccept-new是为了在首次连接新机器时不弹交互确认避免脚本卡在 yes/no 上但如果你对安全要求极高、密钥指纹有强制校验需求这个选项要改回yes。BatchModeyes的意思是禁止交互式密码输入没有配置密钥就直接失败防止脚本挂在那里等密码。这段函数的真正价值在于它强制了「每次远程执行都有回显、有超时、有退出码」。脚本里如果有一百台机器要逐个执行某台失败时你能立刻知道是哪台、为什么而不是整批跑完才发现少了一台。我自己的脚本库里所有远程操作都会走到这层封装上包括后续的监控检查和批量分发。3. 监控告警脚本从采集到群通知的最小闭环很多团队一开始没有正式监控系统或者监控系统覆盖不全临时盯服务器只能靠手动 ssh 上去看。这时候一组好用的巡检脚本比什么都强。我常用的做法是把检查项、采集逻辑、通知动作拆开各管一段最后用 crontab 串起来。3.1 健康检查脚本进程、端口、负载、磁盘一次性查完巡检脚本的核心不是把指标打印得好看而是用退出码和输出约定好「到底有没有问题」。这样后续不管是接入告警群还是写进自动化平台都能直接消费结果。我建议把检查结果分两种有问题的信息打到 stdout 或 stderr退出码非 0全部正常则只退出 0不输出任何噪音。#!/bin/bash # health_check.sh最小健康检查异常时输出信息并返回非 0 set -uo pipefail HOST$(hostname) DATE$(date %Y-%m-%d %H:%M:%S) PROBLEM0 check_port() { local host$1 local port$2 # 用 /dev/tcp 做 TCP 连接探测3 秒超时 if ! timeout 3 bash -c echo /dev/tcp/${host}/${port} 2/dev/null; then echo ${DATE} ${HOST} 端口 ${host}:${port} 连接失败 PROBLEM1 fi } check_proc() { local name$1 # 精确匹配进程名 if ! pgrep -x $name /dev/null; then echo ${DATE} ${HOST} 进程 ${name} 未在运行 PROBLEM1 fi } check_load() { local warn$1 local load load$(awk {print $1} /proc/loadavg) # 用 bc 做浮点比较 if [ $(echo ${load} ${warn} | bc) 1 ]; then echo ${DATE} ${HOST} 负载过高 当前${load} 阈值${warn} PROBLEM1 fi } check_port 127.0.0.1 80 check_port 127.0.0.1 3306 check_proc nginx check_load 4.0 exit $PROBLEM/dev/tcp是 bash 内置的 TCP 能力不需要装 nc 或 telnet适合最小环境。注意它只能在 bash 里用脚本声明必须是#!/bin/bash用#!/bin/sh会直接报错。pgrep -x nginx的-x是精确匹配防止nginx匹配到nginx-worker之类带前缀的进程但要注意 Linux 进程名长度上限是 15 个字符如果某个进程名太长被内核截断-x就永远匹配不上这时候反而要改用pgrep -f做全命令行匹配。bc做浮点比较在大多数机器上都有但如果你的最小化系统没装脚本会直接挂。替代方案是用awk直接比输出 1 或 0后面我会在清理脚本里用到类似写法。这套检查脚本的产出不是给人看的是给调度用的crontab 里跑它失败了才走通知逻辑。3.2 通知脚本把告警消息推到群机器人 Webhook有了检查结果下一步是让负责人知道。短信网关和邮件在大多数中小团队里申请周期长最实用的通道是群机器人 Webhook只要一个 URLcurl 就能发手机端能实时收到。#!/bin/bash # notify.sh通用 Webhook 通知脚本发送文本消息 send_notify() { local webhook$1 local content$2 # 构造通用的 JSON 消息体适配常见群机器人协议 local body body$(printf {msgtype:text,text:{content:%s}} $content) curl -s -m 10 \ -H Content-Type: application/json \ -d $body \ $webhook echo [通知已发送] } # 用法示例 # send_notify https://hook.example.com/xxxx web-01 端口 80 连接失败请检查这段脚本刻意把 URL 和内容都做成参数不写死。实际项目里最常见的错误是通知脚本里写死某个测试 URL后面接生产环境时忘了换导致所有告警都发到测试群里没人看。我的习惯是通知 URL 通过环境变量注入。curl -m 10是给通知请求自身加超时告警系统不能在发送通知这一步把自己也卡死。消息内容的格式可以根据你用的群机器人协议调整但消息体必须是 JSON 这一点是通用的。在检查脚本和通知脚本的配合上我一般会把健康检查的输出直接透传给通知脚本# 告警合并发送只有检查失败才发通知 OUTPUT$(bash health_check.sh 21) if [ $? -ne 0 ]; then send_notify ${WEBHOOK_URL} ${OUTPUT} fi这里有个容易踩的坑OUTPUT$(...)会吞掉所有输出如果检查脚本本身没输出、只有退出码异常你收到的消息就是空的。所以健康检查脚本里每一处异常都必须显式输出内容这也是前面我用echo输出每条异常的根本原因。3.3 调度与告警去重crontab 写法和静默期设计巡检脚本放在 crontab 里跑频率根据业务重要性定。一分钟一次过于频繁容易把自己告警告到麻木五分钟一次是比较常见的折中。但光有 crontab 还不够必须考虑告警风暴的问题——同一台机器挂了每分钟都在推送手机一晚上响几十次最后的结果一定是被静音。我的做法是加一个简单的静默期锁#!/bin/bash # alert_once.sh同一告警源在静默期内只发一次 ALERT_LOCK/tmp/alert_${HOSTNAME}.lock SILENCE_MIN10 LOCK_TIME60 # 0 表示不存在锁允许发通知1 表示仍在静默期 if [ -f $ALERT_LOCK ]; then AGE$(( $(date %s) - $(stat -c %Y $ALERT_LOCK) )) if [ $AGE -lt $((SILENCE_MIN * 60)) ]; then exit 0 fi fi # 发送通知并创建锁文件 bash notify_logic.sh touch $ALERT_LOCK脚本里的stat -c %Y取的是文件最后修改时间这里用文件存在性加时间差完成静默逻辑简单但有效。crontab 配合方式是这样# 每 5 分钟检查一次静默期 10 分钟避免告警轰炸 */5 * * * * bash /opt/scripts/health_check.sh || bash /opt/scripts/alert_once.sh注意 crontab 的行尾不要带多余空格环境变量和 PATH 是 crontab 里两个老大难问题后面避坑章节会专门展开。4. 磁盘与日志清理脚本保留期、白名单与 dry-run 设计服务器磁盘被写满是运维里最常见的故障之一日志文件又是最大的磁盘杀手。很多团队会用 logrotate 管理日志轮转但总有一些日志是程序自己写的、不走 logrotate还有一些是临时文件、备份包、旧安装包堆积。这时候需要自己写清理脚本而清理脚本的核心是宁可少删不能多删。所以 dry-run 和保留期参数设计是灵魂。4.1 先分清哪些事该交给 logrotate哪些才需要自写脚本logrotate 是系统自带的日志轮转工具按大小或时间切割日志支撑保留份数配置对绝大多数标准日志场景已经足够。它的典型配置长这样/var/log/app/*.log { daily rotate 7 compress delaycompress missingok notifempty copytruncate }这里daily是按天轮转rotate 7保留 7 份copytruncate是复制日志后清空原文件适合那些不重新打开日志文件的程序。但 logrotate 解决不了的问题也很明确程序日志名不是规范的.log结尾、日志目录下有一大堆中间件产生的临时文件、业务备份包按日期堆积。这些场景 logrotate 管不了只能写脚本。我给团队定的分工是凡是程序自己会持续写入、文件名规则固定的一律走 logrotate凡是按天生成独立文件、需要按保留天数清理的走自写脚本临时文件和大文件定位则单独用小工具处理。这样不会出现 logrotate 和脚本抢文件的情况。4.2 一个保守的清理脚本find 的 mtime、白名单与 dry-run清理脚本的第一个版本必须是只读的也就是 dry-run 模式先看它会删什么再决定要不要真删。我把这个机制直接做进脚本参数里而不是靠注释提醒。#!/bin/bash # clean_old_files.sh按保留天数清理旧文件默认 dry-run set -uo pipefail # 环境变量控制DRY_RUN1 只打印不删除0 才真正执行 DRY_RUN${DRY_RUN:-1} KEEP_DAYS${KEEP_DAYS:-30} TARGET_DIR${1:-/var/log/app} # 目录不存在或为空时直接退出避免误删 if [ ! -d $TARGET_DIR ]; then echo 错误目录 ${TARGET_DIR} 不存在 2 exit 1 fi # 白名单这些目录/文件即使超期也不清理 WHITELIST.git .snapshot lostfound # 找出超过保留期的普通文件非目录、非链接 FILES$(find $TARGET_DIR -type f -mtime $KEEP_DAYS) for f in $FILES; do # 跳过白名单路径 skip0 for w in $WHITELIST; do case $f in *$w*) skip1 ;; esac done [ $skip -eq 1 ] continue if [ $DRY_RUN 1 ]; then echo [dry-run] 将删除: $f else rm -f $f echo [已删除] $f fi done # 处理空目录超过保留期的空目录也一并清理 find $TARGET_DIR -type d -empty -mtime $KEEP_DAYS -delete echo 清理完成保留天数${KEEP_DAYS} dry-run${DRY_RUN}这段脚本的三个关键点都值得展开。第一个是DRY_RUN设计成环境变量意味着执行时用DRY_RUN0 bash clean_old_files.sh /var/log/app就能真正清理不需要改脚本文件减少误操作的可能。第二个是白名单机制.snapshot是快照目录、lostfound是文件系统修复目录、.git是版本库这些目录里的文件即使看起来旧也不能碰。第三个是空目录清理放在最后避免出现「文件删了目录还在后面还以为有内容」的假象。一个常见争议是find -mtime 30是不是严格等于 30 天前。-mtime 30在 find 里的语义是「文件内容最后修改时间距今超过 24*30 小时」不是自然日。如果业务方要求「保留最近 30 个自然日的日志」应该用-mtime 30配合-daystart或者直接按文件名里的日期判断。我在生产环境碰到过因为没搞清这个差别把当天凌晨的文件提前清理掉的情况这就是边界理解问题。4.3 大文件定位与交互确认清理前的最后一道确认清理脚本负责的是「批量按规则删」但运维里还有一种高频场景是「磁盘满了但不知道是谁占的」。这时候先别急着写删除脚本先找出大文件跟业务方确认再动手。我常用的定位命令组合# 查看目录下各子目录占用从大到小 du -h --max-depth1 /var/log 2/dev/null | sort -hr | head -20 # 查找指定目录下超过 500M 的文件 find /var/log -type f -size 500M -exec ls -lh {} \; 2/dev/null # 查被删除但仍占用空间的文件经典磁盘满但 du 对不上 lsof L1 2/dev/null | awk $7 0lsof L1是找「文件已被删除但进程还持有文件句柄」的场景这是磁盘明明满了、du却算不出占用的大坑。原因是 Unix 下进程打开的文件被 rm 后空间不会释放直到进程关闭文件。遇到这种情况清理脚本没用得找到对应进程重启或让它重新打开日志文件这也是copytruncate为什么存在的原因。大文件定位之后我坚持做人工确认而不是直接写进清理脚本。方法是把 find 结果输出到一个临时清单人眼扫一遍再对清单做删除。这个确认步骤看起来多此一举但它能挡住绝大多数「删错目录」的事故。脚本跑得再快方向错了就是灾难。5. 避坑与排查运维脚本最容易翻车的五个现场脚本写多了之后你会发现真正让人头疼的往往不是业务逻辑而是那些看起来细枝末节的东西——换个行符、多一个空格、少一个环境变量。这里把我踩过的、也帮别人排查过的五类高频问题列出来每条都按「现象 → 原因 → 解决」讲清楚。5.1 脚本在本地好好的传到服务器就报$\r: command not found现象脚本在 Windows 上编辑过或者从某个平台复制出来传到 Linux 执行时第一行就报错提示$\r: command not found用vi打开能看到每行结尾有个^M。原因Windows 文本文件用的是 CRLF\r\n换行Linux 只认 LF\n。\r在 bash 眼里是普通字符会被当成命令的一部分脚本自然跑不起来。常见的坑还包括#!/bin/bash\rshebang 本身都错。解决# 方法一dos2unix 转换 dos2unix script.sh # 方法二sed 直接去掉行尾的 \r sed -i s/\r$// script.sh这个问题的隐蔽之处在于部分工具会自动帮你转换部分不会。Git 的core.autocrlf设置可能让 clone 下来的文件悄悄变成 CRLF。我的处理习惯是脚本文件统一在 Linux 上创建和编辑需要从外部复制时先跑一次file script.sh看输出里有没有CRLF字样再上线。5.2set -e的陷阱grep -q匹配不到就让整个脚本退出现象脚本里写了set -e某行执行echo $x | grep -q keyword当匹配不到关键字时脚本直接退出后面的清理和收尾动作全都没执行。原因set -e的语义是「任何命令返回非 0 就退出」。「匹配不到」对grep -q来说就是返回 1恰好触发了退出。还有同理的grep -q放进if条件里没事因为 if 会把命令的返回值当作判断条件但裸写在脚本中间就会中招。解决要么去掉set -e改用显式错误处理要么在可能返回非 0 的命令后加|| true要么把它改成条件判断if echo $x | grep -q keyword; then echo 找到关键字 else echo 没找到继续执行 fi我的建议是运维脚本不要一律set -e尤其是含多条命令、每条都可能失败的长脚本set -e会让失败点变得不可预知。比较稳的是set -uo pipefail错误处理全部显式写出来每条关键命令后检查退出码这样能精确定位是哪一步挂了。5.3find -exec和xargs的差异文件名带空格直接被拆开现象清理脚本里find ... | xargs rm -f遇到文件名里带空格或中文的日志文件报No such file or directory空格前半截被当成一个文件。原因xargs默认按空白字符切分输入文件名里的空格会被当作分隔符导致一个文件名被拆成两三个。find -exec则不会拆它会完整地把每个文件名传给命令。解决使用-print0和xargs -0配对或者干脆用find -delete# 安全的配对方式null 分隔 find $DIR -type f -mtime 7 -print0 | xargs -0 rm -f # 更简单的方式find 自带删除能力 find $DIR -type f -mtime 7 -delete这里突出一个原则能用find -delete就不要走管道。凡是要把 find 结果交给别的命令处理的场景务必-print0和xargs -0一起用这个习惯能让你少踩很多文件名异常的坑。我自己写的清理脚本里上面那版用for循环处理find输出其实也有这个风险所以在白名单版本里我特意没有用管道但如果你要处理任意文件名请直接按这节的做法改。5.4 变量赋值等号两边多打空格命令直接变笑话现象脚本里写KEEP_DAYS 30执行后$KEEP_DAYS是空值清理脚本把所有新旧文件全删了或者if [ $KEEP_DAYS -gt 0 ]报参数太多。原因bash 里变量赋值KEEP_DAYS30是一个整体等号两边不能有空格。一旦写成KEEP_DAYS 30bash 会把KEEP_DAYS当命令名去执行报command not found变量根本没赋值。解决无话可说只能靠习惯。赋值语句永远紧贴等号不要为了对齐好看加空格。另外引用变量时一定要加双引号[ $KEEP_DAYS -gt 0 ]和[ $KEEP_DAYS -gt 0 ]在变量为空时完全是两个结果。这个坑死过无数人几乎每个脚本事故报告里都有它的身影。5.5 crontab 里脚本跑不起来但手动执行一切正常现象脚本手动执行没问题放进 crontab 后要么不执行要么执行后报command not found要么脚本里依赖的环境变量是空的。原因crontab 的环境和登录 shell 完全不同。PATH通常是精简过的/usr/bin:/bin很多自装软件在/usr/local/bin下直接找不到JAVA_HOME、PYTHON_PATH这类变量根本没有cron 执行时也不是登录 shell~/.bashrc里的配置不会生效。解决脚本开头显式导出环境变量命令尽量用绝对路径#!/bin/bash export PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin export LANGen_US.UTF-8 # 命令用绝对路径或确保在 PATH 内 /usr/local/bin/python3 /opt/scripts/clean_logs.py调试 crontab 问题的固定套路是重定向输出到日志文件等下一次执行后看内容。crontab 里加 /tmp/cron_debug.log 21看是没执行还是执行报错。如果日志里什么都不出现优先怀疑 crontab 服务没启动或时间格式写错如果出现command not found基本就是 PATH 问题。这套排查顺序能解决九成 crontab 相关故障。6. 上线前的验证闭环让脚本不变成新的故障源脚本写完能跑和脚本可以放心上生产是两回事。我见过太多人写完脚本直接在线上执行结果脚本本身的 bug 比它要解决的问题还大。这里分享一套我固定走的验证流程每一步都花不了多少时间但能把事故概率压到最低。第一步是语法检查。bash -n能查出纯语法错误比如 if 没写 fi、括号不匹配。更严格的是用 ShellCheck 做静态分析它能指出变量未加引号、误用eval、set -e导致的隐患等建议脚本库里的每个脚本上线前都过一遍能减少七八成低级错误。bash -n clean_old_files.sh shellcheck clean_old_files.sh第二步是 dry-run 验证。所有涉及删除、覆盖、批量修改的脚本第一次运行必须带只读参数或环境变量。就像前面清理脚本里的DRY_RUN1先让它把「将要做什么」完整打印出来人眼确认一遍再真正执行。我见过最快的翻车方式是清理脚本写得太顺手-delete一加直接跑然后发现保留期设错了。dry-run 就是上生产前的后悔药。第三步是灰度执行。先把脚本放到一台非核心机器上用真实数据跑一遍观察退出码和输出。确认没问题后再扩展到同一批次的其他机器。批量执行脚本尤其要这样做几十台机器同时跑一个不成熟的脚本出了问题就是几十倍的放大。我的习惯是先拿一台机跑日志翻一遍没异常再组织批量。最后是验证结果本身。脚本跑完不等于任务完成要确认它是否达到了预期效果以及是否产生了副作用。比如清理脚本跑完磁盘空间到底释放了没有不能看日志说「删了 100 个文件」就放心要看df -h的实际数字。我给自己定的一个规矩是脚本改动后至少连续观察三天运行日志第三天还正常才算真正稳定。我最早自己写清理脚本时直接在测试环境跑了一遍-delete当时感觉一切正常后来发现它把一个备份目录里「按时间戳命名但没有访问时间」的旧归档全部清掉了备份链断了一环。从那之后所有脚本都默认先 dry-run、再单机、再扩大范围执行这条流程执行到现在几乎没有再因为脚本自身的问题出过线上事故。这套验证闭环不一定最华丽但足够稳希望帮到你。本文还有配套的精品资源点击获取