Linux三剑客

grep

-n:打印行号
-A:after  打印过滤内容的后N行
-B:before 打印过滤内容的前N行
-C:center 打印过滤内容的前后N行
-G:支持基础正则 bre
-E: ere 支持扩展正则 如 grep -E 'root|nginx' /etc/passwd 不加E会把root|nginx当成一个整体去匹配
-P:支持Perl正则
-v:取反 取出文件内要匹配的内容除外的所有内容
-o:只打印匹配到的内容
-w:精确匹配
-i:忽略大小写
-r:递归检索文件中的内容
-R:递归检索文件中的内容,包括软链接文件
-l:只显示文件名
-h:只显示文件内容
-f:对比文件内容,内容少的文件在前面,内容多的文件在后面,取反可以看到不同的文件内容
-c:统计文件内要匹配的内容的行数,类似于 wc -l
-m:显示前N行 类似于 head -n 

gerp案例

## -o:只打印匹配到的内容 & -w:精确匹配 的区别:
[root@web01 ~]# grep -w 'www' /root/web/js
www.hg.com ## www为红色 也就是匹配到了这个整体 而-o就只会打印出要匹配的内容
[root@web01 ~]# grep -o 'www' /root/web/js
www

## -r:递归检索文件中的内容
[root@web01 ~]# mkdir web
[root@web01 ~]# cd web
[root@web01 web]# echo www.hg.com > css
[root@web01 web]# echo www.hg.com > js
[root@web01 web]# echo www.hg.com > index.html

[root@web01 web]# ln -s js abc

[root@web01 web]# grep -r 'www.hg.com' ./
[root@web01 web]# grep -r 'www.hg.com' ./
./index.html:www.hg.com
./js:www.hg.com
./css:www.hg.com

## -R:递归检索文件中的内容,包括软链接文件
[root@web01 web]# grep -R 'www.hg.com' ./
./index.html:www.hg.com
./js:www.hg.com
./css:www.hg.com
./abc:www.hg.com

## -l:只显示文件名
[root@web01 web]# grep -lr 'www.hg.com' ./
./index.html
./js
./css

## -h:只显示文件内容
[root@web01 web]# grep -hr 'www.hg.com' ./
www.hg.com
www.hg.com
www.hg.com

## -f:对比文件内容,内容少的文件在前面,内容多的文件在后面,取反可以看到不同的文件内容
[root@web01 web]# vim a.txt
aa
bb
[root@web01 web]# cp a.txt b.txt
[root@web01 web]# grep -vf a.txt b.txt
[root@web01 web]# echo dd >b.txt
[root@web01 web]# grep -vf a.txt b.txt
cc

# 我们可以拿-vf去检查用户文件是否被改动(增加用户 删除用户) 案例如下:
[root@web01 web]# cp /etc/passwd /tmp/passwd
[root@web01 web]# md5sum /etc/passwd
e99baa8bb22d80e933f9887277bfc348  /etc/passwd
[root@web01 web]# md5sum /tmp/passwd
e99baa8bb22d80e933f9887277bfc348  /tmp/passwd
# 检测增加用户方法
[root@web01 web]# grep -vf /tmp/passwd /etc/passwd
[root@web01 web]# useradd hghg
[root@web01 web]# grep -vf /tmp/passwd /etc/passwd
hghg:x:1000:1000::/home/hghg:/bin/bash
# 检测删除用户方法
[root@web01 web]# cp /etc/passwd /tmp/
[root@web01 web]# userdel -r hghg
## 此时-vf是检测不出来少的内容的 需要用 md5sum 去检测文件是否有改动 有的话就把两个比较的文件调换位置去比较  这样就是被删除的用户
[root@web01 web]# grep -vf /tmp/passwd /etc/passwd
[root@web01 web]# md5sum  -c /etc/passwd
md5sum: /etc/passwd: no properly formatted MD5 checksum lines found
[root@web01 web]# grep -vf /etc/passwd /tmp/passwd
hghg:x:1000:1000::/home/hghg:/bin/bash

## 统计文件内要匹配的内容的行数,类似于 wc -l
[root@web01 web]# grep -c 'nginx' /etc/passwd
1

