awk的使用及日志筛选

awk

1.什么是awk

awk 是一种编程语言,用于在linux/unix下对文本和数据进行处理。数据可以来自标准输入(stdin)、一个或多个文件,或其它命令的输出。它支持用户自定义函数和动态正则表达式等先进功能,是linux/unix下的一个强大编程工具。它在命令行中使用,但更多是作为脚本来使用。awk有很多内建的功能,比如数组、函数等,这是它和C语言的相同之处,灵活性是awk最大的优势。

2.awk命令格式和选项

2.1 语法格式
awk [options] 'script' var=value file(s)
awk [options] -f scriptfile var=value file

命令选项

  • **-F fs:**fs指定输入分隔符,fs可以是字符串或正则表达式,如-F:,默认的分隔符是连续的空格或制表符。
  • **-v var=value:**赋值一个用户定义变量,将外部变量传递给awk。
  • **-f scripfile:**从脚本文件中读取awk命令。
  • **-m[fr] val:**对val值设置内在限制,-mf选项限制分配给val的最大块数目;-mr选项限制记录的最大数目。这两个功能是Bell实验室版awk的扩展功能,在标准awk中不适用。
2.2 awk的变量

变量:内置变量和自定义变量,变量前加-v的选项

2.2.1 awk的内置变量(预定义变量)

说明:[A][N][P][G]表示第一个支持变量的工具,[A]=awk、[N]=nawk、[P]=POSIXawk、[G]=gawk

(1)变量说明

[A] :

  • NF 表示字段数,在执行过程中对应于当前的字段数,$NF引用最后一列,$(NF-1)引用倒数第2列。

  • NR 表示记录数,在执行过程中对应于当前的行号,后可跟多个文件,第二个文件行号继续从第一个文件最后行号开始。

  • OFMT 数字的输出格式(默认值是%.6g)。

  • OFS 输出字段分隔符(默认值是一个空格)。

  • ORS 输出记录分隔符(默认值是一个换行符)。

  • RS 记录分隔符(默认是一个换行符)。

  • FS 字段分隔符(默认是任何空格)。

  • FILENAME 当前输入文件的名。

[N] :

  • ARGC 命令行参数的数目。

  • ARGV 包含命令行参数的数组。

  • ERRNO 最后一个系统错误的描述。

  • RSTART 由match函数所匹配的字符串的第一个位置。

  • RLENGTH 由match函数所匹配的字符串的长度。

  • SUBSEP 数组下标分隔符(默认值是34)。

[G]:

  • ARGIND 命令行中当前文件的位置(从0开始算)。

  • IGNORECASE 如果为真,则进行忽略大小写的匹配。

  • CONVFMT 数字转换格式(默认值为%.6g)。

  • FIELDWIDTHS 字段宽度列表(用空格键分隔)。

[P] :

  • ENVIRON 环境变量关联数组。

  • FNR 同NR,但相对于当前文件。

(2)示例

[root@along ~]# cat awkdemo
hello:world
linux:redhat:lalala:hahaha
along:love:youou
[root@along ~]# awk -v FS=':' '{print $1,$2}' awkdemo  #FS指定输入分隔符
hello world
linux redhat
along love
[root@along ~]# awk -v FS=':' -v OFS='---' '{print $1,$2}' awkdemo  #OFS指定输出分隔符
hello---world
linux---redhat
along---love
[root@along ~]# awk -v RS=':' '{print $1,$2}' awkdemo
hello
world linux
redhat
lalala
hahaha along
love
you
[root@along ~]# awk -v FS=':' -v ORS='---' '{print $1,$2}' awkdemo
hello world---linux redhat---along love---
[root@along ~]# awk -F: '{print NF}' awkdemo
2
4
3
[root@along ~]# awk -F: '{print $(NF-1)}' awkdemo  #显示倒数第2列
hello
lalala
love
[root@along ~]# awk '{print NR}' awkdemo awkdemo1
1
2
3
4
5
[root@along ~]# awk END'{print NR}' awkdemo awkdemo1
5
[root@along ~]# awk '{print FNR}' awkdemo awkdemo1
1
2
3
1
2
[root@along ~]# awk '{print FILENAME}' awkdemo
awkdemo
awkdemo
awkdemo
[root@along ~]# awk 'BEGIN {print ARGC}' awkdemo awkdemo1
3
[root@along ~]# awk 'BEGIN {print ARGV[0]}' awkdemo awkdemo1
awk
[root@along ~]# awk 'BEGIN {print ARGV[1]}' awkdemo awkdemo1
awkdemo
[root@along ~]# awk 'BEGIN {print ARGV[2]}' awkdemo awkdemo1
awkdemo1
2.2.2 自定义变量

