
简介《Linux运维必备工作常用shell脚本》是一份面向Linux运维工程师的实用Shell脚本参考文档适合具备一定命令行基础、希望将日常巡检与维护工作自动化的读者。文档围绕真实运维场景系统整理了日志错误过滤与统计、服务健康检查、网络连通性测试、旧文件清理与备份压缩、循环多文件操作、scp远程传输、用户home目录核查、日志实时监控与异常捕获以及自动化创建用户、进程检查与kill、服务器系统初始化配置等常见需求并配有可直接参考的脚本片段和简要注释。包体为单个PDF文件大小仅100KB内容精炼、便于随时查阅。该资源目前已有1164人学习下载既适合需要快速提升脚本编写效率的运维新手也可作为中高级运维人员的日常工作速查手册。整体结构清晰示例短小直接便于理解后按需调整复用。1. 为什么“Linux运维必备常用shell脚本”先要立规矩而不是背命令运维手里那份《Linux运维必备工作常用shell脚本.pdf》问题从不在于“不够全”而在于把它当成背书的材料。真正回到生产环境时你面对的不是“一条命令能不能跑”而是“这条命令在凌晨两点读写到日志里的内容是什么格式、退出码是多少、下一次执行时会不会污染上一轮结果”。同样一条df -h在终端里看和写进脚本里定时跑要求完全不一样终端里输出错了可以重敲脚本里错了就可能在监控页面上躺一整晚。所以这类资料的合理用法是把它当索引照着索引维护一套自己的脚本库每一条都带上日志、阈值、退出码和可重入的保护。这篇文章按“骨架 → 巡检 → 批量与备份 → 排障 → 自检”这条线把我平时怎么把这些脚本组合成一套可运维、可交接的shell脚本库完整拆开讲。这套东西适合两类人一是刚切到服务器运维、手里有一堆常用命令但还没形成脚本规范的Linux运维工程师二是做桌面运维助手或自动化运维转型的人需要把零散命令固化成能被cron和监控系统反复调用的固定工具。下面的代码都以bash 4为准默认系统是RHEL系或Debian系不需要额外安装重型依赖。2. 脚本骨架set -euo pipefail、日志函数和退出码是脚本库的地基2.1 让每个脚本自动带上 set -euo pipefail出现异常立即退出我见过的运维脚本里最常见的翻车现场不是逻辑写错而是“命令失败了脚本还在继续跑”。比如cd /data/app rm -rf logs如果cd失败rm 会删到当前目录。避免这类问题的第一步就是在每个脚本头部写死三行#!/usr/bin/env bash set -euo pipefail IFS$\n\tset -e只要脚本里有命令返回非零状态整体立即退出。代价是像grep没匹配到这种“正常失败”也会中断流程所以后续要用|| true或if grep显式兜底。set -u变量未定义就报错。典型收益是防止rm -rf $DIR/里DIR没赋值最后变成rm -rf /这种事故。set -o pipefail管道中任何一段失败都会让整条管道返回非零。没有它mysqldump | gzip里mysqldump崩了gzip照样成功备份却是个空壳。IFS$\n\t是把字段分隔符收缩成换行和制表符防止文件名里的空格被 for 循环拆开。这行配合set -u是脚本库地基里的地基建议做成模板文件而不是每次手打。2.2 日志函数统一输出格式排查时不用猜脚本多了以后最难受的是每份脚本打印日志的格式不一样。有人用echo有人用logger报警的时候很难归档。我一般会抽一个公共库文件比如在/opt/ops/lib.sh里放两个函数所有脚本都source它readonly APP_LOG/var/log/ops/$(basename $0).log log_info() { echo $(date %Y-%m-%d %H:%M:%S) [INFO] $* | tee -a $APP_LOG } log_error() { echo $(date %Y-%m-%d %H:%M:%S) [ERROR] $* 2 | tee -a $APP_LOG 2 }tee -a把内容同时送到终端和日志文件log_error里要先重定向到2否则错误信息和正常日志混在同一个输出流cron 里没法区分成功失败。日志文件的目录要提前建好脚本里可以加一句mkdir -p /var/log/ops不要等第一次报错才想起来目录不存在。2.3 退出码约定0是成功其他都是有具体含义的失败脚本被cron或监控平台调用时最终靠退出码判断成功与否。我习惯在前半部分定义一组退出码比裸数字好读readonly E_OK0 readonly E_PARAM2 readonly E_DISK_FULL3 readonly E_SERVICE_DOWN4常见错误码对应关系可以这样沉淀成一张表退出码含义使用场景0成功正常完成且数据有效1通用错误未分类异常2参数错误传入的参数数量或格式不对3资源不足磁盘、内存、容量触达阈值4服务异常端口不通、进程消失、依赖缺失调用方只要对照表就能定位大概方向。函数里return $E_PARAM脚本末尾exit $E_DISK_FULL监控系统拿到的不是一个没头没尾的“1”而是可以直接映射到告警文案的状态。骨架阶段不要追求代码量把这三个部分统一好后面写任何脚本都是往这个框里填肉。3. 巡检脚本磁盘、内存、CPU、进程存活一次跑完并输出结构化结果3.1 磁盘巡检脚本df加阈值比裸跑df靠谱得多运维巡检里最高频的是磁盘。df -h的毛病是输出给人看的脚本没法直接吃。我会写一个check_disk.sh接收一个阈值参数默认写到 80% 就告警#!/usr/bin/env bash source /opt/ops/lib.sh THRESHOLD${1:-80} df -P | awk -v thr$THRESHOLD NR1 { gsub(/%/, , $5) if ($5 thr) { printf DISK_ALERT mount%s used%s%%\n, $6, $5 exit 4 } }df -P是POSIX格式不会因为设备名过长而换行这是给脚本用的关键参数。awk 里用-v thr$THRESHOLD把shell变量传进来gsub去掉百分号后做数值比较。注意这里exit 4会直接结束awk而set -e会把 awk 的退出码4传给脚本刚好落到上面约定的E_DISK_FULL4。如果你用df -h写同样逻辑迟早会因为卷名里出现多余空格而误报。3.2 内存和CPU统计从 /proc 取数不装 sysstat有些最小化安装的机器没有free或者版本太旧我更推荐直接读/proc/meminfo它一定存在且格式稳定total$(awk /MemTotal/ {print $2} /proc/meminfo) avail$(awk /MemAvailable/ {print $2} /proc/meminfo) used_percent$(( (total - avail) * 100 / total )) if (( used_percent 90 )); then log_error memory used${used_percent}% exit 3 else log_info memory ok used${used_percent}% fiMemAvailable是内核 3.14 提供的字段它估算的是“在不触发交换的前提下还可分配多少内存”比used/total这种粗糙算法贴近业务体感。CPU 负载则读/proc/loadavg的第一列但要记得load average包含不可中断睡眠偶发偏高不一定代表CPU被打满。想看真实占用用top -bn1 | head -n5做一次采样更直观脚本里注意加-b批处理模式否则top会用交互界面输出乱码。3.3 服务进程与端口存活检查内存操作中不可忽略的一步进程检查按“端口优先于进程名”的原则设计。比如MySQLpgrep mysqld在服务僵死时仍可能返回成功探查TCP端口更可靠check_port() { local host$1 local port$2 if command -v nc /dev/null 21; then nc -z -w 3 $host $port /dev/null 21 else timeout 3 bash -c echo /dev/tcp/$host/$port /dev/null 21 fi } check_port 127.0.0.1 3306 log_info mysql alive || { log_error mysql down; exit 4; }nc -z -w 3是零数据探测并等待3秒不给服务写入任何业务数据适合巡检场景。没有nc的系统就用bash自带的/dev/tcp设备文件做TCP连接同样能达到目的。两者都失败时进程名检查可以作为兜底但不建议反过来。整个巡检脚本设计成“一个main函数串起多个check函数”每类检查输出一行机器可读的KEYVALUE文本之后无论是接Prometheus的textfile collector还是自己写解析都省事。4. 批量操作与备份for循环的坑、SSH批量分发和定时任务守护4.1 for循环的正确姿势数组和while read让步进可控shell脚本里for循环是高频操作而最常见的坑是变量遍历被空格拆开。要遍历一个目录下所有文件新手这样写for f in $(ls /var/log/*.log); do echo $f done这个写法一旦文件名里带空格$f就会被拆成两个。正确做法是用数组或find whileshopt -s nullglob for f in /var/log/*.log; do [[ -f $f ]] || continue echo $f done while IFS read -r line; do echo $line done (find /var/log -maxdepth 1 -type f -name *.log -print)shopt -s nullglob保证没有匹配文件时通配符展开为空数组而不是保留字面量导致误处理。while IFS read -r line里的IFS避免行尾空格被吞掉-r防止反斜杠被解释这两点是读取文件名时不可减的配置。用find时我发现很多人忘记-maxdepth 1结果递归处理了子目录里的同名文件批量操作时容易波及无关目标。4.2 批量SSH执行无论用expect还是ssh密钥先管好密码与登录安全对几十台上万台服务器批量执行命令我先检查现有基础设施如果已有堡垒机或配置管理平台就优先走它们如果必须用自己写的脚本第一选择是SSH密钥登录而不是expect里嵌明文密码。密钥登录的批量分发脚本骨架如下hosts(172.16.1.10 172.16.1.11 172.16.1.12) cmduptime df -h / for h in ${hosts[]}; do ssh -o ConnectTimeout5 -o StrictHostKeyCheckingno \ -o BatchModeyes opuser$h $cmd \ || log_error node $h failed doneBatchModeyes可以保证当你没有配置密钥或密钥不对时SSH不会停在那里交互式问你密码这在无人值守执行里极其重要避免一个卡住的输入把整批任务挂在第一台机器上。StrictHostKeyCheckingno只建议在首次批量接入时使用并且配合known_hosts校验否则等于自己放弃了中间人防范。如果你的执行环境实在只能密码登录用expect也要把凭证从外部配置读取并打码脚本本身不要出现password字样的明文参数。4.3 备份脚本tar打包、按时间轮转、再同步到远程定时任务要加锁备份脚本看着简单实际容易毁在“重复执行”上。上一轮还没跑完cron又拉起新一轮两边同时写同一个tar文件备份文件直接损坏。我的做法是给脚本加一个简单的flock文件锁exec 9/var/run/ops_backup.lock if ! flock -n 9; then log_error backup already running, exit exit 1 fi STAMP$(date %Y%m%d_%H%M%S) tar czf /backup/app_${STAMP}.tar.gz -C /data app/ find /backup -name app_*.tar.gz -mtime 7 -deleteexec 9文件打开一个持有文件描述符9的锁文件flock -n 9非阻塞尝试加锁失败说明另一个实例在跑直接退出。打好包后立即用find -mtime 7 -delete清理超过7天的旧备份这是最便宜的轮转策略。别在tar的源路径上写/data/app到了恢复场景还得想“当时相对路径是什么”-C /data把归档基准目录单独指定解包时直接落在当前目录/app下面路径行为可预期。如果还要推到远端用rsync -av --partial /backup/ backup-server:/backup/--partial保证中断后保留已传部分下次续传而不是从头来。定时任务里我一般写成10 2 * * * /opt/ops/backup.sh /var/log/ops/backup_cron.log 21这里21和缺一不可否则cron会额外给maillog发一封只有报错内容的信时间久了邮箱淹没问题反而不显眼。5. 日志与故障排查awk、sed、grep 排障组合及命令超时防护5.1 日志切割与清理先归档再删除避免磁盘被日志占满排查故障第一步是拿到日志但系统里堆了几GB日志时最需要的是把“一直在写的日志”和“可以归档的旧日志”分开。常见的logrotate配置之外手动处理时我习惯用find tar按天归档find /var/log/app -name *.log -mtime 1 -print0 \ | xargs -0 tar czf /backup/log/app_$(date %Y%m%d).tar.gz find /var/log/app -name *.log -mtime 7 -delete-print0搭配xargs -0让文件名里的空格、换行都不再是障碍。第二段-mtime 7 -delete删除的是归档后的源文件。想解压中文文件名乱码的问题根源通常是zip的编码和系统locale不一致unzip -O gbk可以指定编码归档阶段多用一个参数比事后解乱码省心。5.2 grep/awk/sed 在排障场景的常用组合把日志切片而不是全文翻线上排查日志时我一般按“先缩小时间范围再做字段提取最后聚合计数”的顺序而不是直接tail -n 1000纯靠肉眼。一段时间内某个接口的耗时分布可以这样切grep 2025-06-01 10: /var/log/app/access.log \ | grep -o cost[0-9]*ms \ | sort -t -k2 -n \ | tail -20grep -o只输出匹配的片段整个管道里的数据量骤减。如果要看错误码分布把grep -o换成sed -n s/.*status\([0-9]*\).*/\1/p | sort | uniq -c直接得到每个状态码的出现次数。awk 适合做字段抽取比如打印第7列的耗时配一个if ($7 3000) print $1, $7就完成了慢请求初筛。最后结合sed -n /2025-06-01 10:01:00/,/2025-06-01 10:02:00/p把窗口外数据滤掉整轮排查就不需要翻动大文件。5.3 给命令加超时和并发防止脚本卡死和清空负载脚本里最容易被忽略的坑是“命令不发返回”。磁盘有坏道、NFS挂载点无响应、DNS解析超时都会让脚本里去不返。统一给外部命令加timeouttimeout 10 ping -c 3 $target /dev/null 21 || log_error ping timeouttimeout 10让ping最多运行10秒超时即杀。这个习惯必须覆盖到ssh、curl、mysql等所有会发起外部连接的命令上否则一个巡检脚本可能因为某个远端服务假死而卡到cron下一轮启动最终拖垮调度。并发批量执行时xargs -P比各自更可控cat host_list.txt | xargs -P 10 -I {} ssh -o ConnectTimeout5 opuser{} uptime-P 10限制同时最多10个SSH连接-I {}把每一行内容替换到命令位置。没有并发控制时对200台机器一次性发起SSH源端的进程数和连接数会瞬间打满CPU没爆、负载先爆。观察这类故障只看ssh进程数量就能确认是并发失控。6. 用 shellcheck 和自检清单把脚本库变成可交接的资产脚本写完不等于能用能跑也不等于可维护。在把新脚本放进cron以前我固定跑两遍检查。第一遍是shellcheck它对bash的静态检查比人眼靠谱得多shellcheck -s bash -S warning /opt/ops/*.sh-S warning把告警级别设为warning以上才提示排除一批琐碎的style建议。最常见的两个告警是SC2086提示变量没加双引号这对应实际运行中的空格拆分问题SC2164提示cd后没检查是否成功——这就是开头说到的rm -rf事故源头。修完shellcheck后用shfmt -i 4 -ci -sr统一格式我倾向于缩进4空格-ci让case语句对齐更清晰-sr把重定向符号规范成 file而不是file 风格统一对交接非常重要。第二遍是格式自查。脚本文件必须是UTF-8无BOM行尾必须是LF。Windows下编辑过的脚本带着CRLFbash执行时会报$\r: command not found之类莫名其妙的错排查时直接浪费半小时。用file script.sh看输出或者sed -n l查看行尾控制符都能快速识别。自检清单我会固定在脚本库根目录的README里每个脚本必须有日志函数调用外部命令必须套timeout涉及删除和覆盖的路径必须是变量且经过存在性检查必须定义非零退出码含义必须用flock挡住并发执行。新脚本只有过完这五条才能提交到生产环境的/opt/ops/目录。这样一套体系下来那份PDF里的命令仍然全都在用但每一行都变成了有日志、有退出码、有超时保护的正式工具换人接手时不需要对着脚本猜当初的意图。本文还有配套的精品资源点击获取