43-Linux学习之旅之Shell 脚本(二)

Linux学习之旅之Shell 脚本(二)

接上文https://blog.csdn.net/2401_88082755/article/details/164628565?spm=1011.2415.3001.10575\&sharefrom=mp_manage_link

七、sed/awk 文本处理

7.1、sed 文本处理工具

7.1.1、sed基础替换命令S
7.1.1.1、 s/old/new/g 分隔符/# | 三种写法、g 全局匹配
  1. s 是 sed 替换核心子命令,语法 s/查找内容/替换内容/标记 ;
  2. 分隔符 / 可自定义为 # | 规避内容包含斜杠转义麻烦;
  3. 标记 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
  1. 末尾数字 n:仅替换该行第 n 处匹配内容, s/80/8080/2 只替换每行第二个 80;
  2. & 代表完整匹配到的原始内容,用于在原有文字前后追加字符;
  3. (内容) 分组捕获, \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 区间替换

两种寻址方式限定替换生效行,不全局全文件替换:

  1. 数字区间 m,ns :从第 m 行到第 n 行执行替换; 1,s代表全文(s 代表全文(s代表全文( 最后一行);
  2. 模式区间 /匹配起始/,/匹配结束/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 为删除子命令,删除寻址匹配到的完整行,常用于清理配置注释、空白行、无效日志:

  1. m,nd 删除第 m~n 行;
  2. /正则/d 删除匹配正则表达式的所有行;
  3. /^$/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 整行替换操作
  1. i insert:在匹配行上方插入文本;
  2. a append:在匹配行下方追加文本;
  3. 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 参数只打印匹配行,屏蔽默认全文件输出:

  1. 数字寻址 m,n :固定行号区间; m~step 从 m 行开始,每隔 step 行匹配; 1~2 奇数行、 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} 代码块:

  1. 正则匹配 /关键词/ :匹配包含指定内容的整行;
  2. 数值条件比较:内置变量 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} 是匹配成功后执行的代码块,支持:

  1. print / printf 输出文本、字段、自定义字符串;
  2. 自定义变量赋值、字段 $n 重新赋值修改文本;
  3. 内置 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 总字段数
  1. $0 :代表当前读取的完整一整行文本;
  2. 1 2 3 ... NF :按分隔符切割后的第 1、2、3... 列字段;
  3. NR Number of Record:当前处理行的行号;
  4. 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记录分隔
  1. FS Field Separator:输入文本切割字段的分隔符,默认空白; -F 等价于前置修改 FS;
  2. OFS Output Field Separator:print 输出多字段时,字段之间的分隔符,默认空格;
  3. RS Record Separator:行读取分隔符,默认换行 \n ;
  4. 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{} 在读取任何文件内容之前执行,仅运行一次;用于:

  1. 自定义分隔符 FS/OFS;
  2. 初始化统计计数变量;
  3. 打印报表固定表头,格式化输出标题。
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、&&||!逻辑运算
  1. if/else if/else :多分支条件判断,用于日志分级、阈值告警;

  2. 三元运算符 条件?成立表达式:不成立表达式 :单行极简判断赋值;

  3. 逻辑运算符:

    && 逻辑与:两边条件同时满足;

    || 逻辑或:任意一边满足;

    ! 逻辑非:取反,条件反转。

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循环
  1. awk 无普通数字数组,只有关联数组(key-value 键值对),常用于 IP 计数、状态码统计、分组聚合;

  2. for(key in arr) :遍历数组所有键,END 块汇总报表必备;

  3. 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循环条件执行逻辑差异
  1. while 条件 ; do 循环体; done

    ​ 条件成立时持续执行循环;条件不成立立刻退出。

  2. 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++))数值循环
  1. for i in 列表/数组 :遍历固定字符串列表、文件通配符、数组元素,适合批量服务、批量文件处理;

  2. 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跳过单次循环
  1. break :终止整个循环,直接跳出; break N 跳出 N 层嵌套循环;
  2. 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. 写法 1:完整 function 关键字声明(可读性高,教学推荐)

    ​ function 函数名 { 函数体; }

  2. 写法 2:简化括号声明(生产脚本常用,简洁)

    ​ 函数名() { 函数体; }

  3. 调用规则:调用仅写 函数名 参数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局部变量隔离全局命名污染
  1. 不加 local 定义的变量是全局变量,整个脚本任意位置均可读写,多函数共用易出现命名冲突、数据覆盖;

  2. 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 文件
  1. 工程化分层思想项目中大量重复工具逻辑(日志打印、文件校验、异常占比计算、IP 脱敏等)如果全部堆在主脚本,代码臃肿、难以维护;将通用函数统一抽离到独立库文件 lib/utils.sh ,形成函数工具库,实现一处编写、多处调用。

  2. 目录规范主脚本同级创建 lib 目录,存放所有公共函数库,区分业务主脚本与通用工具代码,是企业 Shell 项目标准目录结构。

  3. 工具库适配场景适配本日日志分析项目:日志分级打印、文件存在校验、异常百分比计算、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 多分支判断,四类错误码对应四种处理逻辑:

  1. E_OK :无异常,继续执行下一段业务逻辑;
  2. E_ERR :致命错误,打印日志后 exit 1 直接终止脚本;
  3. E_WARN :仅打印警告日志,不中断,继续执行统计、报表流程;
  4. 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、脚本调试模式
  1. 调试排错:通过 set -x 、 bash -x 逐行打印执行过程,快速定位变量赋值、循环、awk 统计、文件路径等逻辑 BUG;支持局部开启 / 关闭调试,屏蔽冗余输出。

  2. 信号自动处理: trap 捕获报错、脚本正常退出、Ctrl+C 强制终止信号,自动打印报错行号、清理临时中转文件,避免磁盘残留垃圾文件。

  3. 健壮严格模式: 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
  1. trap 捕获指定信号/事件,触发时执行自定义命令;
  2. ERR 事件:任意命令返回非 0 错误码时自动触发;
  3. $LINENO 内置变量:当前报错代码行号,快速定位出错位置;
  4. 集成日志打印函数,把报错行号写入分级日志,便于事后查看。
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终止信号
  1. EXIT :无论脚本正常退出、异常崩溃,都会触发;用于统一删除 tmp_stat 临时统计文件;

  2. INT :按下 Ctrl+C 强制终止脚本触发;

  3. 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 三大严格模式作用与组合使用
  1. set -e :任意命令返回非 0 错误码,脚本直接终止退出,不会忽略故障继续向下执行;
  2. set -u :读取未定义变量直接报错退出,防止空路径、空阈值引发统计逻辑错乱;
  3. 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 临时资源清理完成

复制代码
相关推荐
xiaoqiMikko40 分钟前
Linux 实战(五):DNS、hosts、CLOSE-WAIT、SSH 权限与改端口 —— 三台真机实测
linux·centos
田里的水稻44 分钟前
EI_模仿学习IL---工程链路
人工智能·深度学习·学习·机器学习·迁移学习
XHGILY3331 小时前
职业院校实习计划制定:科学规划与落地执行策略深度解析
学习
yv798an1 小时前
从 OpenSBI 引导到 C++17 异步运行时:自制 RISC-V 嵌入式专有 NAS 踩坑全记录
linux
Xudde.1 小时前
CVE-2017-9993漏洞复现
笔记·学习·安全·web安全
程序员zgh1 小时前
C++ erase() 函数用法
c语言·开发语言·c++·学习
mlidongfeng1 小时前
【学习】 NVIDIA CFT(Compute Fabric Transport)特性分析
人工智能·学习·fabric
不悔哥1 小时前
Linux系统编程多线程程序如何安全退出
linux
小李不想当小白2 小时前
USART串口协议(STM32标准库学习笔记)
经验分享·笔记·stm32·单片机·嵌入式硬件·学习