简介:2024年新版Shell脚本集整理了279个开箱即用的脚本,面向Linux/Unix系统运维、信息安全防护和自动化运维学习者。合集将常见服务器维护任务按场景分类,从防御、监控到备份与分析均有对应脚本。内容覆盖DDoS攻击IP自动屏蔽、Linux系统邮件告警、MySQL数据库单循环与多循环备份、Nginx访问日志按天切割与访问分析、网卡实时流量查看等高频运维场景。每个脚本均给出可直接运行的示例,并对日期获取、日志过滤、iptables封禁、mysqldump导出、错误记录与cron定时执行等关键技术点作了解释,方便读者结合自身环境改写后投入生产。压缩包为单个PDF文档,大小4.69MB,内容以代码片段和注释讲解为主,适合作为日常服务器管理的速查手册。目前已有154人学习下载,适合希望快速掌握Shell实战技巧、提升自动化运维能力的初中级运维人员和开发者。
1. 279个开箱即用的shell脚本:运维场景覆盖最全的脚本工具包
做运维或者兼职管服务器的开发者,电脑里通常都躺着几十个自己写的脚本,但真到新装一台机器、突然被攻击、数据库要备份的时候,翻来找去还是那几招。这份脚本包把散落在各种博客、技术社区里的实用脚本收集成了一套279个可直接落地的工具集,从Dos攻击IP自动封禁、MySQL备份、Nginx日志切割分析,到系统初始化、磁盘监控、企业微信告警、FTP和SSH客户端操作,基本覆盖了单机运维和中小团队服务器管理的绝大多数场景。适合刚接触Linux的开发者照着敲一遍理解管道和awk的组合方式,也适合有经验的运维直接改参数丢进crontab里跑。脚本大多是纯Shell,少量Python辅助,拿到手改改路径就能用。
2. 日志与安全三件套:Nginx按天切割、访问分析、Dos攻击IP自动封禁
服务器上最先要做好的事就是日志管理。Nginx日志默认都写进同一个文件,跑上一个月文件体积会变得很大,分析的时候一条命令能卡半天,磁盘也被无谓占用。这个包里给的方案是三个独立脚本组合使用:按天切割归档、定期分析访问情况、对异常高频IP做防火墙封禁。三者可以各自独立跑,也能串成一条防御链路。
2.1 日志按天切割:mv加USR1信号才是标准姿势
网上很多日志切割方案直接kill Nginx进程或者用logrotate,前者会让正在处理的请求断开,后者配置项多容易把日志目录权限搞乱。这套脚本的思路是先把旧日志改名归档,再用USR1信号让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)逻辑上先算好昨天的日期,拼进归档文件名,然后按月建目录,最后通过kill -USR1通知Nginx重新打开日志文件。这个做法的关键是USR1信号,Nginx收到后会优雅地关闭旧文件句柄并创建新的日志文件,不需要重启服务。
归档目录按年月组织,比如/usr/local/nginx/logs/2024-11/default.access.log_2024-11-15,后续做月度清理或压缩都很方便。LOG_FILE_LIST变量支持多个日志文件,空格隔开,如果需要切割error.log,往列表里加就行。注意kill -USR1需要Nginx进程对执行脚本的用户可见,一般用root或nginx用户跑crontab。
2.2 访问日志分析:awk四连统计UV、PV、状态码和热点页面
日志切割解决了文件膨胀问题,分析脚本则从另一个维度帮你发现异常。输入文件路径作为参数,脚本会依次输出访问最多的10个IP、指定时间段内访问最多的IP、访问最多的10个页面,以及状态码与页面组合的统计。
#!/bin/bash LOG_FILE=$1 echo "统计访问最多的10个IP" awk '{a[$1]++}END{print "UV:",length(a);for(v in a)print v,a[v]}' $LOG_FILE |sort -k2 -nr |head -10 echo "----------------------" echo "统计时间段访问最多的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 echo "----------------------" echo "统计访问最多的10个页面" 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 echo "----------------------" echo "统计访问页面状态码数量" awk '{a[$7" "$9]++}END{for(v in a){if(a[v]>5)print v,a[v]}}'awk默认按空格拆分日志行,$1是客户端IP,$4是时间戳,$7是请求路径,$9是状态码。第一段统计把所有IP和出现次数放进数组a,END块里先打印UV(不重复IP数),再遍历数组输出每个IP的访问次数,sort -k2 -nr按第二列数字逆序排列,head -10取出前10。时间段过滤直接字符串比较$4字段,Nginx时间格式统一,比较逻辑成立。注意示例里的时间段写的是2018年的历史日期,实际使用时改成你要分析的时间窗口。
状态码统计把$7和$9拼成一个字段,比如/api/login 404,这样能直接看出哪些接口在报错。这个脚本对小白很友好,第一遍看awk数组逻辑有点绕,跑一次就能明白。
2.3 自动封禁策略:tail配合awk统计高频IP再交给iptables
脚本监控Nginx访问日志,统计当前小时内访问超过10次的IP,逐个检查是否已经在防火墙规则里,不在就写入iptables 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先取日志最后5000行,grep过滤出当前小时的内容,awk统计每个IP出现次数,超过10次就把IP交给for循环。iptables -vnL带v参数显示详细信息,grep -c判断IP是否已存在规则里,避免重复添加。-I INPUT -s $IP -j DROP把IP加在规则链最前面。
阈值10次每小时是比较宽松的,生产环境一般按秒计算,比如10秒内超过20次就可以封。我一般会改成awk '{a[$1]++} END {for(i in a) if(a[i]>20) print i}'只统计最近1000行里出现超20次的IP。如果服务器前面有负载均衡或CDN,$1拿到的是代理IP而不是真实客户端IP,这种情况下用$http_x_forwarded_for字段替代,脚本里注释已经提醒了这点。
3. MySQL备份脚本的两种循环:从整库导出到单表落盘
数据库备份是最不能出错的脚本之一。这个包里MySQL相关脚本给了两种方案,单循环适合中小库快速打包,多循环适合大库分表管理。两者都有一个共同的问题在安全性上:默认密码是硬编码的,生产环境一定要改成从配置文件读取或环境变量注入,否则数据库密码会跟着脚本一起泄露。
3.1 单循环备份:遍历全部数据库逐个dump
脚本先拿到所有数据库列表,过滤掉系统库,然后循环调用mysqldump把每个库导出成独立SQL文件。
#!/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 donemysql命令的-s参数是silent模式,去掉了表格边框线,输出每一行只有一个数据库名,方便后面循环处理。egrep -v正则在CentOS 7自带的MySQL 5.7上能正常工作,过滤掉information_schema等系统库。mysqldump的-B参数表示备份整个数据库,包括建库语句,恢复时直接source就能完整还原。
备份失败只打了一行echo,没有退出机制,如果磁盘满了或密码错误,脚本会静默失败然后继续循环下一个库。改进写法是维护一个失败计数器,最后汇总输出,或者配合上一章提到的mailx在失败时发告警邮件。
| 参数 | 作用 | 建议值 |
|---|---|---|
| --single-transaction | InnoDB表备份时保持一致快照 | 必须加 |
| --routines | 导出存储过程和函数 | 必须加 |
| --triggers | 导出触发器 | 必须加 |
| --set-gtid-purged=OFF | 不输出GTID信息,避免导入到无GTID实例时报错 | 视环境而定 |
3.2 多循环备份:每个表独立导出,恢复时按需加载
单循环的缺点是恢复时整库导入,想恢复单张表还是得全量倒一遍。多循环方案给每个数据库建一个子目录,每张表导出成一个独立SQL文件,恢复单表直接指定文件导入就行。
#!/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_DB_DIR=$BACKUP_DIR/${DB}_${DATE} [ ! -d $BACKUP_DB_DIR ] && mkdir -p $BACKUP_DB_DIR &>/dev/null 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外层循环遍历数据库,内层循环遍历表。备份目录结构是/data/db_backup/dbname_时间戳/表名.sql,时间戳精确到秒,即使一天跑多次备份也不会重名。mysqldump这里不带-B参数,因为指定了库名和表名,导出的SQL文件里只有表结构、数据和索引定义,恢复时先建库再逐表导入。
多循环备份的文件数量会很大,如果数据库有几百张表,一次备份产生几百个文件,stat慢、传输慢、清理也麻烦。折中方案是大表(超过5GB的)单独按表备份,小表合并成一个SQL打tar包。
3.3 备份脚本落地的几个强制习惯
备份脚本写出来只是第一步,真正要跑起来必须配置cron定时任务并做恢复演练。常见的做法是每天凌晨2点全量备份,周日凌晨做一次完整恢复验证,确认备份文件不是坏的。
# crontab配置示例:每天凌晨2点执行备份脚本 0 2 * * * /usr/local/scripts/mysql_backup_single.sh >> /var/log/mysql_backup.log 2>&1输出重定向到日志非常重要,cron的错误默认只发到root的邮箱,而很多服务器根本没配mail服务,脚本报错根本看不到。备份完成后保留最近7天或14天的文件,老的自动清理,避免磁盘被SQL文件撑爆。有条件的把备份文件再传到异地存储或另一台机器,防止服务器硬盘故障导致备份跟着一起丢。
4. 服务器初始化与安全加固:时区同步、SELinux、SSH和内核参数一键配置
新服务器到手要做的事基本是重复劳动:改时区、同步时间、关防火墙、禁用SELinux、调SSH配置、设置文件描述符上限、优化内核参数。这套初始化脚本把这堆操作串成一条命令,跑完再手工检查一遍关键项就行。
4.1 基础环境设置:时区、时间和历史命令记录
刚买的云服务器默认时区往往是UTC,日志时间跟本地对不上,排查问题时看时间戳就像在看玄学,完全对不上号。脚本先把时区软链到Asia/Shanghai,然后检查crontab里有没有ntpdate定时任务,没有就添加每小时的自动同步。
#/bin/bash # 设置时区并同步时间 ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime if ! crontab -l |grep ntpdate &>/dev/null ; then (echo "* 1 * * * ntpdate time.windows.com >/dev/null 2>&1";crontab -l) |crontab fi # 历史命令显示操作时间 if ! grep HISTTIMEFORMAT /etc/bashrc; then echo 'export HISTTIMEFORMAT="%F %T `whoami` "' >> /etc/bashrc fi时区软链是传统做法,新版CentOS用timedatectl set-timezone更正规,但脚本这样写兼容性更好。ntpdate用的时间源是time.windows.com,国内服务器访问这个地址延迟较高,可以换成ntp.aliyun.com或cn.pool.ntp.org,同步稳定性更好。
历史命令加时间戳是很多运维容易忽略但实际很重要的配置。事故排查时看到一行命令没时间戳,根本不知道什么时候执行的,只能猜。HISTTIMEFORMAT用反引号嵌套了whoami,登录用户也会记录进去,多用户共用的服务器上能看出是哪个人执行了什么操作。
4.2 安全加固:SELinux、防火墙、SSH和sudo权限
新装系统SELinux默认是enforcing模式,会导致很多服务行为异常,NGINX读不了文件、PHP连不上数据库、NFS挂载失败,而且报错不会直接说跟SELinux有关,排查起来非常痛苦。
# 禁用selinux sed -i '/SELINUX/{s/permissive/disabled/}' /etc/selinux/config # 关闭防火墙 if egrep "7.[0-9]" /etc/redhat-release &>/dev/null; then systemctl stop firewalld systemctl disable firewalld elif egrep "6.[0-9]" /etc/redhat-release &>/dev/null; then service iptables stop chkconfig iptables off fi # 禁止root远程登录 sed -i 's/#PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config # 禁止定时任务向发送邮件 sed -i 's/^MAILTO=root/MAILTO=""/' /etc/crontabSELinux的sed替换只改配置文件,改完需要重启才生效,或者执行setenforce 0临时关闭。脚本没有做这步,跑完初始化后当前会话还是enforcing状态,新部署的服务依然会被拦截,建议补上setenforce 0。
关闭防火墙的分支判断按系统版本走,CentOS 7以上用systemctl操作firewalld,6.x用service和chkconfig操作iptables。生产环境其实不建议直接关闭防火墙,正确做法是把要用的端口加白名单。如果这台机器还要对外提供服务,关了firewalld等于裸奔。
禁止root远程登录这里有个坑:脚本直接改sshd_config里的PermitRootLogin,如果之前配过公钥登录,改完后root的SSH密钥认证并不会受影响,只有密码登录被禁用。但如果你正在用root密码登录远程改这个脚本,执行完重启sshd前一定要先开一个测试连接,确认普通用户能登录,否则自己把自己锁在外面。
4.3 系统资源限制与内核参数调整
文件描述符上限默认1024,Nginx高并发时很快会触及这个门槛,报too many open files错误。进程数上限、SWAP使用倾向也要在初始化阶段调好。
# 设置最大打开文件数 if ! grep "* soft nofile 65535" /etc/security/limits.conf &>/dev/null; then cat >> /etc/security/limits.conf << EOF * soft nofile 65535 * hard nofile 65535 EOF fi # 系统内核优化 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 # 减少SWAP使用 echo "0" > /proc/sys/vm/swappinesslimits.conf里的*通配符对所有用户生效,soft是软限制,hard是硬限制,普通用户不能超过hard值。这里设置65535对绝大多数场景足够,如果做高并发网关可以再往上调。
内核参数里tcp_syncookies开启SYN Cookie防御半连接攻击,tcp_max_tw_buckets控制TIME_WAIT状态连接数上限,超过后内核会回收旧连接。tcp_fin_timeout从默认60秒降到20秒,减少处于FIN_WAIT_2状态的时间,加快连接释放。这些参数对高并发Web服务器有实际效果,改完要执行sysctl -p加载。
swappiness写入/proc/sys/vm/swappiness是临时生效的,重启就恢复默认值60,需要写进/etc/sysctl.conf才能持久化。这个脚本只做了临时修改,算是一个留白,介意的人可以自己补一行配置。
5. 这些脚本最容易翻车的五个坑:踩过的人都懂
脚本本身大多是能跑的,但直接拿去生产环境用,分分钟教你做人。整理几个最常见的翻车点,都是我实际跑过之后总结出来的。
5.1 脚本里的日志路径和实际Nginx路径对不上
现象:跑Dos攻击防范脚本,提示/usr/local/nginx/logs/demo2.access.log不存在,脚本直接报错退出。
原因:脚本写死的路径是从某台服务器上拷贝来的,编译安装Nginx的路径通常是/usr/local/nginx/logs,但用包管理器装的可能在/var/log/nginx,路径对不上导致tail命令找不到文件。
解决:先确认实际日志路径,find / -name "*.access.log" 2>/dev/null扫一遍,改掉脚本里的LOG_FILE变量。路径确认后先手动跑一遍切割和分析脚本,确认没有报错再进crontab。
5.2 mysqldump备份的密码硬编码和特殊字符
现象:数据库密码含有@、#、&等特殊字符,脚本执行报错Access denied或者密码被截断。
原因:mysqldump命令行接密码时特殊字符会被Shell解释,比如123.com没问题,但换成abc@123时@之后的内容可能被当成主机名处理。
解决:密码写入/etc/my.cnf的[client]段或者~/.my.cnf,脚本里只写-p不带密码,让mysql客户端自动读取配置文件。另一种方案是用环境变量MYSQL_PWD,但要注意ps能看到环境变量,安全性一般。
5.3 运维时区统一问题导致日志时间错乱
现象:Nginx访问日志按天切割脚本跑完,归档文件名是昨天,但文件里最新的记录是今天凌晨的。
原因:date -d "yesterday"拿到的日期取决于服务器本地时区,如果时区是UTC,北京时间早上8点之前切割会把昨天的记录切成今天的。
解决:先把时区统一成Asia/Shanghai再调试切割脚本,否则必须提前一个小时跑切割任务来规避时差。我一般会把脚本里切割时间固定在凌晨4点执行,避开零点访问高峰和时区混淆,确认服务器时区是东八区后再部署。
5.4 iptables规则被清空导致封禁失效
现象:Dos攻击防范脚本跑完,短时间后又出现同一IP的高频访问。
原因:云服务器的安全组或自动恢复机制定期重置防火墙规则,或者服务器重启后iptables规则没有被持久化保存。
解决:iptables-save > /etc/sysconfig/iptables保存规则,或者用云平台的安全组白名单/黑名单功能替代iptables。脚本里加一层自检:跑之前先数一下当前iptables规则数量,少于预期值时先加载备份规则再执行封禁逻辑。
5.5 并发执行SSH批量命令时卡死
现象:多主机hostname获取脚本运行后卡住,长时间无输出,不得不Ctrl+C。
原因:SSH默认在终端交互模式下会等待密码输入,脚本里没加BatchMode参数,碰到目标主机密钥变更或认证失败直接hang住。
解决:ssh命令加-o BatchMode=yes -o ConnectTimeout=5 -o StrictHostKeyChecking=no,BatchMode禁止交互式输入密码,失败立即返回;ConnectTimeout控制连接超时时间,避免耗尽等待;StrictHostKeyChecking=no跳过首次连接确认,批量部署时省去手动输入yes的操作。
6. 把脚本从“能跑”改成“能维护”的三个习惯
脚本拿过来直接跑只能算会用,真正能长久运转下去,还得靠几个维护习惯。第一个习惯,所有脚本统一放在一个目录下,比如/opt/ops_scripts,按功能分子目录放,数据库备份的、日志切割的、安全封禁的各自独立。改密码或路径时全局搜索一下,不比东一个西一个找脚本容易遗漏。
第二个习惯,每个脚本开头写清楚三块信息:脚本作用是什么、依赖哪些命令和工具、验收方式是什么。比如MySQL备份脚本开头注明依赖mysqldump和mysql客户端、需要备份用户的select和lock tables权限、验收方式是恢复验证脚本能跑通。这样三个月后回来看脚本,不用从第一行开始推理逻辑。
第三个习惯,强制加set -euo pipefail。很多脚本坑都在于某个命令失败后继续往下跑,备份失败了还在执行后续循环,切割失败了还在发信号。set -e遇到第一个错误就退出,set -u变量未定义时报错,set -o pipefail管道中任何命令失败都会导致整体失败。脚本开头加上set -euo pipefail,后面再写判断逻辑更放心。代价是某些命令正常返回非零值会导致脚本中断,比如grep没匹配到内容,这时用|| true或if grep ...; then规避。
从那以后我每次部署脚本,都强制走一遍这三步:改路径变量、跑一遍验证输出、加set -euo pipefail。尤其是MySQL备份脚本和老旧主机名的批量探测脚本,少了set -e会在密码错误时无提示地继续执行,生成一堆空文件,看着像正常其实是废数据。把这三个习惯养成,279个脚本里的八成都可以直接当作生产工具长期使用,剩下的两成需要按自己的网络环境和服务器状态调一调参数。
把这些脚本全部过一遍后,你会发现大部分问题都有现成的解法,关键是要知道边界在哪,参数怎么改,踩坑点长什么样。希望这些能实实在在帮你少走几步弯路。
本文还有配套的精品资源,点击获取