Linux学习之旅之Shell 脚本(二)
七、sed/awk 文本处理
7.1、sed 文本处理工具
7.1.1、sed基础替换命令S
7.1.1.1、 s/old/new/g 分隔符/# | 三种写法、g 全局匹配
- s 是 sed 替换核心子命令,语法 s/查找内容/替换内容/标记 ;
- 分隔符 / 可自定义为 # | 规避内容包含斜杠转义麻烦;
- 标记 g = global 全局匹配:一行内所有匹配字符全部替换;不加 g 仅替换每行第一个匹配内容。
bash
#准备测试文档
[root@Ubuntu24-7:~#] cat > test << EOF
nginx port 80, nginx root /html/nginx
nginx port 80
EOF
[root@Ubuntu24-7:~#] cat test
nginx port 80, nginx root /html/nginx
nginx port 80
#s///后不加g表示只匹配每行的第一个;(不加选项-i表示只看结果,不进行替换)
[root@Ubuntu24-7:~#] sed 's/nginx/Nginx/' test
Nginx port 80, nginx root /html/nginx
Nginx port 80
#s///后加g表示全文匹配
[root@Ubuntu24-7:~#] sed 's/nginx/Nginx/g' test
Nginx port 80, Nginx root /html/Nginx
Nginx port 80
#如果匹配的内容有/,可以用其他字符替换,通常使用#、|字符进行代替
[root@Ubuntu24-7:~#] sed 's#/html/nginx#/data/nginx#g' test
nginx port 80, nginx root /data/nginx
nginx port 80
[root@Ubuntu24-7:~#] sed 's|80|8080|g' test
nginx port 8080, nginx root /html/nginx
nginx port 8080
[root@Ubuntu24-7:~#]
7.1.1.2 数字 n 单条替换、& 匹配内容复用、反向引用分组 \1
- 末尾数字 n:仅替换该行第 n 处匹配内容, s/80/8080/2 只替换每行第二个 80;
- & 代表完整匹配到的原始内容,用于在原有文字前后追加字符;
- (内容) 分组捕获, \1 反向引用第一组捕获文本, \2 第二组,常用于截取、重组字符串。
bash
[root@Ubuntu24-7:~#] cat > port << EOF
port=80 port=80 port=80
EOF
[root@Ubuntu24-7:~#] cat port
port=80 port=80 port=80
#s///后加数据表示每行第几个匹配
[root@Ubuntu24-7:~#] sed 's/80/8080/2' port
port=80 port=8080 port=80
#&将前面匹配的内容后面加上&后的内容变成一个整体
[root@Ubuntu24-7:~#] sed 's/80/& #http端口/g' port
port=80 #http端口 port=80 #http端口 port=80 #http端口
# 分组捕获反向引用:提取数字重组格式,s//中\几/表示匹配第几组文本
[root@Ubuntu24-7:~#] echo 'port=3306' | sed 's/port=\([0-9]*\)/mysql_port:\1/'
mysql_port:3306
[root@Ubuntu24-7:~#] echo "user=root pass=123456" | sed 's/user=\([a-z]*\) pass=\([0-9]*\)/账号:\1 密码:\2/'
账号:root 密码:123456
[root@Ubuntu24-7:~#]
7.1.1.3行范围限定 1,10s、/start/,/end/s 区间替换
两种寻址方式限定替换生效行,不全局全文件替换:
- 数字区间 m,ns :从第 m 行到第 n 行执行替换; 1,s代表全文(s 代表全文(s代表全文( 最后一行);
- 模式区间 /匹配起始/,/匹配结束/s :匹配到 start 行开始,匹配到 end 行结束,中间区间执行替换。
bash
[root@Ubuntu24-7:~#] cat > nginx_range.conf <<EOF
# 全局公共配置
error_log /var/log/nginx/error.log;
worker_processes 2;
# server 站点段
server {
listen 80;
root /html/www;
}
# 第二个站点
server {
listen 80;
root /html/blog;
}
EOF
[root@Ubuntu24-7:~#] cat -n nginx_range.conf
1 # 全局公共配置
2 error_log /var/log/nginx/error.log;
3 worker_processes 2;
4 # server 站点段
5 server {
6 listen 80;
7 root /html/www;
8 }
9 # 第二个站点
10 server {
11 listen 80;
12 root /html/blog;
13 }
#匹配1至5行中80替换8080:1-5行没有80所以替换失败
[root@Ubuntu24-7:~#] sed '1,5s/80/8080/g' nginx_range.conf
# 全局公共配置
error_log /var/log/nginx/error.log;
worker_processes 2;
# server 站点段
server {
listen 80;
root /html/www;
}
# 第二个站点
server {
listen 80;
root /html/blog;
}
#替换3至最后一行
[root@Ubuntu24-7:~#] sed '3,$s/80/8080/g' nginx_range.conf
# 全局公共配置
error_log /var/log/nginx/error.log;
worker_processes 2;
# server 站点段
server {
listen 8080;
root /html/www;
}
# 第二个站点
server {
listen 8080;
root /html/blog;
}
#匹配以server开头到}结尾的内容
[root@Ubuntu24-7:~#] sed '/server {/,/}/s/listen 80/listen 8080/g' nginx_range.conf
# 全局公共配置
error_log /var/log/nginx/error.log;
worker_processes 2;
# server 站点段
server {
listen 8080;
root /html/www;
}
# 第二个站点
server {
listen 8080;
root /html/blog;
}
#匹配以#server开头到}结尾的内容
[root@Ubuntu24-7:~#] sed '/# server/,/}/s/listen 80/listen 8080/g' nginx_range.conf
# 全局公共配置
error_log /var/log/nginx/error.log;
worker_processes 2;
# server 站点段
server {
listen 8080;
root /html/www;
}
# 第二个站点
server {
listen 80;
root /html/blog;
}
[root@Ubuntu24-7:~#]
7.1.2 sed删除、插入追加命令
7.1.2.1、d删除匹配行、1,5d删除区间行、/^$/d删除空行
d 为删除子命令,删除寻址匹配到的完整行,常用于清理配置注释、空白行、无效日志:
- m,nd 删除第 m~n 行;
- /正则/d 删除匹配正则表达式的所有行;
- /^$/d 匹配纯空行并删除,精简配置必备操作。
bash
#搞几个空行
[root@Ubuntu24-7:~#] vim nginx_range.conf
[root@Ubuntu24-7:~#] cat -n nginx_range.conf
1 # 全局公共配置
2
3 error_log /var/log/nginx/error.log;
4
5 worker_processes 2;
6
7 # server 站点段
8
9 server {
10
11 listen 80;
12
13 root /html/www;
14 }
15
16 # 第二个站点
17
18 server {
19
20 listen 80;
21
22 root /html/blog;
23
24 }
#删除1至3行
[root@Ubuntu24-7:~#] sed '1,3d' nginx_range.conf
worker_processes 2;
# server 站点段
server {
listen 80;
root /html/www;
}
# 第二个站点
server {
listen 80;
root /html/blog;
}
#删除注释行号
[root@Ubuntu24-7:~#] sed '/^#/d' nginx_range.conf
error_log /var/log/nginx/error.log;
worker_processes 2;
server {
listen 80;
root /html/www;
}
server {
listen 80;
root /html/blog;
}
#删除所有空行
[root@Ubuntu24-7:~#] sed '/^$/d' nginx_range.conf
# 全局公共配置
error_log /var/log/nginx/error.log;
worker_processes 2;
# server 站点段
server {
listen 80;
root /html/www;
}
# 第二个站点
server {
listen 80;
root /html/blog;
}
#删除所有空行以及注释行
[root@Ubuntu24-7:~#] sed '/^$/d;/^#/d' nginx_range.conf
error_log /var/log/nginx/error.log;
worker_processes 2;
server {
listen 80;
root /html/www;
}
server {
listen 80;
root /html/blog;
}
[root@Ubuntu24-7:~#]
7.1.2.2、i 行前插入、a 行后追加、c 整行替换操作
- i insert:在匹配行上方插入文本;
- a append:在匹配行下方追加文本;
- c change:将匹配到的整行完全替换为新内容。
bash
[root@Ubuntu24-7:~#] cat -n nginx_range.conf
1 # 全局公共配置
2
3 error_log /var/log/nginx/error.log;
4
5 worker_processes 2;
6
7 # server 站点段
8
9 server {
10
11 listen 80;
12
13 root /html/www;
14 }
15
16 # 第二个站点
17
18 server {
19
20 listen 80;
21
22 root /html/blog;
23
24 }
#在第一行插入
[root@Ubuntu24-7:~#] sed '1i # 看什么看' nginx_range.conf
# 看什么看
# 全局公共配置
error_log /var/log/nginx/error.log;
worker_processes 2;
# server 站点段
server {
listen 80;
root /html/www;
}
# 第二个站点
server {
listen 80;
root /html/blog;
}
#匹配listen 80后追加
[root@Ubuntu24-7:~#] sed '/listen 80/a 我就看怎么滴' nginx_range.conf
# 全局公共配置
error_log /var/log/nginx/error.log;
worker_processes 2;
# server 站点段
server {
listen 80;
我就看怎么滴
root /html/www;
}
# 第二个站点
server {
listen 80;
我就看怎么滴
root /html/blog;
}
[root@Ubuntu24-7:~#
#匹配listen 80整行替换
[root@Ubuntu24-7:~#] sed '/listen 80/c 那就看吧' nginx_range.conf
# 全局公共配置
error_log /var/log/nginx/error.log;
worker_processes 2;
# server 站点段
server {
那就看吧
root /html/www;
}
# 第二个站点
server {
那就看吧
root /html/blog;
}
7.1.2.3、行号寻址 (1,10)、模式寻址、步长 1~2 奇数行打印
sed 两套寻址规则,搭配步长 ~ 实现间隔行匹配, -n 参数只打印匹配行,屏蔽默认全文件输出:
数字寻址 m,n :固定行号区间; m~step 从 m 行开始,每隔 step 行匹配; 1~2 奇数行、 2~2 偶数行;
模式寻址 /正则/ :匹配满足正则规则的所有行。
bash
[root@Ubuntu24-7:~#] cat > access_test.log <<EOF
192.168.1.10 200 GET /index.html
192.168.1.11 502 GET /api
192.168.1.12 200 POST /login
192.168.1.13 404 GET /test
192.168.1.14 200 GET /static
192.168.1.15 500 GET /pay
192.168.1.16 200 GET /home
192.168.1.17 302 GET /redirect
192.168.1.18 200 GET /user
192.168.1.19 503 GET /gateway
192.168.1.20 200 GET /about
192.168.1.21 403 GET /admin
192.168.1.22 200 GET /shop
192.168.1.23 502 GET /order
192.168.1.24 200 GET /cart
EOF
#打印1至10行
[root@Ubuntu24-7:~#] sed -n '1,10p' access_test.log
192.168.1.10 200 GET /index.html
192.168.1.11 502 GET /api
192.168.1.12 200 POST /login
192.168.1.13 404 GET /test
192.168.1.14 200 GET /static
192.168.1.15 500 GET /pay
192.168.1.16 200 GET /home
192.168.1.17 302 GET /redirect
192.168.1.18 200 GET /user
192.168.1.19 503 GET /gateway
#打印奇数行
[root@Ubuntu24-7:~#] sed -n '1~2p' access_test.log
192.168.1.10 200 GET /index.html
192.168.1.12 200 POST /login
192.168.1.14 200 GET /static
192.168.1.16 200 GET /home
192.168.1.18 200 GET /user
192.168.1.20 200 GET /about
192.168.1.22 200 GET /shop
192.168.1.24 200 GET /cart
#打印偶数行
[root@Ubuntu24-7:~#] sed -n '2~2p' access_test.log
192.168.1.11 502 GET /api
192.168.1.13 404 GET /test
192.168.1.15 500 GET /pay
192.168.1.17 302 GET /redirect
192.168.1.19 503 GET /gateway
192.168.1.21 403 GET /admin
192.168.1.23 502 GET /order
#关键字打印
[root@Ubuntu24-7:~#] sed -n '/5[0-9]*/p' access_test.log
192.168.1.11 502 GET /api
192.168.1.15 500 GET /pay
192.168.1.19 503 GET /gateway
192.168.1.23 502 GET /order
[root@Ubuntu24-7:~#]
#区间打印
[root@Ubuntu24-7:~#] sed -n '/192.168.1.13/,/192.168.1.20/p' access_test.log
192.168.1.13 404 GET /test
192.168.1.14 200 GET /static
192.168.1.15 500 GET /pay
192.168.1.16 200 GET /home
192.168.1.17 302 GET /redirect
192.168.1.18 200 GET /user
192.168.1.19 503 GET /gateway
192.168.1.20 200 GET /about
7.2、awk报表分析工具
7.2.1、awk核心结构pattern {action}
7.2.1.1、 正则 /regex/ 数值比较 NR>10 匹配条件写法
pattern 匹配条件分为两类,满足条件才会执行后面 {action} 代码块:
- 正则匹配 /关键词/ :匹配包含指定内容的整行;
- 数值条件比较:内置变量 NR (当前行号)、字段 $n 做大小判断;无 pattern 时代表全部行匹配,所有行都会执行动作
bash
[root@Ubuntu24-7:~#] cat > access_awk.log <<EOF
192.168.1.5 200 /index.html 0.2s
192.168.1.6 502 /api/pay 3.5s
192.168.1.7 404 /test
192.168.1.5 200 /login 0.1s
192.168.1.8 500 /gateway 4.2s
192.168.1.9 200 /shop 0.3s
192.168.1.5 302 /redirect
192.168.1.10 503 /order 5.1s
EOF
#匹配500的输出整行
[root@Ubuntu24-7:~#] awk '/5[0-9][0-9]/{print $0}' access_awk.log
192.168.1.6 502 /api/pay 3.5s
192.168.1.8 500 /gateway 4.2s
192.168.1.10 503 /order 5.1s
#输出第三行及以后且第一列
[root@Ubuntu24-7:~#] awk 'NR>2 {print $1}' access_awk.log
192.168.1.7
192.168.1.5
192.168.1.8
192.168.1.9
192.168.1.5
192.168.1.10
#输出第三行及以后且第一列和第二列
[root@Ubuntu24-7:~#] awk 'NR>2 {print $1,$2}' access_awk.log
192.168.1.7 404
192.168.1.5 200
192.168.1.8 500
192.168.1.9 200
192.168.1.5 302
192.168.1.10 503
#输出第四列大于3的内容
[root@Ubuntu24-7:~#] awk '$4 > 3 {print "慢请求:",$0}' access_awk.log
慢请求: 192.168.1.6 502 /api/pay 3.5s
慢请求: 192.168.1.8 500 /gateway 4.2s
慢请求: 192.168.1.10 503 /order 5.1s
#输出192.168.1.5且200的内容
[root@Ubuntu24-7:~#] awk '$1=="192.168.1.5" && $2==200 {print $0}' access_awk.log
192.168.1.5 200 /index.html 0.2s
192.168.1.5 200 /login 0.1s
[root@Ubuntu24-7:~#]
7.2.1.2、print 打印、字段赋值、条件判断 action 动作块
{action} 是匹配成功后执行的代码块,支持:
- print / printf 输出文本、字段、自定义字符串;
- 自定义变量赋值、字段 $n 重新赋值修改文本;
- 内置 if/else 分支判断,实现多层业务逻辑。
bash
#自定义输出
[root@Ubuntu24-7:~#] awk '{print "IP地址: ",$1 " 状态码: ",$2}' access_awk.log
IP地址: 192.168.1.5 状态码: 200
IP地址: 192.168.1.6 状态码: 502
IP地址: 192.168.1.7 状态码: 404
IP地址: 192.168.1.5 状态码: 200
IP地址: 192.168.1.8 状态码: 500
IP地址: 192.168.1.9 状态码: 200
IP地址: 192.168.1.5 状态码: 302
IP地址: 192.168.1.10 状态码: 503
#匹配修改
[root@Ubuntu24-7:~#] awk '$2=="502" {$2="ERROR"} {print $0}' access_awk.log
192.168.1.5 200 /index.html 0.2s
192.168.1.6 ERROR /api/pay 3.5s
192.168.1.7 404 /test
192.168.1.5 200 /login 0.1s
192.168.1.8 500 /gateway 4.2s
192.168.1.9 200 /shop 0.3s
192.168.1.5 302 /redirect
192.168.1.10 503 /order 5.1s
#分级标记
[root@Ubuntu24-7:~#] awk '{
if($2 ~ /^5/) {
level="严重异常"
} else if($2 ~ /^4/) {
level="客户端错误"
} else {
level="正常访问"
}
print $0,"【",level,"】"
}' access_awk.log
192.168.1.5 200 /index.html 0.2s 【 正常访问 】
192.168.1.6 502 /api/pay 3.5s 【 严重异常 】
192.168.1.7 404 /test 【 客户端错误 】
192.168.1.5 200 /login 0.1s 【 正常访问 】
192.168.1.8 500 /gateway 4.2s 【 严重异常 】
192.168.1.9 200 /shop 0.3s 【 正常访问 】
192.168.1.5 302 /redirect 【 正常访问 】
192.168.1.10 503 /order 5.1s 【 严重异常 】
[root@Ubuntu24-7:~#]
7.2.2、awk 内置变量体系
7.2.2.1、$0 整行、1 1~1 n 字段、NR 行号、NF 总字段数
- $0 :代表当前读取的完整一整行文本;
- 1 2 3 ... NF :按分隔符切割后的第 1、2、3... 列字段;
- NR Number of Record:当前处理行的行号;
- NF Number of Field:当前行切割后总字段数量, $NF 直接取最后一列。
bash
#输出所有内容
[root@Ubuntu24-7:~#] awk '{print $0}' access_awk.log
192.168.1.5 200 /index.html 0.2s
192.168.1.6 502 /api/pay 3.5s
192.168.1.7 404 /test
192.168.1.5 200 /login 0.1s
192.168.1.8 500 /gateway 4.2s
192.168.1.9 200 /shop 0.3s
192.168.1.5 302 /redirect
192.168.1.10 503 /order 5.1s
#自定义输出
[root@Ubuntu24-7:~#] awk '{print $1,"路径:"$3,"耗时:"$NF}' access_awk.log
192.168.1.5 路径:/index.html 耗时:0.2s
192.168.1.6 路径:/api/pay 耗时:3.5s
192.168.1.7 路径:/test 耗时:/test
192.168.1.5 路径:/login 耗时:0.1s
192.168.1.8 路径:/gateway 耗时:4.2s
192.168.1.9 路径:/shop 耗时:0.3s
192.168.1.5 路径:/redirect 耗时:/redirect
192.168.1.10 路径:/order 耗时:5.1s
#打印每行行号NR + 总行数NF
[root@Ubuntu24-7:~#] awk '{print "行号: "NR,"字段数:"NF}' access_awk.log
行号: 1 字段数:4
行号: 2 字段数:4
行号: 3 字段数:3
行号: 4 字段数:4
行号: 5 字段数:4
行号: 6 字段数:4
行号: 7 字段数:3
行号: 8 字段数:4
#输出脏数据行
[root@Ubuntu24-7:~#] awk 'NF!=4 {print "脏数据:"$0}' access_awk.log
脏数据:192.168.1.7 404 /test
脏数据:192.168.1.5 302 /redirect
[root@Ubuntu24-7:~#]
7.2.2.2、FS 输入分隔符、OFS输出分隔符、RS/ORS记录分隔
- FS Field Separator:输入文本切割字段的分隔符,默认空白; -F 等价于前置修改 FS;
- OFS Output Field Separator:print 输出多字段时,字段之间的分隔符,默认空格;
- RS Record Separator:行读取分隔符,默认换行 \n ;
- ORS Output Record Separator:每行输出结尾换行符,默认 \n 。
bash
[root@Ubuntu24-7:~#] cat > passwd_test.txt <<EOF
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
EOF
#awk默认以空格为分隔符,---F 指定分隔符
[root@Ubuntu24-7:~#] awk -F ':' '{print $1,$6}' passwd_test.txt
root /root
daemon /usr/sbin
www-data /var/www
#BEGIN指定分隔符
[root@Ubuntu24-7:~#] awk 'BEGIN{FS=":"} {print $1,$6}' passwd_test.txt
root /root
daemon /usr/sbin
www-data /var/www
#OFS指定输出间隔符
[root@Ubuntu24-7:~#] awk 'BEGIN{FS=":";OFS=" | "} {print $1,$6}' passwd_test.txt
root | /root
daemon | /usr/sbin
www-data | /var/www
#ORS不换行
[root@Ubuntu24-7:~#] awk 'BEGIN{ORS=","} {print $1}' passwd_test.txt
root:x:0:0:root:/root:/bin/bash,daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin,www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin,[root@Ubuntu24-7:~#]
7.2.2.3、FILENAME当前文件名多文件遍历处理
FILENAME 内置变量:awk 同时传入多个文件时,保存当前正在处理的文件名,用于区分多文件日志、批量报表汇总。
bash
[root@Ubuntu24-7:~#] cat > access_second.log <<EOF
10.0.0.1 200 /api/user 0.1s
10.0.0.2 502 /api/pay 2.8s
EOF
[root@Ubuntu24-7:~#] awk '{print "文件:"FILENAME,"行号:"NR,"内容:"$0}' access_awk.log access_second.log
文件:access_awk.log 行号:1 内容:192.168.1.5 200 /index.html 0.2s
文件:access_awk.log 行号:2 内容:192.168.1.6 502 /api/pay 3.5s
文件:access_awk.log 行号:3 内容:192.168.1.7 404 /test
文件:access_awk.log 行号:4 内容:192.168.1.5 200 /login 0.1s
文件:access_awk.log 行号:5 内容:192.168.1.8 500 /gateway 4.2s
文件:access_awk.log 行号:6 内容:192.168.1.9 200 /shop 0.3s
文件:access_awk.log 行号:7 内容:192.168.1.5 302 /redirect
文件:access_awk.log 行号:8 内容:192.168.1.10 503 /order 5.1s
文件:access_second.log 行号:9 内容:10.0.0.1 200 /api/user 0.1s
文件:access_second.log 行号:10 内容:10.0.0.2 502 /api/pay 2.8s
7.2.3、BEGIN/END预处理后置处理块
7.2.3.1、 BEGIN块:脚本开头初始化变量、打印表头
BEGIN{} 在读取任何文件内容之前执行,仅运行一次;用于:
- 自定义分隔符 FS/OFS;
- 初始化统计计数变量;
- 打印报表固定表头,格式化输出标题。
bash
[root@Ubuntu24-7:~#] awk BEGIN'{FS=" ";print "-----------你瞅啥----------\n"}{print $0}' access_awk.log
-----------你瞅啥----------
192.168.1.5 200 /index.html 0.2s
192.168.1.6 502 /api/pay 3.5s
192.168.1.7 404 /test
192.168.1.5 200 /login 0.1s
192.168.1.8 500 /gateway 4.2s
192.168.1.9 200 /shop 0.3s
192.168.1.5 302 /redirect
192.168.1.10 503 /order 5.1s
[root@Ubuntu24-7:~#] awk 'BEGIN{FS=" ";OFS="\t";printf "%-15s %-8s %s\n","客户端IP","状态码","访问网络"}{printf "%-15s %-8s %s\n",$1,$2,$3}' access_awk.log
客户端IP 状态码 访问网络
192.168.1.5 200 /index.html
192.168.1.6 502 /api/pay
192.168.1.7 404 /test
192.168.1.5 200 /login
192.168.1.8 500 /gateway
192.168.1.9 200 /shop
192.168.1.5 302 /redirect
192.168.1.10 503 /order
7.2.3.2、 END块:全部输入完成后汇总统计、打印结果
END{} 在所有文件全部读取完毕后执行,仅运行一次;用于全局汇总:总请求量、错误总数、平均值、打印统计汇总报表。
bash
[root@Ubuntu24-7:~#]
awk 'BEGIN{FS=" ";OFS="\t";printf "%-15s %-8s %s\n","客户端IP","状态码","访问网络"}{printf "%-15s %-8s %s\n",$1,$2,$3}END{print "------------结束---------------"}' access_awk.log
客户端IP 状态码 访问网络
192.168.1.5 200 /index.html
192.168.1.6 502 /api/pay
192.168.1.7 404 /test
192.168.1.5 200 /login
192.168.1.8 500 /gateway
192.168.1.9 200 /shop
192.168.1.5 302 /redirect
192.168.1.10 503 /order
------------结束---------------
7.2.4、awk判断、循环、数组、内置函数
7.2.4.1、 if/else、三元*expr?true:false、&&||!逻辑运算
if/else if/else :多分支条件判断,用于日志分级、阈值告警;
三元运算符 条件?成立表达式:不成立表达式 :单行极简判断赋值;
逻辑运算符:
&& 逻辑与:两边条件同时满足;
|| 逻辑或:任意一边满足;
! 逻辑非:取反,条件反转。
bash
# 1. if else 多分支标记日志等级
[root@Ubuntu24-7:~#] awk '{
if($2 ~ /^5/){
level="严重错误"}
else if($2 ~ /^4/){
level="客户端错误"}
else{
level="正常"}
print $0,"["level"]"
}' access_awk.log
192.168.1.5 200 /index.html 0.2s [正常]
192.168.1.6 502 /api/pay 3.5s [严重错误]
192.168.1.7 404 /test [客户端错误]
192.168.1.5 200 /login 0.1s [正常]
192.168.1.8 500 /gateway 4.2s [严重错误]
192.168.1.9 200 /shop 0.3s [正常]
192.168.1.5 302 /redirect [正常]
192.168.1.10 503 /order 5.1s [严重错误]
# 2. 三元运算符简写赋值
[root@Ubuntu24-7:~#] awk '{level = $2~/^5/ ? "异常":"正常";print $1,$2,level}' access_awk.log
192.168.1.5 200 正常
192.168.1.6 502 异常
192.168.1.7 404 正常
192.168.1.5 200 正常
192.168.1.8 500 异常
192.168.1.9 200 正常
192.168.1.5 302 正常
192.168.1.10 503 异常
# 3. 逻辑运算 && || ! 组合条件
# IP=192.168.1.5 且状态码200
[root@Ubuntu24-7:~#] awk '$1=="192.168.1.5" && $2==200 {print $0}' access_awk.log
192.168.1.5 200 /index.html 0.2s
192.168.1.5 200 /login 0.1s
7.2.4.2、关联数组统计、for/in遍历、while/do while循环
awk 无普通数字数组,只有关联数组(key-value 键值对),常用于 IP 计数、状态码统计、分组聚合;
for(key in arr) :遍历数组所有键,END 块汇总报表必备;
while(条件){} / do{}while(条件) :数值循环,用于字段拆分、批量运算。
bash
#创建数组
[root@Ubuntu24-7:~#] num=(1 2 3 4 5 6 7)
[root@Ubuntu24-7:~#] num[7]=8
[root@Ubuntu24-7:~#] echo ${num[@]}
1 2 3 4 5 6 7 8
[root@Ubuntu24-7:~#] declare -a | grep num
declare -a num=([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="7" [7]="8")
[root@Ubuntu24-7:~#]
[root@Ubuntu24-7:~#] awk '{
ip_arr[$1]++
}END{
print "==== IP访问统计 ===="
for(ip in ip_arr){
print ip,"访问次数:",ip_arr[ip]
}
}' access_awk.log
==== IP访问统计 ====
192.168.1.7 访问次数: 1
192.168.1.8 访问次数: 1
192.168.1.9 访问次数: 1
192.168.1.10 访问次数: 1
192.168.1.5 访问次数: 3
192.168.1.6 访问次数: 1
[root@Ubuntu24-7:~#] awk 'BEGIN{
i=1
while(i<=5){
print "数字:",i
i++
}
}'
数字: 1
数字: 2
数字: 3
数字: 4
数字: 5
7.3、Shell循环控制
7.3.1、三类循环完整语法
7.3.1.1、 while循环、until循环条件执行逻辑差异
while 条件 ; do 循环体; done
条件成立时持续执行循环;条件不成立立刻退出。
until 条件 ; do 循环体; done
条件不成立时持续执行循环;条件成立立刻退出。
两者逻辑完全取反;while 使用场景远多于 until。
bash
#while满足条件循环,小于等于5输出
[root@Ubuntu24-7:~#] i=1
[root@Ubuntu24-7:~#] while [[ $i -le 5 ]];
do
echo "while计数: $i";
((i++));
done
while计数: 1
while计数: 2
while计数: 3
while计数: 4
while计数: 5
#until不满足条件输出,不大于5输出
[root@Ubuntu24-7:~#] i=1
[root@Ubuntu24-7:~#] until [[ $i -gt 5 ]];
do
echo "until计数: $i";
((i++));
done
until计数: 1
until计数: 2
until计数: 3
until计数: 4
until计数: 5
#while read逐行读取文件
[root@Ubuntu24-7:~#] cat > loop_test.txt <<EOF
nginx 运行正常
mysql 异常停止
redis 运行正常
docker 运行正常
EOF
[root@Ubuntu24-7:~#] while read line;
do
echo "读取行内容:$line";
done < loop_test.txt
读取行内容:nginx 运行正常
读取行内容:mysql 异常停止
读取行内容:redis 运行正常
读取行内容:docker 运行正常
[root@Ubuntu24-7:~#]
7.3.1.2、for i in数组遍历、for ((i=1;i<=N;i++))数值循环
for i in 列表/数组 :遍历固定字符串列表、文件通配符、数组元素,适合批量服务、批量文件处理;
for ((i=起始;判断;自增)) C 语言风格数值循环,用于固定次数计数、分段遍历数字。
bash
[root@Ubuntu24-7:~#] service_arr=("nginx" "mysql" "redis" "docker")
[root@Ubuntu24-7:~#] for ser in "${service_arr[@]}";
do
echo "巡检服务: $ser";
done
巡检服务: nginx
巡检服务: mysql
巡检服务: redis
巡检服务: docker
[root@Ubuntu24-7:~#] for file in *.txt;
do
echo "文件文本: $file";
done
文件文本: loop_test.txt
文件文本: passwd_test.txt
[root@Ubuntu24-7:~#] ls
passwd_test.txt loop_test.txt
[root@Ubuntu24-7:~#]
[root@Ubuntu24-7:~#] for ((i=1;i<=5;i++));
do
echo "数值循环i=$i";
done
数值循环i=1
数值循环i=2
数值循环i=3
数值循环i=4
数值循环i=5
7.3.2、循环流程控制关键字
7.3.2.1、 break跳出当前多层循环、continue跳过单次循环
- break :终止整个循环,直接跳出; break N 跳出 N 层嵌套循环;
- continue :仅跳过当前这一次循环剩余代码,直接进入下一轮循环。
bash
[root@Ubuntu24-7:~#] for ((i=1;i<=5;i++));
do
if [[ $i -eq 3 ]];
then
break;
fi;
echo "数字:$i"; done
数字:1
数字:2
[root@Ubuntu24-7:~#] for ((i=1;i<=5;i++));
do
if [[ $i -eq 3 ]];
then
continue;
fi;
echo "数字:$i"; done
数字:1
数字:2
数字:4
数字:5
[root@Ubuntu24-7:~#]
[root@Ubuntu24-7:~#] service_arr=("nginx" "mysql" "redis" "docker")
[root@Ubuntu24-7:~#] for ser in "${service_arr[@]}";
do
if [[ $ser == "mysql" ]];
then
echo "跳过MySQL检查"
continue
fi
echo "正在检查服务: $ser"
done
正在检查服务: nginx
跳过MySQL检查
正在检查服务: redis
正在检查服务: docker
7.4、综合小实验
bash
#准备文档
[root@Ubuntu24-7:~#] cat > access.log <<'EOF'
192.168.1.10 - - [20/Jun/2026:10:01:22 +0800] "GET /index HTTP/1.1" 200 123 "https://test.com" "Mozilla/5.0 Chrome/120" 0.12
192.168.1.10 - - [20/Jun/2026:10:02:10 +0800] "GET /api/pay HTTP/1.1" 502 456 "-" "Bot/Spider-1.0" 4.89
192.168.1.11 - - [20/Jun/2026:10:03:05 +0800] "GET /test HTTP/1.1" 404 78 "-" "Mozilla/5.0 Safari" 0.22
192.168.1.12 - - [20/Jun/2026:11:01:00 +0800] "POST /order HTTP/1.1" 500 1024 "-" "Spider-Bot-Crawler" 6.21
192.168.1.10 - - [20/Jun/2026:11:05:11 +0800] "GET /cart HTTP/1.1" 200 200 "-" "Mozilla/5.0 Chrome" 0.15
10.0.0.5 - - [20/Jun/2026:11:10:33 +0800] "GET /api/pay HTTP/1.1" 502 320 "-" "BaiduSpider" 3.56
192.168.1.10 - - [20/Jun/2026:12:00:10 +0800] "POST /api/pay HTTP/1.1" 503 890 "-" "GoogleBot" 5.72
192.168.1.13 - - [20/Jun/2026:12:02:44 +0800] "GET /login HTTP/1.1" 200 96 "-" "Mozilla/5.0 Edge" 0.08
192.168.1.14 - - [20/Jun/2026:12:05:18 +0800] "GET /api/pay HTTP/1.1" 403 45 "-" "Mozilla/5.0" 0.31
192.168.1.11 - - [20/Jun/2026:13:01:55 +0800] "GET /goods HTTP/1.1" 200 340 "-" "Mozilla/5.0 Firefox" 0.26
192.168.1.15 - - [20/Jun/2026:13:08:21 +0800] "POST /gateway HTTP/1.1" 500 1250 "-" "360Spider" 7.35
192.168.1.10 - - [20/Jun/2026:13:12:09 +0800] "GET /home HTTP/1.1" 302 67 "-" "Mozilla/5.0 Chrome" 0.10
192.168.1.16 - - [20/Jun/2026:14:01:10 +0800] "GET /list HTTP/1.1" 200 210 "-" "Mozilla/5.0 Chrome/122" 0.18
192.168.1.17 - - [20/Jun/2026:14:05:22 +0800] "POST /upload HTTP/1.1" 413 1040 "-" "Mozilla/5.0 Safari" 1.22
192.168.1.10 - - [20/Jun/2026:14:08:45 +0800] "GET /api/user HTTP/1.1" 200 166 "-" "Bot/TestSpider" 2.10
192.168.1.18 - - [20/Jun/2026:14:12:11 +0800] "GET /static/css HTTP/1.1" 200 56 "-" "Mozilla/5.0 Edge" 0.06
10.0.0.6 - - [20/Jun/2026:14:15:36 +0800] "GET /api/pay HTTP/1.1" 500 980 "-" "SogouSpider" 6.12
EOF
[root@Ubuntu24-7:~#] cat > nginx.conf <<'EOF'
# nginx site config
server {
listen 80;
server_name localhost;
root /html/www;
error_log /var/log/nginx/error.log;
}
# blog站点
server {
listen 80;
server_name blog.test.com;
root /html/blog;
}
EOF
[root@Ubuntu24-7:~#] service_arr=("nginx" "mysql" "redis" "docker" "tomcat")
----------------------------------------------------------------------------------------
#1. 替换8080
[root@Ubuntu24-7:~#] sed -i 's/listen 80/listen 8080/' nginx.conf
[root@Ubuntu24-7:~#] cat nginx.conf
# nginx site config
server {
listen 8080;
server_name localhost;
root /html/www;
error_log /var/log/nginx/error.log;
}
# blog站点
server {
listen 8080;
server_name blog.test.com;
root /html/blog;
}
#2. 删除空行和注释行
[root@Ubuntu24-7:~#] sed -i '/^#/d;/^$/d' nginx.conf
[root@Ubuntu24-7:~#] cat nginx.conf
server {
listen 8080;
server_name localhost;
root /html/www;
error_log /var/log/nginx/error.log;
}
server {
listen 8080;
server_name blog.test.com;
root /html/blog;
}
#3. 替换文件路径/data/www
[root@Ubuntu24-7:~#] sed -i 's#/html#/data/www#' nginx.conf
[root@Ubuntu24-7:~#] cat nginx.conf
server {
listen 8080;
server_name localhost;
root /data/www/www;
error_log /var/log/nginx/error.log;
}
server {
listen 8080;
server_name blog.test.com;
root /data/www/blog;
}
#4. 追加内容
[root@Ubuntu24-7:~#] sed -i '/listen/a \ client_max_body_size 10M;' nginx.conf
[root@Ubuntu24-7:~#] cat nginx.conf
server {
listen 8080;
client_max_body_size 10M;
server_name localhost;
root /data/www/www;
error_log /var/log/nginx/error.log;
}
server {
listen 8080;
client_max_body_size 10M;
server_name blog.test.com;
root /data/www/blog;
}
[root@Ubuntu24-7:~#]
#5. 范围输出
[root@Ubuntu24-7:~#] sed -n '1,7p' nginx.conf
server {
listen 8080;
client_max_body_size 10M;/
server_name localhost;
root /data/www/www;
error_log /var/log/nginx/error.log;
}
[root@Ubuntu24-7:~#]
--------------------------------------------------------------------------
[root@Ubuntu24-7:~#] awk 'END{print "PV总请求数:",NR}' access.log
PV总请求数: 17
[root@Ubuntu24-7:~#] awk '{ip[$1]=1} END{print "UV独立IP总数:",length(ip)}' access.log
UV独立IP总数: 11
[root@Ubuntu24-7:~#] awk '{print $1}' access.log | sort -u | wc -l
11
[root@Ubuntu24-7:~#] awk '{print $9}' access.log |sort | uniq -c | sort -rn
7 200
3 500
2 502
1 503
1 413
1 404
1 403
1 302
[root@Ubuntu24-7:~#]
[root@Ubuntu24-7:~#] awk '{print $1}' access.log |sort | uniq -c | sort -rn | head
6 192.168.1.10
2 192.168.1.11
1 192.168.1.18
1 192.168.1.17
1 192.168.1.16
1 192.168.1.15
1 192.168.1.14
1 192.168.1.13
1 192.168.1.12
1 10.0.0.6
[root@Ubuntu24-7:~#] awk '{print $NF,$0}' access.log | sort -nr | head -10
7.35 192.168.1.15 - - [20/Jun/2026:13:08:21 +0800] "POST /gateway HTTP/1.1" 500 1250 "-" "360Spider" 7.35
6.21 192.168.1.12 - - [20/Jun/2026:11:01:00 +0800] "POST /order HTTP/1.1" 500 1024 "-" "Spider-Bot-Crawler" 6.21
6.12 10.0.0.6 - - [20/Jun/2026:14:15:36 +0800] "GET /api/pay HTTP/1.1" 500 980 "-" "SogouSpider" 6.12
5.72 192.168.1.10 - - [20/Jun/2026:12:00:10 +0800] "POST /api/pay HTTP/1.1" 503 890 "-" "GoogleBot" 5.72
4.89 192.168.1.10 - - [20/Jun/2026:10:02:10 +0800] "GET /api/pay HTTP/1.1" 502 456 "-" "Bot/Spider-1.0" 4.89
3.56 10.0.0.5 - - [20/Jun/2026:11:10:33 +0800] "GET /api/pay HTTP/1.1" 502 320 "-" "BaiduSpider" 3.56
2.10 192.168.1.10 - - [20/Jun/2026:14:08:45 +0800] "GET /api/user HTTP/1.1" 200 166 "-" "Bot/TestSpider" 2.10
1.22 192.168.1.17 - - [20/Jun/2026:14:05:22 +0800] "POST /upload HTTP/1.1" 413 1040 "-" "Mozilla/5.0 Safari" 1.22
0.31 192.168.1.14 - - [20/Jun/2026:12:05:18 +0800] "GET /api/pay HTTP/1.1" 403 45 "-" "Mozilla/5.0" 0.31
0.26 192.168.1.11 - - [20/Jun/2026:13:01:55 +0800] "GET /goods HTTP/1.1" 200 340 "-" "Mozilla/5.0 Firefox" 0.26
[root@Ubuntu24-7:~#] awk 'BEGIN{
printf "%-12s | %-3s | %-11s | %s\n","客户端IP","状态码","请求路径","响应耗时"
}
{printf "%-15s | %-6s | %-15s | %s\n",$1,$9,$7,$NF}
' access.log
客户端IP | 状态码 | 请求路径 | 响应耗时
192.168.1.10 | 200 | /index | 0.12
192.168.1.10 | 502 | /api/pay | 4.89
192.168.1.11 | 404 | /test | 0.22
192.168.1.12 | 500 | /order | 6.21
192.168.1.10 | 200 | /cart | 0.15
10.0.0.5 | 502 | /api/pay | 3.56
192.168.1.10 | 503 | /api/pay | 5.72
192.168.1.13 | 200 | /login | 0.08
192.168.1.14 | 403 | /api/pay | 0.31
192.168.1.11 | 200 | /goods | 0.26
192.168.1.15 | 500 | /gateway | 7.35
192.168.1.10 | 302 | /home | 0.10
192.168.1.16 | 200 | /list | 0.18
192.168.1.17 | 413 | /upload | 1.22
192.168.1.10 | 200 | /api/user | 2.10
192.168.1.18 | 200 | /static/css | 0.06
10.0.0.6 | 500 | /api/pay | 6.12
八、模块化、调试排错
8.1、Shell函数与模块化设计
8.1.1、函数定义与调用规范
8.1.1.1、 function name (){}两种定义写法、无括号调用
Shell 提供两种标准函数定义语法,作用完全等价;函数调用时不加括号,直接写函数名传参,区别于其
他编程语言。
写法 1:完整 function 关键字声明(可读性高,教学推荐)
function 函数名 { 函数体; }
写法 2:简化括号声明(生产脚本常用,简洁)
函数名() { 函数体; }
调用规则:调用仅写 函数名 参数1 参数2 ,不能加 ()
bash
#1、方法一:function
[root@Ubuntu24-7:~#] function ncs { echo "你瞅啥";echo "瞅你咋地"; }
[root@Ubuntu24-7:~#] ncs
你瞅啥
瞅你咋地
#2、方法二:()
[root@Ubuntu24-7:~#] wjkk() { echo "我瞅瞅";echo "不让瞅"; }
[root@Ubuntu24-7:~#] wjkk
我瞅瞅
不让瞅
[root@Ubuntu24-7:~#]
8.1.1.2、**函数内 (1-)n 独立位置参数、$# 函数内部参数计数
函数调用时传入的参数,在函数内部通过位置变量读取,仅在当前函数内生效:
1 :第 1 个传入参数、 2 :第 2 个参数 ...... ${10} 第 10 个参数(两位数必须加大括号)
$# :函数内部传入参数总个数,用于判断是否漏传参数、做参数校验
$@ :函数全部参数集合,可循环遍历所有入参
bash
[root@Ubuntu24-7:~#] vim os_type.sh
[root@Ubuntu24-7:~#] cat os_type.sh
#!/bin/bash
#定义变量
os_file=/etc/os-release
os_type=$(awk -F '=' '/^ID=/{print $2}' ${os_file})
#定制检测系统函数
os_type_check() {
if [ "${os_type}" == "ubuntu" ]; then
OS_TYPE="Ubuntu"
else
OS_TYPE="CentOS"
fi
}
os_type_check
echo "你的系统版本是: ${OS_TYPE}"
[root@Ubuntu24-7:~#] /bin/bash os_type.sh
8.1.1.3、 local var=value局部变量隔离全局命名污染
不加 local 定义的变量是全局变量,整个脚本任意位置均可读写,多函数共用易出现命名冲突、数据覆盖;
local 变量名=值 仅在当前函数内部有效,函数执行完毕自动销毁,隔离全局命名污染,模块化脚本必备规范。
bash
[root@Ubuntu24-7:~#] vim os_type.sh
[root@Ubuntu24-7:~#] cat os_type.sh
#!/bin/bash
#定义变量
os_file=/etc/os-release
os_type=$(awk -F '=' '/^ID=/{print $2}' ${os_file})
#定制检测系统函数
os_type_check() {
if [ "${os_type}" == "ubuntu" ]; then
OS_TYPE="Ubuntu"
else
OS_TYPE="CentOS"
fi
echo "函数内:${OS_TYPE}"
}
os_type_check
echo "你的系统版本是: ${OS_TYPE}"
echo "函数外:${OS_TYPE}"
[root@Ubuntu24-7:~#] /bin/bash os_type.sh
函数内:Ubuntu
你的系统版本是: Ubuntu
函数外:Ubuntu
[root@Ubuntu24-7:~#] vim os_type.sh
[root@Ubuntu24-7:~#] cat os_type.sh
#!/bin/bash
#定义变量
os_file=/etc/os-release
os_type=$(awk -F '=' '/^ID=/{print $2}' ${os_file})
#定制检测系统函数
os_type_check() {
if [ "${os_type}" == "ubuntu" ]; then
local OS_TYPE="Ubuntu" #添加local
else
local OS_TYPE="CentOS" #添加local
fi
echo "函数内:${OS_TYPE}"
echo "你的系统版本是: ${OS_TYPE}"
}
os_type_check
echo "你的系统版本是: ${OS_TYPE}"
echo "函数外:${OS_TYPE}"
[root@Ubuntu24-7:~#] /bin/bash os_type.sh
函数内:Ubuntu
你的系统版本是: Ubuntu
你的系统版本是:
函数外:
8.1.2、函数库抽离复用
8.1.2.1、通用工具函数写入独立 lib/utils.sh 文件
工程化分层思想项目中大量重复工具逻辑(日志打印、文件校验、异常占比计算、IP 脱敏等)如果全部堆在主脚本,代码臃肿、难以维护;将通用函数统一抽离到独立库文件 lib/utils.sh ,形成函数工具库,实现一处编写、多处调用。
目录规范主脚本同级创建 lib 目录,存放所有公共函数库,区分业务主脚本与通用工具代码,是企业 Shell 项目标准目录结构。
工具库适配场景适配本日日志分析项目:日志分级打印、文件存在校验、异常百分比计算、IP 脱敏、报表格式化等通用函数。
bash
[root@Ubuntu24-7:~#] mkdir -p lib
[root@Ubuntu24-7:~#] cd lib
[root@Ubuntu24-7:~/lib#] cat > utils.sh << 'EOF'
#!/bin/bash
# 1.日志分级打印函数
log_print() {
local level="$1"
local msg="$2"
echo "[$(date +%Y-%m-%d\ %H:%M:%S)] [$level] $msg"
}
# 2.校验文件是否存在,不存在返回异常码1
check_file_exist() {
local file_path="$1"
if [[ ! -f "$file_path" ]];then
log_print "ERROR" "文件 $file_path 不存在!"
return 1
fi
return 0
}
# 3.计算异常请求百分比,输出保留2位小数字符串
calc_err_ratio() {
local total="$1"
local err_num="$2"
awk -v t="$total" -v e="$err_num" 'BEGIN{printf "%.2f%%", e/t*100}'
}
# 4.内网IP脱敏函数
ip_mask() {
local line="$1"
echo "$line" | sed 's/192\.168\.[0-9]\+\.[0-9]\+/***.***.***.**/g'
}
EOF
# 测试
[root@Ubuntu24-7:~/lib#] source utils.sh #source当前终端临时生效
[root@Ubuntu24-7:~/lib#] log_print "ERR" "完蛋啦"
[2026-10-09 04:37:20] [ERR] 完蛋啦
[root@Ubuntu24-7:~/lib#] calc_err_ratio 5 12
240.00%
[root@Ubuntu24-7:~/lib#]
---------------------------
[root@Ubuntu24-7:~/lib#] ls ~
access.log
[root@Ubuntu24-7:~/lib#] cp ~/access.log ./
[root@Ubuntu24-7:~/lib#] ls
access.log utils.sh
[root@Ubuntu24-7:~/lib#] check_file_exist "access.log"
[root@Ubuntu24-7:~/lib#] echo $?
0
[root@Ubuntu24-7:~/lib#] vim main.sh
[root@Ubuntu24-7:~/lib#] cat main.sh
#!/bin/bash
# 加载环境函数
source ./utils.sh
# 配置变量
LOG_FILE="access.log"
# 校验文件是否存在
check_file_exist "access.log"
if [[ $? -ne 0 ]];then
exit 1
fi
log_print "INFO" "开始分析日志文件 ${LOG_FILE}"
# 统计总PV访问数
toal_pv=$(awk 'END{print NR}' "${LOG_FILE}")
err_5xx=$(awk '$9~/^5/{cnt++}END{print cnt}' "${LOG_FILE}")
# 调用函数计算错误率
err_rate=$(calc_err_ratio "${toal_pv}" "${err_5xx}")
log_print "WARN" "总请求数:${toal_pv} ;5xx错误数:${err_5xx} ;异常占比数:${err_rate} "
# 脱敏输出日志条目
log_print "INFO" "日志接口支付条目"
awk '$7 ~ /\/api\/pay/' access.log | while read line;
do
ip_mask "$line"
done
[root@Ubuntu24-7:~/lib#] /bin/bash main.sh
[2026-10-09 05:10:25] [INFO] 开始分析日志文件 access.log
[2026-10-09 05:10:25] [WARN] 总请求数:17 ;5xx错误数:6 ;异常占比数:35.29%
[2026-10-09 05:10:25] [INFO] 日志接口支付条目
***.***.***.** - - [20/Jun/2026:10:02:10 +0800] "GET /api/pay HTTP/1.1" 502 456 "-" "Bot/Spider-1.0" 4.89
10.0.0.5 - - [20/Jun/2026:11:10:33 +0800] "GET /api/pay HTTP/1.1" 502 320 "-" "BaiduSpider" 3.56
***.***.***.** - - [20/Jun/2026:12:00:10 +0800] "POST /api/pay HTTP/1.1" 503 890 "-" "GoogleBot" 5.72
***.***.***.** - - [20/Jun/2026:12:05:18 +0800] "GET /api/pay HTTP/1.1" 403 45 "-" "Mozilla/5.0" 0.31
10.0.0.6 - - [20/Jun/2026:14:15:36 +0800] "GET /api/pay HTTP/1.1" 500 980 "-" "SogouSpider" 6.12
--------------------------------
8.1.3、脚本模块化分层架构
/root/lib
├── config.sh # 全局配置模块
├── log.sh # 分级日志输出工具模块
├── notify.sh # 异常告警推送模块
├── main.sh # 程序主入口调度脚本
├── utils.sh # 通用工具函数库
├── access.log # Nginx测试日志
├── app.log # SpringBoot测试日志
└── mysql_slow.log # MySQL慢查询日志
8.1.3.1、config.sh 全局配置模块:日志路径、阈值、时间窗口
所有可变参数统一集中存放于此,包括各类日志文件路径、异常告警阈值、报表输出目录、统计时间窗口,业务脚本不再硬编码路径 / 数字,改需求只改本文件。
bash
[root@Ubuntu24-7:~/lib#] cat > config.sh << 'EOF'
#!/bin/bash
# 一、日志文件路径配置
NGINX_LOG="./access.log"
SPRING_LOG="./app.log"
MYSQL_SLOW_LOG="./mysql_slow.log"
# 二、报表、临时文件目录
TMP_DIR="./tmp_stat"
REPORT_HTML="./report.html"
# 三、告警阈值配置
ALERT_5XX_NUM=3 # Nginx 5xx错误超过该值触发告警
ALERT_ERROR_LOG_NUM=2 # SpringBoot ERROR日志阈值
ALERT_SLOW_SQL_NUM=1 # MySQL慢查询告警阈值
SLOW_QUERY_TIME=3 # 慢SQL判定时间阈值(秒)
# 四、统计时间窗口(小时)
STAT_HOUR_WINDOW=24
EOF
----------------------
8.1.3.2、log.sh 日志输出、 notify.sh 告警通知、main.sh 主逻辑入口
bash
[root@Ubuntu24-7:~/lib#] cat > log.sh << 'EOF'
#!/bin/bash
# 日志分级打印函数,参数1:级别(INFO/WARN/ERROR/CRIT) 参数2:日志内容
log_print() {
local log_level="$1"
local log_msg="$2"
local now_time=$(date +%Y-%m-%d\ %H:%M:%S)
echo "[$now_time] [$log_level] $log_msg"
}
EOF
---------------
[root@Ubuntu24-7:~/lib#] cat > notify.sh << 'EOF'
#!/bin/bash
# 告警推送函数,参数:告警标题、告警详情
send_alert() {
local alert_title="$1"
local alert_content="$2"
log_print "CRIT" "===== 触发业务告警 ====="
log_print "CRIT" "告警标题: $alert_title"
log_print "CRIT" "告警详情: $alert_content"
# 生产环境此处替换企业微信/邮件curl推送代码
}
EOF
---------------------------
[root@Ubuntu24-7:~/lib#] vim main.sh
[root@Ubuntu24-7:~/lib#] cat main.sh
#!/bin/bash
# ----------加载所有环境前缀-----------
# 加载环境函数
source ./config.sh
source ./notify.sh
source ./log.sh
source ./utils.sh
# ----------初始化临时目录------------
# 检查是否存在目录,没有创建
[[ ! -d "${TMP_DIR}" ]] && mkdir -p "${TMP_DIR}"
# 如果有.tmp临时文件则覆盖内容,没有则创建
> "${TMP_DIR}/nginx_stat.tmp"
# ----------业务逻辑:Nginx日志统计示例--------
log_print "INFO" "开始执行Nginx日志统计,文件:${NGINX_LOG}"
# 前置校验日志文件
if ! check_file_exist "${NGINX_LOG}";then
exit 1
fi
# 校验文件是否存在
check_file_exist "access.log"
if [[ $? -ne 0 ]];then
exit 1
fi
log_print "INFO" "开始分析日志文件 ${LOG_FILE}"
# 统计总PV访问数i,5xx错误率
toal_pv=$(awk 'END{print NR}' "${NGINX_LOG}")
err_5xx=$(awk '$9~/^5/{cnt++}END{print cnt}' "${NGINX_LOG}")
err_ratio=$(calc_err_ratio "${toal_pv}" "${err_5xx}")
log_print "WARN" "总请求数:${toal_pv} ;5xx错误数:${err_5xx} ;异常占比数:${err_ratio} "
# --------阈值判断:触发告警-------
if [[ ${err_5xx} -gt ${ALERT_5XX_NUM} ]]; then
send_alert "Nginx服务端异常" "5xx数值:${err_5xx} ,阈值:${ALERT_5XX_NUM} ,占比:${err_ratio}"
fi
# 脱敏输出日志条目
log_print "INFO" "日志接口支付条目"
awk '$7 ~ /\/api\/pay/' access.log | while read line;
do
ip_mask "$line"
done
log_print "INFO" "日志分析结束"
# 验证
[root@Ubuntu24-7:~/lib#] /bin/bash main.sh
[2026-10-09 05:57:40] [INFO] 开始执行Nginx日志统计,文件:./access.log
[2026-10-09 05:57:40] [INFO] 开始分析日志文件
[2026-10-09 05:57:40] [WARN] 总请求数:17 ;5xx错误数:6 ;异常占比数:35.29%
[2026-10-09 05:57:40] [CRIT] ===== 触发业务告警 =====
[2026-10-09 05:57:40] [CRIT] 告警标题: Nginx服务端异常
[2026-10-09 05:57:40] [CRIT] 告警详情: 5xx数值:6 ,阈值:3 ,占比:35.29%
[2026-10-09 05:57:40] [INFO] 日志接口支付条目
***.***.***.** - - [20/Jun/2026:10:02:10 +0800] "GET /api/pay HTTP/1.1" 502 456 "-" "Bot/Spider-1.0" 4.89
10.0.0.5 - - [20/Jun/2026:11:10:33 +0800] "GET /api/pay HTTP/1.1" 502 320 "-" "BaiduSpider" 3.56
***.***.***.** - - [20/Jun/2026:12:00:10 +0800] "POST /api/pay HTTP/1.1" 503 890 "-" "GoogleBot" 5.72
***.***.***.** - - [20/Jun/2026:12:05:18 +0800] "GET /api/pay HTTP/1.1" 403 45 "-" "Mozilla/5.0" 0.31
10.0.0.6 - - [20/Jun/2026:14:15:36 +0800] "GET /api/pay HTTP/1.1" 500 980 "-" "SogouSpider" 6.12
[2026-10-09 05:57:40] [INFO] 日志分析结束
8.1.3.3、常量定义 E_OK=0、E_ERR=1、E_WARN=2、E_CRIT=3
| 常量 | 值 | 说明 |
|---|---|---|
| E_OK | 0 | 正常成功(标准 shell 成功返回码) |
| E_ERR | 1 | 一般性错误 |
| E_WARN | 2 | 警告,业务异常但不终止脚本 |
| E_CRIT | 3 | 严重故障,触发告警 |
bash
[root@Ubuntu24-7:~/lib#] cat >> config.sh << 'EOF'
# *************************************
# * 模块:标准化全局错误码常量
# * 作用:全项目函数统一返回,主脚本分层处理故障
# *************************************
E_OK=0 # 执行成功,无异常
E_ERR=1 # 普通致命错误,脚本无法继续运行
E_WARN=2 # 警告阈值触发,任务可继续执行
E_CRIT=3 # 严重故障,触发告警推送
EOF
-----------------
#备份
[root@Ubuntu24-7:~/lib#] mkdir bak
[root@Ubuntu24-7:~/lib#] cp utils.sh bak/utils.sh.bak
[root@Ubuntu24-7:~/lib#] vim utils.sh
[root@Ubuntu24-7:~/lib#] cat utils.sh
#!/bin/bash
# 加载全局错误常量 #添加加载环境变量
source ./config.sh
# 1.日志分级打印函数
log_print() {
local level="$1"
local msg="$2"
echo "[$(date +%Y-%m-%d\ %H:%M:%S)] [$level] $msg"
}
# 2.校验文件是否存在,不存在返回异常码1
# 函数:校验日志文件是否存在
# 入参:文件路径
# 返回码:E_OK / E_ERR
check_file_exist() {
local file_path="$1"
if [[ ! -f "$file_path" ]];then
log_print "ERROR" "文件 $file_path 不存在!"
return ${E_ERR} #修改参数
fi
return ${E_OK} #修改参数
}
# 3.计算异常请求百分比,输出保留2位小数字符串
calc_err_ratio() {
local total="$1"
local err_num="$2"
awk -v t="$total" -v e="$err_num" 'BEGIN{printf "%.2f%%", e/t*100}'
}
# 4.内网IP脱敏函数
ip_mask() {
local line="$1"
echo "$line" | sed 's/192\.168\.[0-9]\+\.[0-9]\+/***.***.***.**/g'
}
# 5.增加触发阈值
# 函数:判断Nginx 5xx错误是否触发严重告警阈值
# 入参:当前错误数量、告警阈值
# 返回码:E_OK / E_WARN / E_CRIT
judge_err_threshold() {
local err_num="$1"
local alert_threshold="$2"
local warn_threshold=$(( alert_threshold - 1 ))
if [[ ${err_num} -eq 0 ]];then
return ${E_OK}
elif [[ ${err_num} -ge ${warn_threshold} && ${err_num} -lt ${alert_threshold} ]];then
log_print "WARN" "5xx错误接近告警阈值,当前:${err_num}"
return ${E_WARN}
elif [[ ${err_num} -ge ${alert_threshold} ]];then
log_print "CRIT" "5xx错误达到告警阈值,当前:${err_num}"
return ${E_CRIT}
fi
}
[root@Ubuntu24-7:~/lib#]
----------------------------------
# 验证
[root@Ubuntu24-7:~/lib#] source ./config.sh
[root@Ubuntu24-7:~/lib#] source ./utils.sh
[root@Ubuntu24-7:~/lib#] check_file_exist no_file.log
[2026-10-09 07:54:48] [ERROR] 文件 no_file.log 不存在!
[root@Ubuntu24-7:~/lib#] echo $?
1
[root@Ubuntu24-7:~/lib#] check_file_exist access.log
[root@Ubuntu24-7:~/lib#] echo $?
0
[root@Ubuntu24-7:~/lib#] judge_err_threshold 4 3
[2026-10-09 07:55:12] [CRIT] 5xx错误达到告警阈值,当前:4
[root@Ubuntu24-7:~/lib#] echo $?
3
[root@Ubuntu24-7:~/lib#] judge_err_threshold 2 3
[2026-10-09 07:55:26] [WARN] 5xx错误接近告警阈值,当前:2
[root@Ubuntu24-7:~/lib#] echo $?
2
#退出当前终端刷新环境
[root@Ubuntu24-7:~/lib#] exit
logout
8.1.3.4、 主脚本根据函数返回码执行退出/告警/重试分支
主脚本调用工具函数后,通过 $? 捕获标准化返回码,使用 if/elif 多分支判断,四类错误码对应四种处理逻辑:
- E_OK :无异常,继续执行下一段业务逻辑;
- E_ERR :致命错误,打印日志后 exit 1 直接终止脚本;
- E_WARN :仅打印警告日志,不中断,继续执行统计、报表流程;
- E_CRIT :严重故障,调用 send_alert 推送告警消息,流程不终止,正常输出报表留存现场数
bash
[root@Ubuntu24-7:~#] cd lib/
[root@Ubuntu24-7:~/lib#] cp main.sh bak/main.sh.bak
[root@Ubuntu24-7:~/lib#] cat > main.sh << 'EOF'
#!/bin/bash
# 分层加载模块(加载顺序:配置→日志→告警→工具库)
source ./config.sh
source ./log.sh
source ./notify.sh
source ./utils.sh
# 1.前置校验Nginx日志文件
log_print "INFO" "第一步:校验Nginx日志文件完整性"
check_file_exist "${NGINX_LOG}"
ret_code=$? # 捕获函数返回码存入变量,防止后续命令覆盖$?
# 分支1:处理文件校验返回码
if [[ ${ret_code} -eq ${E_ERR} ]];then
log_print "ERROR" "致命错误:日志文件缺失,程序退出"
exit ${E_ERR}
elif [[ ${ret_code} -eq ${E_OK} ]];then
log_print "INFO" "日志文件校验通过,开始统计指标"
fi
# 2.统计5xx错误数量
err_5xx=$(awk '$9 ~ /^5/{cnt++} END{print cnt+0}' "${NGINX_LOG}")
log_print "INFO" "当前Nginx 5xx错误总数:${err_5xx}"
# 3.阈值判断函数,获取故障等级返回码
judge_err_threshold "${err_5xx}" "${ALERT_5XX_NUM}"
ret_code=$?
# 分支2:按标准化错误码分层处理告警逻辑
case ${ret_code} in
${E_OK})
log_print "INFO" "指标正常,无任何告警"
;;
${E_WARN})
log_print "WARN" "指标临近阈值,持续监控"
;;
${E_CRIT})
log_print "CRIT" "严重故障,触发告警推送"
send_alert "Nginx服务异常激增" "5xx错误数量:${err_5xx},告警阈值:${ALERT_5XX_NUM}"
;;
*)
log_print "ERROR" "阈值判断函数执行异常,返回码:${ret_code}"
;;
esac
# 4.后续报表生成流程不受警告/严重故障影响
log_print "INFO" "开始生成HTML统计报表"
# 此处省略报表生成awk逻辑
log_print "INFO" "日志分析全流程执行完毕,正常退出"
exit ${E_OK}
EOF
[root@Ubuntu24-7:~/lib#]
----------------------------------------------
# 正确测试
[root@Ubuntu24-7:~/lib#] /bin/bash main.sh
[2026-10-09 08:17:47] [INFO] 第一步:校验Nginx日志文件完整性
[2026-10-09 08:17:47] [INFO] 日志文件校验通过,开始统计指标
[2026-10-09 08:17:47] [INFO] 当前Nginx 5xx错误总数:6
[2026-10-09 08:17:47] [CRIT] 5xx错误达到告警阈值,当前:6
[2026-10-09 08:17:47] [CRIT] 严重故障,触发告警推送
[2026-10-09 08:17:47] [CRIT] ===== 触发业务告警 =====
[2026-10-09 08:17:47] [CRIT] 告警标题: Nginx服务异常激增
[2026-10-09 08:17:47] [CRIT] 告警详情: 5xx错误数量:6,告警阈值:3
[2026-10-09 08:17:47] [INFO] 开始生成HTML统计报表
[2026-10-09 08:17:47] [INFO] 日志分析全流程执行完毕,正常退出
#失败测试
[root@Ubuntu24-7:~/lib#] mv access.log access.log.bak
[root@Ubuntu24-7:~/lib#] /bin/bash main.sh
[2026-10-09 08:18:49] [INFO] 第一步:校验Nginx日志文件完整性
[2026-10-09 08:18:49] [ERROR] 文件 ./access.log 不存在!
[2026-10-09 08:18:49] [ERROR] 致命错误:日志文件缺失,程序退出
#恢复
[root@Ubuntu24-7:~/lib#] mv access.log.bak access.log
8.2、Shell脚本调试与错误处理
8.2.1、脚本调试模式
调试排错:通过 set -x 、 bash -x 逐行打印执行过程,快速定位变量赋值、循环、awk 统计、文件路径等逻辑 BUG;支持局部开启 / 关闭调试,屏蔽冗余输出。
信号自动处理: trap 捕获报错、脚本正常退出、Ctrl+C 强制终止信号,自动打印报错行号、清理临时中转文件,避免磁盘残留垃圾文件。
健壮严格模式: set -euo pipefail 组合强制脚本对异常、未定义变量、管道失败敏感,提前暴露隐患;搭配 && 前置校验、 || 短路容错,大幅降低线上脚本崩溃概率。
8.2.1.1、set -x 开启调试、set +x 局部关闭,分段屏蔽冗余输出
set -x :开启跟踪调试,脚本执行时打印每一行原始命令、变量替换后完整内容,以 + 前缀区分调试日志;
set +x :关闭跟踪调试,实现局部分段调试,只对可疑代码块开启打印,避免大量无关日志刷屏;
适用场景:排查路径变量为空、awk 参数传参错误、循环参数读取错乱、函数入参丢失等问题。
bash
[root@Ubuntu24-7:~#] set -x # 开启脚本调试,每条命令执行前打印出来
[root@Ubuntu24-7:~#] LOG_FILE="lib/access.log"
+ LOG_FILE=lib/access.log
[root@Ubuntu24-7:~#] cat "$LOG_FILE" | awk 'END{print NR}'
+ awk 'END{print NR}'
+ cat lib/access.log
17
[root@Ubuntu24-7:~#] set +x #关闭脚本调试
+ set +x
-------------------------
[root@Ubuntu24-7:~/lib#] vim main.sh
[root@Ubuntu24-7:~/lib#] grep -A 10 'set -x' main.sh
set -x
# 2.统计5xx错误数量
err_5xx=$(awk '$9 ~ /^5/{cnt++} END{print cnt+0}' "${NGINX_LOG}")
log_print "INFO" "当前Nginx 5xx错误总数:${err_5xx}"
# 3.阈值判断函数,获取故障等级返回码
judge_err_threshold "${err_5xx}" "${ALERT_5XX_NUM}"
ret_code=$?
# -------------关闭脚本调试----------
set +x
# 分支2:按标准化错误码分层处理告警逻辑
# 测试set
[root@Ubuntu24-7:~/lib#] /bin/bash main.sh
[2026-10-10 00:47:30] [INFO] 第一步:校验Nginx日志文件完整性
[2026-10-10 00:47:30] [INFO] 日志文件校验通过,开始统计指标
++ awk '$9 ~ /^5/{cnt++} END{print cnt+0}' ./access.log
+ err_5xx=6
+ log_print INFO '当前Nginx 5xx错误总数:6'
+ local level=INFO
+ local 'msg=当前Nginx 5xx错误总数:6'
++ date '+%Y-%m-%d %H:%M:%S'
+ echo '[2026-10-10 00:47:30] [INFO] 当前Nginx 5xx错误总数:6'
[2026-10-10 00:47:30] [INFO] 当前Nginx 5xx错误总数:6
+ judge_err_threshold 6 3
+ local err_num=6
+ local alert_threshold=3
+ local warn_threshold=2
+ [[ 6 -eq 0 ]]
+ [[ 6 -ge 2 ]]
+ [[ 6 -lt 3 ]]
+ [[ 6 -ge 3 ]]
+ log_print CRIT 5xx错误达到告警阈值,当前:6
+ local level=CRIT
+ local msg=5xx错误达到告警阈值,当前:6
++ date '+%Y-%m-%d %H:%M:%S'
+ echo '[2026-10-10 00:47:30] [CRIT] 5xx错误达到告警阈值,当前:6'
[2026-10-10 00:47:30] [CRIT] 5xx错误达到告警阈值,当前:6
+ return 3
+ ret_code=3
+ set +x
[2026-10-10 00:47:30] [CRIT] 严重故障,触发告警推送
[2026-10-10 00:47:30] [CRIT] ===== 触发业务告警 =====
[2026-10-10 00:47:30] [CRIT] 告警标题: Nginx服务异常激增
[2026-10-10 00:47:30] [CRIT] 告警详情: 5xx错误数量:6,告警阈值:3
[2026-10-10 00:47:30] [INFO] 开始生成HTML统计报表
[2026-10-10 00:47:30] [INFO] 日志分析全流程执行完毕,正常退出
[root@Ubuntu24-7:~/lib#]
# 恢复脚本
[root@Ubuntu24-7:~/lib#] sed -i '/set/d' main.sh
[root@Ubuntu24-7:~/lib#] grep 'set' main.sh
8.2.1.2、bash -x script.sh 外部无修改离线排错调试
无需修改脚本源码,执行时加 -x 参数全局开启调试,适合线上临时排错、快速复现故障,脚本开发完成后离线排查首选。
命令格式: bash -x 脚本文件名
优势:不改动线上业务脚本,排查完毕直接恢复原执行方式,无遗留调试代码。
bash
[root@Ubuntu24-7:~/lib#] /bin/bash -x main.sh
+ source ./config.sh
++ NGINX_LOG=./access.log
++ SPRING_LOG=./app.log
++ MYSQL_SLOW_LOG=./mysql_slow.log
++ TMP_DIR=./tmp_stat
++ REPORT_HTML=./report.html
++ ALERT_5XX_NUM=3
................................. #中间省略
+ local level=INFO
+ local msg=开始生成HTML统计报表
++ date '+%Y-%m-%d %H:%M:%S'
+ echo '[2026-10-10 00:50:37] [INFO] 开始生成HTML统计报表'
[2026-10-10 00:50:37] [INFO] 开始生成HTML统计报表
+ log_print INFO 日志分析全流程执行完毕,正常退出
+ local level=INFO
+ local msg=日志分析全流程执行完毕,正常退出
++ date '+%Y-%m-%d %H:%M:%S'
+ echo '[2026-10-10 00:50:37] [INFO] 日志分析全流程执行完毕,正常退出'
[2026-10-10 00:50:37] [INFO] 日志分析全流程执行完毕,正常退出
+ exit 0
[root@Ubuntu24-7:~/lib#]
8.2.2、 trap 信号捕获清理机制
8.2.2.1、trap 捕获错误打印行号:trap 'echo Error $LINENO' ERR
- trap 捕获指定信号/事件,触发时执行自定义命令;
- ERR 事件:任意命令返回非 0 错误码时自动触发;
- $LINENO 内置变量:当前报错代码行号,快速定位出错位置;
- 集成日志打印函数,把报错行号写入分级日志,便于事后查看。
bash
[root@Ubuntu24-7:~/lib#] vim main.sh
[root@Ubuntu24-7:~/lib#] grep -C 5 'trap' main.sh
source ./config.sh
source ./log.sh
source ./notify.sh
source ./utils.sh
trap 'log_print "ERROR" "脚本执行异常,出错行号:$LINENO"' ERR #添加
# 1.前置校验Nginx日志文件
log_print "INFO" "第一步:校验Nginx日志文件完整性"
check_file_exist "${NGINX_LOG}"
ret_code=$? # 捕获函数返回码存入变量,防止后续命令覆盖$?
--------------
#测试
[root@Ubuntu24-7:~/lib#] /bin/bash main.sh
[2026-10-10 00:56:27] [INFO] 第一步:校验Nginx日志文件完整性
[2026-10-10 00:56:27] [INFO] 日志文件校验通过,开始统计指标
[2026-10-10 00:56:27] [INFO] 当前Nginx 5xx错误总数:6
[2026-10-10 00:56:27] [CRIT] 5xx错误达到告警阈值,当前:6
[2026-10-10 00:56:27] [ERROR] 脚本执行异常,出错行号:28 # 有提示
[2026-10-10 00:56:27] [CRIT] 严重故障,触发告警推送
[2026-10-10 00:56:27] [CRIT] ===== 触发业务告警 =====
[2026-10-10 00:56:27] [CRIT] 告警标题: Nginx服务异常激增
[2026-10-10 00:56:27] [CRIT] 告警详情: 5xx错误数量:6,告警阈值:3
[2026-10-10 00:56:27] [INFO] 开始生成HTML统计报表
[2026-10-10 00:56:27] [INFO] 日志分析全流程执行完毕,正常退出
#恢复
[root@Ubuntu24-7:~/lib#] sed -i '/trap/d' main.sh
8.2.2.2、EXIT 退出清理临时文件、INT/TERM 捕获Ctrl+C终止信号
EXIT :无论脚本正常退出、异常崩溃,都会触发;用于统一删除 tmp_stat 临时统计文件;
INT :按下 Ctrl+C 强制终止脚本触发;
TERM :系统 kill 正常终止脚本触发;
统一回调清理函数,保证任何退出场景都自动回收资源。
bash
[root@Ubuntu24-7:~/lib#] vim main.sh
[root@Ubuntu24-7:~/lib#] cat main.sh
#!/bin/bash
# 分层加载模块(加载顺序:配置→日志→告警→工具库)
source ./config.sh
source ./log.sh
source ./notify.sh
source ./utils.sh
#新增
# 定义全局清理函数
clean_tmp() {
log_print "INFO" "执行临时文件清理,删除目录:${TMP_DIR}"
rm -rf "${TMP_DIR}"
log_print "INFO" "资源清理完成,脚本退出"
}
# 1. 脚本任何退出场景(正常/崩溃)都执行清理
trap clean_tmp EXIT
# 2. Ctrl+C 中断捕获
trap 'log_print "WARN" "检测到Ctrl+C强制终止,开始清理资源"; clean_tmp' INT
# 3. kill 终止信号捕获
trap 'log_print "WARN" "收到kill终止信号,开始清理资源"; clean_tmp' TERM
# 测试
[root@Ubuntu24-7:~/lib#] /bin/bash main.sh
[2026-10-10 01:06:52] [INFO] 第一步:校验Nginx日志文件完整性
[2026-10-10 01:06:52] [INFO] 日志文件校验通过,开始统计指标
[2026-10-10 01:06:52] [INFO] 当前Nginx 5xx错误总数:6
[2026-10-10 01:06:52] [CRIT] 5xx错误达到告警阈值,当前:6
[2026-10-10 01:06:52] [CRIT] 严重故障,触发告警推送
[2026-10-10 01:06:52] [CRIT] ===== 触发业务告警 =====
[2026-10-10 01:06:52] [CRIT] 告警标题: Nginx服务异常激增
[2026-10-10 01:06:52] [CRIT] 告警详情: 5xx错误数量:6,告警阈值:3
[2026-10-10 01:06:52] [INFO] 开始生成HTML统计报表
[2026-10-10 01:06:52] [INFO] 日志分析全流程执行完毕,正常退出
#增加两条
[2026-10-10 01:06:52] [INFO] 执行临时文件清理,删除目录:./tmp_stat
[2026-10-10 01:06:52] [INFO] 资源清理完成,脚本退出
-----------------------------------------------
[root@Ubuntu24-7:~/lib#] vim main.sh
[root@Ubuntu24-7:~/lib#] grep -B 3 'sleep' main.sh
# 此处省略报表生成awk逻辑
log_print "INFO" "日志分析全流程执行完毕,正常退出"
sleep 30 # 增加休眠三十秒
# 测试
[root@Ubuntu24-7:~/lib#] /bin/bash main.sh
[2026-10-10 01:09:38] [INFO] 第一步:校验Nginx日志文件完整性
[2026-10-10 01:09:38] [INFO] 日志文件校验通过,开始统计指标
[2026-10-10 01:09:38] [INFO] 当前Nginx 5xx错误总数:6
[2026-10-10 01:09:38] [CRIT] 5xx错误达到告警阈值,当前:6
[2026-10-10 01:09:38] [CRIT] 严重故障,触发告警推送
[2026-10-10 01:09:38] [CRIT] ===== 触发业务告警 =====
[2026-10-10 01:09:38] [CRIT] 告警标题: Nginx服务异常激增
[2026-10-10 01:09:38] [CRIT] 告警详情: 5xx错误数量:6,告警阈值:3
[2026-10-10 01:09:38] [INFO] 开始生成HTML统计报表
[2026-10-10 01:09:38] [INFO] 日志分析全流程执行完毕,正常退出
# 休眠期间按键盘 Ctrl+x出现新的内容
^C[2026-10-10 01:09:39] [WARN] 检测到Ctrl+C强制终止,开始清理资源
[2026-10-10 01:09:39] [INFO] 执行临时文件清理,删除目录:./tmp_stat
[2026-10-10 01:09:39] [INFO] 资源清理完成,脚本退出
[2026-10-10 01:09:39] [INFO] 执行临时文件清理,删除目录:./tmp_stat
[2026-10-10 01:09:39] [INFO] 资源清理完成,脚本退出
--------------------------------------------------------
# 新开一个终端
[root@Ubuntu24-7:~/lib#] /bin/bash main.sh
# 执行脚本后用新的窗口kill 脚本进程
[root@Ubuntu24-7:~#] ps aux | grep main
root 2135 0.0 0.1 7476 3692 pts/0 S+ 01:11 0:00 /bin/bash main.sh
root 2151 50.0 0.1 6548 2324 pts/1 S+ 01:11 0:00 grep --color=auto main
[root@Ubuntu24-7:~#] kill 2135
[root@Ubuntu24-7:~/lib#] /bin/bash main.sh
[2026-10-10 01:11:36] [INFO] 第一步:校验Nginx日志文件完整性
[2026-10-10 01:11:36] [INFO] 日志文件校验通过,开始统计指标
[2026-10-10 01:11:36] [INFO] 当前Nginx 5xx错误总数:6
[2026-10-10 01:11:36] [CRIT] 5xx错误达到告警阈值,当前:6
[2026-10-10 01:11:36] [CRIT] 严重故障,触发告警推送
[2026-10-10 01:11:36] [CRIT] ===== 触发业务告警 =====
[2026-10-10 01:11:36] [CRIT] 告警标题: Nginx服务异常激增
[2026-10-10 01:11:36] [CRIT] 告警详情: 5xx错误数量:6,告警阈值:3
[2026-10-10 01:11:36] [INFO] 开始生成HTML统计报表
[2026-10-10 01:11:36] [INFO] 日志分析全流程执行完毕,正常退出
# 脚本检查到kill开始终止脚本
[2026-10-10 01:12:06] [WARN] 收到kill终止信号,开始清理资源
[2026-10-10 01:12:06] [INFO] 执行临时文件清理,删除目录:./tmp_stat
[2026-10-10 01:12:06] [INFO] 资源清理完成,脚本退出
[2026-10-10 01:12:06] [INFO] 执行临时文件清理,删除目录:./tmp_stat
[2026-10-10 01:12:06] [INFO] 资源清理完成,脚本退出
# 恢复脚本
[root@Ubuntu24-7:~/lib#] sed -i '/sleep/d' main.sh
8.2.3、严格模式与健壮错误处理
8.2.3.1、set -euo pipefail 三大严格模式作用与组合使用
- set -e :任意命令返回非 0 错误码,脚本直接终止退出,不会忽略故障继续向下执行;
- set -u :读取未定义变量直接报错退出,防止空路径、空阈值引发统计逻辑错乱;
- set -o pipefail :管道中任意一段命令失败,整条管道返回错误码,默认仅取最后一段结果。
bash
[root@Ubuntu24-7:~/lib#] vim main.sh
[root@Ubuntu24-7:~/lib#] grep -B 5 'set' main.sh
source ./config.sh
source ./log.sh
source ./notify.sh
source ./utils.sh
set -euo pipefail
# 测试
[root@Ubuntu24-7:~/lib#] /bin/bash main.sh
[2026-10-10 01:25:03] [INFO] 第一步:校验Nginx日志文件完整性
[2026-10-10 01:25:03] [INFO] 日志文件校验通过,开始统计指标
[2026-10-10 01:25:03] [INFO] 当前Nginx 5xx错误总数:6
[2026-10-10 01:25:03] [CRIT] 5xx错误达到告警阈值,当前:6
[2026-10-10 01:25:03] [INFO] 执行临时文件清理,删除目录:./tmp_stat
[2026-10-10 01:25:03] [INFO] 资源清理完成,脚本退出
--------------
8.3、综合小实验
8.3.1、项目模块拆分与开发规范
8.3.1.1、环境初始化
log_box/
├── config.sh # 第一步开发:全局配置与错误码
├── parser.sh # 第二步开发:日志解析过滤
├── analyzer.sh # 第三步开发:核心时间窗口关联
├── alerter.sh # 第四步开发:阈值告警
├── reporter.sh # 第五步开发:HTML报表
└── main.sh # 最后整合:主程序入口+容错机制
bash
# 1. 创建日志目录
[root@Ubuntu24-7:~#] mkdir -p /var/log/ruoyi
# 2. 创建Nginx日志
[root@Ubuntu24-7:~#] cat > /var/log/ruoyi/access.log <<'EOF'
192.168.1.10 - - [20/Jun/2026:14:30:02 +0800] "GET /api/index/home HTTP/1.1" 200 1250 150
192.168.1.12 - - [20/Jun/2026:14:31:15 +0800] "GET /api/user/login HTTP/1.1" 200 860 220
192.168.1.10 - - [20/Jun/2026:14:32:05 +0800] "GET /api/order/list HTTP/1.1" 502 152 6200
192.168.1.15 - - [20/Jun/2026:14:32:18 +0800] "GET /api/goods/info HTTP/1.1" 404 45 95
192.168.1.11 - - [20/Jun/2026:14:30:10 +0800] "GET /api/user/info HTTP/1.1" 200 890 120
103.28.12.5 - - [20/Jun/2026:14:35:22 +0800] "GET /api/goods HTTP/1.1" 404 30 80
192.168.1.10 - - [20/Jun/2026:14:33:10 +0800] "GET /api/pay HTTP/1.1" 502 120 5500
192.168.1.18 - - [20/Jun/2026:14:34:05 +0800] "POST /api/order/create HTTP/1.1" 504 98 6800
192.168.1.20 - - [20/Jun/2026:14:36:12 +0800] "GET /api/message/list HTTP/1.1" 200 650 180
110.25.36.89 - - [20/Jun/2026:14:37:08 +0800] "GET /api/notice HTTP/1.1" 403 28 60
192.168.1.10 - - [20/Jun/2026:14:38:22 +0800] "GET /api/order/detail HTTP/1.1" 502 135 5900
EOF
# 3. 创建应用日志
[root@Ubuntu24-7:~#] cat > /var/log/ruoyi/app.log <<'EOF'
2026-06-20 14:30:02 INFO http-nio-8080 : 首页请求正常,加载系统菜单资源
2026-06-20 14:31:15 INFO http-nio-8080 : 用户登录校验成功,生成登录Token
2026-06-20 14:32:03 INFO http-nio-8080 : 接收订单查询请求,参数校验通过
2026-06-20 14:32:06 ERROR com.ruoyi.order : ConnectionTimeout 数据库连接获取超时,订单查询请求失败
2026-06-20 14:33:11 ERROR com.ruoyi.pay : 支付接口响应超时,第三方支付回调未响应
2026-06-20 14:34:06 ERROR com.ruoyi.order : GatewayTimeout 订单创建接口请求超时
2026-06-20 14:35:22 INFO http-nio-8080 : 商品列表请求正常返回数据
2026-06-20 14:36:12 INFO http-nio-8080 : 消息列表查询成功,无未读消息
2026-06-20 14:38:23 ERROR com.ruoyi.order : SQLQueryException 订单数据查询异常,数据库查询阻塞
EOF
# 4. 创建MySQL慢查询日志
[root@Ubuntu24-7:~#] cat > /var/log/ruoyi/slow.log <<'EOF'
# Time: 2026-06-20T14:32:04.123456
# Query_time: 5.200000 Lock_time:0.100000 Rows_examined:12000
SELECT * FROM sys_user WHERE create_time > '2026-01-01';
# Time: 2026-06-20T14:33:10.456789
# Query_time: 4.800000 Lock_time:0.080000 Rows_examined:9800
SELECT * FROM sys_order WHERE order_status = 0;
# Time: 2026-06-20T14:34:05.789123
# Query_time: 6.100000 Lock_time:0.120000 Rows_examined:15000
UPDATE sys_order SET order_status=2 WHERE create_time < '2026-06-01';
EOF
[root@Ubuntu24-7:~#] ls /var/log/ruoyi/
access.log app.log slow.log
8.3.1.2、创建配置文件
config.sh : Nginx/ 应用 /Mysql 日志路径、30s 时间窗口阈值
bash
[root@Ubuntu24-7:~/log_box#] vim config.sh
[root@Ubuntu24-7:~/log_box#] cat config.sh
#!/bin/bash
# 功能:日志分析工具箱-全局配置模块
#
# 定义日志路径变量
LOG_FILE="/var/log/ruoyi"
NGINX_LOG="${LOG_FILE}/access.log"
APP_LOG="${LOG_FILE}/app.log"
SLOW_LOG="${LOG_FILE}/slow.log"
# 定义时间窗口:NGINX报错检索秒数
TIME_WINDOW=30
# 定义告警阈值
#
ALERT_5XX_COUNT=3
SPIDER_IP_THRESHOLD=10
# 临时目录与报表路径
TMP_DIR="./tmp"
HTML_REPORT="./report.html"
# 钉钉机器人地址(预留扩展)
DING_WEBHOOK=""
# 企业级标准化错误码
E_OK=0
E_ERR=1
E_WARN=2
E_CRIT=3
---------------
# 验证
[root@Ubuntu24-7:~/log_box#] vim demo_test.sh
[root@Ubuntu24-7:~/log_box#] /bin/bash demo_test.sh
/var/log/ruoyi/access.log
/var/log/ruoyi/APP.log
/var/log/ruoyi/slow.log
8.3.1.3、创建日志封装函数
parser.sh:三类日志统一格式化解析函数封装
bash
[root@Ubuntu24-7:~/log_box#] vim parser.sh
[root@Ubuntu24-7:~/log_box#] cat parser.sh
#!/bin/bash
# 功能: 日志分析工具箱-日志解析模块
#
# 加载配置文件变量
source ./config.sh
# 配置解析NGINX日志函数 筛选5XX
parse_nginx_5xx(){
awk '$9 ~ /^5/{print $0}' ${NGINX_LOG} > ${TMP_DIR}/nginx_5xx.tmp
}
# 配置解析应用错误日志函数 筛选error
#
parse_app_error(){
grep 'ERROR' ${APP_LOG} > ${TMP_DIR}/app_error.tmp
}
# 配置解析MySQL慢查询日志函数
#
parse_mysql_slow(){
grep -E 'Time|Query_time|SELECT|UPDATE|DELETE' ${SLOW_LOG} > ${TMP_DIR}/slow_sql.tmp
}
# 配置统一路口
#
parse_all_log(){
[ -d ${TMP_DIR} ] && rm -rf ${TMP_DIR}/* || mkdir ${TMP_DIR}
parse_nginx_5xx
parse_app_error
parse_mysql_slow
}
------------------
# 测试
[root@Ubuntu24-7:~/log_box#] mkdir -p bak
[root@Ubuntu24-7:~/log_box#] cp demo_test.sh bak/
[root@Ubuntu24-7:~/log_box#] mv demo_test.sh demo_parser_test.sh
[root@Ubuntu24-7:~/log_box#] vim demo_parser_test.sh
[root@Ubuntu24-7:~/log_box#] /bin/bash demo_parser_test.sh
[root@Ubuntu24-7:~/log_box#] ls
bak config.sh demo_parser_test.sh parser.sh tmp
[root@Ubuntu24-7:~/log_box#] ls tmp/
app_error.tmp nginx_5xx.tmp slow_sql.tmp
[root@Ubuntu24-7:~/log_box#] cat tmp/app_error.tmp
2026-06-20 14:32:06 ERROR com.ruoyi.order : ConnectionTimeout 数据库连接获取超时,订单查询请求失败
2026-06-20 14:33:11 ERROR com.ruoyi.pay : 支付接口响应超时,第三方支付回调未响应
2026-06-20 14:34:06 ERROR com.ruoyi.order : GatewayTimeout 订单创建接口请求超时
2026-06-20 14:38:23 ERROR com.ruoyi.order : SQLQueryException 订单数据查询异常,数据库查询阻塞
8.3.1.4、创建日志分析展示窗口
analyzer.sh**:时间窗口错误关联、慢查询因果匹配**
bash
[root@Ubuntu24-7:~/log_box#] vim analyzer.sh
[root@Ubuntu24-7:~/log_box#] cat analyzer.sh
#!/bin/bash
# 功能: 日志分析工具箱-因果关联分析模块
#
# 加载配置环境变量
source ./config.sh
# 创建日志分析展示窗口函数
analysis_link(){
echo -e "\n-----${TIME_WINDOW}s时间窗口故障分析----"
echo -e "\n 1. Nginx 错误输出(5xx)"
cat ${TMP_DIR}/nginx_5xx.tmp
echo -e "\n 2. 应用层异常日志"
cat ${TMP_DIR}/app_error.tmp
echo -e "\n 3. 数据库慢查询根源"
cat ${TMP_DIR}/slow_sql.tmp
echo -e "\n【故障因果总结】慢SQL阻塞 → 应用接口超时 → Nginx网关5xx报错"
}
-------------------------
# 测试
[root@Ubuntu24-7:~/log_box#] cp demo_parser_test.sh bak/
[root@Ubuntu24-7:~/log_box#] mv demo_parser_test.sh demo_analyzer_test.sh
[root@Ubuntu24-7:~/log_box#] vim demo_analyzer_test.sh
[root@Ubuntu24-7:~/log_box#] /bin/bash demo_analyzer_test.sh
-----30s时间窗口故障分析----
1. Nginx 错误输出(5xx)
192.168.1.10 - - [20/Jun/2026:14:32:05 +0800] "GET /api/order/list HTTP/1.1" 502 152 6200
192.168.1.10 - - [20/Jun/2026:14:33:10 +0800] "GET /api/pay HTTP/1.1" 502 120 5500
192.168.1.18 - - [20/Jun/2026:14:34:05 +0800] "POST /api/order/create HTTP/1.1" 504 98 6800
192.168.1.10 - - [20/Jun/2026:14:38:22 +0800] "GET /api/order/detail HTTP/1.1" 502 135 5900
2. 应用层异常日志
2026-06-20 14:32:06 ERROR com.ruoyi.order : ConnectionTimeout 数据库连接获取超时,订单查询请求失败
2026-06-20 14:33:11 ERROR com.ruoyi.pay : 支付接口响应超时,第三方支付回调未响应
2026-06-20 14:34:06 ERROR com.ruoyi.order : GatewayTimeout 订单创建接口请求超时
2026-06-20 14:38:23 ERROR com.ruoyi.order : SQLQueryException 订单数据查询异常,数据库查询阻塞
3. 数据库慢查询根源
# Time: 2026-06-20T14:32:04.123456
# Query_time: 5.200000 Lock_time:0.100000 Rows_examined:12000
SELECT * FROM sys_user WHERE create_time > '2026-01-01';
# Time: 2026-06-20T14:33:10.456789
# Query_time: 4.800000 Lock_time:0.080000 Rows_examined:9800
SELECT * FROM sys_order WHERE order_status = 0;
# Time: 2026-06-20T14:34:05.789123
# Query_time: 6.100000 Lock_time:0.120000 Rows_examined:15000
UPDATE sys_order SET order_status=2 WHERE create_time < '2026-06-01';
【故障因果总结】慢SQL阻塞 → 应用接口超时 → Nginx网关5xx报错
8.3.1.5、创建告警推送
alerter.sh 告警信息推送
bash
[root@Ubuntu24-7:~/log_box#] vim alerter.sh
[root@Ubuntu24-7:~/log_box#] cat alerter.sh
#!/bin/bash
# 功能: 日志分析工具箱-告警推送模块
#
# 加载配置环境变量
source ./config.sh
# 配置告警推送函数
send_alert(){
local title="$1"
local content="$2"
echo -e "\n[CRIT系统告警] ${title}"
echo "告警详情:${content}"
}
------------------------
# 测试
[root@Ubuntu24-7:~/log_box#] cp demo_analyzer_test.sh bak/
[root@Ubuntu24-7:~/log_box#] mv demo_analyzer_test.sh demo_alerter_test.sh
[root@Ubuntu24-7:~/log_box#] vim demo_alerter_test.sh
[root@Ubuntu24-7:~/log_box#] cat demo_alerter_test.sh
source ./alerter.sh
send_alert "喜羊羊" "美羊羊"
[root@Ubuntu24-7:~/log_box#] /bin/bash demo_alerter_test.sh
[CRIT系统告警] 喜羊羊
告警详情:美羊羊
8.3.1.6、创建报表函数
reporter.sh:awk 生成HTML告警报表
bash
[root@Ubuntu24-7:~/log_box#] vim reporter.sh
[root@Ubuntu24-7:~/log_box#] cat reporter.sh
#!/bin/bash
# 功能: 日志分析工具箱-HTML报表模块
#
#
# 加载全局配置变量
source ./config.sh
gen_html(){
# 读取日志内容,保留原生换行符
local nginx_log=$(cat ${TMP_DIR}/nginx_5xx.tmp)
local app_log=$(cat ${TMP_DIR}/app_error.tmp)
local slow_log=$(cat ${TMP_DIR}/slow_sql.tmp)
# 生成带样式、自动换行、排版规整的HTML报表
cat > ${HTML_REPORT} <<HTML
<html>
<meta charset="utf-8">
<title>微服务日志故障分析报表</title>
<style>
body{font-size:14px;line-height:1.8;margin:20px;font-family:"微软雅黑";}
h2{color:#2d8cf0;border-bottom:2px solid #eee;padding-bottom:10px;}
h3{color:#333;margin-top:20px;}
pre{background:#f5f5f5;padding:15px;border-radius:5px;white-space:pre-wrap;word-wrap:break-word;}
</style>
<body>
<h2>微服务日志故障分析报表(自动生成)</h2>
<h3>一、Nginx 网关 5xx 故障记录</h3>
<pre>${nginx_log}</pre>
<h3>二、SpringBoot 应用层异常日志</h3>
<pre>${app_log}</pre>
<h3>三、MySQL 数据库慢查询根因日志</h3>
<pre>${slow_log}</pre>
</body>
</html>
HTML
echo -e "\n[INFO] 美化版HTML故障报表生成完成,日志自动分行展示"
}
------------
# 测试
[root@Ubuntu24-7:~/log_box#] cp demo_alerter_test.sh bak/
[root@Ubuntu24-7:~/log_box#] mv demo_alerter_test.sh demo_reporter_test.sh
[root@Ubuntu24-7:~/log_box#] vim demo_reporter_test.sh
[root@Ubuntu24-7:~/log_box#] cat demo_reporter_test.sh
source ./reporter.sh
gen_html
[root@Ubuntu24-7:~/log_box#] /bin/bash demo_reporter_test.sh
[INFO] 美化版HTML故障报表生成完成,日志自动分行展示
[root@Ubuntu24-7:~/log_box#] ls
alerter.sh bak demo_reporter_test.sh reporter.sh tmp
analyzer.sh config.sh parser.sh report.html
# 将生成的HTML文件下载到windows本地打开

8.3.1.7、创建项目总入口
main.sh 项目统一入口
bash
[root@Ubuntu24-7:~/log_box#] vim main.sh
[root@Ubuntu24-7:~/log_box#] cat main.sh
#!/bin/bash
# 功能: 业务主脚本-日志分析工具箱主程序
#
#
# 开启企业级严格模式
#set -euo pipefail
# 全局临时文件清理函数
clean_tmp() {
rm -rf "${TMP_DIR}"
echo "[INFO] 临时资源清理完成"
}
# 全场景信号捕获,自动兜底清理
trap clean_tmp EXIT INT TERM
# 模块化加载(固定顺序:配置优先)
source ./config.sh
source ./parser.sh
source ./analyzer.sh
source ./alerter.sh
source ./reporter.sh
# 日志文件前置容错校验
check_env() {
[[ -f "${NGINX_LOG}" ]] || { echo "[ERROR] Nginx日志缺失"; exit ${E_ERR}; }
[[ -f "${APP_LOG}" ]] || { echo "[ERROR] 应用日志缺失"; exit ${E_ERR}; }
[[ -f "${SLOW_LOG}" ]] || { echo "[ERROR] 慢查询日志缺失"; exit ${E_ERR}; }
}
alert_send_check(){
# 获取真正错误日志条目
local err_num=$(wc -l < ${TMP_DIR}/nginx_5xx.tmp)
# 阈值判断触发告警
if [ ${err_num} -ge ${ALERT_5XX_COUNT} ]
then
send_alert "Nginx 服务5xx异常激增" "当前异常数:${err_num},阈值:${ALERT_5XX_COUNT}"
else
echo "Nginx服务正常,没有大量的异常日志出现"
fi
}
main() {
# 校验环境
echo -e "\n----正在校验环境----"
check_env
# 解析日志
echo -e "\n----正在解析日志----"
parse_all_log
# 执行30s时间窗口因果关联分析
echo -e "\n----正在关联日志----"
analysis_link
# 生成可视化HTML报表
echo -e "\n----正在生成HTML报表----"
gen_html
# 告警的判断逻辑
echo -e "\n----正在判断告警逻辑----"
alert_send_check
echo "[INFO] 日志分析全流程执行完成"
exit ${E_OK}
}
main
-----------
# 测试
[root@Ubuntu24-7:~/log_box#] /bin/bash main.sh
----正在校验环境----
----正在解析日志----
----正在关联日志----
-----30s时间窗口故障分析----
1. Nginx 错误输出(5xx)
192.168.1.10 - - [20/Jun/2026:14:32:05 +0800] "GET /api/order/list HTTP/1.1" 502 152 6200
192.168.1.10 - - [20/Jun/2026:14:33:10 +0800] "GET /api/pay HTTP/1.1" 502 120 5500
192.168.1.18 - - [20/Jun/2026:14:34:05 +0800] "POST /api/order/create HTTP/1.1" 504 98 6800
192.168.1.10 - - [20/Jun/2026:14:38:22 +0800] "GET /api/order/detail HTTP/1.1" 502 135 5900
2. 应用层异常日志
2026-06-20 14:32:06 ERROR com.ruoyi.order : ConnectionTimeout 数据库连接获取超时,订单查询请求失败
2026-06-20 14:33:11 ERROR com.ruoyi.pay : 支付接口响应超时,第三方支付回调未响应
2026-06-20 14:34:06 ERROR com.ruoyi.order : GatewayTimeout 订单创建接口请求超时
2026-06-20 14:38:23 ERROR com.ruoyi.order : SQLQueryException 订单数据查询异常,数据库查询阻塞
3. 数据库慢查询根源
# Time: 2026-06-20T14:32:04.123456
# Query_time: 5.200000 Lock_time:0.100000 Rows_examined:12000
SELECT * FROM sys_user WHERE create_time > '2026-01-01';
# Time: 2026-06-20T14:33:10.456789
# Query_time: 4.800000 Lock_time:0.080000 Rows_examined:9800
SELECT * FROM sys_order WHERE order_status = 0;
# Time: 2026-06-20T14:34:05.789123
# Query_time: 6.100000 Lock_time:0.120000 Rows_examined:15000
UPDATE sys_order SET order_status=2 WHERE create_time < '2026-06-01';
【故障因果总结】慢SQL阻塞 → 应用接口超时 → Nginx网关5xx报错
----正在生成HTML报表----
[INFO] 美化版HTML故障报表生成完成,日志自动分行展示
----正在判断告警逻辑----
[CRIT系统告警] Nginx 服务5xx异常激增
告警详情:当前异常数:4,阈值:3
[INFO] 日志分析全流程执行完成
[INFO] 临时资源清理完成
-------------------------------------------
# 如果不想让-----30s时间窗口故障分析----输出在窗口,可以将analyzer.sh替换以下内容
[root@Ubuntu24-7:~/log_box#] cat analyzer.sh
#!/bin/bash
# 功能: 日志分析工具箱-因果关联分析模块
#
# 加载配置环境变量
source ./config.sh
# 创建日志分析展示窗口函数
#analysis_link(){
# echo -e "\n-----${TIME_WINDOW}s时间窗口故障分析----"
# echo -e "\n 1. Nginx 错误输出(5xx)"
# cat ${TMP_DIR}/nginx_5xx.tmp
#
# echo -e "\n 2. 应用层异常日志"
# cat ${TMP_DIR}/app_error.tmp
#
# echo -e "\n 3. 数据库慢查询根源"
# cat ${TMP_DIR}/slow_sql.tmp
#
# echo -e "\n【故障因果总结】慢SQL阻塞 → 应用接口超时 → Nginx网关5xx报错"
#}
analysis_link(){
echo -e "\n-----${TIME_WINDOW}s时间窗口故障分析----"
cat > /tmp/heihei.txt <<-eof
${TIME_WINDOW}s时间窗口故障因果链关系梳理
"1. 网关层故障锚点 (Nginx 5xx)"
$(cat "${TMP_DIR}/nginx_5xx.tmp")
"2. 应用层异常日志"
$(cat "${TMP_DIR}/app_error.tmp")
"3. 数据库层慢查询异常日志"
$(cat "${TMP_DIR}/nginx_5xx.tmp")
"故障因果总结】慢SQL阻塞 → 应用接口超时 → Nginx网关5xx报错"
eof
}
[root@Ubuntu24-7:~/log_box#]
8.3.1.8、封禁异常IP客户端
bash
[root@Ubuntu24-7:~/log_box#] vim duoip.sh
[root@Ubuntu24-7:~/log_box#] cat duoip.sh
#!/bin/bash
> nginx_abnormal.log
# ip:权重 ,权重越大出现概率越高
# 前5个为攻击IP权重很大,后面普通IP权重很小
ip_weight=(
"110.25.36.89:30"
"22.33.44.55:28"
"66.77.88.99:25"
"101.202.33.44:22"
"45.67.89.12:20"
"78.90.12.34:3"
"123.45.67.89:3"
"98.76.54.32:2"
"199.88.77.66:2"
"55.44.33.22:2"
"10.11.12.13:1"
"14.15.16.17:1"
"31.32.33.34:1"
"41.42.43.44:1"
"51.52.53.54:1"
"61.62.63.64:1"
"71.72.73.74:1"
"81.82.83.84:1"
"91.92.93.94:1"
"131.132.133.134:1"
"141.142.143.144:1"
"151.152.153.154:1"
"161.162.163.164:1"
"171.172.173.174:1"
)
url_pool=(
"/test1" "/api/user" "/api/order" "/admin" "/static/js/a.js"
"/api/login" "/api/pay" "/health" "/api/list" "/api/detail"
"/config.json" "/favicon.ico" "/api/upload" "/api/delete" "/api/query"
)
status_pool=(404 404 404 500 500 502 503 403 200 200)
# 根据权重随机选出一个IP
rand_ip_by_weight(){
local total=0
local -a arr=("${ip_weight[@]}")
# 计算总权重
for item in "${arr[@]}";do
w=${item#*:}
total=$((total + w))
done
local rnd=$(( RANDOM % total ))
local acc=0
for item in "${arr[@]}";do
ip_part=${item%:*}
w_part=${item#*:}
acc=$((acc + w_part))
if [[ $rnd -lt $acc ]];then
echo "$ip_part"
return
fi
done
}
for ((i=0; i<2000; i++)); do
ip=$(rand_ip_by_weight)
uri=${url_pool[$((RANDOM % ${#url_pool[@]}))]}
status=${status_pool[$((RANDOM % ${#status_pool[@]}))]}
sec=$((RANDOM % 60))
size=$((RANDOM % 200 + 10))
rt=$((RANDOM % 200 + 10))
printf "%s - - [20/Jun/2026:14:%02d:%02d +0800] \"GET %s HTTP/1.1\" %s %d %d\n" \
"$ip" "40" "$sec" "$uri" "$status" "$size" "$rt" >> nginx_abnormal.log
done
echo "生成完成,文件:nginx_abnormal.log"
echo "统计访问频次最高前10个IP命令示例:"
echo 'awk "{print \$1}" nginx_abnormal.log |sort |uniq -c |sort -nr |head -10'
[root@Ubuntu24-7:~/log_box#] mv nginx_abnormal.log /var/log/ruoyi/access.log
[root@Ubuntu24-7:~/log_box#] awk "{print \$1}" /var/log/ruoyi/access.log |sort |uniq -c |sort -nr |head -10
398 22.33.44.55
355 110.25.36.89
327 66.77.88.99
292 101.202.33.44
269 45.67.89.12
37 78.90.12.34
37 199.88.77.66
36 123.45.67.89
29 55.44.33.22
27 98.76.54.32
-----------------------------------
[root@Ubuntu24-7:~/log_box#] vim ip_ban.sh
[root@Ubuntu24-7:~/log_box#] cat ip_ban.sh
# 异常IP客户端地址封杀
# 恶意爬虫IP检测与自动封禁
ban_spider_ip(){
echo -e "\n========== 恶意访问IP风控检测 =========="
# 筛选Nginx日志中4xx异常访问IP,统计访问次数
awk '$9~/^(5|4)/{print $1}' ${NGINX_LOG} | sort | uniq -c |sort -rn | head -10 > ${TMP_DIR}/ip_count.tmp
echo "【异常访问IP访问统计】"
# cat ${TMP_DIR}/ip_count.tmp
# 遍历超过阈值的恶意IP,自动加入防火墙屏蔽规则
while read count ip
do
if [[ -n ${ip} && ${count} -ge ${SPIDER_IP_THRESHOLD} ]];then
echo -e "\n[WARN] 发现恶意爬虫IP:${ip},访问次数:${count}"
# 屏蔽IP,拒绝所有访问
iptables -A INPUT -s ${ip} -j DROP
echo "[SUCCESS] 已自动封禁恶意IP:${ip}"
fi
done < ${TMP_DIR}/ip_count.tmp
}
# 异常ip地址的处理
yichang_ip_fs(){
read -p "是否对异常ip进行处理(yes|no): " result_value
if [ "${result_value}" == "yes" ];then
ban_spider_ip
fi
}
------------------
[root@Ubuntu24-7:~/log_box#] vim main.sh
[root@Ubuntu24-7:~/log_box#] cat main.sh
#!/bin/bash
# 功能: 业务主脚本-日志分析工具箱主程序
#
#
# 开启企业级严格模式
#set -euo pipefail
# 全局临时文件清理函数
clean_tmp() {
rm -rf "${TMP_DIR}"
echo "[INFO] 临时资源清理完成"
}
# 全场景信号捕获,自动兜底清理
trap clean_tmp EXIT INT TERM
# 模块化加载(固定顺序:配置优先)
source ./config.sh
source ./parser.sh
source ./analyzer.sh
source ./alerter.sh
source ./reporter.sh
source ./ip_ban.sh #添加
# 日志文件前置容错校验
check_env() {
[[ -f "${NGINX_LOG}" ]] || { echo "[ERROR] Nginx日志缺失"; exit ${E_ERR}; }
[[ -f "${APP_LOG}" ]] || { echo "[ERROR] 应用日志缺失"; exit ${E_ERR}; }
[[ -f "${SLOW_LOG}" ]] || { echo "[ERROR] 慢查询日志缺失"; exit ${E_ERR}; }
}
alert_send_check(){
# 获取真正错误日志条目
local err_num=$(wc -l < ${TMP_DIR}/nginx_5xx.tmp)
# 阈值判断触发告警
if [ ${err_num} -ge ${ALERT_5XX_COUNT} ]
then
send_alert "Nginx 服务5xx异常激增" "当前异常数:${err_num},阈值:${ALERT_5XX_COUNT}"
else
echo "Nginx服务正常,没有大量的异常日志出现"
fi
}
main() {
# 校验环境
echo -e "\n----正在校验环境----"
check_env
# 解析日志
echo -e "\n----正在解析日志----"
parse_all_log
# 执行30s时间窗口因果关联分析
echo -e "\n----正在关联日志----"
analysis_link
# 生成可视化HTML报表
echo -e "\n----正在生成HTML报表----"
gen_html
# 告警的判断逻辑
echo -e "\n----正在判断告警逻辑----"
alert_send_check
# 异常ip地址处理 # 添加
echo -e "\n----准备处理异常IP-----"
yichang_ip_fs
echo "[INFO] 日志分析全流程执行完成"
exit ${E_OK}
}
main
[root@Ubuntu24-7:~/log_box#] iptables -vnL
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
--------------------
# 测试
[root@Ubuntu24-7:~/log_box#] /bin/bash main.sh
----正在校验环境----
----校验环境完成----
----正在解析日志----
----解析日志完成----
----正在关联日志----
-----30s时间窗口故障分析----
----关联日志完成----
----正在生成HTML报表----
[INFO] 美化版HTML故障报表生成完成,日志自动分行展示
----生成HTML报表完成----
----正在判断告警逻辑----
[CRIT系统告警] Nginx 服务5xx异常激增
告警详情:当前异常数:759,阈值:3
----判断告警逻辑完成----
----准备处理异常IP-----
是否对异常ip进行处理(yes|no): yes
========== 恶意访问IP风控检测 ==========
【异常访问IP访问统计】
[WARN] 发现恶意爬虫IP:22.33.44.55,访问次数:318
[SUCCESS] 已自动封禁恶意IP:22.33.44.55
[WARN] 发现恶意爬虫IP:110.25.36.89,访问次数:284
[SUCCESS] 已自动封禁恶意IP:110.25.36.89
[WARN] 发现恶意爬虫IP:66.77.88.99,访问次数:257
[SUCCESS] 已自动封禁恶意IP:66.77.88.99
[WARN] 发现恶意爬虫IP:101.202.33.44,访问次数:233
[SUCCESS] 已自动封禁恶意IP:101.202.33.44
[WARN] 发现恶意爬虫IP:45.67.89.12,访问次数:219
[SUCCESS] 已自动封禁恶意IP:45.67.89.12
[WARN] 发现恶意爬虫IP:123.45.67.89,访问次数:30
[SUCCESS] 已自动封禁恶意IP:123.45.67.89
[WARN] 发现恶意爬虫IP:199.88.77.66,访问次数:29
[SUCCESS] 已自动封禁恶意IP:199.88.77.66
[WARN] 发现恶意爬虫IP:78.90.12.34,访问次数:27
[SUCCESS] 已自动封禁恶意IP:78.90.12.34
[WARN] 发现恶意爬虫IP:98.76.54.32,访问次数:25
[SUCCESS] 已自动封禁恶意IP:98.76.54.32
[WARN] 发现恶意爬虫IP:55.44.33.22,访问次数:22
[SUCCESS] 已自动封禁恶意IP:55.44.33.22
[INFO] 日志分析全流程执行完成
[INFO] 临时资源清理完成
source ./config.sh
source ./parser.sh
source ./analyzer.sh
source ./alerter.sh
source ./reporter.sh
source ./ip_ban.sh #添加
日志文件前置容错校验
check_env() {
\[ -f "${NGINX_LOG}" ] || { echo "ERROR Nginx日志缺失"; exit KaTeX parse error: Expected 'EOF', got '}' at position 10: {E_ERR}; }̲ \[ -f "{APP_LOG}" ] || { echo "ERROR 应用日志缺失"; exit KaTeX parse error: Expected 'EOF', got '}' at position 10: {E_ERR}; }̲ \[ -f "{SLOW_LOG}" ] || { echo "ERROR 慢查询日志缺失"; exit ${E_ERR}; }
}
alert_send_check(){
获取真正错误日志条目
local err_num=(wc -l \< {TMP_DIR}/nginx_5xx.tmp)
# 阈值判断触发告警
if [ ${err_num} -ge ${ALERT_5XX_COUNT} ]
then
send_alert "Nginx 服务5xx异常激增" "当前异常数:${err_num},阈值:${ALERT_5XX_COUNT}"
else
echo "Nginx服务正常,没有大量的异常日志出现"
fi
}
main() {
校验环境
echo -e "\n----正在校验环境----"
check_env
# 解析日志
echo -e "\n----正在解析日志----"
parse_all_log
# 执行30s时间窗口因果关联分析
echo -e "\n----正在关联日志----"
analysis_link
# 生成可视化HTML报表
echo -e "\n----正在生成HTML报表----"
gen_html
# 告警的判断逻辑
echo -e "\n----正在判断告警逻辑----"
alert_send_check
# 异常ip地址处理 # 添加
echo -e "\n----准备处理异常IP-----"
yichang_ip_fs
echo "[INFO] 日志分析全流程执行完成"
exit ${E_OK}
}
main
root@Ubuntu24-7:\~/log_box# iptables -vnL
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
测试
root@Ubuntu24-7:\~/log_box# /bin/bash main.sh
----正在校验环境----
----校验环境完成----
----正在解析日志----
----解析日志完成----
----正在关联日志----
-----30s时间窗口故障分析----
----关联日志完成----
----正在生成HTML报表----
INFO 美化版HTML故障报表生成完成,日志自动分行展示
----生成HTML报表完成----
----正在判断告警逻辑----
CRIT系统告警 Nginx 服务5xx异常激增
告警详情:当前异常数:759,阈值:3
----判断告警逻辑完成----
----准备处理异常IP-----
是否对异常ip进行处理(yes|no): yes
========== 恶意访问IP风控检测 ==========
【异常访问IP访问统计】
WARN 发现恶意爬虫IP:22.33.44.55,访问次数:318
SUCCESS 已自动封禁恶意IP:22.33.44.55
WARN 发现恶意爬虫IP:110.25.36.89,访问次数:284
SUCCESS 已自动封禁恶意IP:110.25.36.89
WARN 发现恶意爬虫IP:66.77.88.99,访问次数:257
SUCCESS 已自动封禁恶意IP:66.77.88.99
WARN 发现恶意爬虫IP:101.202.33.44,访问次数:233
SUCCESS 已自动封禁恶意IP:101.202.33.44
WARN 发现恶意爬虫IP:45.67.89.12,访问次数:219
SUCCESS 已自动封禁恶意IP:45.67.89.12
WARN 发现恶意爬虫IP:123.45.67.89,访问次数:30
SUCCESS 已自动封禁恶意IP:123.45.67.89
WARN 发现恶意爬虫IP:199.88.77.66,访问次数:29
SUCCESS 已自动封禁恶意IP:199.88.77.66
WARN 发现恶意爬虫IP:78.90.12.34,访问次数:27
SUCCESS 已自动封禁恶意IP:78.90.12.34
WARN 发现恶意爬虫IP:98.76.54.32,访问次数:25
SUCCESS 已自动封禁恶意IP:98.76.54.32
WARN 发现恶意爬虫IP:55.44.33.22,访问次数:22
SUCCESS 已自动封禁恶意IP:55.44.33.22
INFO 日志分析全流程执行完成
INFO 临时资源清理完成