生产级Shell脚本实践:日志轮转、磁盘预警与服务自愈
简介本资源是一份面向Linux运维工程师及Shell初学者的实用脚本速查手册聚焦日常高频运维场景的自动化提效需求。文档以PDF格式呈现共1个文件体积仅100KB轻量易携涵盖日志过滤如ERROR/FATAL统计、服务健康检查多IP批量ping、旧文件清理mtime90天自动删除、目录备份压缩、远程文件传输scp、用户home目录校验、实时日志监控tailegrep告警等11类典型任务每段脚本均附带注释、执行逻辑与实操示例。内容源自一线运维实践代码规范、变量清晰、错误处理完整兼顾可读性与可复用性特别适合快速查阅、即改即用。目前已有1165人学习下载是提升Shell工程化能力、构建个人运维工具箱的高性价比入门参考。1. 这不是“脚本合集”而是运维人每天睁眼就要跑的「生存清单」从日志轮转、磁盘预警到服务自愈一份真正能塞进 crontab 并扛住生产压测的 Shell 脚本实践手册你手头那份《Linux运维必备工作常用shell脚本.pdf》——别急着打印装订。它大概率是网上流传的“万能模板包”backup.sh里写死/home/backup路径却没判断挂载点是否可用check_disk.sh用df -h解析百分比结果遇到 LVM thin pool 或 overlay2 容器层直接误报 98%更别说restart_service.sh里systemctl restart nginx后不检查systemctl is-active --quiet nginx服务启动失败却静默退出……这些不是小疏漏是凌晨三点告警电话响起时你翻 PDF 找不到对应逻辑的根源。本文不讲 for 循环语法或$?含义那是 shell 脚本入门该干的事只聚焦一线运维真实高频场景如何写出能放进/usr/local/bin/、被crontab -e每5分钟调用、在 CentOS 7/8、Ubuntu 20.04/22.04、Rocky Linux 9 上稳定运行超6个月、且故障时能精准甩出错误上下文而非command not found的生产级脚本。适合刚脱离“查命令手册阶段”的中级运维、正被监控告警淹没的值班工程师以及想把重复操作固化为自动化能力的 DevOps 实践者。我们不造轮子只打磨刀刃。2. 从“能跑”到“敢放生产”五个核心脚本的选型逻辑与最小可行实现运维脚本不是功能越多越好而是每个字节都要回答“它在什么条件下会失效”。我筛掉所有依赖 Python/Perl 解释器、需要手动安装 jq/yq、或硬编码绝对路径的“伪生产脚本”只保留纯 Bash 系统自带工具coreutils、procps-ng、util-linux、systemd就能闭环的方案。以下五个脚本覆盖 83% 的日常工单类型基于近 2 年某金融云平台工单统计每个都附可直抄的最小实现、关键参数说明及设计意图。2.1 日志轮转不用 logrotate用纯 Bash 实现按大小时间双维度切割safe_log_rotate.shlogrotate 配置复杂、调试困难而很多轻量服务如自研中间件、Python Flask API日志直接输出到文件需脚本接管。核心诉求不丢日志、不阻塞主进程、切割后自动压缩、旧日志按天清理。#!/bin/bash # safe_log_rotate.sh - 专治 nohup.out / app.log 类无结构日志 LOG_FILE/var/log/myapp/app.log MAX_SIZE100M # 触发切割的阈值支持 K/M/G KEEP_DAYS7 # 保留多少天的 .gz 归档 ARCHIVE_DIR/var/log/myapp/archive # 1. 创建归档目录-p 避免 mkdir 报错 mkdir -p $ARCHIVE_DIR # 2. 获取当前日志大小字节避免 df -h 解析误差 CURRENT_SIZE$(stat -c %s $LOG_FILE 2/dev/null || echo 0) # 3. 转换 MAX_SIZE 为字节Bash 数学运算不依赖 bc case $MAX_SIZE in *K) SIZE_BYTES$(( ${MAX_SIZE%K} * 1024 )) ;; *M) SIZE_BYTES$(( ${MAX_SIZE%M} * 1024 * 1024 )) ;; *G) SIZE_BYTES$(( ${MAX_SIZE%G} * 1024 * 1024 * 1024 )) ;; *) SIZE_BYTES$MAX_SIZE ;; esac # 4. 切割条件文件存在且大于阈值 if [[ -f $LOG_FILE ]] [[ $CURRENT_SIZE -gt $SIZE_BYTES ]]; then # 生成带毫秒的时间戳避免并发切割重名 TIMESTAMP$(date %Y%m%d_%H%M%S_$(date %N | cut -c1-3)) ARCHIVE_NAME${ARCHIVE_DIR}/$(basename $LOG_FILE).${TIMESTAMP}.gz # 关键先移动再清空确保应用写入不中断 # mv 原子性保证即使应用正在写新文件句柄仍有效 mv $LOG_FILE $ARCHIVE_NAME touch $LOG_FILE # 创建新空文件应用继续追加 # 压缩归档gzip -f 强制覆盖-q 静默 gzip -fq $ARCHIVE_NAME # 清理过期归档find mtime-mtime 7 表示 7 天前 find $ARCHIVE_DIR -name $(basename $LOG_FILE).*.gz -mtime $KEEP_DAYS -delete 2/dev/null fi逻辑说明stat -c %s直接读取 inode 大小比du -b更准且不触发磁盘 I/O 统计开销mvtouch组合是 Unix 下日志切割的黄金法则——应用持有文件描述符mv不影响其写入位置touch创建新 inode 让后续写入落到新文件时间戳含毫秒$(date %N | cut -c1-3)解决 cron 秒级并发问题避免app.log.20240501_120000.gz被覆盖find -mtime 7比ls | head -n -7更可靠后者在文件名含空格时崩溃。2.2 磁盘空间预警绕过 df 的陷阱精准识别真实可用空间smart_disk_alert.shdf -h在 LVM thin pool、Docker overlay2、ZFS zvol 上常显示“已用 100%”但实际还能写。真凶是inode 耗尽、thin pool 元数据满、或容器层快照堆积。此脚本分层探测#!/bin/bash # smart_disk_alert.sh - 分层检测磁盘风险不止看 df % ALERT_THRESHOLD85 # 空间使用率阈值百分比 CRITICAL_THRESHOLD95 # 严重阈值 CHECK_PATHS(/ /var /home /data) # 待检路径 for path in ${CHECK_PATHS[]}; do if [[ ! -d $path ]]; then continue; fi # 1. 基础 df 检查-P POSIX 格式避免列错位 DF_OUTPUT$(df -P $path | tail -1) USE_PCT$(echo $DF_OUTPUT | awk {print $5} | sed s/%//) AVAIL_KB$(echo $DF_OUTPUT | awk {print $4}) # 2. inode 使用率关键小文件多的系统易爆 INODE_PCT$(df -iP $path | tail -1 | awk {print $5} | sed s/%//) # 3. LVM thin pool 检查若存在 THIN_POOL_FULL0 if command -v lvs /dev/null 21; then THIN_POOL$(lvs --noheadings -o lv_name,lv_attr,origin,pool_lv | \ awk -v p$path $2 ~ /^V/ $4 ! {print $1,$4} | \ grep -E $(basename $path)|root | head -1 | awk {print $2}) if [[ -n $THIN_POOL ]]; then POOL_DATA$(lvs --noheadings -o data_percent $THIN_POOL 2/dev/null | tr -d [:space:]) if [[ -n $POOL_DATA ]] (( $(echo $POOL_DATA 90 | bc -l 2/dev/null) )); then THIN_POOL_FULL1 fi fi fi # 4. Docker overlay2 检查仅当 /var/lib/docker 存在 OVERLAY_FULL0 if [[ -d /var/lib/docker ]] [[ $path /var || $path / ]]; then OVERLAY_SIZE$(du -sh /var/lib/docker/overlay2 2/dev/null | awk {print $1} | sed s/[GMKB]//) if [[ -n $OVERLAY_SIZE ]] (( OVERLAY_SIZE 50000 )); then # 50GB OVERLAY_FULL1 fi fi # 5. 综合判定并告警 if (( USE_PCT CRITICAL_THRESHOLD )) || (( INODE_PCT 95 )) || [[ $THIN_POOL_FULL 1 ]] || [[ $OVERLAY_FULL 1 ]]; then echo [$(date)] CRITICAL: $path usage $USE_PCT%, inode $INODE_PCT%, thin_pool:$THIN_POOL_FULL, overlay:$OVERLAY_FULL | logger -t disk-alert # 此处可集成企业微信/钉钉 webhook或发送邮件 elif (( USE_PCT ALERT_THRESHOLD )); then echo [$(date)] WARNING: $path usage $USE_PCT%, inode $INODE_PCT% | logger -t disk-alert fi done参数说明df -P强制 POSIX 输出格式列顺序固定Filesystem 1K-blocks Used Available Use% Mounted on避免df -h在不同 locale 下列数错乱df -iP单独查 inode小文件服务器如 Git 仓库、日志归档必须监控此项LVM thin pool 检测逻辑lvs -o lv_attr中V表示 virtualthin pool-o origin,pool_lv提取关联关系再用lvs -o data_percent查数据占用Docker overlay2 大小阈值设为 50GB 是经验值——生产环境单节点 Docker 存储超此值大概率存在未清理的 dangling image 或 stopped container。2.3 服务健康自愈不只是 systemctl restartservice_guard.shsystemctl restart xxx是最危险的“一键恢复”。它不检查依赖服务状态、不验证端口监听、不捕获启动日志。此脚本执行三步验证#!/bin/bash # service_guard.sh - 重启前验证依赖重启后验证端口与日志 SERVICE_NAMEnginx DEPENDENCY_SERVICES(redis-server postgresql) # 必须运行的依赖 CHECK_PORT80 # 主服务监听端口 LOG_CHECK_PATTERNworker process.*exited # 关键错误模式从 journalctl 提取 RESTART_TIMEOUT30 # systemctl start 超时秒数 # 1. 检查依赖服务状态 for dep in ${DEPENDENCY_SERVICES[]}; do if ! systemctl is-active --quiet $dep; then echo [$(date)] DEPENDENCY FAILED: $dep is not active | logger -t service-guard exit 1 fi done # 2. 检查主服务是否已运行且端口就绪 if systemctl is-active --quiet $SERVICE_NAME; then if ss -tln | grep -q :$CHECK_PORT ; then echo [$(date)] $SERVICE_NAME is healthy, skipping restart | logger -t service-guard exit 0 fi fi # 3. 执行重启并验证 echo [$(date)] Restarting $SERVICE_NAME... | logger -t service-guard if ! systemctl restart --no-block --timeout$RESTART_TIMEOUTs $SERVICE_NAME; then echo [$(date)] RESTART FAILED: systemctl restart $SERVICE_NAME failed | logger -t service-guard exit 1 fi # 4. 等待服务就绪最多 10 秒 for i in $(seq 1 10); do if systemctl is-active --quiet $SERVICE_NAME ss -tln | grep -q :$CHECK_PORT ; then echo [$(date)] $SERVICE_NAME restarted successfully | logger -t service-guard exit 0 fi sleep 1 done # 5. 最终检查扫描最近 10 行 journal 日志找致命错误 LATEST_LOG$(journalctl -u $SERVICE_NAME -n 10 --no-pager 2/dev/null | grep -i $LOG_CHECK_PATTERN | head -1) if [[ -n $LATEST_LOG ]]; then echo [$(date)] LOG ERROR DETECTED: $LATEST_LOG | logger -t service-guard exit 1 fi echo [$(date)] $SERVICE_NAME restart timeout or unknown error | logger -t service-guard exit 1设计意图--no-block避免 systemctl 阻塞脚本--timeout防止服务卡死拖垮整个巡检ss -tln比netstat -tln更快、更少依赖grep :80 确保精确匹配端口非8080journalctl -n 10限制日志扫描范围避免journalctl -u nginx全量读取导致 I/O 峰值错误模式worker process.*exited是 Nginx worker 异常退出的典型标志可按服务定制如 Tomcat 用OutOfMemoryError。2.4 用户会话强制清理精准 kill 掉闲置 SSH 会话idle_session_killer.shpkill -u username会误杀用户所有进程包括后台任务。此脚本只 kill 超过阈值的sshd进程并保留screen/tmux会话#!/bin/bash # idle_session_killer.sh - 杀掉空闲超 30 分钟的 ssh 会话放过 screen/tmux IDLE_THRESHOLD_MINUTES30 EXEMPT_PROCESSES(screen tmux vim nano less) # 白名单进程 # 获取所有 sshd 连接及其启动时间 while IFS read -r line; do PID$(echo $line | awk {print $2}) USER$(echo $line | awk {print $1}) START_TIME$(echo $line | awk {print $3}) # 计算空闲时间秒 IDLE_SECONDS$(($(date %s) - START_TIME)) if (( IDLE_SECONDS IDLE_THRESHOLD_MINUTES * 60 )); then # 检查该 PID 下是否有白名单进程 HAS_EXEMPT$(ps -o comm -g $PID 2/dev/null | grep -E ^($(IFS|; echo ${EXEMPT_PROCESSES[*]}))$ | head -1) if [[ -z $HAS_EXEMPT ]]; then echo [$(date)] KILLING idle ssh session: PID $PID, USER $USER, idle $(($IDLE_SECONDS/60)) min | logger -t idle-killer kill -9 $PID 2/dev/null fi fi done (ps -o user,pid,lstart -C sshd | tail -n 2 | \ while read u p t; do # 将 lstart (YYYY-MM-DD HH:MM:SS) 转为秒 TS$(date -d $t %s 2/dev/null || echo 0) echo $u $p $TS done | sort -k3 -n)关键点ps -o user,pid,lstart输出精确启动时间lstart比etime更可靠etime可能因进程 fork 而重置ps -o comm -g $PID获取进程组内所有命令名-g确保捕获子进程如ssh启动的vim白名单进程screen/tmux是交互式会话守护者vim/nano是编辑中状态less是查看中状态避免误杀。2.5 网络连通性深度诊断不止 ping查路由、DNS、TLS 握手net_diagnose.shping baidu.com成功 ≠ 业务可用。此脚本模拟真实请求链路#!/bin/bash # net_diagnose.sh - 五层诊断ICMP → DNS → TCP → TLS → HTTP TARGET_HOSTapi.example.com TARGET_PORT443 HTTP_PATH/health TIMEOUT5 # 1. ICMP 连通性 if ! ping -c 1 -W $TIMEOUT $TARGET_HOST /dev/null 21; then echo [$(date)] ICMP FAIL: Cannot ping $TARGET_HOST | logger -t net-diagnose exit 1 fi # 2. DNS 解析 if ! nslookup $TARGET_HOST /dev/null 21; then echo [$(date)] DNS FAIL: Cannot resolve $TARGET_HOST | logger -t net-diagnose exit 1 fi # 3. TCP 连接绕过防火墙拦截 ICMP if ! timeout $TIMEOUT bash -c echo /dev/tcp/$TARGET_HOST/$TARGET_PORT 2/dev/null; then echo [$(date)] TCP FAIL: Cannot connect to $TARGET_HOST:$TARGET_PORT | logger -t net-diagnose exit 1 fi # 4. TLS 握手验证证书链 if ! timeout $TIMEOUT openssl s_client -connect $TARGET_HOST:$TARGET_PORT -servername $TARGET_HOST /dev/null 21 | grep -q Verify return code: 0; then echo [$(date)] TLS FAIL: SSL handshake failed for $TARGET_HOST:$TARGET_PORT | logger -t net-diagnose exit 1 fi # 5. HTTP 健康检查curl -I 避免下载大响应体 if ! timeout $TIMEOUT curl -I -s -k -m $TIMEOUT https://$TARGET_HOST$HTTP_PATH | grep -q HTTP/1.1 200; then echo [$(date)] HTTP FAIL: Health check failed for https://$TARGET_HOST$HTTP_PATH | logger -t net-diagnose exit 1 fi echo [$(date)] ALL CHECKS PASSED for $TARGET_HOST | logger -t net-diagnose为什么比单纯 curl 强timeout ... bash -c echo /dev/tcp/...是 Bash 内置 TCP 检测无需安装 telnetopenssl s_client验证证书有效性避免curl -k忽略证书错误导致误判-I参数只获取 header-s静默-m设置超时防止大响应体阻塞。3. 避坑指南这五个脚本在生产环境踩过的 12 个真实坑含现象、根因与解法运维脚本最大的敌人不是语法错误而是环境假设偏差。以下是我在线上环境CentOS 7.9 / Ubuntu 22.04 / Rocky 9.2部署上述脚本时被反复打脸的 12 个坑。每一条都来自凌晨告警电话后的血泪复盘。3.1 现象safe_log_rotate.sh切割后应用日志停止写入原因应用以O_APPEND方式打开日志文件mv后新文件无O_APPEND属性应用写入时覆盖而非追加。解法mv后不touch改用cp /dev/null $LOG_FILE清空原文件保持 inode 和属性或让应用支持SIGHUP重载日志如 Nginxnginx -s reload。3.2 现象smart_disk_alert.sh在 ZFS 文件系统上df -P输出列数异常原因ZFS 的df输出包含REFER列导致awk {print $5}取到REFER值而非Use%。解法改用df -P $path | awk NR2 {for(i1;iNF;i) if($i ~ /%$/) {print $(i-1)} }动态定位%列。3.3 现象service_guard.sh重启 Nginx 时ss -tln检查失败但systemctl status显示 active原因Nginx 配置了listen 80 ssl http2但证书文件权限错误systemctl start成功进程启动但ss查不到监听SSL 初始化失败。解法在ss检查后增加nginx -t配置语法检查失败则journalctl -u nginx -n 20提取错误。3.4 现象idle_session_killer.sh杀掉了用户的screen会话原因ps -o comm -g $PID获取的是进程组 leader 的命令名即sshd而非子进程screen。解法改用pgrep -P $PID -f screen\|tmux检查子进程或lsof -p $PID -a -i | grep -q ESTABLISHED判断是否仍有活跃连接。3.5 现象net_diagnose.sh在内网环境nslookup失败但业务正常原因内网 DNS 服务器配置了 split DNSnslookup默认走/etc/resolv.conf而应用走的是systemd-resolved的 stub resolver。解法nslookup改为dig short $TARGET_HOST $(grep nameserver /etc/resolv.conf | head -1 | awk {print $2})指定 DNS 服务器。3.6 现象safe_log_rotate.sh在 NFS 挂载点上mv失败报Invalid cross-device link原因NFS 服务器不支持跨文件系统rename()系统调用。解法检测stat -c %d $LOG_FILE设备号若与stat -c %d $ARCHIVE_DIR不同则用cp $LOG_FILE $ARCHIVE_NAME $LOG_FILE替代mv。3.7 现象smart_disk_alert.sh在 LVM thin pool 上lvs --noheadings输出为空原因lvs命令需 root 权限而脚本由普通用户 cron 执行。解法在/etc/sudoers添加Cmnd_Alias DISK_CMD /sbin/lvs, /sbin/pvs脚本中调用sudo lvs ...并确保Defaults env_keep PATH。3.8 现象service_guard.sh对 Java 应用如 Tomcatss -tln检查总失败原因Tomcat 默认绑定localhost:8080ss -tln只显示127.0.0.1:8080而脚本grep :8080 匹配失败多了127.0.0.1:。解法ss -tln | grep -E :(8080|80) | grep -v 127.0.0.1或改用ss -tln | awk $4 ~ /:[0-9]$/ $4 !~ /127\.0\.0\.1/。3.9 现象idle_session_killer.sh在 Ubuntu 22.04 上ps -o lstart输出格式为YYYY-MM-DD HH:MM:SS但date -d解析失败原因date -d在某些 locale 下不识别HH:MM:SS格式。解法统一设置LC_ALLC date -d $t %s或用awk解析echo $t | awk {gsub(/[-:]/,,$1); print mktime($1 $2)}。3.10 现象net_diagnose.sh的openssl s_client在无网络时卡死超时失效原因openssl s_client默认无超时timeout命令无法终止其内部阻塞。解法改用timeout $TIMEOUT sh -c exec openssl s_client -connect $0:$1 -servername $0 /dev/null $TARGET_HOST $TARGET_PORT确保 exec 替换进程。3.11 现象safe_log_rotate.sh在高并发写入时mv后touch新文件应用短暂写入失败原因touch创建新文件有微小窗口应用可能在此刻尝试写入不存在的文件。解法mv后立即cp /dev/null $LOG_FILE/dev/null是原子写入且保持原文件权限cp -p会复制权限但/dev/null无权限可复制。3.12 现象所有脚本在cron中执行时logger不记录systemctl命令报Failed to get D-Bus connection原因cron 默认环境变量精简DBUS_SESSION_BUS_ADDRESS未设置且logger可能找不到 syslog socket。解法在 crontab 中添加SHELL/bin/bash和PATH/usr/local/bin:/usr/bin:/bin脚本开头加export PATH/usr/local/bin:/usr/bin:/binlogger改用logger -s -t script-name msg-s输出到 stderr由 cron 邮件捕获。4. 让脚本真正“活”在生产环境权限、调度、审计与灰度上线四步法写完脚本只是开始。一个脚本能否成为团队信任的“基础设施”取决于它如何融入现有运维体系。以下是我在三个不同规模团队5人小团队、50人中台、200人云平台沉淀出的四步落地法每一步都对应一个具体动作和检查清单。4.1 权限最小化不给 root只给“刚好够用”的能力给脚本chmod 755chown root:wheel是最大误区。生产环境应遵循Principle of Least Privilege脚本名必需权限推荐实现方式检查命令safe_log_rotate.sh读写日志目录setfacl -m u:logrotate:rwx /var/log/myappgetfacl /var/log/myapp | grep logrotatesmart_disk_alert.sh读取/proc,lvsusermod -a -G disk logalertgroups logalert | grep diskservice_guard.shsystemctl控制服务sudoers中logalert ALL(root) NOPASSWD: /bin/systemctl restart nginxsudo -l -U logalertidle_session_killer.shkill其他用户进程CAP_KILLcapabilitysudo setcap cap_killep /usr/local/bin/idle_session_killer.shnet_diagnose.shping,nslookup,openssl默认全有无需额外授权which ping nslookup openssl关键动作绝不用chmod ussetuid给脚本提权这是安全红线sudoers配置必须指定完整命令路径和参数如NOPASSWD: /bin/systemctl restart nginx禁止NOPASSWD: /bin/systemctlsetcap方式比sudoers更细粒度但需确认内核支持getcap /path/to/binary可查。4.2 调度策略cron 不是万能的要懂它的“心跳”与“盲区”*/5 * * * * /usr/local/bin/script.sh是最常见也最危险的写法。cron 的本质是离散事件触发器它不保证上一次执行结束才触发下一次也不感知系统负载。场景风险解决方案实现代码脚本执行时间 5 分钟多个实例并发磁盘写入冲突加锁机制flock -n /tmp/script.lock -c /usr/local/bin/script.sh系统重启后脚本未运行监控断档rebootsystemd timer双保险systemctl enable --now script.timer高峰期如 00:00大量脚本同时触发CPU/IO 尖峰错峰调度0,10,20,30,40,50 * * * * /usr/local/bin/script.sh脚本失败需人工介入故障扩大失败告警 自动降级if ! /usr/local/bin/script.sh; then echo FAIL | logger -t script; exit 1; fi实操建议所有生产脚本必须包裹flock锁文件路径统一为/tmp/${SCRIPT_NAME}.locksystemd timer比 cron 更可靠支持Persistenttrue补偿重启丢失的任务但需编写.timer和.service文件错峰调度不是随机而是按业务低谷如数据库备份后 10 分钟设定。4.3 审计追踪每一次执行都是可回溯的“数字指纹”没有日志的脚本等于黑匣子。审计不是为了追责而是为了快速定位“为什么这个月告警多了 3 倍”。# 在每个脚本开头添加审计头 AUDIT_ID$(date %Y%m%d_%H%M%S_$(hostname -s)_$$) echo [$(date)] START $AUDIT_ID $(basename $0) with args: $* | logger -t audit # ... 脚本主体 ... echo [$(date)] END $AUDIT_ID $(basename $0) exit_code: $? | logger -t audit审计日志规范AUDIT_ID包含日期、时间、主机名、PID确保全局唯一logger -t audit将日志打入rsyslog的auditfacility可单独配置日志轮转/etc/rsyslog.d/50-audit.confexit_code记录最终状态配合grep END.*exit_code: 0 /var/log/messages可统计成功率。4.4 灰度上线从一台机器开始用数据说话任何脚本上线前必须经过灰度验证。我的标准流程是Stage 1单机验证在一台非核心机器如 Jump Server上运行bash -x script.sh观察所有echo和logger输出确认逻辑无误Stage 2小流量加入cron但只对 1 个服务如只监控nginx不监控redis持续 24 小时Stage 3全量开启全部功能但logger级别设为info不触发告警Stage 4生产logger切换为err接入企业微信告警同时监控脚本自身 CPU/内存ps aux \| grep script.sh。灰度检查清单✅journalctl -t audit -n 100查看审计日志是否完整✅grep CRITICAL\|WARNING /var/log/messages \| tail -20确认告警内容合理✅ps aux \| grep script.sh \| wc -l确认无僵尸进程✅df -h \| grep Use%对比脚本运行前后磁盘变化应无突增。5. 进阶技巧用 Bash 写出“类库”结构让脚本可维护、可测试、可复用把脚本写成“意大利面条式”spaghetti code是运维人的通病。当backup.sh里混着 FTP 上传、MySQL dump、日志清理逻辑时修改一个功能就得通读 300本文还有配套的精品资源点击获取