[toc]

awk概述

awk不是一个命令,是一门语言。

awk又叫做GNU awk,gawk

1
2
ls -l `which awk`
lrwxrwxrwx. 1 root root 4 Nov 28 11:08 /usr/bin/awk -> gawk

平时我们都是当作命令使用,所以我们称之为单行脚本
也可以使用awk写脚本,在Linux系统中就有很多awk脚本

1
2
# 查找awk脚本文件
find /usr/share/ -type f -name '*.awk'

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输出内容使用双引号,awk输出变量使用单引号,shell输出变量使用双引号;

awk执行流程

  • 读取文件之前(BEGIN{})
    • 1.读取文件之前,先看命令的选项,例如 -F,-v
    • 2.如果写了BEGIN{}则先在BEGIN{}中的指令
  • 读取文件时({})
    • 1.awk在读取文件时,也是一行一行的读\
    • 2.读取一行之后,判断是否满足条件,如果是,则执行{对应动作}
    • 3.如果不满足条件,awk继续读取下一行,直到满足条件或者到文件的最后一行
  • 读取文件之后(END{})
    • 1.所有文件读取完成之后,走END{}中的指令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 示例一
awk 'BEGIN{xxx}{print $1}END{print 1/3}' zls.txt

# 示例二
awk -F: 'BEGIN{print "name","uid"}{print $1,$3}END{print "文件处理完成"}' /etc/passwd|column -t

# 示例三
awk -F: 'BEGIN{print "name","uid","gid"}{print $1,$3,$4}END{print "sb"}' /etc/passwd|column -t
name uid gid
root 0 0
bin 1 1
daemon 2 2
adm 3 4
lp 4 7
sync 5 0
shutdown 6 0
halt 7 0
mail 8 12
operator 11 0
games 12 100
ftp 14 50

# column
column -t # 将输出内容对齐

awk的行与列

awk中行和列,一般不叫行和列,有专业叫法

行:记录 record

列:字段 field