## -m:显示前N行 类似于 head -n
[root@web01 web]# grep -m 5 'nginx' /etc/passwd
nginx:x:998:998:Nginx web server:/var/lib/nginx:/sbin/nologin
nginx1:x:999:999:Nginx web server:/var/lib/nginx1:/sbin/nologin
nginx:x:1000:1000:Nginx web server:/var/lib/nginx2:/sbin/nologin
nginx:x:1001:1001:Nginx web server:/var/lib/nginx3:/sbin/nologin
nginx:x:1002:1002:Nginx web server:/var/lib/nginx4:/sbin/nologin

awk

awk的内置变量和动作和选项

awk内置变量 变量含义 awk选项 选项含义 awk动作 动作含义
NR Number of Record 行号 -F 指定分隔符 gsub 替换
RS Record Separator 行的分隔符(\n) -v 指定变量(可以使用内置变量、也可以自定义变量) print 打印
FS Field Separator 列的分隔符(空格)
NF Number Of Filed 每一行有多少列

注意:awk输出变量使用单引号,bash输出变量使用双引号

## awk不是一个命令,是一门编程语言。
## awk又叫做GNU awk,gawk 可以看到awk其实是gawk的软链接
[root@web01 ~]# ls -l $(which awk)
lrwxrwxrwx. 1 root root 4 Jul  5  2021 /usr/bin/awk -> gawk

awk执行流程(重点)

awk执行一共有三个阶段

  • 一阶段:读取文件之前
    • 语法:BEGIN{}
    • 1.读取文件之前,先看命令的选项,例如 -F,-v
    • 2.如果写了BEGIN{} 则先执行在BEGIN {}中的指令
  • 二阶段:读取文件时
    • 语法:{}
    • 1.awk在读取文件时,也是一行一行的读\
    • 2.读取一行之后,判断是否满足条件,如果是,则执行{对应动作} ,然后将满足条件执行完动作的读取到内存中
    • 3.如果不满足条件,awk继续读取下一行,每有一行满足条件的内容,就把满足条件的那一行内容读取到内存中一次 直到读取完文件的最后一行
  • 三阶段:读取文件之后
    • 语法:END{}
    • 1.所有文件读取完成之后,走END{}中的指令,可以把二阶段读取出来的文件内容用END{}中的指令去操作
## 案例如下:
[root@web01 ~]# vim abc.txt
root 0 0
bin 1 1

[root@web01 ~]# awk 'BEGIN{print "xxx"}{print $1}END{print 1/3}'  abc.txt
# 1.先 BEGIN{print "xxx"} 打印一个xxx 
# 2.然后{print $1}打印文件 abc.txt 中的第一列
# 3.最后END{print 1/3}打印 1/3 的计算结果

[root@web01 ~]# awk 'BEGIN{print "xxx"}{print $1}END{print 1/3}'  abc.txt
xxx
root
bin
0.333333

## 处理文件案例:取出/etc/passwd中的 用户名 uid pid
[root@web01 ~]# awk -F: 'BEGIN{print "name","uid","gid"}{print $1,$3,$4}END{print "用户过滤完成"}' /etc/passwd|column -t
name             uid    gid
root             0      0
bin              1      1
daemon           2      2
adm              3      4
lp               4      7
用户过滤完成
# column -t : 让输出结果的列对齐 不是awk里的指令 是另外的指令

awk的行与列

行:专业用语里把行叫做 记录 (record)

列:专业用语里把列叫做 字段 (field)

awk取行

## NR:Number of Record 行号
# 这里我们的需求是只要打印出行 所以不需要 {print $n} 这些 直接 NR==n 就可以了
[root@web01 ~]# awk 'NR==1' /etc/passwd
root:x:0:0:root:/root:/bin/bash

## 范围取行:取出文件的1到3行
# 写法一:
[root@web01 ~]# awk 'NR>=1 && NR<=3' /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
# 写法二:
[root@web01 ~]# awk 'NR==1,NR==3' /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin

## 取出文件中包含hg和cls的行
[root@web01 ~]# vim hg.txt 
1,hg,666
2,wls,777
3,cls,888
4,lls,999
[root@web01 ~]# awk '/hg|cls/' hg.txt
1,hg,666
3,cls,888

## 正则表达式
awk '/正则表达式/'
awk '/正则表达式/,/正则表达式/'
## 取出文件中前三行以外的内容
[root@web01 ~]# awk 'NR>=3' hg.txt 
3,cls,888
4,lls,999

## 取出文件中包含hg到cls的行
[root@web01 ~]# awk '/hg/,/cls/' hg.txt 
1,hg,666
2,wls,777
3,cls,888