自定义变量( 区分字符大小写)

(1)-v var=value

① 先定义变量,后执行动作print

[root@along ~]# awk -v name="along" -F: '{print name":"$0}' awkdemo
along:hello:world
along:linux:redhat:lalala:hahaha
along:along:love:you

② 在执行动作print后定义变量

[root@along ~]# awk -F: '{print name":"$0;name="along"}' awkdemo
:hello:world
along:linux:redhat:lalala:hahaha
along:along:love:you

(2)在program 中直接定义

可以把执行的动作放在脚本中,直接调用脚本 -f

[root@along ~]# cat awk.txt
{
    
    name="along";print name,$1}
[root@along ~]# awk -F: -f awk.txt awkdemo
along hello
along linux
along along

3.awk运算与判断

作为一种程序设计语言所应具有的特点之一,awk支持多种运算,这些运算与C语言提供的基本相同。awk还提供了一系列内置的运算函数(如log、sqr、cos、sin等)和一些用于对字符串进行操作(运算)的函数(如length、substr等等)。这些函数的引用大大的提高了awk的运算功能。作为对条件转移指令的一部分,关系判断是每种程序设计语言都具备的功能,awk也不例外,awk中允许进行多种测试,作为样式匹配,还提供了模式匹配表达式(匹配)和!(不匹配)。作为对测试的一种扩充,awk也支持用逻辑运算符。

3.1 运算符
3.1.1 算术运算符
运算符 描述
+ - 加,减
* / & 乘,除与求余
+ - ! 一元加,减和逻辑非
^ *** 求幂
++ – 增加或减少,作为前缀或后缀
3.1.2 逻辑运算符
运算符 描述
|| 逻辑或
&& 逻辑与
3.1.3 正则运算符
运算符 描述
~ !~ 匹配正则表达式和不匹配正则表达式
^ 行首
$ 行尾
. 除了换行符以外的任意单个字符
* 前导字符的零个或多个
.* 所有字符
[] 字符组内的任一字符
[^] 对字符组内的每个字符取反(不匹配字符组内的每个字符)
^[^] 非字符组内的字符开头的行
[a-z] 小写字母
[A-Z] 大写字母
[a-Z] 小写和大写字母
[0-9] 数字
\< 单词头单词一般以空格或特殊字符做分隔,连续的字符串被当做单
\> 单词尾

注意:正则需要用/正则/包围住

3.1.4 关系运算符
运算符 描述
< 小于
<= 小于等于
> 大于
>= 大于等于
!= 不等于
== 等于
3.1.5 其他运算符
运算符 描述
$ 字段引用
空格 字符串连接符
?: C条件表达式
in 数组中是否存在某键值
3.2 函数

常用内置函数:

  • tolower():字符转为小写。
  • length():返回字符串长度。
  • substr():返回子字符串。
  • sin():正弦。
  • cos():余弦。
  • sqrt():平方根。
  • rand():随机数。

4.流程控制语句

在linux awk的while、do-while和for语句中允许使用break,continue语句来控制流程走向,也允许使用exit这样的语句来退出。break中断当前正在执行的循环并跳到循环外执行下一条语句。if 是流程选择用法。awk中,流程控制语句,语法结构,与c语言类型。有了这些语句,其实很多shell程序都可以交给awk,而且性能是非常快的。下面是各个语句用法。

4.1 条件判断语句
4.1.1 awk条件判断

awk允许指定输出条件,只输出符合条件的行。

输出条件要写在动作的前面。

awk '条件 动作' 文件名

请看下面的例子。

awk -F ':' '/usr/ {print $1}' demo.txt
root
daemon
bin
sys

上面代码中,print命令前面是一个正则表达式,只输出包含usr的行。

下面的例子只输出奇数行,以及输出第三行以后的行。

# 输出奇数行
awk -F ':' 'NR % 2 == 1 {print $1}' demo.txt
root
bin
sync

# 输出第三行以后的行
awk -F ':' 'NR >3 {print $1}' demo.txt
sys
sync

下面的例子输出第一个字段等于指定值的行。

awk -F ':' '$1 == "root" {print $1}' demo.txt
root

awk -F ':' '$1 == "root" || $1 == "bin" {print $1}' demo.txt
root
bin
4.1.2 if 语句

(1)语法

if(condition){
    
    statement;}[else statement]  双分支
if(condition1){
    
    statement1}else if(condition2){
    
    statement2}else{
    
    statement3}  多分支

