ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Linux运维Shell脚本实战:巡检、日志清理、批量执行与守护脚本

Linux运维Shell脚本实战:巡检、日志清理、批量执行与守护脚本 简介这是一份面向Linux运维工程师与Shell初学者的实用脚本合集围绕日常运维自动化场景整理帮助读者用脚本替代重复手工操作提升排障与巡检效率。资源包内含1个PDF文件大小约100KB内容以脚本代码与命令示例为主便于随时查阅与复制实践。文档覆盖日志过滤与错误统计、服务健康检查、旧文件清理、文件与目录备份压缩、多主机循环ping、远程文件传输、用户home目录校验、日志实时监控、批量创建用户、进程检查与kill、服务器系统初始化配置等十余个主题每个场景均给出可直接参考的Shell写法与关键参数说明。目前已有1165人学习下载适合需要积累运维脚本模板、梳理自动化思路或准备面试复习的读者可作为案头速查手册使用。1. 从一份 shell 脚本合集说起运维日常里哪些活儿值得脚本化凌晨两点被告警叫醒登机器一看是磁盘满了日志目录里躺着三个月前的归档没人清。手动删完顺手写了个清理脚本丢进 crontab这事就算翻篇了。类似场景在 Linux 运维里反复出现日志轮转、进程守护、批量巡检、配置备份、服务健康检查。这些活儿单次做不费劲天天做就是纯消耗。一份靠谱的 shell 脚本合集价值不在于脚本本身多精巧而在于把「每次都要想一遍」变成「照着跑就行」。这篇笔记围绕 Linux 运维常用 shell 脚本这个方向把选型思路、可复现的脚本骨架、参数怎么调、坑在哪讲清楚。适合刚接手服务器的新手照着搭也适合干了几年的人回头补一补脚本规范。下面从最容易被低估的巡检脚本开始拆。2. 运维脚本的四个高频场景与最小可用骨架2.1 系统巡检把散落的命令收进一个入口巡检是运维脚本里最该先做的。CPU、内存、磁盘、负载、关键进程、端口监听这些指标平时靠top、df -h、ss -lntp一条条敲机器一多就顾不过来。脚本化的核心思路是把采集逻辑和判断逻辑分开采集只负责拿数据判断负责给结论。#!/bin/bash # check_system.sh - 基础巡检输出可直接读的结论 set -euo pipefail THRESHOLD_DISK85 # 磁盘使用率告警线按业务调整 THRESHOLD_MEM90 # 内存使用率告警线 THRESHOLD_LOAD4 # 1 分钟负载告警线建议设为 CPU 核数的 1.5 倍 echo 巡检时间: $(date %F %T) # 磁盘只关心本地挂载点过滤 tmpfs 和 overlay df -hP | awk NR1 $1!~/tmpfs|overlay/ {print $6, $5} | while read -r mount usage; do pct${usage%\%} if [ $pct -ge $THRESHOLD_DISK ]; then echo [告警] 挂载点 $mount 使用率 ${pct}% fi done # 内存用 free 的 available 列比 free 列更贴近真实可用 mem_pct$(free | awk /Mem:/ {printf %d, ($3-$6-$7)/$2*100}) [ $mem_pct -ge $THRESHOLD_MEM ] echo [告警] 内存使用率 ${mem_pct}% # 负载取 1 分钟值和核数对比 cores$(nproc) load1$(awk {print $1} /proc/loadavg) awk -v l$load1 -v c$cores -v t$THRESHOLD_LOAD \ BEGIN {if (l c*t/4) print [告警] 1分钟负载 l 超过阈值} # 关键进程进程名写死在数组里按需增删 for proc in sshd crond nginx; do pgrep -x $proc /dev/null || echo [告警] 进程 $proc 未运行 done逻辑说明set -euo pipefail让脚本遇到未定义变量、管道中间失败时直接退出避免带着错误继续跑。磁盘判断用df -hP的 POSIX 输出格式列位置固定比默认格式好解析。内存用available而不是free因为free包含了可回收的 cache容易误报。负载阈值没有写死数字而是拿核数做基准换机器不用改脚本。参数说明三个 THRESHOLD 变量是唯一需要按环境调的地方。磁盘线一般 85数据库机器可以放到 90内存线 90 偏保守容器环境可以到 95负载线用核数倍数表达4 核机器 1 分钟负载超过 6 就该看。2.2 日志清理别让归档把磁盘吃干净日志清理脚本翻车最多的地方是「删错了」。常见做法是按修改时间找文件但find -mtime的粒度是天遇到按小时切的日志就不够用。更稳的方式是用-mmin按分钟算配合-name限定后缀再加一个 dry-run 开关先看要删什么。#!/bin/bash # clean_logs.sh - 按保留天数清理日志支持预演 set -euo pipefail LOG_DIR${1:-/var/log/app} KEEP_DAYS${2:-7} DRY_RUN${3:-yes} # yes 只打印no 才真删 # 把天数换算成分钟find 的 -mmin 更精确 keep_min$((KEEP_DAYS * 24 * 60)) find $LOG_DIR -type f \( -name *.log -o -name *.log.* \) \ -mmin $keep_min -print0 | while IFS read -r -d f; do if [ $DRY_RUN yes ]; then echo [预演] 将删除: $f ($(du -h $f | cut -f1)) else rm -f -- $f echo [已删除] $f fi done逻辑说明-print0配合read -d 处理带空格或特殊字符的文件名这是 shell 脚本里最常见的坑之一。--放在rm后面防止文件名以-开头被当成选项。DRY_RUN 默认 yes第一次跑一定先预演确认输出列表没问题再改成 no。参数说明第一个参数是日志目录第二个是保留天数第三个是预演开关。保留天数按业务定访问日志一般 7 天审计日志可能 90 天。注意-mmin是「修改时间」如果日志被追加写入修改时间会一直更新这种场景要改用-name里的日期做判断。2.3 批量执行for 循环和 while read 怎么选批量在多台机器上跑命令是运维脚本绕不开的需求。shell 脚本 for 循环写起来顺手但读文件时有个经典陷阱for host in $(cat hosts.txt)会按空格和换行同时切分主机名里有空格就完蛋。正确做法是while read配合重定向。#!/bin/bash # batch_run.sh - 从文件读主机列表逐台执行命令 set -euo pipefail HOST_FILE${1:-hosts.txt} CMD${2:-uptime} SSH_OPTS-o ConnectTimeout5 -o StrictHostKeyCheckingaccept-new while IFS read -r host; do # 跳过空行和注释行 [[ -z $host || $host ~ ^# ]] continue echo ----- $host ----- # shellcheck disableSC2086 ssh $SSH_OPTS $host $CMD || echo [失败] $host 执行出错 done $HOST_FILE逻辑说明IFS read -r保留行内空格和反斜杠-r防止反斜杠被解释。|| echo让单台失败不影响整体循环这是批量脚本必须有的容错。ConnectTimeout5避免某台机器网络不通时卡住整个批次。参数说明主机文件一行一个地址支持#注释。命令作为第二个参数传入复杂命令建议写成脚本再分发不要塞一长串引号。SSH_OPTS 里的StrictHostKeyCheckingaccept-new在首次连接时自动接受指纹内网环境方便公网环境建议改成严格模式。2.4 服务守护用脚本兜住那些不会自愈的进程有些老服务没有 systemd 单元挂了不会自动拉起。守护脚本的思路是检查进程是否存在不存在就启动同时记录日志避免反复重启刷屏。#!/bin/bash # guard.sh - 进程守护配合 crontab 每分钟执行 set -euo pipefail PROC_NAMEmyapp START_CMD/opt/myapp/bin/start.sh LOG_FILE/var/log/guard.log LOCK_FILE/tmp/guard_${PROC_NAME}.lock # 用 flock 防止上一轮还没跑完下一轮又进来 exec 9$LOCK_FILE flock -n 9 || exit 0 if ! pgrep -x $PROC_NAME /dev/null; then echo $(date %F %T) $PROC_NAME 未运行尝试启动 $LOG_FILE $START_CMD $LOG_FILE 21 || echo $(date %F %T) 启动失败 $LOG_FILE fi逻辑说明flock -n 9拿不到锁就直接退出避免 crontab 每分钟触发时多个实例同时启动服务。pgrep -x精确匹配进程名不加-x会匹配到包含该名字的其他进程。启动命令的输出重定向到日志方便事后查为什么起不来。参数说明PROC_NAME 要和ps里看到的进程名一致注意有些程序会改自己的进程名。START_CMD 建议用绝对路径crontab 环境变量和登录 shell 不一样。LOCK_FILE 放/tmp下重启后自动清掉。3. 参数、阈值和调度脚本跑得稳不稳就看这几处3.1 阈值不是拍脑袋要留出观察窗口巡检脚本里的阈值新手最容易直接抄网上的数字。磁盘 85%、内存 90% 这些是通用起点但真正该做的是先跑一周只采集不告警看正常波动范围再把阈值设在波动上沿之上。比如某台机器内存白天稳定在 70%晚上批处理跑到 88%那阈值设 90% 就会天天误报设 92% 才合理。负载同理4 核机器 1 分钟负载偶尔冲到 8 是正常的持续 5 分钟超过 6 才值得看。3.2 crontab 的时间表达式和脚本执行环境脚本手动跑没问题放进 crontab 就报错九成是环境变量问题。crontab 的 PATH 通常只有/usr/bin:/bin脚本里用到的命令如果装在/usr/local/bin就会 command not found。解决办法是在脚本开头显式设置 PATH或者所有命令写绝对路径。# crontab -e 里的写法 # 分 时 日 月 周 命令 */5 * * * * /opt/scripts/check_system.sh /var/log/check.log 21 0 3 * * * /opt/scripts/clean_logs.sh /var/log/app 7 no /var/log/clean.log 21逻辑说明*/5表示每 5 分钟0 3 * * *表示每天凌晨 3 点。重定向 log 21把标准输出和错误都写进日志否则 crontab 会把输出当邮件发时间长了/var/mail会撑爆。清理脚本第三个参数写no才是真删第一次上线务必先用yes跑一天。3.3 脚本里的错误处理set -e 不是万能药set -e在命令返回非零时退出但它有几个不生效的场景命令在if条件里、在或||后面、在管道非最后一段。所以光靠set -e不够关键步骤要显式判断。# 不推荐set -e 管不到管道中间 tar -czf backup.tar.gz /data | gzip backup.tar.gz.gz # 推荐分步执行每步检查 if ! tar -czf backup.tar.gz /data; then echo 打包失败 2 exit 1 fi if ! gzip -f backup.tar.gz; then echo 压缩失败 2 exit 1 fi逻辑说明管道里前一个命令失败set -e默认不触发要配合set -o pipefail才管用。但即便开了 pipefail复杂管道还是分步写更清楚出错时也知道是哪一步。2把错误信息写到标准错误方便和正常输出分开收集。3.4 日志和退出码给未来的自己留线索脚本跑完就完了出问题想查却什么都没有这是最常见的后悔药场景。每个脚本至少要做两件事关键动作写日志退出时给明确的退出码。退出码约定0 成功1 一般错误2 参数错误3 依赖缺失。这样上层调度系统能根据退出码决定重试还是告警。# 脚本末尾统一处理 log() { echo $(date %F %T) [$1] $2 $LOG_FILE; } trap log ERROR 脚本在第 $LINENO 行异常退出 ERR逻辑说明trap ... ERR在命令失败时触发$LINENO给出出错行号排查时直接定位。log 函数统一时间格式方便后续用 grep 按时间段过滤。注意 trap 要放在脚本靠前的位置放太晚前面的错误捕获不到。4. 避坑与排查shell 脚本里那些反复踩的坑4.1 变量没加引号路径带空格就翻车现象脚本手动跑正常处理某个目录时提示「没有那个文件或目录」但目录明明存在。原因变量展开后如果含空格会被 shell 拆成多个参数。rm $FILE在 FILE 是/data/my log/app.log时变成删两个文件。解决所有变量引用加双引号rm $FILE。这条规则没有例外养成肌肉记忆。4.2 for 循环读文件行被空格切开现象主机列表文件里有一行web 01循环时变成两次第二次拿到的01被当成主机名去连。原因for x in $(cat file)的展开会做单词分割。解决改用while IFS read -r line; do ... done file。如果确实要用 for先设置IFS$\n但 while read 更直观。4.3 脚本在 crontab 里找不到命令现象手动执行/opt/scripts/backup.sh成功加到 crontab 后日志里全是command not found。原因crontab 的 PATH 和登录 shell 不同通常只有/usr/bin:/bin。解决脚本开头加export PATH/usr/local/bin:/usr/bin:/bin:$PATH或者所有外部命令写绝对路径。用which查一下命令实际位置。4.4 清理脚本删掉了正在写入的日志现象清理脚本跑完应用报错说日志文件不存在但应用还在运行。原因find -mtime 7按修改时间找正在写入的日志修改时间是当前不会被选中但如果应用是每天新建文件、旧文件不再写入旧文件就会被删而应用可能还持有文件句柄。解决清理前用lsof | grep 文件名确认没有进程占用或者只清理明确带日期后缀的归档文件不动当前活跃日志。4.5 并发执行导致重复启动现象守护脚本配在 crontab 每分钟跑某次服务启动慢下一分钟的脚本又启动一次起了两个实例抢端口。原因脚本没有互斥机制。解决用flock加锁拿不到锁就退出。注意锁文件要放在所有实例都能访问的位置/tmp通常可以但多用户环境下要加用户名区分。5. 从能跑到好用脚本版本管理和灰度上线的几个习惯脚本写多了最大的问题不是写不出来而是改了之后不知道哪台机器上跑的是哪个版本。我自己的习惯是给每个脚本加一个版本变量配合--version参数输出部署时用配置管理工具推不手工 scp。#!/bin/bash VERSION1.2.0 if [ ${1:-} --version ]; then echo check_system.sh $VERSION exit 0 fi逻辑说明版本号写在脚本里--version直接返回不执行后续逻辑。这样批量巡检时可以先收集各机器上的脚本版本确认一致再跑正式检查。版本号建议用语义化版本改动逻辑升中版本改阈值升小版本。灰度上线方面新脚本或改过的脚本不要一次推全量。先在一台非核心机器上跑一周观察日志里有没有异常退出、告警是否合理。确认没问题再推同机房的其他机器最后推核心机器。清理类脚本尤其要这样删数据的事没有后悔药。验证脚本是否按预期工作除了看日志还可以用退出码做自动化检查。比如在巡检脚本最后根据告警数量返回不同退出码无告警返回 0有告警返回 1采集失败返回 2。上层监控系统根据退出码决定是否触发通知比解析文本输出可靠得多。# 统计告警数并决定退出码 alert_count$(grep -c \[告警\] $OUTPUT_FILE || true) if [ $alert_count -eq 0 ]; then exit 0 elif [ $alert_count -gt 0 ]; then exit 1 else exit 2 fi逻辑说明grep -c统计告警行数|| true防止 grep 没匹配到返回非零导致set -e退出。退出码 1 表示有告警但脚本本身正常2 表示脚本执行出错。监控侧收到 1 发通知收到 2 发告警并检查脚本本身。最后说个习惯每个脚本头部写清楚用途、参数、依赖和最后修改时间别嫌麻烦。三个月后回头看没有注释的脚本和天书没区别。我一般会在脚本开头留一段注释块写明白这个脚本解决什么问题、怎么调阈值、出问题先看哪里。这个习惯帮我省过很多次重新读代码的时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表