### awk 可以修改结束标记:
# RS :Record Separator 是awk的内置变量 默认的变量值就是\n
# 把原本每一行的分隔符 也就是换行符\n 换成指定的换行符 , 也就是文件中每一个 , 就相当于换行符 进行了换行
[root@web01 ~]# awk -vRS=, 'NR==1' hg.txt
1
[root@web01 ~]# awk -vRS=, '{print $0}' hg.txt  ## $0是所有的列
1
hg
666
2
wls
777
3
cls
888
4
lls
999

awk取列

### FS:awk的内置变量 列的分隔符 默认为空格 默认贪婪匹配空格 和 -F指定分隔符作用一样 写法:-vFS=

## 打印出/etc/passwd文件中每一行的第一列内容
[root@web01 ~]# awk -vFS=: '{print $1}' /etc/passwd
root
bin
daemon

## 打印出/etc/passwd文件中每一行的第一列内容和最后一列内容 NF:awk的内置变量 每一行有多少列 相当于最后一列
[root@web01 ~]# awk -vFS=: '{print $1,$NF}' /etc/passwd
root /bin/bash
bin /sbin/nologin
daemon /sbin/nologin

## OFS:修改输出后内容的分隔符 (分隔符可以是 数字,字母,其他特殊符号)
# 当指定的替换符不一样时的写法:
[root@web01 ~]# awk -F: '{print $1"#"$2"0"$3"|"$4"a"$5" "$6","$NF}' /etc/passwd
root#x00|0aroot /root,/bin/bash
bin#x01|1abin /bin,/sbin/nologin
daemon#x02|2adaemon /sbin,/sbin/nologin

# 当指定的替换符是一样时的写法:
[root@web01 ~]# awk -F: -vOFS=# '{print $1,$2,$3,$4,$5,$6,$NF}' /etc/passwd
root#x#0#0#root#/root,/bin/bash
bin#x#1#1#bin#/bin,/sbin/nologin
daemon#x#2#2#daemon#/sbin,/sbin/nologin

## 取出每一行所以的列($0:所有的列)
[root@web01 ~]# awk -F: '{print $0}' /etc/passwd

awk取行取列

## 取出top中的运行时间
[root@web01 ~]# top -n1 |awk 'NR==1{print $5}'
1
# top -n1:让top内容只输出一次 不是awk的指令 是另外的指令

## 取出网卡配置文件的IP地址
[root@web01 ~]# awk -F= '/IPADDR/{print $2}' /etc/sysconfig/network-scripts/ifcfg-eth0
10.0.0.61

## 练习题1:请找出姓氏是张的人,他们第二次捐款的数额及姓名
[root@web01 ~]# vim user.txt
Zeng Laoshi       133411023        :110 :100:75
Deng Ziqi            44002231        :250 :10:88
Zhang Xinyu      877623568      :120 :300:200
Gu Linazha         11029987         :120 :30:79
Di Lireba             253097001      :220 :100:200
Jiang Shuying    535432779       :309 :10:2
Ju Jingyi             68005178         :130 :280:385
Zhang Yuqi         376788757       :500 :290:33
Wen Zhang         259872003      :100 :200:300

[root@m01 ~]# awk -F '[ :]+' 'BEGIN{print "姓","名","捐款数额"}/^Zhang/{print $1,$2,$5}' user.txt|column -t
姓     名     捐款数额
Zhang  Xinyu  300
Zhang  Yuqi   290

## 练习题2:在文件的每一行前加上了序号 请再次找出姓氏是张的人,他们第二次捐款的数额及姓名
[root@web01 ~]# vim user.txt
1 Zeng Laoshi       133411023        :110 :100:75
2 Deng Ziqi            44002231        :250 :10:88
3 Zhang Xinyu      877623568      :120 :300:200
4 Gu Linazha         11029987         :120 :30:79
5 Di Lireba             253097001      :220 :100:200
6 Jiang Shuying    535432779       :309 :10:2
7 Ju Jingyi             68005178         :130 :280:385
8 Zhang Yuqi         376788757       :500 :290:33
9 Wen Zhang         259872003      :100 :200:300

# 错误写法:此时还使用上面的方法 已经取不出想要的内容了 因为没有Zhang开头的行了
[root@m01 ~]# awk -F '[ :]+' 'BEGIN{print "姓","名","捐款数额"}/^Zhang/{print $2,$3,$6}' user.txt|column -t
姓  名  捐款数额