(2)使用场景:对awk 取得的整行或某个字段做条件判断

(3)示例

[root@along ~]# awk -F: '{if($3>10 && $3<1000)print $1,$3}' /etc/passwd
operator 11
games 1
[root@along ~]# awk -F: '{if($NF=="/bin/bash") print $1,$NF}' /etc/passwd
root /bin/bash
along /bin/bash
---输出总列数大于3的行
[root@along ~]# awk -F: '{if(NF>2) print $0}' awkdemo
linux:redhat:lalala:hahaha
along:love:you
---第3列>=1000为Common user,反之是root or Sysuser
[root@along ~]# awk -F: '{if($3>=1000) {printf "Common user: %s\n",$1} else{printf "root or Sysuser: %s\n",$1}}' /etc/passwd
root or Sysuser: root
root or Sysuser: bin
Common user: along
---磁盘利用率超过40的设备名和利用率
[root@along ~]# df -h|awk -F% '/^\/dev/{print $1}'|awk '$NF > 40{print $1,$NF}'
/dev/mapper/cl-root 43
---test=100>90为very good; 90>test>60为good; test<60为no pass
[root@along ~]# awk 'BEGIN{ test=100;if(test>90){print "very good"}else if(test>60){ print "good"}else{print "no pass"}}'
very good
[root@along ~]# awk 'BEGIN{ test=80;if(test>90){print "very good"}else if(test>60){ print "good"}else{print "no pass"}}'
good
[root@along ~]# awk 'BEGIN{ test=50;if(test>90){print "very good"}else if(test>60){ print "good"}else{print "no pass"}}'
no pass
4.2 循环语句
4.2.1 while循环

(1)语法

while(condition){
    
    statement;}

注:条件“真”,进入循环;条件“假”, 退出循环

(2)使用场景

对一行内的多个字段逐一类似处理时使用

对数组中的各元素逐一处理时使用

(3)示例

---以along开头的行,以:为分隔,显示每一行的每个单词和其长度
[root@along ~]# awk -F: '/^along/{i=1;while(i<=NF){print $i,length($i); i++}}' awkdemo
along 5
love 4
you 3
---以:为分隔,显示每一行的长度大于6的单词和其长度
[root@along ~]# awk -F: '{i=1;while(i<=NF) {if(length($i)>=6){print $i,length($i)}; i++}}' awkdemo
redhat 6
lalala 6
hahaha 6
---计算1+2+3+...+100=5050
[root@along ~]# awk 'BEGIN{i=1;sum=0;while(i<=100){sum+=i;i++};print sum}'
5050
4.2.2 do-while循环

(1)语法

do{
    
    statement;}while(condition)

意义:无论真假,至少执行一次循环体

(2)计算1+2+3+…+100=5050

[root@along ~]# awk 'BEGIN{sum=0;i=1;do{sum+=i;i++}while(i<=100);print sum}'
5050
4.2.3 for 循环

(1)语法

for(expr1;expr2;expr3) {
    
    statement;}

(2)特殊用法:遍历数组中的元素

for(var in array) {
    
    forbody} 

(3)示例

---显示每一行的每个单词和其长度
[root@along ~]# awk -F: '{for(i=1;i<=NF;i++) {print$i,length($i)}}' awkdemo
hello 5
world 5
linux 5
redhat 6
lalala 6
hahaha 6
along 5
love 4
you 3
---求男m、女f各自的平均
[root@along ~]# cat sort.txt
score
[m=>170]
xiaoming m 90
xiaohong f 93
xiaohei m 80
xiaofang f 99
[root@along ~]# awk '{m[$2]++;score[$2]+=$3}END{for(i in m){printf "%s:%6.2f\n",i,score[i]/m[i]}}' sort.txt
m: 85.00
f: 96.00
4.3 其他语句
  • break 当 break 语句用于 while 或 for 语句时,导致退出程序循环。

  • continue 当 continue 语句用于 while 或 for 语句时,使程序循环移动到下一个迭代。

  • next 能能够导致读入下一个输入行,并返回到脚本的顶部。这可以避免对当前输入行执行其他的操作过程。

  • exit 语句使主输入循环退出并将控制转移到END,如果END存在的话。如果没有定义END规则,或在END中应用exit语句,则终止脚本的执行。

5.awk数组

数组是awk的灵魂,处理文本中最不能少的就是它的数组处理。因为数组索引(下标)可以是数字和字符串在awk中数组叫做关联数组(associative arrays)。awk 中的数组不必提前声明,也不必声明大小。数组元素用0或空字符串来初始化,这根据上下文而定。

