awk
# awk
awk 是一个处理文本的编程语言工具,能用简短的程序处理标准输入或文件、数据排序、计算以及生成报表等等。
在 Linux 系统下默认 awk 是 gawk,它是 awk 的 GNU 版本。可以通过命令查看应用的版本:
ls -l /bin/awk
基本的命令语法:
awk option 'pattern {action}' file
其中 pattern 表示 AWK 在数据中查找的内容,而 action 是在找到匹配内容时所执行的一系列命令。花括号用于根据特定的模式对一系列指令进行分组。
awk 处理的工作方式与数据库类似,支持对记录和字段处理,这也是 grep 和 sed 不能实现的。
在 awk 中,缺省的情况下将文本文件中的一行视为一个记录,逐行放到内存中处理,而将一行中的某一部分作为记录中的一个字段。用 1,2,3...数字的方式顺序的表示行(记录)中的不同字段。用$后跟数字,引用对应的字段,以逗号分隔,0 表示整个行。
# 1. 命令选项
选项 | 描述 |
---|---|
-f | program-file 从文件中读取 awk 程序源文件 |
-F | fs 指定 fs 为输入字段分隔符 |
-v | var=value 变量赋值 |
--posix | 兼容 POSIX 正则表达式 |
--dump-variables=[file] | 把 awk 命令时的全局变量写入文件,默认文件是 awkvars.out |
--profile=[file] | 格式化 awk 语句到文件,默认是 awkprof.out |
# 2. 模式
模式 | 描述 |
---|---|
BEGIN{ } | 给程序赋予初始状态,先执行的工作 |
END{ } | 程序结束之后执行的一些扫尾工作 |
/regular expression/ | 为每个输入记录匹配正则表达式 |
pattern && pattern | 逻辑 and,满足两个模式 |
! pattern | 逻辑 not,不满足模式 |
pattern1, pattern2 | 范围模式,匹配所有模式 1 的记录,直到匹配到模式 2 |
pattern || pattern | 逻辑 or,满足其中一个模式 |
# 2.1 从文件读取 awk 程序处理文件
# vi test.awk
{print $2}
# tail -n3 /etc/services |awk -f test.awk
48049/tcp
48128/tcp
49000/tcp
2
3
4
5
6
7
# 2.2 指定分隔符,打印指定字段
# 打印第二字段,默认以空格分隔:
# tail -n3 /etc/services |awk '{print $2}'
48049/tcp
48128/tcp
48128/udp
# 指定冒号为分隔符打印第一字段:
# awk -F ':' '{print $1}' /etc/passwd
root
bin
daemon
adm
lp
sync
......
# 还可以指定多个分隔符,作为同一个分隔符处理:
# tail -n3 /etc/services |awk -F'[/#]' '{print $3}'
iqobject
iqobject
Matahari Broker
# tail -n3 /etc/services |awk -F'[/#]' '{print $1}'
iqobject 48619
iqobject 48619
matahari 49000
# tail -n3 /etc/services |awk -F'[/#]' '{print $2}'
tcp
udp
tcp
# tail -n3 /etc/services |awk -F'[/#]' '{print $3}'
iqobject
iqobject
Matahari Broker
# tail -n3 /etc/services |awk -F'[ /]+' '{print $2}'
48619
48619
49000
# []元字符的意思是符号其中任意一个字符,也就是说每遇到一个/或#时就分隔一个字段,当用多个分隔符时,就能更方面处理字段了。
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
# 2.3 变量赋值
# awk -v a=123 'BEGIN{print a}'
123
# 系统变量作为 awk 变量的值:
# a=123
# awk -v a=$a 'BEGIN{print a}'
123
# 或使用单引号
# awk 'BEGIN{print '$a'}'
123
2
3
4
5
6
7
8
9
10
11
# 2.4 输出 awk 全局变量到文件
# seq 5 |awk --dump-variables '{print $0}'
1
2
3
4
5
# cat awkvars.out
ARGC: number (1)
ARGIND: number (0)
ARGV: array, 1 elements
BINMODE: number (0)
CONVFMT: string ("%.6g")
ERRNO: number (0)
FIELDWIDTHS: string ("")
FILENAME: string ("-")
FNR: number (5)
FS: string (" ")
IGNORECASE: number (0)
LINT: number (0)
NF: number (1)
NR: number (5)
OFMT: string ("%.6g")
OFS: string (" ")
ORS: string ("\n")
RLENGTH: number (0)
RS: string ("\n")
RSTART: number (0)
RT: string ("\n")
SUBSEP: string ("\034")
TEXTDOMAIN: string ("messages")
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
# 2.5 BEGIN 和 END
- BEGIN 模式是在处理文件之前执行该操作,常用于修改内置变量、变量赋值和打印输出的页眉或标题。
例如:打印页眉
# tail /etc/services |awk 'BEGIN{print "Service\t\tPort\t\t\tDescription\n==="}{print $0}'
Service Port Description
===
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
matahari 49000/tcp # Matahari Broker
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- END 模式是在程序处理完才会执行。
例如:打印页尾
# tail /etc/services |awk '{print $0}END{print "===\nEND......"}'
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
matahari 49000/tcp # Matahari Broker
===
END......
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 2.6 格式化输出 awk 命令到文件
# tail /etc/services |awk --profile 'BEGIN{print "Service\t\tPort\t\t\tDescription\n==="}{print $0}END{print "===\nEND......"}'
Service Port Description
===
nimgtw 48003/udp # Nimbus Gateway
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service Protocol
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
===
END......
# cat awkprof.out
# gawk profile, created Sat Jan 7 19:45:22 2017
# BEGIN block(s)
BEGIN {
print "Service\t\tPort\t\t\tDescription\n==="
}
# Rule(s)
{
print $0
}
# END block(s)
END {
print "===\nEND......"
}
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
# 2.7 /re/正则匹配
# 匹配包含 tcp 的行:
# tail /etc/services |awk '/tcp/{print $0}'
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
iqobject 48619/tcp # iqobject
matahari 49000/tcp # Matahari Broker
# 匹配开头是 blp5 的行:
# tail /etc/services |awk '/^blp5/{print $0}'
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
# 匹配第一个字段是 8 个字符的行:
# tail /etc/services |awk '/^[a-z0-9]{8} /{print $0}'
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
matahari 49000/tcp # Matahari Broker
# 如果没有匹配到,请查看你的 awk 版本(awk --version)是不是 3,因为 4 才支持{}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 2.8 逻辑 and、or 和 not
# 匹配记录中包含 blp5 和 tcp 的行:
# tail /etc/services |awk '/blp5/ && /tcp/{print $0}'
blp5 48129/tcp # Bloomberg locator
# 匹配记录中包含 blp5 或 tcp 的行:
# tail /etc/services |awk '/blp5/ || /tcp/{print $0}'
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
iqobject 48619/tcp # iqobject
matahari 49000/tcp # Matahari Broker
# 不匹配开头是#和空行:
# awk '! /^#/ && ! /^$/{print $0}' /etc/httpd/conf/httpd.conf
或
# awk '! /^#|^$/' /etc/httpd/conf/httpd.conf
或
# awk '/^[^#]|"^$"/' /etc/httpd/conf/httpd.conf
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 2.9 匹配范围
# tail /etc/services |awk '/^blp5/,/^com/'
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
# 对匹配范围后记录再次处理,例如匹配关键字下一行到最后一行:
# seq 5 |awk '/3/,/^$/{printf /3/?"":$0"\n"}'
4
5
# 另一种判断真假的方式实现:
# seq 5 |awk '/3/{t=1;next}t'
4
5
# 1 和 2 都不匹配 3,不执行后面{},执行 t,t 变量还没赋值,为空,空在 awk 中就为假,就不打印当前行。匹配到 3,执行 t=1,next 跳出,不执行 t。4 也不匹配 3,执行 t,t 的值上次赋值的 1,为真,打印当前行,以此类推。(非 0 的数字都为真,所以 t 可以写任意非 0 数字)如果想打印匹配行都最后一行,就可以这样了:
# seq 5 |awk '/3/{t=1}t'
3
4
5
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 3. 内置变量
- FS 输入字段分隔符,默认是空格或制表符
- OFS 输出字段分隔符,默认是空格
- RS 输入记录分隔符,默认是换行符\n
- ORS 输出记录分隔符,默认是换行符\n
- NF 统计当前记录中字段个数
- NR 统计记录编号,每处理一行记录,编号就会+1
- FNR 统计记录编号,每处理一行记录,编号也会+1,与 NR 不同的是,处理第二个文件时,编号会重新计数。
- ARGC 命令行参数数量
- ARGV 命令行参数数组序列数组,下标从 0 开始,ARGV[0]是 awk
- ARGIND 当前正在处理的文件索引值。第一个文件是 1,第二个文件是 2,以此类推
- ENVIRON 当前系统的环境变量
- FILENAME 输出当前处理的文件名
- IGNORECASE 忽略大小写
- SUBSEP 数组中下标的分隔符,默认为"\034"
# 3.1 FS 和 OFS
在程序开始前重新赋值 FS 变量,改变默认分隔符为冒号,与-F 一样。
# awk 'BEGIN{FS=":"}{print $1,$2}' /etc/passwd |head -n5
root x
bin x
daemon x
adm x
lp x
2
3
4
5
6
也可以使用-v 来重新赋值这个变量:
# awk -vFS=':' '{print $1,$2}' /etc/passwd |head -n5 # 中间逗号被换成了 OFS 的默认值
root x
bin x
daemon x
adm x
lp x
2
3
4
5
6
由于 OFS 默认以空格分隔,反向引用多个字段分隔的也是空格,如果想指定输出分隔符这样:
# awk 'BEGIN{FS=":";OFS=":"}{print $1,$2}' /etc/passwd |head -n5
root:x
bin:x
daemon:x
adm:x
lp:x
2
3
4
5
6
也可以通过字符串拼接实现分隔:
# awk 'BEGIN{FS=":"}{print $1"#"$2}' /etc/passwd |head -n5
root#x
bin#x
daemon#x
adm#x
lp#x
2
3
4
5
6
# 3.2 RS 和 ORS
RS 默认是\n 分隔每行,如果想指定以某个字符作为分隔符来处理记录:
# echo "www.baidu.com/user/test.html" |awk 'BEGIN{RS="/"}{print $0}'
www.baidu.com
user
test.html
2
3
4
RS 也支持正则,简单演示下:
# seq -f "str%02g" 10 |sed 'n;n;a\-----' |awk 'BEGIN{RS="-+"}{print $1}'
str01
str04
str07
str10
2
3
4
5
将输出的换行符替换为+号:
# seq 10 |awk 'BEGIN{ORS="+"}{print $0}'
1+2+3+4+5+6+7+8+9+10+
2
替换某个字符:
# tail -n2 /etc/services |awk 'BEGIN{RS="/";ORS="#"}{print $0}'
iqobject 48619#udp # iqobject
matahari 49000#tcp # Matahari Broker
2
3
4
# 3.3 NF
# NF 是字段个数。
# echo "a b c d e f" |awk '{print NF}'
6
# 打印最后一个字段:
# echo "a b c d e f" |awk '{print $NF}'
f
# 打印倒数第二个字段:
# echo "a b c d e f" |awk '{print $(NF-1)}'
e
# 排除最后两个字段:
# echo "a b c d e f" |awk '{$NF="";$(NF-1)="";print $0}'
a b c d
# 排除第一个字段:
# echo "a b c d e f" |awk '{$1="";print $0}'
b c d e f
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 3.4 NR 和 FNR
# NR 统计记录编号,每处理一行记录,编号就会+1,FNR 不同的是在统计第二个文件时会重新计数。打印行数:
# tail -n5 /etc/services |awk '{print NR,$0}'
1 com-bardac-dw 48556/tcp # com-bardac-dw
2 com-bardac-dw 48556/udp # com-bardac-dw
3 iqobject 48619/tcp # iqobject
4 iqobject 48619/udp # iqobject
5 matahari 49000/tcp # Matahari Broker
# 打印总行数:
# tail -n5 /etc/services |awk 'END{print NR}'
5
# 打印第三行:
# tail -n5 /etc/services |awk 'NR==3'
iqobject 48619/tcp # iqobject
# 打印第三行第二个字段:
# tail -n5 /etc/services |awk 'NR==3{print $2}'
48619/tcp
# 打印前三行:
# tail -n5 /etc/services |awk 'NR<=3{print NR,$0}'
1 com-bardac-dw 48556/tcp # com-bardac-dw
2 com-bardac-dw 48556/udp # com-bardac-dw
3 iqobject 48619/tcp # iqobject
# 看下 NR 和 FNR 的区别:
# cat a
a
b
c
# cat b
c
d
e
# awk '{print NR,FNR,$0}' a b
1 1 a
2 2 b
3 3 c
4 1 c
5 2 d
6 3 e
# 可以看出 NR 每处理一行就会+1,而 FNR 在处理第二个文件时,编号重新计数。同时也知道 awk 处理两个文件时,是合并到一起处理。
# awk 'FNR==NR{print $0"1"}FNR!=NR{print $0"2"}' a b
a1
b1
c1
c2
d2
e2
# 当 FNR==NR 时,说明在处理第一个文件内容,不等于时说明在处理第二个文件内容。一般 FNR 在处理多个文件时会用到。
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
# 3.5 ARGC 和 ARGV
ARGC 是命令行参数数量,ARGV 是将命令行参数存到数组,元素由 ARGC 指定,数组下标从 0 开始
# awk 'BEGIN{print ARGC}' 1 2 3
4
# awk 'BEGIN{print ARGV[0]}'
awk
# awk 'BEGIN{print ARGV[1]}' 1 2
1
# awk 'BEGIN{print ARGV[2]}' 1 2
2
2
3
4
5
6
7
8
9
10
11
# 3.6 ARGIND
ARGIND 是当前正在处理的文件索引值,第一个文件是 1,第二个文件是 2,以此类推,从而可以通过这种方式判断正在处理哪个文件。
# awk '{print ARGIND,$0}' a b
1 a
1 b
1 c
2 c
2 d
2 e
# awk 'ARGIND==1{print "a->"$0}ARGIND==2{print "b->"$0}' a b
a->a
a->b
a->c
b->c
b->d
b->e
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 3.7 ENVIRON
ENVIRON 调用系统变量。
# awk 'BEGIN{print ENVIRON["HOME"]}'
/root
# 如果是设置的环境变量,还需要用 export 导入到系统变量才可以调用:
# awk 'BEGIN{print ENVIRON["a"]}'
# export a
# awk 'BEGIN{print ENVIRON["a"]}'
123
2
3
4
5
6
7
8
9
# 3.8 FILENAME
FILENAME 是当前处理文件的文件名。
# awk 'FNR==NR{print FILENAME"->"$0}FNR!=NR{print FILENAME"->"$0}' a b
a->a
a->b
a->c
b->c
b->d
b->e
2
3
4
5
6
7
# 3.9 忽略大小写
# echo "A a b c" |xargs -n1 |awk 'BEGIN{IGNORECASE=1}/a/'
A
a
# 等于 1 代表忽略大小写。
2
3
4
# 4. 操作符
操作符 | 描述 |
---|---|
(....) | 分组 |
$ | 字段引用 |
++ -- | 递增和递减 |
+ - ! | 加号,减号,和逻辑否定 |
* / % | 乘,除和取余 |
+ - | 加法,减法 |
|| & | 管道,用于 getline,print 和 printf |
< > <= >= != == | 关系运算符 |
~ !~ | 正则表达式匹配,否定正则表达式匹配 |
in | 数组成员 |
&& || | 逻辑 and,逻辑 or |
?: | 简写条件表达式:expr1 ? expr2 : expr3 第一个表达式为真,执行 expr2,否则执行 expr3 |
= += -= *= /= %= ^= | 变量赋值运算符 |
在awk 中,有 3 种情况表达式为假:
- 数字是 0 ,空字符串和未定义的值 。
- 数值运算 , 未定义变量初始值为 0 。
- 字符运算,未定义变量初始值为空。
# awk 'BEGIN{n=0;if(n)print "true";else print "false"}'
false
# awk 'BEGIN{s="";if(s)print "true";else print "false"}'
false
# awk 'BEGIN{if(s)print "true";else print "false"}'
false
2
3
4
5
6
7
8
# 4.1 截取整数
# echo "123abc abc123 123abc123" |xargs -n1 | awk '{print +$0}'
123
0
123
# echo "123abc abc123 123abc123" |xargs -n1 | awk '{print -$0}'
-123
0
-123
2
3
4
5
6
7
8
9
# 4.2 感叹号
# 打印奇数行:
# seq 6 |awk 'i=!i'
1
3
5
# 打印偶数行:
# seq 6 |awk '!(i=!i)'
2
4
6
# 读取第一行:i 是未定义变量,也就是 i=!0,!取反意思。感叹号右边是个布尔值,0 或空字符串为假,非 0 或非空字符串为真,!0 就是真,因此 i=1,条件为真打印当前记录。没有 print 为什么会打印呢?因为模式后面没有动作,默认会打印整条记录。
# 读取第二行:因为上次 i 的值由 0 变成了 1,此时就是 i=!1,条件为假不打印。
# 读取第三行:上次条件又为假,i 恢复初始值 0,取反,继续打印。以此类推...
# 可以看出,运算时并没有判断行内容,而是利用布尔值真假判断输出当前行。
# 不匹配某行
# tail /etc/services |awk '!/blp5/{print $0}'
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
matahari 49000/tcp # Matahari Broker
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
# 4.3 乘法和除法
# seq 5 |awk '{print $0*2}'
2
4
6
8
10
# seq 5 |awk '{print $0%2}'
1
0
1
0
1
# 打印偶数行:
# seq 5 |awk '$0%2==0{print $0}'
2
4
# 打印奇数行:
# seq 5 |awk '$0%2!=0{print $0}'
1
3
5
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 4.4 管道符使用
# seq 5 |shuf |awk '{print $0|"sort"}'
1
2
3
4
5
2
3
4
5
6
# 4.5 正则表达式匹配
# seq 5 |awk '$0~3{print $0}'
3
# seq 5 |awk '$0!~3{print $0}'
1
2
4
5
# seq 5 |awk '$0~/[34]/{print $0}'
3
4
# seq 5 |awk '$0!~/[34]/{print $0}'
1
2
5
# seq 5 |awk '$0~/[^34]/{print $0}'
1
2
5
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 4.6 判断数组成员
# awk 'BEGIN{a["a"]=123}END{if("a" in a)print "yes"}' </dev/null
yes
2
# 4.7 三目运算符
# awk 'BEGIN{print 1==1?"yes":"no"}' # 三目运算作为一个表达式,里面不允许写 print
yes
# seq 3 |awk '{print $0==2?"yes":"no"}'
no
yes
no
# 替换换行符为逗号:
# seq 5 |awk '{print n=(n?n","$0:$0)}'
1
1,2
1,2,3
1,2,3,4
1,2,3,4,5
# seq 5 |awk '{n=(n?n","$0:$0)}END{print n}'
1,2,3,4,5
# 说明:读取第一行时,n 没有变量,为假输出$0 也就是 1,并赋值变量 n,读取第二行时,n 是 1 为真,输出 1,2 以此类推,后面会一直为真。每三行后面添加新一行:
# seq 10 |awk '{print NR%3?$0:$0 "\ntxt"}'
1
2
3
txt
4
5
6
txt
7
8
9
txt
10
# 在两行合并一行:
# seq 6 |awk '{printf NR%2!=0?$0" ":$0" \n"}'
1 2
3 4
5 6
# seq 6 |awk 'ORS=NR%2?" ":"\n"'
1 2
3 4
5 6
# seq 6 |awk '{if(NR%2)ORS=" ";else ORS="\n";print}'
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
# 4.8 变量赋值
# 字段求和:
# seq 5 |awk '{sum+=1}END{print sum}'
5
# seq 5 |awk '{sum+=$0}END{print sum}'
15
2
3
4
5
6
# 5. 流程控制
# 5.1 if 语句
格式:
if (condition) statement [ else statement ]
单分支:
# seq 5 |awk '{if($0==3)print $0}'
3
# 也支持正则匹配判断,一般在写复杂语句时使用:
# echo "123abc#456cde 789aaa#aaabbb " |xargs -n1 |awk -F# '{if($2~/[0-9]/)print $2}'
456cde
# echo "123abc#456cde 789aaa#aaabbb " |xargs -n1 |awk -F# '{if($2!~/[0-9]/)print $2}'
aaabbb
# 或
# echo "123abc#456cde 789aaa#aaabbb" |xargs -n1 |awk -F# '$2!~/[0-9]/{print $2}'
aaabbb
2
3
4
5
6
7
8
9
10
11
12
双分支:
# seq 5 |awk '{if($0==3)print $0;else print "no"}'
no
no
3
no
no
2
3
4
5
6
多分支:
# cat file
1 2 3
4 5 6
7 8 9
# awk '{if($1==4){print "1"} else if($2==5){print "2"} else if($3==6){print "3"} else
{print "no"}}' file
no
1
no
2
3
4
5
6
7
8
9
10
# 5.2 while 语句
格式:
while (condition) statement
遍历打印所有字段:
# awk '{i=1;while(i<=NF){print $i;i++}}' file
1
2
3
4
5
6
7
8
9
# awk 是按行处理的,每次读取一行,并遍历打印每个字段。
2
3
4
5
6
7
8
9
10
11
# 5.3 for 语句
格式:
for (expr1; expr2; expr3) statement
遍历打印所有字段:
# cat file
1 2 3
4 5 6
7 8 9
# awk '{for(i=1;i<=NF;i++)print $i}' file
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
10
11
12
13
14
15
倒叙打印文本:
# awk '{for(i=NF;i>=1;i--)print $i}' file
3
2
1
6
5
4
9
8
7
# 都换行了,这并不是我们要的结果。怎么改进呢?
# awk '{for(i=NF;i>=1;i--){printf $i" "};print ""}' file # print 本身就会新打印一行
3 2 1
6 5 4
9 8 7
# 或
# awk '{for(i=NF;i>=1;i--)if(i==1)printf $i"\n";else printf $i" "}' file
3 2 1
6 5 4
6 5 4
9 8 7
# 在这种情况下,是不是就排除第一行和倒数第一行呢?我们正序打印看下
# 排除第一行:
# awk '{for(i=2;i<=NF;i++){printf $i" "};print ""}' file
2 3
5 6
8 9
# 排除第二行:
# awk '{for(i=1;i<=NF-1;i++){printf $i" "};print ""}' file
1 2
4 5
7 8
# IP 加单引号:
# echo '10.10.10.1 10.10.10.2 10.10.10.3' |awk '{for(i=1;i<=NF;i++)printf
"\047"$i"\047"}
'10.10.10.1' '10.10.10.2' '10.10.10.3'
\047 是 ASCII 码,可以通过 showkey -a 命令查看。
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
# 5.4 for 语句遍历数组
格式:
for (var in array) statement
# seq -f "str%.g" 5 |awk '{a[NR]=$0}END{for(v in a)print v,a[v]}'
4 str4
5 str5
1 str1
2 str2
3 str3
2
3
4
5
6
# 5.5 break 和 continue 语句
break 跳过所有循环,continue 跳过当前循环。
# awk 'BEGIN{for(i=1;i<=5;i++){if(i==3){break};print i}}'
1
2
# awk 'BEGIN{for(i=1;i<=5;i++){if(i==3){continue};print i}}'
1
2
4
5
2
3
4
5
6
7
8
9
# 5.6 删除数组和元素
格式:
delete array[index] 删除数组元素
delete array 删除数组
2
# seq -f "str%.g" 5 |awk '{a[NR]=$0}END{delete a;for(v in a)print v,a[v]}'
空的…
# seq -f "str%.g" 5 |awk '{a[NR]=$0}END{delete a[3];for(v in a)print v,a[v]}'
4 str4
5 str5
1 str1
2 str2
2
3
4
5
6
7
8
# 5.7 exit 语句
格式:
exit [ expression ]
exit 退出程序,与 shell 的 exit 一样。[ expr ]是 0-255 之间的数字。
# seq 5 |awk '{if($0~/3/)exit (123)}'
# echo $?
123
2
3
4
# 6. 数组
数组:存储一系列相同类型的元素,键/值方式存储,通过下标(键)来访问值。
awk 中数组称为关联数组,不仅可以使用数字作为下标,还可以使用字符串作为下标。数组元素的键和值存储在 awk 程序内部的一个表中,该表采用散列算法,因此数组元素是随机排序。
数组格式:
array[index]=value
# 6.1 自定义数组
# awk 'BEGIN{a[0]="test";print a[0]}'
test
2
# 6.2 通过 NR 设置记录下标,下标从 1 开始
# tail -n3 /etc/passwd |awk -F: '{a[NR]=$1}END{print a[1]}'
systemd-network
# tail -n3 /etc/passwd |awk -F: '{a[NR]=$1}END{print a[2]}'
zabbix
# tail -n3 /etc/passwd |awk -F: '{a[NR]=$1}END{print a[3]}'
user
2
3
4
5
6
7
8
# 6.3 通过 for 循环遍历数组
# tail -n5 /etc/passwd |awk -F: '{a[NR]=$1}END{for(v in a)print a[v],v}'
zabbix 4
user 5
admin 1
systemd-bus-proxy 2
systemd-network 3
# tail -n5 /etc/passwd |awk -F: '{a[NR]=$1}END{for(i=1;i<=NR;i++)print a[i],i}'
admin 1
systemd-bus-proxy 2
systemd-network 3
zabbix 4
user 5
# 上面打印的 i 是数组的下标。
# 第一种 for 循环的结果是乱序的,刚说过,数组是无序存储。
# 第二种 for 循环通过下标获取的情况是排序正常。
# 所以当下标是数字序列时,还是用 for(expr1;expr2;expr3)循环表达式比较好,保持顺序不变。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 6.4 通过++方式作为下标
# tail -n5 /etc/passwd |awk -F: '{a[x++]=$1}END{for(i=0;i<=x-1;i++)print a[i],i}'
admin 0
systemd-bus-proxy 1
systemd-network 2
zabbix 3
user 4
# x 被 awk 初始化值是 0,没循环一次+1
2
3
4
5
6
7
8
# 6.5 使用字段作为下标
# tail -n5 /etc/passwd |awk -F: '{a[$1]=$7}END{for(v in a)print a[v],v}'
/sbin/nologin admin
/bin/bash user
/sbin/nologin systemd-network
/sbin/nologin systemd-bus-proxy
/sbin/nologin zabbix
2
3
4
5
6
7
# 6.6 统计相同字段出现次数
# tail /etc/services |awk '{a[$1]++}END{for(v in a)print a[v],v}'
2 com-bardac-dw
1 3gpp-cbsp
2 iqobject
1 matahari
2 isnetserv
2 blp5
# tail /etc/services |awk '{a[$1]+=1}END{for(v in a)print a[v],v}'
2 com-bardac-dw
1 3gpp-cbsp
2 iqobject
1 matahari
2 isnetserv
2 blp5
# tail /etc/services |awk '/blp5/{a[$1]++}END{for(v in a)print a[v],v}'
2 blp5
# 第一个字段作为下标,值被++初始化是 0,每次遇到下标(第一个字段)一样时,对应的值就会被+1,因此实现了统计出现次数。想要实现去重的的话就简单了,只要打印下标即可。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 6.7 统计 TCP 连接状态
# netstat -antp |awk '/^tcp/{a[$6]++}END{for(v in a)print a[v],v}'
9 LISTEN
6 ESTABLISHED
6 TIME_WAIT
2
3
4
# 6.8 只打印出现次数大于等于 2 的
# tail /etc/services |awk '{a[$1]++}END{for(v in a) if(a[v]>=2){print a[v],v}}'
2 com-bardac-dw
2 iqobject
2 isnetserv
2 blp5
2
3
4
5
# 6.9 去重
# 只打印重复的行:
# tail /etc/services |awk 'a[$1]++'
isnetserv 48128/udp # Image Systems Network Services
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/udp # iqobject
# 不打印重复的行:
# tail /etc/services |awk '!a[$1]++'
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
iqobject 48619/tcp # iqobject
matahari 49000/tcp # Matahari Broker
# 先明白一个情况,当值是 0 是为假,非 0 整数为真,知道这点就不难理解了。
# 只打印重复的行说明:当处理第一条记录时,执行了++,初始值是 0 为假,就不打印,如果再遇到相同的记录,值就会+1,不为 0,则打印。
# 不打印重复的行说明:当处理第一条记录时,执行了++,初始值是 0 为假,感叹号取反为真,打印,如果再遇到相同的记录,值就会+1,不为 0 为真,取反为假就不打印。
# tail /etc/services |awk '{if(a[$1]++)print $1}'
isnetserv
blp5
com-bardac-dw
iqobject
# 使用三目运算:
# tail /etc/services |awk '{print a[$1]++?$1:"no"}'
no
no
isnetserv
no
blp5
no
com-bardac-dw
no
iqobject
no
# tail /etc/services |awk '{if(!a[$1]++)print $1}'
3gpp-cbsp
isnetserv
blp5
com-bardac-dw
iqobject
matahari
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
# 6.10 统计每个相同字段的某字段总数
# tail /etc/services |awk -F'[ /]+' '{a[$1]+=$2}END{for(v in a)print v, a[v]}'
com-bardac-dw 97112
3gpp-cbsp 48049
iqobject 97238
matahari 49000
isnetserv 96256
blp5 96258
2
3
4
5
6
7
# 6.11 多维数组
awk 的多维数组,实际上 awk 并不支持多维数组,而是逻辑上模拟二维数组的访问方式,比如 a[a,b]=1,使用 SUBSEP(默认\034)作为分隔下标字段,存储后是这样 a\034b。
# awk 'BEGIN{a["x","y"]=123;for(v in a) print v,a[v]}'
xy 123
2
我们可以重新复制 SUBSEP 变量,改变下标默认分隔符:
# awk 'BEGIN{SUBSEP=":";a["x","y"]=123;for(v in a) print v,a[v]}'
x:y 123
2
根据指定的字段统计出现次数:
# cat file
A 192.168.1.1 HTTP
B 192.168.1.2 HTTP
B 192.168.1.2 MYSQL
C 192.168.1.1 MYSQL
C 192.168.1.1 MQ
D 192.168.1.4 NGINX
# awk 'BEGIN{SUBSEP="-"}{a[$1,$2]++}END{for(v in a)print a[v],v}' file
1 D-192.168.1.4
1 A-192.168.1.1
2 C-192.168.1.1
2 B-192.168.1.2
2
3
4
5
6
7
8
9
10
11
12
13
# 7. 内置函数
函数名 | 描述 |
---|---|
int(expr) | 截断为整数 |
sqrt(expr) | 平方根 |
rand() | 返回一个随机数 N,0 和 1 范围,0 < N < 1 |
srand([expr]) | 使用 expr 生成随机数,如果不指定,默认使用当前时间为种子,如果前面有种子则使用生成随机数 |
asort(a, b) | 对数组 a 的值进行排序,把排序后的值存到新的数组 b 中,新排序的数组下标从 1 开始 |
asorti(a, b) | 对数组 a 的下标进行排序,同上 |
sub(r, s [, t]) | 对输入的记录用 s 替换 r 正则匹配,t 可选针对某字段替换,但只替换第一个字符串 |
gsub(r, s [, t]) | 对输入的记录用 s 替换 r 正则匹配,t 可选针对某字段替换,否则替换所有字符串 |
gensub(r, s, h [, t]) | 对输入的记录用 s 替换 r 正则匹配,h 替换指定索引位置 |
index(s,t) | 返回 s 中字符串 t 的索引位置,0 为不存在 |
length([s]) | 返回 s 的长度 |
match(s, r [, a]) | 测试字符串 s 是否包含匹配 r 的字符串,如果不包含返回 0 |
split(s, a [, r [,seps] ]) | 根据分隔符 seps 将 s 分成数组 a |
substr(s, i [, n]) | 截取字符串 s 从 i 开始到长度 n,如果 n 没指定则是剩余部分 |
tolower(str) | str 中的所有大写转换成小写 |
toupper(str) | str 中的所有小写转换成大写 |
systime() | 当前时间戳 |
strftime([format [,timestamp[, utc-flag]]]) | 格式化输出时间,将时间戳转为字符串 |
# 7.1 int()
截断为整数:
# echo -e "123abc\nabc123\n123abc123" | awk '{print int($0)}'
123
0
123
# awk 'BEGIN{print int(10/3)}'
3
2
3
4
5
6
7
# 7.2 sqrt()
获取 9 的平方根:
# awk 'BEGIN{print sqrt(9)}'
3
2
# 7.3 rand() 和 srand()
# rand()并不是每次运行就是一个随机数,会一直保持一个不变:
# awk 'BEGIN{print rand()}'
0.237788
# 当执行 srand()函数后,rand()才会发生变化,所以一般在 awk 着两个函数结合生成随机数,但是也有很大几率生成一样:
# awk 'BEGIN{srand();print rand()}'
0.31687
# 如果想生成 1-10 的随机数可以这样:
# awk 'BEGIN{srand();print int(rand()*10)}'
4
# 如果想更完美生成随机数,还得做相应的处理!
2
3
4
5
6
7
8
9
10
11
12
13
# 7.4 asort() 和 asorti()
# 排序数组:
# seq -f "str%.g" 5 |awk '{a[x++]=$0}END{s=asort(a,b);for(i=1;i<=s;i++)print
b[i],i}'
str1 1
str2 2
str3 3
str4 4
str5 5
# seq -f "str%.g" 5 |awk '{a[x++]=$0}END{s=asorti(a,b);for(i=1;i<=s;i++)print
b[i],i}'
0 1
1 2
2 3
3 4
4 5
# asort 将 a 数组的值放到数组 b,a 下标丢弃,并将数组 b 的总行号赋值给 s,新数组 b 下标从 1 开始,然后遍历。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 7.5 sub() 和 gsub()
# 替换正则匹配的字符串:
# tail /etc/services |awk '/blp5/{sub(/tcp/,"icmp");print $0}'
blp5 48129/icmp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
# tail /etc/services |awk '/blp5/{gsub(/c/,"9");print $0}'
blp5 48129/t9p # Bloomberg lo9ator
blp5 48129/udp # Bloomberg lo9ator
# echo "1 2 2 3 4 5" |awk 'gsub(2,7,$2){print $0}'
1 7 2 3 4 5
# echo "1 2 3 a b c" |awk 'gsub(/[0-9]/, '0'){print $0}'
0 0 0 a b c
# 在指定行前后加一行:
# seq 5 | awk 'NR==2{sub('/.*/',"txt\n&")}{print}'
1
txt
2
3
4
5
# seq 5 | awk 'NR==2{sub('/.*/',"&\ntxt")}{print}'
1
2
txt
3
4
5
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
# 7.6 index()
# 获取字段索引起始位置:
# tail -n 5 /etc/services |awk '{print index($2,"tcp")}'
7
0
7
0
7
2
3
4
5
6
7
# 7.7 length()
# 统计字段长度:
# tail -n 5 /etc/services |awk '{print length($2)}'
9
9
9
9
9
# 统计数组的长度:
# tail -n 5 /etc/services |awk '{a[$1]=$2}END{print length(a)}'
3
2
3
4
5
6
7
8
9
10
11
# 7.8 match
# echo "123abc#456cde 789aaa#234bbb 999aaa#aaabbb" |xargs -n1 |awk '{print
match($0,234)}'
0
8
0
# 如果记录匹配字符串 234,则返回索引位置,否则返回 0。
# 那么,我们只想打印包含这个字符串的记录就可以这样:
# echo "123abc#456cde 789aaa#234bbb 999aaa#aaabbb" |xargs -n1 |awk
'{if(match($0,234)!=0)print $0}'
789aaa#234bbb
2
3
4
5
6
7
8
9
10
11
# 7.9 split()
# 切分记录为数组 a:
# echo -e "123#456#789\nabc#cde#fgh" |awk '{split($0,a);for(v in a)print a[v],v}'
123#456#789 1
abc#cde#fgh 1
# 以#号切分记录为数据 a:
# echo -e "123#456#789\nabc#cde#fgh" |awk '{split($0,a,"#");for(v in a)print a[v],v}'
123 1
456 2
789 3
abc 1
cde 2
fgh 3
2
3
4
5
6
7
8
9
10
11
12
13
# 7.10 substr()
# 截取字符串索引 4 到最后:
# echo -e "123#456#789\nabc#cde#fgh" |awk '{print
substr($0,4)}'
#456#789
#cde#fgh
# 截取字符串索引 4 到长度 5:
# echo -e "123#456#789\nabc#cde#fgh" |awk '{print substr($0,4,5)}'
#456#
#cde#
2
3
4
5
6
7
8
9
10
# 7.11 tolower() 和 toupper()
# 转换小写:
# echo -e "123#456#789\nABC#cde#fgh" |awk '{print tolower($0)}'
123#456#789
abc#cde#fgh
# 转换大写:
# echo -e "123#456#789\nabc#cde#fgh" |awk '{print toupper($0)}'
123#456#789
ABC#CDE#FGH
2
3
4
5
6
7
8
9
# 7.12 时间处理
# 返回当前时间戳:
# awk 'BEGIN{print systime()}'
1483297766
# 将时间戳转为日期和时间
# echo "1483297766" |awk '{print strftime("%Y-%m-%d %H:%M:%S",$0)}'
2017-01-01 14:09:26
2
3
4
5
6
7
# 8. I/O 语句
语句 | 描述 |
---|---|
getline | 读取下一个输入记录设置给$0 |
getline var | 读取下一个输入记录并赋值给变量 var |
command | getline [var] | 运行 Shell 命令管道输出到$0 或 var |
next | 停止当前处理的输入记录后面动作 |
打印当前记录 | |
printf fmt, expr-list | 格式化输出 |
printf fmt, expr-list >file | 格式输出和写到文件 |
system(cmd-line) | 执行命令和返回状态 |
print ... >> file | 追加输出到文件 |
print ... | command | 打印输出作为命令输入 |
# 8.1 getline
# 获取匹配的下一行:
# seq 5 |awk '/3/{getline;print}'
4
# seq 5 |awk '/3/{print;getline;print}'
3
4
# 在匹配的下一行加个星号:
# seq 5 |awk '/3/{getline;sub(".*","&*");print}'
4*
# seq 5 |awk '/3/{print;getline;sub(".*","&*")}{print}'
1
2
3
4*
5
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2)getline var 把 a 文件的行追加到 b 文件的行尾:
# cat a
a b c
# cat b
1 one 2 two 3 three
# awk '{getline line<"a";print $0,line}' b
1 one a 2 two b 3 three c 把 a 文件的行替换 b 文件的指定字段:
# awk '{getline line<"a";gsub($2,line,$2);print}' b
1 a 2 b 3 c 把 a 文件的行替换 b 文件的对应字段:
# awk '{getline line<"a";gsub("two",line,$2);print}' b
1 one 2 b 3 three 3)command | getline [var] 获取执行 shell 命令后结果的第一行:
# awk 'BEGIN{"seq 5"|getline var;print var}'
1 循环输出执行 shell 命令后的结果:
# awk 'BEGIN{while("seq 5"|getline)print}'
1 2 3 4 5 4)next 不打印匹配行:
# seq 5 |awk '{if($0==3){next}else{print}}'
1 2 4 5 删除指定行:
# seq 5 |awk 'NR==1{next}{print $0}'
2 3 4 5 如果前面动作成功,就遇到 next,后面的动作不再执行,跳过。 或者:
# seq 5 |awk 'NR!=1{print}'
2 3 4 5 把第一行内容放到每行的前面:
# cat a
hello 1 a 2 b 3 c
# awk 'NR==1{s=$0;next}{print s,$0}' a
hello 1 a hello 2 b hello 3 c
# awk 'NR==1{s=$0}NF!=1{print s,$0}' a
hello 1 a hello 2 b hello 3 c 5)system() 执行 shell 命令判断返回值:
# awk 'BEGIN{if(system("grep root /etc/passwd &>/dev/null")==0)print "yes";else print
"no"}' yes 6)打印结果写到文件
# tail -n5 /etc/services |awk '{print $2 > "a.txt"}'
# cat a.txt
48049/tcp 48128/tcp 48128/udp 48129/tcp 48129/udp 7)管道连接 shell 命令 将结果通过 grep 命令过滤:
# tail -n5 /etc/services |awk '{print $2|"grep tcp"}'
48556/tcp 48619/tcp 49000/tcp 8.3.9 9 f printf 语句 格式化输出,默认打印字符串不换行。 格式:printf [format] arguments Format 描述 %s 一个字符串 %d,%i 一个小数 %f 一个浮点数 %.ns 输出字符串,n 是输出几个字符 %m.nf 输出浮点数,m 是输出整数位数,n 是输出的小数位数 %x 不带正负号的十六进制,使用 a 至 f 表示 10 到 15 %X 不带正负号的十六进制,使用 A 至 F 表示 10 至 15 %% 输出单个% %-5s 左对齐,对参数每个字段左对齐,宽度为 5 %-4.2f 左对齐,宽度为 4,保留两位小数 %5s 右对齐,不加横线表示右对齐 示例: 将换行符换成逗号:
# seq 5 |awk '{if($0!=5)printf "%s,",$0;else print $0}'
1,2,3,4,5 小括号中的 5 是最后一个数字。 输出一个字符:
# awk 'BEGIN{printf "%.1s\n","abc"}'
a 保留一个小数点:
# awk 'BEGIN{printf "%.2f\n",10/3}'
3.33 格式化输出:
# awk 'BEGIN{printf "user:%s\tpass:%d\n","abc",123}'
user:abc pass:123 左对齐宽度 10:
# awk 'BEGIN{printf "%-10s %-10s %-10s\n","ID","Name","Passwd"}'
ID Name Passwd 右对齐宽度 10:
# awk 'BEGIN{printf "%10s %10s %10s\n","ID","Name","Passwd"}'
ID Name Passwd 打印表格:
# vi test.awk
BEGIN{ print "+--------------------+--------------------+"; printf "|%-20s|%-20s|\n","Name","Number"; print "+--------------------+--------------------+"; }
# awk -f test.awk
+--------------------+--------------------+ |Name |Number | +--------------------+--------------------+ 格式化输出:
# awk -F: 'BEGIN{printf "UserName\t\tShell\n-----------------------------\n"}{printf
"%-20s %-20s\n",$1,$7}END{print "END...\n"}' /etc/passwd 打印十六进制:
# awk 'BEGIN{printf "%x %X",123,123}'
7b 7B 8.3. 10 自定义 函数 格式:function name(parameter list) { statements } 示例:
# awk 'function myfunc(a,b){return a+b}BEGIN{print myfunc(1,2)}'
3 8.3.11 1 需求案例 1)分析 Nginx 日志 日志格式: '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent " $http_referer" "$http_user_agent" "$http_x_forwarded_for"' 统计访问 IP 次数:
# awk '{a[$1]++}END{for(v in a)print v,a[v]}' access.log
统计访问访问大于 100 次的 IP:
# awk '{a[$1]++}END{for(v in a){if(a[v]>100)print v,a[v]}}' access.log
统计访问 IP 次数并排序取前 10:
# awk '{a[$1]++}END{for(v in a)print v,a[v] |"sort -k2 -nr |head -10"}' access.log
统计时间段访问最多的 IP:
# awk '$4>="[02/Jan/2017:00:02:00" && $4<="[02/Jan/2017:00:03:00"{a[$1]++}END{for(v in
a)print v,a[v]}' access.log 统计上一分钟访问量:
# date=$(date -d '-1 minute' +%d/%d/%Y:%H:%M)
# awk -vdate=$date '$4~date{c++}END{print c}' access.log
统计访问最多的 10 个页面:
# awk '{a[$7]++}END{for(v in a)print v,a[v] |"sort -k1 -nr|head -
n10"}' access.log 统计每个 URL 数量和返回内容总大小:
# awk '{a[$7]++;size[$7]+=$10}END{for(v in a)print a[v],v,size[v]}' access.log
统计每个 IP 访问状态码数量:
# awk '{a[$1" "$9]++}END{for(v in a)print v,a[v]}' access.log
统计访问 IP 是 404 状态次数:
# awk '{if($9~/404/)a[$1" "$9]++}END{for(i in a)print v,a[v]}' access.log
2)两个文件对比 找出 b 文件在 a 文件相同记录:
# seq 1 5 > a
# seq 3 7 > b
方法 1:
# awk 'FNR==NR{a[$0];next}{if($0 in a)print $0}' a b
3 4 5
# awk 'FNR==NR{a[$0];next}{if($0 in a)print FILENAME,$0}' a b
b 3 b 4 b 5
# awk 'FNR==NR{a[$0]}NR>FNR{if($0 in a)print $0}' a b
3 4 5
# awk 'FNR==NR{a[$0]=1;next}(a[$0]==1)' a b # a[$0]是通过 b 文件每行获取值,如果是 1
说明有
# awk 'FNR==NR{a[$0]=1;next}{if(a[$0]==1)print}' a b
3 4 5 方法 2:
# awk 'FILENAME=="a"{a[$0]}FILENAME=="b"{if($0 in a)print $0}' a b
3 4 5 方法 3:
# awk 'ARGIND==1{a[$0]=1}ARGIND==2 && a[$0]==1' a b
3 4 5 找出 b 文件在 a 文件不同记录: 方法 1:
# awk 'FNR==NR{a[$0];next}!($0 in a)' a b
6 7
# awk 'FNR==NR{a[$0]=1;next}(a[$0]!=1)' a b
# awk 'FNR==NR{a[$0]=1;next}{if(a[$0]!=1)print}' a b
6 7 方法 2:
# awk 'FILENAME=="a"{a[$0]=1}FILENAME=="b" && a[$0]!=1' a b
方法 3:
# awk 'ARGIND==1{a[$0]=1}ARGIND==2 && a[$0]!=1' a b
3)合并两个文件 将 a 文件合并到 b 文件:
# cat a
zhangsan 20 lisi 23 wangwu 29
# cat b
zhangsan man lisi woman wangwu man
# awk 'FNR==NR{a[$1]=$0;next}{print a[$1],$2}' a b
zhangsan 20 man lisi 23 woman wangwu 29 man
# awk 'FNR==NR{a[$1]=$0}NR>FNR{print a[$1],$2}' a b
zhangsan 20 man lisi 23 woman wangwu 29 man 将 a 文件相同 IP 的服务名合并:
# cat a
192.168.1.1: httpd 192.168.1.1: tomcat 192.168.1.2: httpd 192.168.1.2: postfix 192.168.1.3: mysqld 192.168.1.4: httpd
# awk 'BEGIN{FS=":";OFS=":"}{a[$1]=a[$1] $2}END{for(v in a)print v,a[v]}' a
192.168.1.4: httpd 192.168.1.1: httpd tomcat 192.168.1.2: httpd postfix 192.168.1.3: mysqld 说明:数组 a 存储是$1=a[$1] $2,第一个 a[$1]是以第一个字段为下标,值是 a[$1] $2,也就是 $1=a[$1] $2,值的 a[$1]是用第一个字段为下标获取对应的值,但第一次数组 a 还没有元素,那么 a[$1]是空值,此时数组存储是 192.168.1.1=httpd,再遇到 192.168.1.1 时,a[$1]通过第一字段 下标获得上次数组的 httpd,把当前处理的行第二个字段放到上一次同下标的值后面,作为下标 192.168.1.1 的新值。此时数组存储是 192.168.1.1=httpd tomcat。每次遇到相同的下标(第一个 字段)就会获取上次这个下标对应的值与当前字段并作为此下标的新值。 4)将第一列合并到一行
# cat file
1 2 3 4 5 6 7 8 9
# awk '{for(i=1;i<=NF;i++)a[i]=a[i]$i" "}END{for(v in a)print a[v]}' file
1 4 7 2 5 8 3 6 9 说明: for 循环是遍历每行的字段,NF 等于 3,循环 3 次。 读取第一行时: 第一个字段:a[1]=a[1]1" " 值 a[1]还未定义数组,下标也获取不到对应的值,所以为空,因此 a[1]=1 。 第二个字段:a[2]=a[2]2" " 值 a[2]数组 a 已经定义,但没有 2 这个下标,也获取不到对应的 值,为空,因此 a[2]=2 。 第三个字段:a[3]=a[3]3" " 值 a[2]与上面一样,为空,a[3]=3 。 读取第二行时: 第一个字段:a[1]=a[1]4" " 值 a[2]获取数组 a 的 2 为下标对应的值,上面已经有这个下标了, 对应的值是 1,因此 a[1]=1 4 第二个字段:a[2]=a[2]5" " 同上,a[2]=2 5 第三个字段:a[3]=a[3]6" " 同上,a[2]=3 6 读取第三行时处理方式同上,数组最后还是三个下标,分别是 1=1 4 7,2=2 5 8,3=3 6 9。最后 for 循环输出所有下标值。 5)字符串拆分,统计出现的次数 字符串拆分: 方法 1:
# echo "hello world" |awk -F '' '{print $1}'
h
# echo "hello" |awk -F '' '{for(i=1;i<=NF;i++)print $i}'
h e l l o 方法 2:
# echo "hello" |awk '{split($0,a,"''");for(v in a)print a[v]}'
l o h e l 统计字符串中每个字母出现的次数:
# echo "a.b.c,c.d.e" |awk -F '[.,]' '{for(i=1;i<=NF;i++)a[$i]++}END{for(v in a)print
v,a[v]}' a 1 b 1 c 2 d 1 e 1 6)统计平均成绩
# cat file
job 80 dave 84 tom 75 dave 73 job 72 tom 83 dave 88
# awk '{a[$1]+=$2;b[$1]++}END{for(i in a)print i,a[i]/b[i]}' file
job 76 dave 81.6667 tom 79 7)费用统计
# cat file
zhangsan 8000 1 zhangsan 5000 1 lisi 1000 1 lisi 2000 1 wangwu 1500 1 zhaoliu 6000 1 zhaoliu 2000 1 zhaoliu 3000 1
# awk '{name[$1]++;cost[$1]+=$2;number[$1]+=$3}END{for(v in name)print
v,cost[v],number[v]}' file zhangsan 5000 1 lisi 3000 2 wangwu 1500 1 zhaoliu 11000 3 8)获取数字字段最大值
# cat file
a b 1 c d 2 e f 3 g h 3 i j 2 获取第三字段最大值:
# awk 'BEGIN{max=0}{if($3>max)max=$3}END{print max}' file
3 打印第三字段最大行:
# awk 'BEGIN{max=0}{a[$0]=$3;if($3>max)max=$3}END{for(v in a)print v,a[v],max}' a
g h 3 3 3 e f 3 3 3 c d 2 2 3 a b 1 1 3 i j 2 2 3
# awk 'BEGIN{max=0}{a[$0]=$3;if($3>max)max=$3}END{for(v in a)if(a[v]==max)print v}' a
g h 3 e f 3 9)去除第一行和最后一行
# seq 5 |awk 'NR>2{print s}{s=$0}'
2 3 4 读取第一行,NR=1,不执行 print s,s=1 读取第二行,NR=2,不执行 print s,s=2 (大于为真) 读取第三行,NR=3,执行 print s,此时 s 是上一次 p 赋值内容 2,s=3 最后一行,执行 print s,打印倒数第二行,s=最后一行 获取 Nginx 负载均衡配置端 IP 和端口:
# cat nginx.conf
upstream example-servers1 { server 127.0.0.1:80 weight=1 max_fails=2 fail_timeout=30s; } upstream example-servers2 { server 127.0.0.1:80 weight=1 max_fails=2 fail_timeout=30s; server 127.0.0.1:82 backup; }
# awk '/example-servers1/,/}/{if(NR>2){print s}{s=$2}}' nginx.conf
127.0.0.1:80
# awk '/example-servers1/,/}/{if(i>1)print s;s=$2;i++}' nginx.conf
# awk '/example-servers1/,/}/{if(i>1){print s}{s=$2;i++}}' nginx.conf
127.0.0.1:80 读取第一行,i 初始值为 0,0>1 为假,不执行 print s,x=example-servers1,i=1 读取第二行,i=1,1>1 为假,不执行 print s,s=127.0.0.1:80,i=2 读取第三行,i=2,2>1 为真,执行 print s,此时 s 是上一次 s 赋值内容 127.0.0.1:80,i=3 最后一行,执行 print s,打印倒数第二行,s=最后一行。 这种方式与上面一样,只是用 i++作为计数器。 10)知道上述方式,就可以实现这种需求了,打印匹配行的上一行
# seq 5 |awk '/3/{print s}{s=$0}'
2 其他参考资料:http://www.gnu.org/software/gawk/manual/gawk.html