# 正确写法:  ~ :包含,$2~/Zhang/ 匹配第二列包含Zhang的行
[root@web01 ~]# awk -F '[ :]+' 'BEGIN{print "姓","名","捐款数额"}$2~/Zhang/{print $2,$3,$6}' user.txt|column -t
姓     名     捐款数额
Zhang  Xinyu  300
Zhang  Yuqi   290

## 找出姓氏是张 名是xinyu的人
# 写法一:$2~/Zhang/ 匹配第二列包含Zhang &&和 $3~/Xinyu 第三列包含Xinyu的行
[root@web01 ~]# awk -F '[ :]+' 'BEGIN{print "姓","名","捐款数额"}$2~/Zhang/ && $3~/Xinyu/{print $2,$3,$6}' user.txt|column -t
姓     名     捐款数额
Zhang  Xinyu  300
# 写法二:把姓名显示在一起把{print $2 $3,$(NF-1)}中分隔符,删除或换成空格 倒数第二列可以直接用运算NF-1表示
[root@web01 ~]# awk -F '[ :]+' 'BEGIN{print "姓名","捐款数额"}$2~/Zhang/ && $3~/Xinyu/{print $2 $3,$(NF-1)}' user.txt |column -t
姓名        捐款数额
ZhangXinyu  300

### 练习题3:显示所有以25开头的QQ号及姓名
[root@web01 ~]# awk '$4~/^25/{print $4,$2 $3}' user.txt 
[root@web01 ~]#  awk '$4~/^25/{print $4,$2 $3}' user.txt 
253097001 DiLireba
259872003 WenZhang

### 练习题4:显示所有QQ号最后一位是1或者3的人,全名及QQ
# 写法一:
[root@web01 ~]# awk '$4~/1$|3$/{print $2$3,$4}' user.txt 
ZengLaoshi 133411023
DengZiqi 44002231
DiLireba 253097001
WenZhang 259872003
# 写法二:
[root@web01 ~]# awk '$4~/(1|3)$/{print $2$3,$4}' user.txt 
ZengLaoshi 133411023
DengZiqi 44002231
DiLireba 253097001
WenZhang 259872003
# 写法三:
[root@web01 ~]# awk '$4~/[13]$/{print $2$3,$4}' user.txt 
ZengLaoshi 133411023
DengZiqi 44002231
DiLireba 253097001
WenZhang 259872003

### 练习题5:让每个捐款值都以$开头 用gsub把 : 修改为 $ 即可 gsub就是awk里的替换指令
## gsub("被替换的内容","替换的新内容")
[root@web01 ~]# awk '{gsub(/:/,"$");print $0}' user.txt 
1 Zeng Laoshi 133411023 $110 $100$75
2 Deng Ziqi 44002231 $250 $10$88
3 Zhang Xinyu 877623568 $120 $300$200
4 Gu Linazha 11029987 $120 $30$79
5 Di Lireba 253097001 $220 $100$200
6 Jiang Shuying 535432779 $309 $10$2
7 Ju Jingyi 68005178 $130 $280$385
8 Zhang Yuqi 376788757 $500 $290$33
9 Wen Zhang 259872003 $100 $200$300

## gsub 固定写法:(/被替换的内容/,"替换的新内容",$第N列)
[root@web01 ~]# awk '{gsub(/:/,"$",$5);print $0}' user.txt 
1 Zeng Laoshi 133411023 $110 :100:75
2 Deng Ziqi 44002231 $250 :10:88
3 Zhang Xinyu 877623568 $120 :300:200
4 Gu Linazha 11029987 $120 :30:79
5 Di Lireba 253097001 $220 :100:200
6 Jiang Shuying 535432779 $309 :10:2
7 Ju Jingyi 68005178 $130 :280:385
8 Zhang Yuqi 376788757 $500 :290:33
9 Wen Zhang 259872003 $100 :200:300

### awk循环的的定义方法:
function gsub(){
    $1
    $2
    $3
    xxxx
}

### awk传参的方法:
gsub(xx,aaa,d)

### 综合应用练习题:找出ifconfig中范围是1-255的数字
# RS:awk的内置变量 行的分隔符 指定0到9的数字为分隔符贪婪匹配所有的数字 取出大于1和小于255的列
[root@web01 ~]# ifconfig |awk -vRS='[^0-9]+' '$0>=1 && $0<=255'
10
61
255
255
255
10
........

awk模式与动作