array[index-expression]

(1)可使用任意字符串;字符串要使用双引号括起来

(2)如果某数组元素事先不存在,在引用时,awk 会自动创建此元素,并将其值初始化为“空串”

(3)若要判断数组中是否存在某元素,要使用“index in array”格式进行遍历

(4)若要遍历数组中的每个元素,要使用for 循环**:for(var in array)** {for-body}

5.1 数组的定义

数字做数组索引(下标):

Array[1]="sun"
Array[2]="kai"

字符串做数组索引(下标):

Array["first"]="www"
Array"[last"]="name"
Array["birth"]="1987"
5.2 数组使用

(1)数组的基本使用示例

[root@along ~]# cat awkdemo2
aaa
bbbb
aaa
123
123
123
---去除重复的行
[root@along ~]# awk '!arr[$0]++' awkdemo2
aaa
bbbb
123
---打印文件内容,和该行重复第几次出现
[root@along ~]# awk '{!arr[$0]++;print $0,arr[$0]}' awkdemo2
aaa 1
bbbb 1
aaa 2
123 1
123 2
123 3

分析:把每行作为下标,第一次进来,相当于print ias…一样结果为空,打印空,!取反结果为1,打印本行,并且++变为不空,下次进来相同的行就是相同的下标,本来上次的值,!取反为空,不打印,++变为不空,所以每次重复进来的行都不打印。

(2)数组遍历

awk 关联数组 key=>value 无序

[root@along ~]# awk 'BEGIN{abc["ceo"]="along";abc["coo"]="mayun";abc["cto"]="mahuateng";for(i in abc){print i,abc[i]}}'
coo mayun
ceo along
cto mahuateng
[root@along ~]# awk '{for(i=1;i<=NF;i++)abc[$i]++}END{for(j in abc)print j,abc[j]}' awkdemo2
aaa 2
bbbb 1
123 3

6.数值和字符串的应用处理

6.1 数值的处理
  • rand():返回0和1之间一个随机数,需有个种子 srand(),没有种子,一直输出0.237788

演示:

[root@along ~]# awk 'BEGIN{print rand()}'
0.237788
[root@along ~]# awk 'BEGIN{srand(); print rand()}'
0.51692
[root@along ~]# awk 'BEGIN{srand(); print rand()}'
0.189917
---取0-50随机数
[root@along ~]# awk 'BEGIN{srand(); print int(rand()*100%50)+1}'
12
[root@along ~]# awk 'BEGIN{srand(); print int(rand()*100%50)+1}'
24
6.2 字符串的处理
  • length([s]) :返回指定字符串的长度
  • sub(r,s,[t]) :对t 字符串进行搜索r 表示的模式匹配的内容,并将第一个匹配的内容替换为s
  • gsub(r,s,[t]) :对t 字符串进行搜索r 表示的模式匹配的内容,并全部替换为s 所表示的内容
  • split(s,array,[r]) :以r 为分隔符,切割字符串s ,并将切割后的结果保存至array 所表示的数组中,第一个索引值为1, 第二个索引值为2,…

演示:

[root@along ~]# echo "2008:08:08 08:08:08" | awk 'sub(/:/,"-",$1)'
2008-08:08 08:08:08
[root@along ~]# echo "2008:08:08 08:08:08" | awk 'gsub(/:/,"-",$0)'
2008-08-08 08-08-08
[root@along ~]# echo "2008:08:08 08:08:08" | awk '{split($0,i,":")}END{for(n in i){print n,i[n]}}'
4 08
5 08
1 2008
2 08
3 08 08

7.awk脚本

(1)awk脚本编写

将awk 程序写成脚本,直接调用或执行

示例:

[root@along ~]# cat f1.awk
{
    
    if($3>=1000)print $1,$3}
[root@along ~]# cat f2.awk
#!/bin/awk -f
{
    
    if($3 >= 1000)print $1,$3}
[root@along ~]# chmod +x f2.awk
[root@along ~]# ./f2.awk -F: /etc/passwd
along 1000

(2)向awk脚本传递参数

格式:

awkfile var=value var2=value2... Inputfile

注意 :在BEGIN 过程 中不可用。直到 首行输入完成以后,变量才可用 。可以通过**-v 参数**,让awk 在执行BEGIN 之前得到变量的值。命令行中每一个指定的变量都需要一个-v。

示例:

[root@along ~]# cat test.awk
#!/bin/awk -f
{
    
    if($3 >=min && $3<=max)print $1,$3}
