Linux运维常用脚本:巡检、批量执行与故障自愈实践

发布时间:2026/10/17 17:44:42
Linux运维常用脚本:巡检、批量执行与故障自愈实践 简介面向 Linux 运维工程师和初中级运维学习者的常用脚本合集聚焦日常巡检、批量管理、网站部署、安全防护等高频场景可在真实服务器环境中直接参考改造。压缩包共 20 个文件以 19 个 shell 脚本为主附带 1 个 nginx.conf 配置示例整体 12KB轻量实用便于下载后逐行阅读与测试。包内覆盖 MySQL 备份、批量创建用户、远程批量执行命令、网站异常检测、LNMP/Java 项目发布、DoS 攻击自动屏蔽、日志切割与分析、主从同步监控、服务器资源利用率统计、目录变更同步等常见运维任务适合用来构建自己的运维工具箱。已有 82 人学习对有脚本编写基础或希望提升自动化运维效率的读者较有参考价值。1. 运维脚本解决的不是“会不会命令”是“半夜能不能睡”半夜被磁盘报警吵醒爬起来要查是哪个目录把空间写满再手动清理、重启服务——这个流程我经历过很多次。后来把高频操作焊成一批 Linux 运维常用脚本报警来了先让脚本把 CPU、内存、磁盘、进程状态一次性捞出来我只需要对着输出做判断。写脚本不是为了展示语法是把巡检、批量执行、进程守护、故障定位变成可以重复使用的东西。适合刚接手服务器、还没有监控体系的运维也适合开发兼运维的小团队。2. 巡检脚本把常用命令拼成一个 5 分钟就出体检报告的模板2.1 为什么第一个脚本选巡检Linux 运维里最重复的事就是每天或每周登录服务器跑同一串命令。今天uptime明天free -h后天df -h看起来很轻松但机器数量一多漏一台、看错一行、忘记看 inode 都是常事。与其背 linux 常用命令大全不如把常用命令组合放进脚本里输出固定格式扫一眼就能判断有没有异常。而且巡检脚本不修改系统状态出错也只是报错退出来风险最低适合刚学 shell 脚本的人拿它入门 shell 脚本。巡检脚本要解决的第一个问题不是“跑得多快”而是“看全”。磁盘满了、inode 满了、负载高但 CPU 不忙、内存被 cache 吃掉这几种情况对应的命令完全不同手动操作容易漏。脚本固定把这些指标放在一块就是给自己上保险。桌面运维和服务器运维在这一点上思路完全一致先有固定检查项再谈自动化处理。2.2 最小体检脚本CPU、内存、磁盘、inode 一次看全下面这个脚本可以整体复制到/usr/local/bin/check_host.sh它只做读取操作不修改系统适合第一次上手#!/bin/bash # 最小巡检脚本只读命令不修改系统适合第一次上手 echo 时间与主机 date %F %T echo 负载与核数 uptime echo 逻辑核数$(nproc) echo 内存free 的 available 列才是真实可用内存 free -h | awk NR2{printf total%s used%s available%s\n, $2, $3, $7} echo 磁盘排除 tmpfs 等内存盘 df -h | grep -vE ^(tmpfs|devtmpfs|overlay) | awk {print $6, $5, 总量: $2} echo inode 使用率根分区需要单独关注 df -i | grep -vE ^(tmpfs|devtmpfs|overlay) | awk {print $6, $5} echo 占用内存前 5 进程 ps aux --sort-%mem | head -n 6代码里的awk负责把命令输出整理成整洁的行free -h那行NR2表示取第二行的 Mem 数据$2/$3/$7对应 total、used、availabledf -h的$6/$5对应挂载点和使用率inode 输出结构类似。grep -vE排除 tmpfs、devtmpfs 和 overlay 这类和磁盘容量无关的内存文件系统不然 overlay 那行很容易吓人一跳。ps aux --sort-%mem | head -n 6取内存占用前 5 个进程并保留一行表头配合 free 能看到是谁把内存吃了。这个脚本跑完大概一两秒输出可以直接贴到群里给同事看。要注意free -h的 available 列比 used 列更能代表真实剩余因为 Linux 会把空闲内存拿去做 cache手动看很容易把 cache 当成“被占用”。2.3 阈值怎么设不天天“狼来了”也不当深夜传呼机光看输出还不够更顺手的是让脚本自己判断是否超过阈值。常见做法是把阈值定义到文件头部方便不同团队按业务改#!/bin/bash # check_threshold.sh在巡检脚本基础上增加分级提示 # 顶部集中定义阈值改数字不用翻下面的命令 DISK_WARN90 # 磁盘使用率超过 90% 提示 INODE_WARN80 # inode 使用率超过 80% 提示 LOAD_WARN_BASE1.0 # 负载超过核数*1.0 提示 CORE_NUM$(nproc) LOAD_NOW$(uptime | awk -Fload average: {print $2} | cut -d, -f1 | tr -d ) # 负载比较awk 支持浮点数exit !(condition) 让 if 直接吃到结果 if awk -v now$LOAD_NOW -v core$CORE_NUM -v base$LOAD_WARN_BASE \ BEGIN{exit !(now core*base)}; then echo [WARN] 负载 $LOAD_NOW 超过告警线 $CORE_NUM*$LOAD_WARN_BASE fi这个片段把阈值放脚本顶部改数不用翻命令。awk 的exit !(condition)用来做比较条件成立时exit 0if进入告警分支。写顺了之后一般会把这段和 2.2 的脚本合并再把告警送到钉钉或 webhook或者写到单独日志文件里等定时任务来扫。阈值不能拍脑袋定。磁盘 90% 是比较通用的做法但 MySQL 数据盘和日志盘建议分开看日志盘往往 80% 就该动手inode 80% 相对保守因为 inode 突发上涨经常伴随小文件风暴等 90% 再处理可能会遇到删除都要慢慢数文件的情况。负载倍数要看业务类型纯计算任务到 1.5 倍核数仍然能扛IO 密集任务 0.8 倍就可能开始抖动。我的经验是先按保守值跑两周把统计里常见数字记下来再按“平时值加 50% 冗余”来定告警线而不是照着别人的模板抄一份就完。3. 批量执行让一台机器的操作在 50 台上只跑一次3.1 手敲循环的问题漏机器、漏结果、事后无记录机器少的时候一台一台登录敲命令看不出问题三五十台就不一样了中途中控断开要重来、哪台命令执行失败没细看、等跑完自己也没法证明每台都执行到位。有人喜欢直接在命令行写 for 循环但命令行里的循环没有日志、没有超时、没有并行控制跑挂了只能靠肉眼找原因。所以我把“主机列表 ssh 日志”封装成脚本当作这一类批量操作的基础件它也算 it 运维效率工具里投入产出比最高的一种。批量脚本的适用面很广升级部署、改配置、收集某条命令输出都行。我一般把机器列表放在一个hosts.txt里格式是“主机 用户”每一台占一行。脚本只做一件事逐台连接、把命令跑完、把输出按主机名落盘。3.2 批量执行脚本主机列表、超时参数和日志落盘#!/bin/bash # batch_exec.sh批量在 hosts.txt 的机器上执行一条命令 # 用法./batch_exec.sh uptime df -h # hosts.txt 每行格式主机IP 用户名用户名为空时用当前用户 CMD$* HOSTS_FILEhosts.txt LOG_ROOT./exec_logs/$(date %Y%m%d_%H%M%S) mkdir -p $LOG_ROOT while read -r host user; do [ -z $host ] continue if [ -z $user ]; then SSH_DEST$host else SSH_DEST$user$host fi echo $host: 开始 ssh \ -o ConnectTimeout5 \ -o ServerAliveInterval10 \ -o StrictHostKeyCheckingaccept-new \ $SSH_DEST $CMD $LOG_ROOT/$host.log 21 echo $host: 退出码 $? done $HOSTS_FILE echo 执行记录在 $LOG_ROOT关键参数在 ssh 的-o里ConnectTimeout5让建立 TCP 连接最多等 5 秒避免连不上的机器把整批拖死ServerAliveInterval10每 10 秒发一个保活包长任务期间不会被网关断开StrictHostKeyCheckingaccept-new对首次连接的机器自动接受主机指纹省去手动输 yes内网巡检常见公网环境要谨慎。退出码$?必须立刻记录因为下一条命令会把旧值覆盖掉。主循环把每台机器的输出分别存成一个日志文件文件名就是主机名。这个细节很重要失败时能直接按机器名去翻日志而不是在一大坨输出里 grep。循环读 hosts.txt 用的while read -r host user支持注释行和空格分隔如果主机列表里还有端口号再多加一列在 ssh 命令里拼-p $port就行。3.3 想提速就并行给并发数做上限串行跑 50 台机器每台 2 秒也要一分多钟。想更快就把循环里的 ssh 放到后台执行但要有并发数上限#!/bin/bash # batch_exec_parallel.sh并行版本用 wait 控制收尾 # 用法./batch_exec_parallel.sh 待执行命令 10 hosts.txt CMD$1 MAX_PARALLEL${2:-10} # 并发数默认 10 HOSTS_FILE${3:-hosts.txt} LOG_ROOT./exec_logs/$(date %Y%m%d_%H%M%S) mkdir -p $LOG_ROOT pids() while read -r host user; do [ -z $host ] continue dest${user:$user}$host ssh -o ConnectTimeout5 -o ServerAliveInterval10 $dest $CMD \ $LOG_ROOT/$host.log 21 pids($!) # 记录每个后台任务的 PID if [ ${#pids[]} -ge $MAX_PARALLEL ]; then wait ${pids[]} # 攒够一批后统一等完 pids() fi done $HOSTS_FILE wait # 收尾剩余的 echo 完成日志在 $LOG_ROOT这个版本用数组保存后台 PID每攒够${#pids[]}个就wait一次。控制并发数是因为 ssh 本身开销不大但太多并发会打爆中控机器的连接数也会让目标机器的 sshd 负载异常10 到 20 是比较安全的区间。并发之后输出顺序和主机列表顺序不再一致排障时以日志文件为准不能看终端打印顺序。提示并发数超过 30 时先确认中控机的 MaxStartups 和系统连接数上限否则批量连接本身就是新的故障源。另外要留意命令里的特殊字符。CMD里有$时本地 shell 和远程 shell 会各展开一次引号层级很容易搞错带复杂引号的命令建议先本地试跑后再写成脚本文件分发别直接往命令行里塞。4. 故障自愈进程守护脚本与现场采集的命令清单4.1 进程守护脚本拉起、计数、防抖业务进程挂了谁能第一个知道监控不完善的小团队很多时候是用户先发现。进程守护脚本是性价比最高的临时方案每隔一段时间检查进程在不在不在就拉起连拉几次都起不来就告警退出。这个思路和 systemd 的 Restart 策略一样适合还没有把服务纳管到 systemd、或者进程由别人脚本启动的场景。#!/bin/bash # keepalive.sh检测 pidfile 对应的进程不存在就拉起 # 用法./keepalive.sh 启动命令 /var/run/xxx.pid /var/log/keepalive_xxx.log 21 START_CMD$1 PID_FILE$2 CHECK_INTERVAL10 MAX_RESTART3 restart_count0 check_alive() { [ -f $PID_FILE ] || return 1 pid$(cat $PID_FILE 2/dev/null) [ -n $pid ] || return 1 kill -0 $pid 2/dev/null # kill -0 不杀进程只探测是否存活 } while true; do if check_alive; then restart_count0 sleep $CHECK_INTERVAL continue fi if [ $restart_count -ge $MAX_RESTART ]; then echo $(date %F %T) 连续重启 $MAX_RESTART 次仍未成功退出请人工介入 exit 1 fi echo $(date %F %T) 进程不在尝试拉起 nohup $START_CMD /var/log/app_output.log 21 echo $! $PID_FILE restart_count$((restart_count 1)) sleep 5 done这段脚本关键在kill -0它不发送任何信号只检查 PID 对应的进程是否存在是 shell 里最稳的存活探测方式。restart_count是防抖的关键避免进程因为配置错误反复重启把系统打崩连续 3 次失败就退出等于把“人工介入”这个问题抛出来。拉起用nohup ... 让进程脱离当前终端stdout 和 stderr 都重定向到日志避免挂在 SSH 会话里。要说明边界如果启动命令是python app.py --port 8000$START_CMD被无引号展开后执行没问题如果程序路径或参数里有空格建议把整条命令写成数组或者在脚本外面做成一个start.sh再由守护脚本调用避免引号解析把自己绕晕。更正式的环境应当用 systemd 的Restarton-failure接管这条脚本更适合过渡期和临时任务。4.2 故障现场采集一条命令把现场打包带走进程挂了之后最重要的不是立刻重启而是先留下现场。很多故障重启之后就再也复现不了日志却散落在 journald、/var/log和应用目录里。很多 linux 运维故障案例复盘到最后都是“现场信息没留下”。我写过一条采集脚本把核心信息一次性落盘再打包#!/bin/bash # collect_fault.sh故障现场采集输出 /tmp/fault_时间戳.tar.gz OUT_DIR/tmp/fault_$(date %Y%m%d_%H%M%S) mkdir -p $OUT_DIR { echo 时间/负载 date %F %T uptime echo 内存 free -h echo 磁盘 df -h df -i echo CPU/内存占用前10进程 ps aux --sort-%cpu | head -n 11 ps aux --sort-%mem | head -n 11 echo 内核日志 tail dmesg | tail -n 30 echo systemd 日志 tail journalctl -n 100 --no-pager 2/dev/null || true echo 监听端口 ss -tlnp 2/dev/null } $OUT_DIR/info.txt tar czf $OUT_DIR.tar.gz -C /tmp $(basename $OUT_DIR) echo 现场包$OUT_DIR.tar.gzdmesg 里经常有数据库进程被 OOM killer 干掉、磁盘 IO 错误这类容易被日志掩盖的信息systemd journal 记录服务重启时间和最近错误ss -tlnp看端口对应关系排查“进程还在但连不上”很有用。把这些收集到一个文件里再打包比故障时现场想起一条命令敲一条可靠得多。等收集完再重启哪怕问题只能复现一次也不怕。4.3 用 cron 定时跑和手动执行的三个不同脚本写好了大多要通过 cron 定时跑。新手最常踩的三个不同点第一cron 默认 shell 是/bin/shbash 的高阶语法比如数组直接报语法错所以脚本第一行要写#!/bin/bash并给足可执行权限第二cron 的环境变量和登录 shell 不同PATH非常短/usr/local/bin下的命令经常找不到第三cron 的输出如果不重定向会通过邮件发到本地多半不会有人看。定时任务里统一写成绝对路径加日志重定向# crontab 示例每 5 分钟跑一次巡检日志只追加不覆盖 */5 * * * * /usr/local/bin/check_host.sh /var/log/host_check.log 21路径全部写绝对路径重定向必须有这是 cron 和命令行执行最大的差异。脚本里的日志路径也要设计好留够轮转空间不然巡检脚本自己会变成告警源。5. 运维脚本避坑指南5 个让我半夜爬起来改脚本的教训5.1 set -e 遇到 ssh 失败整个脚本直接退出现象批量脚本里加了一行set -e跑的时候某台机器 ssh 返回退出码 255整个脚本当场终止剩下 40 台机器一台没跑。我看日志只看到一半还以为脚本执行到一半被系统杀了。原因set -e的意思是“任何命令返回非零就退出脚本”而批量和巡检场景恰恰充满“单台失败但整体流程要继续”的情况。ssh、grep、awk 都可能因为自然原因返回非零。解决不要把set -e当成万能保险。批量循环里对可能失败的命令用if ! ssh ...; then echo 失败; fi显式捕获或者局部set e关掉再恢复。想保留“变量未定义就报错”的保护时单独用set -u就够了。5.2 cron 里找不到命令、日志乱码PATH 和 LANG 的问题现象同一个脚本手动执行一切正常放进 crontab 后第一行就报somecmd: command not found或者中文日志变成一坨看不懂的编码。原因cron 的 PATH 默认很短很多自定义安装的工具在/usr/local/bin下它根本找不到LANG 没有从登录环境继承中文输出会被搞乱。解决脚本开头固定两行export PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin export LANGC.UTF-8 export LC_ALLC.UTF-8把这个习惯带入所有要进 cron 的脚本能省掉大量“手动好、定时坏”的排查时间。这种问题最容易被当成玄学其实原因往往就是这么简单。5.3 Windows 里改过的脚本在 Linux 上提示$\r: command not found现象脚本在 Windows 的文本编辑器里改完传到 Linux 执行第一行就报$\r: command not found或者脚本执行到一半行为怪异。原因Windows 行尾是回车加换行Linux 只认换行多出来的\r被当成命令的一部分。bash 解释到行尾时看到一个不认识的字符就抛错。解决在 Linux 一侧转换一次sed -i s/\r$// /usr/local/bin/check_host.sh或者装dos2unix直接执行。预防的办法是编辑器里把行尾设为 LF文件传输时保持二进制别用自动转行的方案。5.4 批量 ssh 不加超时一台不通全体陪等现象批量重启脚本跑到第三个 IP那台机器网络不通ssh 默认会等很长时间才返回整个任务看着像卡死。原因TCP 连接对不可达 IP 的失败探测依赖系统超时可能超过两分钟批量场景一台卡住后面的全部排队。解决前面批量脚本里的-o ConnectTimeout5就是干这个的更保险的做法是整条命令也套一个 timeouttimeout 15 ssh -o ConnectTimeout5 $host $CMDtimeout 15表示 15 秒还没跑完就强制杀掉 ssh 进程并返回非零退出码。长任务场景要按业务情况调大而不是直接去掉。5.5 守护脚本没管日志磁盘被自己写满现象keepalive 脚本每 5 秒检测一次挂掉的进程每次重启都追加日志第二天再看磁盘告警了重启日志文件已经好几个 G。原因nohup $START_CMD /var/log/app_output.log 21是无限追加进程挂得越频繁增长越快守护脚本自己的输出如果没轮转同样会变成另一个日志炸弹。解决正式环境用 logrotate 管轮转临时过渡可以用最简单的方式每次启动前把应用日志截为最近 200 行tail -n 200 /var/log/app_output.log /tmp/app_log_tail 2/dev/null mv /tmp/app_log_tail /var/log/app_output.log这个“先截断再写入”的做法虽然糙但在没有 logrotate 的环境里非常实用。日志不是越多越好运维脚本自己也要有“后悔药”。6. 让脚本“敢用”从语法检查到干跑模式的三个习惯6.1 上线前bash -n、shellcheck 和测试目录干跑脚本在交给别人或交给定时任务之前至少做三道自检bash -n查语法shellcheck查常见错误然后在测试目录里实际跑一遍。shellcheck 是免费的静态检查工具对未加引号、判断符号写错、废弃语法这类问题非常敏感。命令很简单bash -n /usr/local/bin/check_host.sh shellcheck /usr/local/bin/check_host.sh在测试目录干跑时要换掉所有写死的路径或者用一个空目录验证脚本不会误删系统文件。这一步是给自己买后悔药等脚本进了生产再翻车代价得乘以机器数量。6.2 给脚本加一个“干跑模式”重构时才敢下手运维脚本改多了以后最怕的是不知道这次改动会不会把生产搞炸。我习惯给关键脚本开头统一预留一个--check参数把真正执行的命令换成打印出来CHECK_MODE0 [ ${1:-} --check ] CHECK_MODE1 run() { if [ $CHECK_MODE -eq 1 ]; then echo [CHECK] $* else $ fi } # 原来直接执行的位置统一改成 run run rm -f /var/run/app.pid在测试环境先加--check跑一遍确认命令集合等于自己预期的操作清单再摘掉参数上生产。配合静态检查新脚本上线和旧脚本重构都敢动手。另一个习惯是把所有绝对路径定义成变量放在脚本开头排障时改一处就能全局生效这样做久了一个团队维护下来的脚本风格会越来越接近互相 review 时一眼能看懂。我现在接手一台新机器或一个新环境第一件事就是把这套脚本按它的目录结构过一遍确认路径和 cron 行为符合预期再交给同事。脚本这东西写出来只是起点敢放心地让它替你守着才叫落地。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询