awk -F: 'NR==1{print $1,$3}' /etc/passwd
## 上面这条命令我们可以看到,`'NR==1{print $1,$3}'`
## 可以理解为:`'模式{动作}'` == `'条件{指令}'`

awk中的模式

  • 正则表达式
# 正则表达式写法
'/正则表达式/flag'
'$1~/正则表达式/flag'
'$1!~/正则表达式/flag'

只不过我们在awk中很少使用flag
  • 比较表达式
NR==1
NR>=10
NR<=100
NR>=1 && NR<=10
$1>=100
  • 范围模式
## 精确匹配行号:从第10行到第20行
NR==10,NR==20

## 精确匹配字符串:从该字符串的行到另一个字符串所在行
'/root/,/wyk/'
'/从哪个字符串所在行/,/到那个字符串所在行/'  #中间的行都包含进去

## 模糊匹配字符串:从含有该字符串所在行到含有另一字符串所在行
'$1~/oo/,$1~/wy/'
  • 特殊模式
BEGIN
END

awk中的动作

在awk中,我们最常用的动作就是 print

当然我们还有别的动作可以使用:

  • print打印
  • gsub替换
  • 变量赋值
  • 统计计算
# 以下面的创建100用户并给予随机密码为案例
useradd name;pass=`echo $RANDOM|md5sum|cut -c 1-10`;echo $pass|passwd --stdin name;echo $pass:$user >> /tmp/user.txt

seq 100|awk '{print "useradd test"$1";pass=`echo $RANDOM|md5sum|cut -c 1-10`;echo $pass|passwd --stdin test"$1";echo $pass:test"$1" >> /tmp/user.txt"}'|bash

如果要使用BEGIN模式,那么一定要成双成对的出现:BEGIN{}

那么我们要知道,BEGIN{}中,大括号里面的内容,会在读取文件内容之前执行

主要应用场景:

  • 1.计算
[root@m01 ~]# awk 'BEGIN{print 1/3}'
0.333333
  • 2.awk功能测试
  • 3.输出表格的表头

END模式

一般来说,END{}要比BEGIN{}重要一些,BEGIN{}可有可无,计算其实可以放在读取文件的时候,也可以执行

END{}中,大括号里面的内容,会在awk读取完文件的最后一行后,进行处理

作用:一般我们使用END{}来显示对日志内容分析后的一个结果
当然,还有其他功能,比如文件读取完了,可以显示一些尾部信息

# 1.统计/etc/service文件中一共有多少行
### 只能统计文件的所有行数的写法
[root@m01 ~]# awk 'END{print NR}' /etc/services 
11176

## 出了结果 但还会输出过程
[root@m01 ~]# awk '{hang++;print hang}' /etc/services
1
...
11176

## 不需要过程,只要结果的写法
[root@m01 ~]# awk '{hang++}END{print hang}' /etc/services 
11176

### 流氓写法
[root@m01 ~]# sed -n '$=' /etc/services 
11176
[root@m01 ~]# grep -c '.*' /etc/services 
11176
[root@m01 ~]# wc -l /etc/services 
11176 /etc/services

# 2.统计/etc/service中空行的数量
[root@m01 ~]# awk '/^$/{print}' /etc/services
# 显示了17行空格 想要显示数量的正确写法如下:
[root@m01 ~]# awk '/^$/{i++}END{print i}' /etc/services 
17

# 3.统计出下列文件中所有人的年龄和
[root@m01 ~]# vim user.txt 
姓名      年龄
熬兴老师    23
苍颈空    18
西冶详    99

### awk方式
[root@m01 ~]# awk 'NR>1{print $2}' user.txt 
23
18
99
[root@m01 ~]# awk 'NR>1{n+=$2}END{print n}' user.txt 
140

### 脚本方式
#!/usr/bin/bash
n=0
for line in `cat user.txt`;do
if [[ $line =~ [0-9]+  ]];then
        ((n+=$line))
fi
done
echo $n

# 4.统计nginx日志中,状态码是200的次数以及,状态码是200时占用的流量
### awk方式
[root@m01 ~]# zcat www.blackgoatking.com_access.log-20220623.gz |awk 'BEGIN{print "状态码200的次数","总流量"}$10~/200/{code++;byte+=$11}END{print code,byte}'|column -t
状态码200的次数  总流量
3100             190477111

### 脚本写法:
awk '
BEGIN{
    print "状态码200的次数","总流量"
}
$10~/200/{
    code++;byte+=$11
}
END{
    print code,byte
}'