[root@along ~]# chmod +x test.awk
[root@along ~]# ./test.awk -F: min=100 max=200 /etc/passwd
systemd-network 192

8.awk的筛选练习

筛选给定时间范围内的日志

/var/log/的路径下复制某个日志文件,编写脚本文件来对所需要的时间范围内的日志进行指定筛选。

[root@localhost /]# cat time.log      #拷贝的日志文件的内容
[2023-07-30T17:59:05+0800] INFO === Started libdnf-0.63.0 ===
[2023-07-30T17:59:06+0800] INFO === Started libdnf-0.63.0 ===
[2023-07-30T17:59:06+0800] INFO === Started libdnf-0.63.0 ===
[2023-07-31T17:10:29+0800] INFO === Started libdnf-0.63.0 ===
[2023-07-31T17:21:57+0800] INFO === Started libdnf-0.63.0 ===
[2023-07-31T17:22:02+0800] INFO === Started libdnf-0.63.0 ===
[2023-07-31T17:22:03+0800] INFO === Started libdnf-0.63.0 ===
[2023-07-31T17:26:35+0800] INFO === Started libdnf-0.63.0 ===
[2023-07-31T17:26:46+0800] INFO === Started libdnf-0.63.0 ===
[2023-07-31T17:26:47+0800] INFO === Started libdnf-0.63.0 ===
[2023-07-31T17:26:47+0800] INFO === Started libdnf-0.63.0 ===
[2023-07-31T17:30:30+0800] INFO === Started libdnf-0.63.0 ===
[2023-07-31T17:32:07+0800] INFO === Started libdnf-0.63.0 ===
[2023-07-31T17:32:08+0800] INFO === Started libdnf-0.63.0 ===
[2023-07-31T17:32:08+0800] INFO === Started libdnf-0.63.0 ===
[2023-07-31T18:17:11+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-01T16:07:13+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T01:18:14+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T01:19:37+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T01:19:52+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T01:19:53+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T01:19:53+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T01:19:54+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T09:48:37+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T09:54:20+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T09:54:21+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T09:54:22+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:31:20+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:31:36+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:31:36+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:31:36+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:32:12+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:34:31+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:34:33+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:34:33+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T11:12:38+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T14:22:38+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-05T23:57:34+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-05T23:57:48+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-05T23:58:40+0800] INFO === Started libdnf-0.63.0 ===

awk中提供了mkrime()函数,可以将时间转化为epoch值。

[root@localhost /]# awk 'BEGIN{print mktime("2023 08 01 03 42 40")}'
1690832560
#将2023-08-01 03:42:40的时间转化为时间戳,便于进行比较 

编写脚本文件

BEGIN{
    
    
  #创建需要的比较的基准epoch值,用于筛选
  which_time1 = mktime("2023 08 02 00 00 00")
  which_time2 = mktime("2023 08 04 00 00 00")
}

{
    
    
  #取出日志中的时间字符串
  match($0,"^.*\\[(.*)\\].*",arr)
  
  #调用创建的strptime1函数将摘取出来的字符串转化为epoch值
  tmp_time = strptime1(arr[1])
  
  #将所摘取日志的时间戳与基准时间戳进行比较,符合指定范围内的内容摘取出来
  if(tmp_time > which_time1 && tmp_time < which_time2){
    
    print}
}

#创建将摘取时间进行重构的函数,返回时间戳
function strptime1(str,arr,Y,M,D,H,m,S){
    
    
  #patsplit来取时间中的数字
  patsplit(str,arr,"[0-9]{1,4}")
  Y=arr[1]
  M=arr[2]
  D=arr[3]
  H=arr[4]
  m=arr[5]
  S=arr[6]
  return mktime(sprintf("%s %s %s %s %s %s",Y,M,D,H,m,S))
}

调用脚本文件对所保存的time.log文件中所需要的内容进行定位筛选。

[root@localhost /]# awk -f awkdemo.awk time.log 
[2023-08-02T01:18:14+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T01:19:37+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T01:19:52+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T01:19:53+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T01:19:53+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T01:19:54+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T09:48:37+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T09:54:20+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T09:54:21+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T09:54:22+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:31:20+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:31:36+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:31:36+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:31:36+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:32:12+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:34:31+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:34:33+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T10:34:33+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T11:12:38+0800] INFO === Started libdnf-0.63.0 ===
[2023-08-02T14:22:38+0800] INFO === Started libdnf-0.63.0 ===

猜你喜欢

转载自blog.csdn.net/qq_44829421/article/details/132126629
今日推荐