awk取记录(取行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
NR:Number of Record

# 取一行示例一
awk 'NR==1' /etc/passwd
root:x:0:0:root:/root:/bin/bash


# 范围取行示例二
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

# 范围取行示例三
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

# 条件取行(包含zls和cls)示例四
awk '/zls|cls/' zls.txt
1,zls,666
3,cls,888
zls,111

# 条件取行示例五
awk 'NR>=3' zls.txt
/tmp/check_2.txt
/tmp/check_3.txt
/tmp/check_4.txt
/tmp/check_5.txt
3,cls,888
zls,111
4,lls,999

# 条件取行示例六
awk '/zls/,/cls/' zls.txt
1,zls,666
/tmp/check_1.txt
/tmp/check_2.txt
/tmp/check_3.txt
cls
zls
4,lls,999
cls

# "-vRS"控制换行符
Record Separator
awk -vRS=',' 'NR==1' zls.txt

awk取列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FS:内置变量,列分隔符  -F: = -vFS=:

# 示例一
awk -vFS=: '{print $1}' /etc/passwd

# 范围取列(NF为最后一行)示例二
awk -vFS=: '{print $1,$NF}' /etc/passwd

# 范围取列(指定分隔符号)示例三
awk -F: '{print $1"#"$2"#"$3"#"$4"#"$5"|"$6","$NF}' /etc/passwd

## 修改输出后内容的分隔符
awk -F: -vOFS=# '{print $1,$2,$3,$4,$5,$6,$NF}' /etc/passwd

awk -F: '{print $0}' /etc/passwd

awk取行取列实战

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# 取出top中的运行时间
top -n1 |awk 'NR==1{print $5}'
1

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

# 实战练习
cat 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

## 请找出姓氏是张的人,他们第二次捐款的数额及姓名
awk -F '[ :]+' 'BEGIN{print "姓","名","捐款数额"}$2~/Zhang/{print $2,$3,$6}' user.txt
姓 名 捐款数额
Zhang Xinyu 300
Zhang Yuqi 290

awk -F '[ :]+' 'BEGIN{print "姓","名","捐款数额"}$2~/Zhang/ && $3~/X/{print $2,$3,$6}' user.txt
姓 名 捐款数额
Zhang Xinyu 300

awk -F '[ :]+' 'BEGIN{print "姓名","捐款数额"}$2~/Zhang/ && $3~/X/{print $2 $3,$6}' user.txt |column -t
姓名 捐款数额
ZhangXinyu 300

awk -F '[ :]+' 'BEGIN{print "姓名","捐款数额"}$2~/Zhang/ && $3~/X/{print $2 $3,$(NF-1)}' user.txt |column -t
姓名 捐款数额
ZhangXinyu 300

## 显示所有以25开头的QQ号及姓名
awk '$4~/^25/{print $2 $3,$4}' user.txt
DiLireba 253097001
WenZhang 259872003

## 显示所有QQ号最后一位是1或者3的人,全名及QQ
awk '$3~/1$|3$/{print $1$2,$3}' user.txt
ZengLaoshi 133411023
DengZiqi 44002231
DiLireba 253097001
WenZhang 259872003

awk '$3~/(1|3)$/{print $1$2,$3}' user.txt
ZengLaoshi 133411023
DengZiqi 44002231
DiLireba 253097001
WenZhang 259872003

awk '$3~/[13]$/{print $1$2,$3}' user.txt
ZengLaoshi 133411023
DengZiqi 44002231
DiLireba 253097001
WenZhang 259872003

## 显示每个捐款值都以$开头 $110:$00$75
awk -F: -vOFS='$' '{print $2,$3,$4}' zls.txt

awk '{gsub(/:/,"$",$4)}' user.txt

awk '{gsub(/:/,"$");print $0}' 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

awk '{gsub(/:/,"$",$4);print $0}' 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

function gsub(){
$1
$2
$3
xxxx
}

gsub(xx,aaa,d)

gsub("被替换的内容","替换的新内容")
gsub("被替换的内容","替换的新内容",第N列)

# 综合应用:找出ifconfig中范围是1-255的数字
ifconfig |awk -vRS='[^0-9]+' '$0>=1 && $0<=255'
10
61
255
255
255
10
255
6
80
20
29
3
64
20
29
3
2
5
2
6
1
172
16
1
61
255
255
255
172
16
1
255
6
80
20
29
3
9
64
20
29
3
9
46
9
117
29
7
73
127
1
255
6
1
128
10

awk的模式与动作

1
awk -F: 'NR==1{print 1 $1,$3}' /etc/passwd

上面这条命令我们可以看到, 'NR==1{print $1,$3}' 可以理解为, '模式{动作}'== '条件{指令}'

awk中的模式

  • 正则表达式

    1
    2
    3
    4
    5
    # 正则表达式写法
    '/正则表达式/flag'
    '$1~/正则表达式/flag'
    '$1!~/正则表达式/flag'
    只不过我们在awk中很少使用flag
  • 比较表达式

    1
    2
    3
    4
    5
    NR==1
    NR>=10
    NR<=100
    NR>=1 && NR<=10
    $1>=100
  • 范围模式

    1
    2
    3
    4
    5
    6
    7
    ## 精确匹配行号:从第10行到第20行
    NR==10,NR==20
    ## 精确匹配字符串:从该字符串的行到另一个字符串所在行
    '/root/,/zls/'
    '/从哪个字符串所在行/,/到那个字符串所在行/' #中间的行都包含进去
    ## 模糊匹配字符串:从含有该字符串所在行到含有另一字符串所在行
    '$1~/oo/,$1~/zl/'
  • 特殊模式

    BEGIN 和 END

awk中的动作

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

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

  • print打印
  • gsub替换
  • 变量赋值
  • 统计计算
1
useradd name;pass=`echo $RANDOM|md5sum|cut -c 1-10`;echo $pass|passwd --stdin name;echo $pass:$user >> /tmp/user.txt

BEGIN模式

使用BEGIN模式,需要成双成对出现:BEGIN{}

BEGIN{}中,大括号里的内容,会在读取文件内容之前执行

主要应用场景:

  • 统计或计算

    1
    awk 'BEGIN{print 1/3}'
  • awk功能测试

  • 输出表格的表头

END模式

END{}要比BEGIN{}重要一些,BEGIN{}可有可无,计算其实可以放在读取文件的时候,也可以执行
END{}中,大括号里面的内容,会在awk读取完文件的最后一行后,进行处理
作用:一般我们使用END{}来显示对日志内容分析后的一个结果 当然,还有其他功能,比如文件读取完了,可以显示一些尾部信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# 统计/etc/service文件中的一共有多少行
awk '{hang++;print hang}' /etc/services
1
...
11176

## 示例一:不需要过程只要结果
awk '{hang++}END{print hang}' /etc/services
11176

## 示例二:只能统计文件的所有行
awk 'END{print NR}' /etc/services
11176

## 示例三:其它方法
wc -l /etc/services
11176 /etc/services

grep -c '.*' /etc/services
11176

sed -n '$=' /etc/services
11176


# 统计/etc/service空行的数量
awk '/^$/{print}' /etc/services | wc -l
17
awk '/^$/{i++}END{print i}' /etc/services
17

## 示例一:统计访问日志状态码
zcat blog.xxx.com+access.log.gz | awk '$10~/200/{code++}END{print code}'


# 统计出下列文件中所有人的年龄和
cat user.txt
姓名 年龄
大萨 23
长囧 18
西施 99

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

## 示例二:awk方式
awk 'NR>1{print $2}' user.txt
23
18
99
awk 'NR>1{n+=$2}END{print n}' user.txt


# 统计nginx日志中,状态码是200的次数以及,状态码是200时占用的流量
zcat blog.xxx.com_access.log.gz | awk 'BEGIN{print "状态码200的次数","总流量"}$10~/200/{code++;bute+=$11}END{print code,byte}' | column -t
状态码200的次数 总流量
3100 15645341

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


# 统计nginx日志中状态码是4xx和5xx的次数及总流量
zcat blog.xxx.com_access.log.gz | awk '$10~/^[45]/{i++;n+=$11}END{prnt i,n}'
564 342332


# 综合应用:分别统计每种状态码的次数和每个状态码的总流量
zcat blog.xxx.com_access.log.gz |awk '
BEGIN{
print "状态码","总流量"
}
$10~/^2/{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数组

awk的数组赋值

1
2
# awk的数组赋值
awk 'BEGIN{array[0]="hqw";arrat[1]="hbb"}'

awk数组的取值

1
2
# awk数组的取值
awk 'BEGIN{array[0]="hqw";array[1]="hbb";print array[0],array[1]}'

数组的循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
for (条件){
动作
}
for 条件;do
动作
done

# 打印出来是下标
awk 'BEGIN{array[0]="hqw";array[1]="hbb";for(num in array){print num}}'
0
1
# 所以加上array[下标]这样就识别出了
awk 'BEGIN{array[0]="hqw";array[1]="hbb";for(num in array){print array[num]}}'
hqw
hbb

## 过滤日志IP访问次数(不懂看上面)
zcat blog.xxx.com_access.log.gz | awk '{array[$1]++}END{for(ip in array){print ip,array[ip]}}'

array[$1]++
#就是array[ip]++ 相同IP会加一次

## 统计nginx日志中,每个IP访问使用的流量总和
zcat blog.xxx.com_access.log.gz | awk '{ip[$1]++;liuliang[$1]+=$11}END{for (i in ip){print i,ip[i],liuliang[i]}}'

i # ip
ip[i] # 次数
liuliang[i] # 流量总和

awk判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 单分支
if(条件){
动作
}

# 多分支
if(条件){
动作
}else{
动作
}

if(条件){
动作
}else if(条件){
动作
}else{
动作
}

# 实战示例:
## 需求:判断磁盘孔家岸使用率大于70%,大于就显示磁盘空间不足,不大就显示正常
df -h| awk -F '[ %]+' 'NR==2{if($5>70){print "磁盘空间不足"}else{print "磁盘空间充足,现在的使 用率是:"$5"%"}}'