# 5.统计nginx日志中状态码是4xx和5xx的次数及总流量
[root@m01 ~]# zcat www.blackgoatking.com_access.log-20220623.gz|awk '$10~/^[45]/{i++;n+=$11}END{print i,n}'
580 519243

# 6.综合应用:分别统计每种状态码的次数和每个状态码的总流量
zcat www.blackgoatking.com_access.log-20220623.gz |awk '
BEGIN{
    print "状态码","总流量"
}
$10~/200/{
    i1++;n1+=$11
}
$10~/^3/{
    i2++;n2+=$11
}
$10~/^4/{
    i3++;n3+=$11
}
$10~/^5/{
    i4++;n4+=$11
}
END{
    print "200次数:"i1,"200的流量:"n1
    print "3xx次数:"i2,"3xx的流量:"n2
    print "4xx次数:"i3,"4xx的流量:"n3
    print "5xx次数:"i4,"5xx的流量:"n4
}'|column -t

awk数组

在awk中的数组数据类型,是非常好用的一个类型,不像是shell,当然shell中数组也有它自己的优点。

awk中的数组,专门用来统计不同的分类。

例如:
1.nginx日志中每个IP出现的次数
2.nginx日志中每种状态码出现的次数
3.nginx日志中每个URI的访问次数

[root@m01 ~]# zcat www.blackgoatking.com_access.log-20220623.gz|awk '{print $1}'|sort|uniq -c|sort -nr
    292 58.215.115.67
    277 58.215.115.30
    ......

awk数组赋值

[root@m01 ~]# awk 'BEGIN{array[0]="wyk";array[1]="wjh"}'

awk数组取值

[root@m01 ~]# awk 'BEGIN{array[0]="wyk";array[1]="wjh";print array[0],array[1]}'
wyk wjh

shell脚本中循环数组

## 数组赋值
array[0]='wyk'
array[1]='wjh'

## 数组取值
for name in ${array[*]};do
  echo $name
done

awk循环数组

## shell脚本中循环写法
for 条件;do
    动作
done

## wak中循环写法
for(条件){
    动作
}

## 取数组元素的元素标号的方法
[root@m01 ~]# awk 'BEGIN{array[0]="wyk";array[1]="wjh";for(num in array){print num}}'
0
1
## 取数组元素的元素值的方法
[root@m01 ~]# awk 'BEGIN{array[0]="wyk";array[1]="wjh";for(num in array){print array[num]}}'
wyk
wjh

## 统计nginx日志中的每一个IP地址访问的次数
[root@m01 ~]# zcat www.blackgoatking.com_access.log-20220623.gz |awk '{array[$1]++}END{for(ip in array){print ip,array[ip]}}'
36.156.89.168 32
58.215.115.41 117
58.215.115.82 93
......

#1.取出下列域名并根据域名,进行统计排序处理
[root@m01 ~]# vim black.txt
https://blog.blackgoatking.com/index.html
https://blog.blackgoatking.com/1.html
http://post.blackgoatking.com/index.html
http://mp3.blackgoatking.com/index.html
https://blog.blackgoatking.com/3.html
http://post.blackgoatking.com/2.html

[root@m01 ~]# awk -F/ '{domain[$3]++}END{for(name in domain){print name,domain[name]}}' black.txt 
blog.blackgoatking.com 3
post.blackgoatking.com 2
mp3.blackgoatking.com 1

#2.统计nginx日志中,每个IP访问使用的流量总和
[root@m01 ~]# zcat www.blackgoatking.com_access.log-20220623.gz |awk '{ip[$1]++;liuliang[$1]+=$11}END{for(i in ip){print i,ip[i],liuliang[i]}}'
120.226.153.97 8 54894
101.72.213.200 4 251260
36.156.89.170 17 99703
36.156.89.171 23 211626
......

awk的判断

awk判断与shell判断对比

## shell脚本中判断的写法
if [ 条件 ];then
    动作
fi
# 多分支
if [ 条件 ];then
else
fi
# 多条件
if [ 条件 ];then
elif [ 条件 ];then
else
fi

## 
awk中判断的写法
if(条件){
    动作
}
# 多分支
if(条件){
    动作
}else{
    动作
}
# 多条件
if(条件){
    动作
}else if(条件){
    动作
}else{
    动作
}

## awk命令行判断语法
awk '{}END{for(条件){if(条件){动作}else if(条件){动作}else{动作}}}'

