简介:这是2024年新版推出的279个开箱即用Shell脚本合集,整个资源只含一个PDF文档,压缩包大小约4.69MB,适合Linux/Unix运维人员、系统管理员以及希望提升自动化能力的脚本学习者。内容覆盖DDoS攻击自动屏蔽、Linux系统发送告警、MySQL数据库单循环与多循环备份、Nginx访问日志按天切割与深度分析、网卡实时流量查看等高频运维场景,每个脚本都包含可复用的关键命令和注释,方便直接抄录到生产环境并二次修改。文档内部按功能模块组织,既是一本随身运维手册,也可作为Shell编程教学案例,帮助读者理解日志采集、iptables防火墙策略、mysqldump备份、cron定时任务、正则匹配和awk统计等实战方法,并将复杂命令拆解为易理解的步骤。目前已有154人学习下载,适合需要快速筛选可用脚本、提升服务器管理效率的开发者参考。
1. 开箱即用的 279 个 Shell 脚本:先去平台化,再去踩坑
一份「279 个开箱即用的 Shell 脚本」合集摆在面前,很多人第一反应是收藏,第二反应是不敢跑。毕竟脚本这东西,网上下的包里有玄学,变量没替换、路径不对、权限不够,随便一个都能让自动化变成事故现场。这份合集覆盖了运维日常最常碰的场景:Nginx 日志切割与分析、DDoS 攻击自动封禁、MySQL 数据库备份、系统初始化、批量服务器巡检、进程状态统计、企业微信告警、FTP 与 SSH 客户端封装。与其说它是一份教程,不如说是一个软件/插件式的运维工具包。适合刚接手服务器的新手快速抄作业,也适合熟手拿去做二次改造——但前提是,你得知道每个脚本的边界在哪、哪些参数必须换、哪些逻辑本身就有问题。
2. 日志与安全的四板斧:Nginx 切割、访问分析和 DDoS 自动封禁怎么改参数
日志处理和安全防护是运维最早碰到的需求,也是这份脚本包里最具「拿来就能用」气质的部分。但这一段恰恰是最容易翻车的地方,因为涉及日志轮转、信号机制、防火墙规则三条线,任何一环没想清楚,脚本跑起来都是表面正常、实际无效。
2.1 按天切割 Nginx 日志:mv 之后必须做的事
Nginx 的 access.log 如果不切,几个月就能把磁盘撑爆。常规做法是每天 0 点把日志挪走,再让 Nginx 重新打开新文件。脚本合集里那套按天切割的写法,核心逻辑如下:
#!/bin/bash LOG_DIR=/usr/local/nginx/logs YESTERDAY_TIME=$(date -d "yesterday" +%F) LOG_MONTH_DIR=$LOG_DIR/$(date +"%Y-%m") LOG_FILE_LIST="default.access.log" for LOG_FILE in $LOG_FILE_LIST; do [ ! -d $LOG_MONTH_DIR ] && mkdir -p $LOG_MONTH_DIR mv $LOG_DIR/$LOG_FILE $LOG_MONTH_DIR/${LOG_FILE}_${YESTERDAY_TIME} done kill -USR1 $(cat /var/run/nginx.pid)这个脚本的核心不在 mv,而在最后一行kill -USR1。Nginx 主进程收到 USR1 信号后会重新打开日志文件,否则你 mv 走旧文件,Nginx 的文件句柄还指着它,日志照样往旧文件里写,切割等于没切。
参数上有几个点要按自己环境改:LOG_DIR是 Nginx 日志根目录,LOG_FILE_LIST支持多个站点日志用空格隔开,想切 error.log 就加进去;/var/run/nginx.pid的路径以你实际编译安装的位置为准,用nginx -V或ps -ef | grep nginx都能确认。如果 Nginx 是编译安装在 /usr/local/nginx 下,pid 文件一般就在/usr/local/nginx/logs/nginx.pid,照抄默认路径会 kill 失败。
切割频率也建议改一下:脚本本身按天切,但访问量大的站点建议按小时甚至按日志大小切。常见做法是配合 cron 每天 0 点执行,加上日志保留策略,比如只保留最近 30 天的切割文件,否则切完不清理,磁盘压力只是换了个地方堆积。
2.2 日志分析脚本:UV、PV、TOP IP 的统计口径
拿到切割后的日志,下一步就是分析。合集里的日志分析脚本用 awk 一把梭,统计访问最多的 IP、时间段访问量、访问最多的页面、状态码分布。核心统计片段长这样:
# 统计访问最多的 10 个 IP,同时输出 UV awk '{a[$1]++}END{print "UV:",length(a);for(v in a)print v,a[v]}' $LOG_FILE | sort -k2 -nr | head -10 # 统计时间段内访问最多的 IP awk '$4>="[01/Dec/2018:13:20:25" && $4<="[27/Nov/2018:16:20:49"{a[$1]++}END{for(v in a)print v,a[v]}' $LOG_FILE | sort -k2 -nr | head -10 # 统计访问最多的 10 个页面,带 PV awk '{a[$7]++}END{print "PV:",length(a);for(v in a){if(a[v]>10)print v,a[v]}}' $LOG_FILE | sort -k2 -nr这里几个参数要拆开看。a[$1]的$1是日志第一列,也就是$remote_addr,统计 IP 必须用这个字段。sort -k2 -nr表示按第二列做数值逆序排序,-n保证 100 排在 99 后面而不是按字符串排,-r是从大到小。head -10控制输出条数,想看 Top 20 就改成head -20。
时间段过滤那行用的是$4,对应当前日志格式的[$time_local],需要注意里面带方括号和冒号,awk 里比较字符串时[不能转义错误,否则直接匹配不到任何行。状态码统计那段a[$7" "$9]把页面和状态码拼在一起做聚合,目的是看哪些页面产生了大量 5xx,这种问题定位比看单一指标更直接。
这套 awk 统计的边界在于:它只能处理标准格式的日志,如果 Nginx 配置里改了 log_format,字段位置全变,脚本就要跟着改。想省事的话,我一般会直接把它接到 cron 里每天生成一份日报,再把结果路径固化下来,比手工跑靠谱得多。
2.3 DDoS 攻击自动封禁:从日志到 iptables 的阈值与边界
防护脚本的思路很直接:盯着 Nginx 日志,把最近一段时间内请求次数超过阈值的 IP 直接丢进 iptables INPUT 链 DROP 掉。脚本核心部分:
#!/bin/bash DATE=$(date +%d/%b/%Y:%H:%M) LOG_FILE=/usr/local/nginx/logs/demo2.access.log ABNORMAL_IP=$(tail -n5000 $LOG_FILE | grep $DATE | awk '{a[$1]++}END{for(i in a)if(a[i]>10)print i}') for IP in $ABNORMAL_IP; do if [ $(iptables -vnL | grep -c "$IP") -eq 0 ]; then iptables -I INPUT -s $IP -j DROP echo "$(date +'%F_%T') $IP" >> /tmp/drop_ip.log fi done几个参数决定这套防护的灵敏度。tail -n5000是回溯窗口,只看最近 5000 行日志,攻击量大的时候这 5000 行可能只覆盖几十秒,阈值 10 很容易被触发。grep $DATE匹配当前分钟的时间戳,保证统计颗粒度是「这一分钟内的请求」。if(a[i]>10)print i里的 10 就是每分钟阈值,内部业务系统可以调到 50 甚至 100,对外服务的网站建议先按 30 观察再压。
这段脚本有边界,必须知道:它只能挡应用层的小流量突刺,真正的流量型 DDoS 在带宽层面就把入口打满了,Nginx 日志根本写不进去,脚本也跑不起来。另外iptables -vnL | grep -c "$IP"这种判断方式,在规则条数多了以后容易误判——比如已经封了1.2.3.0/24网段,再检查1.2.3.100时 grep 匹配不到,就会重复插规则。更稳的做法是用ipset存黑名单,判断和封禁都走集合操作,脚本合集的优点是有完整逻辑可以参考,缺点是没有 ipset 版本,需要自己改写。
配套的 80 端口请求分析脚本也是同一思路:netstat -ant统计 :22 端口连接数最高的前 20 个 IP,如果第 20 名的请求数还大于 500,就执行sar -A导出系统活动报告到 alert.txt。这个脚本适合排查 SSH 暴力破解和端口扫描,注意-F'[ :]+'是 netstat 输出的字段分隔符写法,awk 里改成别的分隔符会导致$4取不到源 IP。
3. 数据备份与系统初始化:MySQL 循环备份的取舍和初始化脚本的边界
数据库备份和服务器初始化是这份合集里另一个高频区域。MySQL 备份脚本有两个版本,单循环和多循环,摆在一起看很有意思——它们解决的是不同量级的问题,选错方案就是给自己挖坑。系统初始化脚本则是一把双刃剑,适合新机器,不适合已有业务的服务器。
3.1 MySQL 单循环备份:逻辑清晰但藏着三个隐患
单循环脚本的逻辑是:先查出所有数据库列表,过滤掉系统库,再逐库 mysqldump。核心代码:
#!/bin/bash DATE=$(date +%F_%H-%M-%S) HOST=localhost USER=backup PASS=123.com BACKUP_DIR=/data/db_backup DB_LIST=$(mysql -h$HOST -u$USER -p$PASS -s -e "show databases;" 2>/dev/null | egrep -v "Database|information_schema|mysql|performance_schema|sys") for DB in $DB_LIST; do BACKUP_NAME=$BACKUP_DIR/${DB}_${DATE}.sql if ! mysqldump -h$HOST -u$USER -p$PASS -B $DB > $BACKUP_NAME 2>/dev/null; then echo "$BACKUP_NAME 备份失败!" fi done这个脚本能用的前提是把HOST、USER、PASS、BACKUP_DIR四个变量全改掉。-B参数很关键,它会在导出文件里自动带上CREATE DATABASE和USE语句,恢复的时候不用手动建库。egrep -v过滤掉了 information_schema、mysql、performance_schema、sys 这几个系统库,但如果你自己建过test库或者其他公共库,正则里没排除,照样会备份。
隐患有三个。第一,密码明文写在脚本里,任何能读这个文件的人都能拿到数据库密码,我一般建议改成从/etc/my.cnf的[client]段读取,或者用MYSQL_PWD环境变量配合权限控制。第二,2>/dev/null把 mysqldump 的报错全吞了,出问题时只会看到一个「备份失败」的提示,具体是连接失败还是权限不足根本看不到,排查全靠瞎猜。第三,备份文件没做压缩,数据量上来后磁盘占用非常快,建议在>重定向后加一条gzip处理,或者直接用mysqldump ... | gzip > ${BACKUP_NAME}.gz。
3.2 多循环备份:每张表一个文件,恢复更灵活但文件数爆炸
多循环脚本在单循环外面又套了一层:先查库,再进每个库查表,每个表导出一个独立的 SQL 文件。核心片段:
for DB in $DB_LIST; do BACKUP_DB_DIR=$BACKUP_DIR/${DB}_${DATE} [ ! -d $BACKUP_DB_DIR ] && mkdir -p $BACKUP_DB_DIR TABLE_LIST=$(mysql -h$HOST -u$USER -p$PASS -s -e "use $DB;show tables;" 2>/dev/null) for TABLE in $TABLE_LIST; do BACKUP_NAME=$BACKUP_DB_DIR/${TABLE}.sql if ! mysqldump -h$HOST -u$USER -p$PASS $DB $TABLE > $BACKUP_NAME 2>/dev/null; then echo "$BACKUP_NAME 备份失败!" fi done done多循环的价值在恢复粒度:线上出了问题时,如果只是某一张大表的数据被误删,单循环方式要导入整个库的文件,时间成本和风险都高;多循环方式直接挑那一张表的 SQL 恢复就行。但代价也很明显:表一多,目录下文件数量爆炸,inode 吃紧,备份耗时也会变长。
我的建议是分场景选型:中小业务、表数量百张以内、单表不大,用单循环加 gzip 最省事;核心库有几百 GB 且需要频繁单表恢复的,再上多循环。别把两种脚本都跑上,那是重复浪费磁盘和 IO。另外这两种脚本都缺一个「备份完自动清理旧文件」的环节,记得自己加一条find $BACKUP_DIR -type f -name "*.sql" -mtime +15 -delete保底。
3.3 系统初始化脚本:新机器一把梭,但要逐行确认再跑
初始化脚本是这份合集里改动风险最大的一份,它会把时区、SELinux、防火墙、历史命令、SSH 配置、文件描述符、内核参数全改一遍。给新装的 CentOS 机器跑没问题,给线上跑就是事故苗子。
# 设置时区并同步时间 ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime # 历史命令显示操作时间 echo 'export HISTTIMEFORMAT="%F %T `whoami` "' >> /etc/bashrc # SSH 超时时间 echo "export TMOUT=600" >> /etc/profile # 禁止 root 远程登录 sed -i 's/#PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config # 设置最大打开文件数 echo "* soft nofile 65535" >> /etc/security/limits.conf echo "* hard nofile 65535" >> /etc/security/limits.conf # 内核参数优化 cat >> /etc/sysctl.conf << EOF net.ipv4.tcp_syncookies = 1 net.ipv4.tcp_max_tw_buckets = 20480 net.ipv4.tcp_max_syn_backlog = 20480 net.core.netdev_max_backlog = 262144 net.ipv4.tcp_fin_timeout = 20 EOF逐行过一遍就能看出问题:时区那行用ln -s硬链接 /etc/localtime,如果系统已经是别的时区,这个软链会覆盖但不会清理旧文件;TMOUT=600设置全局 10 分钟超时,对跑长任务的会话是噩梦,建议按用户维度配置而不是写进全局 profile;PermitRootLogin no改完之前必须确认已经有一个 sudo 用户能登上去,否则重启后你自己也进不来,这就是典型的「后悔药都来不及吃」。
内核参数里tcp_syncookies = 1是防 SYN 攻击的经典选项,tcp_max_tw_buckets控制 TIME_WAIT 数量上限,tcp_fin_timeout = 20缩短 FIN 等待时间,这几个值对高并发短连接场景友好,对长连接为主的数据库服务器反而可能引发连接异常。脚本里还包含 ntpdate 定时同步、IPv4 转发、swappiness 调低等操作,其中 swappiness 改的是/proc/sys/vm/swappiness,重启就还原,要持久化必须再写进 sysctl.conf。
这份脚本的正确打开方式是:新装完系统、没接业务之前跑一遍,跑完reboot验证 SELinux 状态和 SSH 登录都符合预期。如果机器已经上了业务,只挑其中无害的几项手工执行,比如 HISTTIMEFORMAT、limits、sysctl,防火墙和 SELinux 的改动宁可不做。
4. 批量与监控场景:多机磁盘巡检、并发 SSH 和进程采集脚本的取舍
当服务器数量从一台变成几十台,Shell 脚本的价值才真正显现。合集里这组脚本把「批量」和「并发」玩得比较典型,但也有一些写法只适合小型环境,量大了必须改造成可靠的方式,不能无脑照抄。
4.1 host.info 驱动的多机磁盘巡检:清单文件比脚本本身更重要
磁盘巡检脚本没有把 IP 写死在脚本里,而是用一个 host.info 清单文件驱动,这个设计值得单独拿出来讲。清单格式是每行三个字段:IP、用户名、SSH 端口,脚本靠 awk 按 IP 关联取值:
#!/bin/bash HOST_INFO=host.info for IP in $(awk '/^[^#]/{print $1}' $HOST_INFO); do USER=$(awk -v ip=$IP 'ip==$1{print $2}' $HOST_INFO) PORT=$(awk -v ip=$IP 'ip==$1{print $3}' $HOST_INFO) TMP_FILE=/tmp/disk.tmp ssh -p $PORT $USER@$IP 'df -h' > $TMP_FILE USE_RATE_LIST=$(awk 'BEGIN{OFS="="}/^\/dev/{print $NF,int($5)}' $TMP_FILE) for USE_RATE in $USE_RATE_LIST; do PART_NAME=${USE_RATE%=*} USE_RATE=${USE_RATE#*=} if [ $USE_RATE -ge 80 ]; then echo "Warning: $PART_NAME Partition usage $USE_RATE%!" fi done done-v ip=$IP是 awk 传外部变量的标准姿势,脚本里在单引号内取$1做匹配,比拼接字符串安全。OFS="="把分区和使用率拼成分区=百分比的形式,后面用${USE_RATE%=*}和${USE_RATE#*=}拆开,这种写法比cut -d= -f1少一次管道,纯 Shell 风格的取舍值得学。
边界在这几处:df -h的输出在不同系统上列数有差异,/^\/dev/只匹配以 /dev 开头的行,像 overlay、tmpfs 这些不会被统计;阈值 80 是写死的,想按环境调就改成USE_RATE -ge $ALARM_VALUE并支持外部传参。巡检日志目前只打印到标准输出,没有落盘,建议改成追加到/var/log/disk_check.log并加上时间戳。另外这个脚本必须配好 SSH 免密登录才能跑通,否则循环卡在密码输入那一步,这就是后话避坑章要展开的内容。
4.2 并发 SSH 采集主机名并排序:& 与 wait 的粗粒度并发
另一个批量脚本解决的是「同时操作多台机器」的问题:对一组 IP 并发执行 SSH,拿到每台机器的 hostname 和往返耗时,落盘到 hostname.txt,最后找出耗时最短的那台机器并拉取它的 top 输出。并发关键代码:
#!/bin/bash ALL_HOSTS=(192.168.1.10 192.168.1.11) for host in ${ALL_HOSTS[*]}; do { start_time=$(date +'%s') ssh $host "hostname" &>/dev/null sleep 2 stop_time=$(date +'%s') time_consuming=$((stop_time-start_time)) echo "$host: $time_consuming" >> hostname.txt } & done wait host=$(sort -n -k 2 hostname.txt | head -1 | awk -F':' '{print $1}') ssh $host "top -b -n 1"花括号包起来的代码块最后加一个&,整个块就变成后台任务,for 循环一次性把全部 SSH 丢到后台,wait等待所有任务结束。这是 Shell 并发最简单可靠的写法,不需要 xargs -P 也不需要 GNU parallel。耗时统计里的sleep 2是人为加进去的缓冲,目的是让每台机器的耗时有个可比较的基线,实际用的时候按网络状况删掉或调整。
需要注意echo "$host: $time_consuming" >> hostname.txt是追加写,多进程并发追加会不会串行?Shell 的重定向对短行写入是原子的,在低并发场景下问题不大,但量大了还是建议每个任务写独立临时文件,最后再合并。sort -n -k 2只对空格分隔的文本有效,hostname.txt 里是IP: 耗时格式,awk 指定-F':'取第一段就是 IP。这个脚本适合用来做「找出最空闲或网络最好的节点」这类粗粒度判断,不适合做严谨的性能基准测试,因为 SSH 握手开销和 DNS 解析时间都会混进耗时的统计里。
4.3 /proc 进程状态统计、jstack 定时采集与 GC 日志均值
这批脚本走的是「从 /proc 和日志文件里直接抠数据」的路子,不依赖额外监控工具。进程状态统计脚本遍历 /proc 下所有数字目录,读每个进程的 status 文件里的 State 字段:
ALL_PROCESS=$(ls /proc/ | egrep '[0-9]+') for pid in ${ALL_PROCESS[*]}; do test -f /proc/$pid/status && state=$(egrep "State" /proc/$pid/status | awk '{print $2}') case "$state" in R) running_count=$((running_count+1)) ;; T) stoped_count=$((stoped_count+1)) ;; S) sleeping_count=$((sleeping_count+1)) ;; Z) zombie_count=$((zombie_count+1)) echo "$pid" >> zombie.txt kill -9 "$pid" ;; esac done从/proc/$pid/status读 State 比ps aux更直接,因为ps本身也读 /proc,多一层解析反而可能漏掉瞬时状态。Zombie 的kill -9值得商榷——僵尸进程已经死掉,kill -9杀的是它的父进程没回收,正确做法是处理父进程或者等 init 回收,这里直接 kill 实际效果取决于父进程行为,写进脚本里当清理手段没问题,别指望它根治。
jstack 采集脚本的意图是每小时对指定 Java 进程打一次线程快照,并控制目录内文件数量不超过 10 个,超过就删最旧。但有一个明显问题:CURRENT_TIME=$(date +'%F'-'%H:%M:%S')写在 while 循环外面,导致所有 jstack 输出全部追加到同一个文件,滚动删除逻辑失效,文件无限变大。修正很简单,把CURRENT_TIME移进循环体内,每次迭代重新生成。采集的关键字/inceptor/也要改成你自己的 Java 进程名,grep inceptor | grep -v grep这种过滤方式在进程名带特殊字符时还得加引号。
GC 日志统计用 awk 从 hive-server2.log 里截取第二列,tr -d ':'去掉冒号后累加求平均和最大值。这个脚本的字段位置完全取决于你的 GC 日志格式,换了 JDK 版本或换打印参数后第二列可能就不是耗时,跑之前先手工head -5看一眼格式,别让统计结果成为新的黑匣子。
5. 落地避坑实录:脚本直接照抄常见的五个翻车点
279 个脚本说「开箱即用」,但真正开箱即用的是整理过、验证过、参数替换过的那部分。以下五个坑是脚本合集里最容易让人栽跟头的地方,按「现象、原因、解决」写清楚,省得你们再踩一遍。
5.1 SELinux 改完等于没改
现象:跑完系统初始化脚本,getenforce输出还是 Enforcing,SELinux 依旧开启。
原因:脚本里那行sed -i '/SELINUX/{s/permissive/disabled/}' /etc/selinux/config只把SELINUX=permissive替换成disabled,如果你的配置本来就是SELINUX=enforcing,正则匹配后替换命令不生效,等于白跑。
解决:把替换改成直接强制赋值:
sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config跑完确认一下:grep ^SELINUX= /etc/selinux/config,看到disabled才算真的改掉。注意改 SELINUX 必须重启才生效,测试机上验证完再批量。
5.2 Nginx 日志切割后文件还在涨
现象:mv 完旧日志,第二天发现 access.log 没变小的同时,昨天切出去的文件却在继续增长。
原因:Nginx 还握着旧文件句柄,你 mv 之后没有向 master 进程发送USR1信号,Nginx 依旧往原 inode 上写。
解决:在 mv 循环结束后追加信号命令:
kill -USR1 $(cat /var/run/nginx.pid)如果 Nginx 是编译安装的,pid 路径可能不是/var/run,用ps -ef | grep nginx找到 master 进程号直接传。注意执行 kill 的用户要对 pid 文件有读权限,root 跑最保险,放到 cron 里时确保脚本属主是 root。
5.3 FTP 上传脚本报错或传了个空文件
现象:用脚本包里 FTP 客户端的 upload 方法传文件,抛出异常或者传上去的是 0 字节文件。
原因:storbinary的第二个参数需要的是一个已打开的二进制文件对象,原脚本把fp = open(localpath, 'rb')注释掉,直接传了文件路径字符串进去,这个 bug 在纯 Python 代码里一眼就能看到。
解决:改为先打开文件再传递:
def upload(self, localpath, remotepath=None): if not localpath: return 'Please select a local file.' if not remotepath: remotepath = path.basename(localpath) with open(localpath, 'rb') as fp: self._ftp.storbinary('STOR ' + remotepath, fp)用 with 语法确保文件用完自动关闭,比原脚本里手动fp.close()更稳。顺便检查 download 方法里retrbinary('RETR ' + remotepath, fp.write)的写文件权限,下载目录不存在时会抛 IOError。
5.4 iptables 封禁重启即失忆
现象:DDoS 封禁脚本明明把攻击 IP 加进了 iptables,服务器一重启,防护规则全没了,同样的攻击又来了。
原因:iptables -I INPUT -s $IP -j DROP只是内存里的规则,没有持久化到磁盘,重启后 iptables 服务加载的是空的规则集。
解决:封禁脚本跑完后执行规则保存:
iptables-save > /etc/sysconfig/iptablesCentOS 7 及以后如果没启用 iptables-services,还要先systemctl enable iptables让规则开机自动加载。更彻底的方案是把封禁逻辑改成 ipset 加 TTL 超时自动解封,黑名单不存在持久化问题,攻击停了 IP 自动释放,这才是应对短时攻击的正道。
5.5 批量 SSH 巡检卡到天荒地老
现象:执行 100 台服务器的磁盘巡检脚本,第一个 IP 就停住不动,或者整个循环跑完要一个多小时。
原因:目标机器的 SSH 没配免密登录,脚本进入交互式密码输入状态,没有输入自然卡死。就算配了密码,串行循环每台机器一次 SSH 握手,100 台就是 100 次秒级延迟,时间全耗在等待上。
解决:先铺 SSH key,再改脚本加上 BatchMode:
ssh-keygen -t rsa -N '' -f ~/.ssh/id_rsa ssh-copy-id -i ~/.ssh/id_rsa.pub root@192.168.1.10脚本里加两个选项,让没配 key 的机器立即失败而不是卡住:
ssh -o BatchMode=yes -o StrictHostKeyChecking=no -p $PORT $USER@$IP 'df -h'BatchMode=yes禁止交互式输入密码,StrictHostKeyChecking=no跳过首次连接的 host key 确认。加上这两行以后,脚本要么秒过,要么快速报错,不会再出现半夜跑批卡死到天亮的悲剧。
6. 把 279 个脚本变成运维资产:统一三件套、静态检查和灰度验证
脚本攒到 279 个,最大的问题已经不是「没有脚本」,而是「这个脚本怎么保证是可维护的」。我拿到这类合集的第一步,不是直接跑,而是把里面自己需要用到的脚本全部过一遍「三件套」改造,顺手再过一个 shellcheck。这套习惯花不了多少时间,但能避免非常多线上事故。
三件套的第一件是脚本头部加严苛参数:
#!/bin/bash set -euo pipefail LOG_DIR="/var/log/scripts" log() { echo "[$(date +'%F %T')] $*" >> "$LOG_DIR/$(basename "$0").log"; } main() { local backup_dir="${1:-/data/backup}" # 具体逻辑 } main "$@"set -e让脚本在第一个出错命令处退出,-u对未定义变量直接报错,-o pipefail让管道中任何一段失败都算整体失败。这三个参数能让几百个脚本里「mysqldump 失败但脚本继续往下走」这类静默故障直接暴露出来。配合一个 log 函数统一记录时间戳,排查问题不用再去猜脚本是哪一秒跑的、第几步挂的。
第二件是静态检查。Shell 脚本没有编译器,shellcheck 就是 linter:
shellcheck -S warning backup_mysql.sh跑完会报三类问题:没加引号的变量、可移植性问题、明显的逻辑错误。批量检查整个目录的话用shellcheck -S warning /opt/scripts/*.sh,几百个脚本一次扫完,重点看 error 级别的问题,warning 级别按需处理。
第三件是调用方式标准化。脚本类的资源最容易死在「手工跑没问题,cron 跑就出岔子」,因为 cron 环境没有你的 PATH 和变量。统一模板:
30 2 * * * /opt/scripts/backup_mysql.sh >> /var/log/scripts/cron.log 2>&1脚本内部固定用/usr/bin/mysqldump这类绝对路径,变量声明全部移到 main 函数里,外部参数通过"$@"传进去,这样同一个脚本既能手工调试,也能直接上 cron。
灰度验证我是从一次数据恢复演练里长出来的教训:有一次新写的备份脚本跑了一周都没出声,等到真要恢复数据才发现 SQL 文件是坏的,原因是 mysqldump 的退出码被管道吞掉了。从那以后我每次拿到新脚本,都会强制走一遍三件套流程,再把脚本丢到测试机上用 mock 数据跑三次,确认退出码为 0、输出文件内容可解析,最后才挂 cron。脚本不是写出来就行,能扛住线上环境才算真的落地。这份合集里的 279 个脚本也一样,挑出适合你场景的那几十个,过一遍 shellcheck、改掉变量和路径,跑通了再谈效率。希望帮到你。
本文还有配套的精品资源,点击获取