## 脚本中awk中判断语法
awk '{
    读文件的动作
}END{
    for(条件){
        if(条件){
            动作
        }else if(条件){
            动作
        }else{
            动作
        }
    }
}'

## 判断磁盘使用率大于70%,大于显示磁盘空间不足,不大于显示正常
# 1.取出磁盘使用率
[root@m01 ~]# df -h|awk -F '[ %]+' 'NR==2{print $5}'
# 2.进行if判断
[root@m01 ~]# df -h|awk -F '[ %]+' 'NR==2{if($5>70){print "磁盘空间不足"}else{print "磁盘空间还行"}}'
磁盘空间还行
# 3.加上当前磁盘使用率
[root@m01 ~]# df -h|awk -F '[ %]+' 'NR==2{if($5>70){print "磁盘空间不足"}else{print "磁盘空间还行,当前磁盘使用率:"$5"%"}}'
磁盘空间还行,当前磁盘使用率:9%
### 练习题:
# 1.从1加到100
[root@m01 ~]#  awk 'BEGIN{for(sz=1;sz<=100;sz++){n+=sz}print n}'
5050

# 2.企业面试题:统计每个学生的总成绩和平均成绩
stu01 70 80 90 100 90 80
stu02 89 78 98 67 97 90
stu03 56 12 33 44 55 66 77
stu04 89 78 77 99 100 30
stu05 98 97 96 95 94 93
stu06 100 20 20 20 20 20

# 写死了的方法:
[root@m01 ~]# awk 'BEGIN{print "学生名","总成绩","平均成绩"}{n=$2+$3+$4+$5+$6+$7;n2=n/(NF-1)}{print $1,n,n2}' cj.txt|column -t
学生名  总成绩  平均成绩
stu01   510     85
stu02   519     86.5
stu03   266     38
stu04   473     78.8333
stu05   573     95.5
stu06   200     33.3333

# 正确写法:
[root@m01 ~]# awk 'BEGIN{print "学生姓名","总成绩","平均成绩"}{n=0;for(i=2;i<=NF;i++){n+=$i};{print $1,n,n/(NF-1)}}' 1.txt 
学生姓名 总成绩 平均成绩
stu01 510 85
stu02 519 86.5
stu03 343 49
stu04 473 78.8333
stu05 573 95.5
stu06 200 33.3333

sed

在sed中,我们核心的内容,主要分为四个部分:

当然我们还有一些进阶内容:模式空间与保持空间

sed命令执行流程

举个例子:

# 以下是abc.txt文件内容
1,hg,666
2,wls,777
3,cls,888
4,lls,999

sed执行流程(重点)

执行 `sed -n '3p' zls.txt`命令后,sed都做了啥? 读懂以下流程(重点)

1.sed先是按行读取文件内容
2.每读取一行内容,都会进行一次判断,是不是你想要的行
3.如果不是,则判断是不是加了-n选项
4.如果加了-n,就读取下一行
5.如果没加-n,就会将所有内容输出到命令行(默认输出)
6.如果是,你想要的那一行(第三行)则判断执行的后续动作(p d s a i c)
7.动作处理完成后,输出指定的内容
8.即便是读取完了,内容也输出了,sed也会继续往后读,直到文件的最后一行

sed – 查

sed命令选项 选项含义 sed命令动作 动作含义
-n 取消默认输出 p print打印
-r 支持扩展正则 d delete删除
a append追加
i insert插入
p:打印,显示

## sed显示单行内容 
[root@m01 ~]# sed '3p' hg.txt
1,hg,666
2,wls,777
3,cls,888
3,cls,888  # 按行读取所有内容 并将内容默认输出出来 指定了第3行内容 所以将第3行内容输出了出来 所以这里是两次
4,lls,999

## sed -n取消默认输出
[root@m01 ~]# sed -n '3p' hg.txt
3,cls,888

## sed显示多行内容并取消默认输出
[root@m01 ~]# sed -n '1,3p' hg.txt
1,zls,666
2,wls,777
3,cls,888

## -r 模糊查询
[root@m01 ~]# sed -n '/hg/p' hg.txt 
1,hg,666

[root@m01 ~]# sed -nr '/hg|cls/p' hg.txt  
1,hg,666
3,cls,888

[root@m01 ~]# sed -n '/hg/,/cls/p' hg.txt  
1,hg,666
2,wls,777
3,cls,888

## sed实现 grep -A
[root@m01 ~]# sed -n '/hg/,+2p' hg.txt
1,hg,666
2,wls,777
3,cls,888

[root@m01 ~]# grep 'hg' -A 2 hg.txt 
1,hg,666
2,wls,777
3,cls,888

## sed 隔指定行数读取一行
[root@m01 ~]# sed -n '1~2p' hg.txt
1,hg,666
3,cls,888
[root@m01 ~]# sed -n '1~3p' hg.txt 
1,hg,666
4,lls,999

sed – 删

d:delete 删除

## 删除指定行数,不修改原文件
[root@m01 ~]# sed  '2d' hg.txt

## 删除最后一行
[root@m01 ~]# sed  '$d' hg.txt 
1,hg,666
3,cls,888

## 包含hg的行到cls的行都删掉
[root@m01 ~]# sed -n '/hg/,/cls/d' hg.txt

## -i 真实的改动文件内容
[root@web01 ~]# sed -n '/hg/,/cls/d' hg.txt 
[root@web01 ~]# sed -ni '/hg/,/cls/d' hg.txt
[root@web01 ~]# cat abc.txt 
1,hg,666
2,wls,777
3,cls,888
4,lls,999
[root@web01 ~]# cat hg.txt 
# 此时文件内容都被删除了 因为sed会一行行的匹配内容直到匹配完所有的行 并每一行都执行最后的输出动作
# 所以我们想要删除内容时 正确写法如下 此时会将指定删除后的内容输出出来
[root@web01 ~]# sed -i '/hg/,/cls/d' hg.txt
[root@web01 ~]# cat hg.txt
4,lls,999

sed – 增

## 菜 c a i
# c:replace 替换整行内容
[root@m01 ~]# cat hg.txt 
1,hg,666
3,cls,888
4,lls,999
[root@m01 ~]# sed '2c2,aoxing,438' hg.txt 
1,hg,666
2,aoxing,438
4,lls,999

# a:append  追加在指定内容后面(不能指定0 因为没有0这个序号的行)
[root@m01 ~]# sed '$a5,aoxing,438' hg.txt 
1,hg,666
3,cls,888
4,lls,999
5,aoxing,438
[root@m01 ~]# sed '2a5,aoxing,438' hg.txt 
1,hg,666
3,cls,888
5,huanglong,438
4,lls,999
[root@m01 ~]# sed '1a2,aoxing,438' hg.txt 
1,hg,666
2,huanglong,438
3,cls,888
4,lls,999

# i:insert 插入
[root@m01 ~]# sed '$i2,aoxing,438' hg.txt 
1,hg,666
3,cls,888
2,aoxing,438
4,lls,999
[root@m01 ~]# sed '1i2,aoxing,438' hg.txt 
2,aoxing,438
1,hg,666
3,cls,888
4,lls,999

sed – 改

s:substitute 替换
g:global 全局

s###g
s@@@g
s啥都行g

## 基础用法
[root@m01 ~]# sed 's#wyk#WYK#g' wyk.txt
1,WYK,666
2,wls,777
3,cls,888
4,lls,999

## 使用正则
[root@m01 ~]# sed 's#[0-9]#666#g' wyk.txt
666,wyk,666666666
666,wls,666666666
666,cls,666666666
666,lls,666666666

## 后向引用                                  # \1表示前面内容里的第一个()里的内容
[root@m01 ~]# ifconfig eth0|sed -nr 's#^.*inet (.*)  net.*#\1#gp'
10.0.0.61
[root@m01 ~]# ip a s eth1|sed -nr 's#^.*inet (.*)/24.*#\1#gp'
172.16.1.61

sed的模式空间

# 将文件中的,所有换行符,替换成空格
# sed读取内容是 是把文件内容一行一行读取出来以后 放到内存里 但放到内存里的内容是不带换行符的 
N:在读取文件是,让sed把下一行内容一起读进去 但只会带一行
[root@web01 ~]# sed 'N;s#\n# #g' hg.txt
1,hg,666 3,cls,888
4,lls,999

# 想要全部换 就得有都是行 加行数减一的N 很麻烦 所以我们可以使用以下的方法
[root@web01 ~]# sed 'N;N;s#\n# #g' hg.txt
1,hg,666 3,cls,888 4,lls,999

## sed的循环: label 写法如下 这里循环了N 没输出一行就循环一次N
[root@web01 ~]# sed ':label;N;s#\n# #g;t label' hg.txt
1,hg,666 3,cls,888 4,lls,999