1. 使用方法
// 命令格式
awk [options] 'pattern{action}' filenames
// 脚本格式
awk -f script_file filenames
1.1 命令格式
pattern
表示 AWK 在数据中查找的内容,可以用正则表达式来进行匹配,用/
斜杠括起来。
action
是在找到匹配内容后执行的一系列命令。{}
不需要在程序中始终出现,但它们用于根据特定的模式对指令进行分组。
1.2 行处理方式
awk默认是以行为单位处理文本的,对filenames每一行都执行{}
中的语句。
记录:默认为文本中的每一行
字段:默认每个记录中由空格或TAB分隔的字符串
$0
表示一条记录,即文本中的一行
$1
表示记录中的第一个字段
下面通过解释一个简单的命令,来了解以下工作原理:
➜ ~ awk '{print $0}' /etc/passwd
root:x:0:0::/root:/bin/bash
nobody:x:65534:65534:Nobody:/:/sbin/nologin
dbus:x:81:81:System Message Bus:/:/sbin/nologin
bin:x:1:1::/:/sbin/nologin
daemon:x:2:2::/:/sbin/nologin
mail:x:8:12::/var/spool/mail:/sbin/nologin
ftp:x:14:11::/srv/ftp:/sbin/nologin
http:x:33:33::/srv/http:/sbin/nologin
...
'{print $0}
:awk对/etc/passwd
的每一行都执行了print
命令。
$0
:上面说了$0
代表一条记录,这里默认是/etc/passwd
的每一行。
下面对一个复杂一点的命令进行解释,来直观的感受一下pattern
、字段
、分隔符
的效果:
➜ ~ awk -F ":" '/^system.*$/{print $1}' /etc/passwd
systemd-journal-remote
systemd-network
systemd-resolve
systemd-timesync
systemd-coredump
-F ":"
:指定分隔符为:
。上面说过,默认记录的分割符是空格或者TAB。
/^system.*$/
:正则表达式匹配system开头的记录。
{print $1}
:输出每条记录的第一个字段。
上图解释一下:
2. BEGIN和END模块
2.1 BEGIN
awk在开始处理输入文件之前会执行BEGIN
模块,所以它是初始化FS
(字段分隔符)变量、打印页眉或初始化其它在程序中以后会引用的全局变量的极佳位置。
2.2 END
awk在处理完输入文件之后会执行END
模块,通常,END
块用于执行最终计算或打印应该出现在输出流结尾的摘要信息。
2.3 使用
统计/etc/passwd中帐号人数
➜ ~ awk 'BEGIN {count=0;print "[start] user count is", count} {count++;print $0} END {print "[end] user count is", count}' /etc/passwd
[start] user count is 0
root:x:0:0::/root:/bin/bash
nobody:x:65534:65534:Nobody:/:/sbin/nologin
dbus:x:81:81:System Message Bus:/:/sbin/nologin
...
[end] user count is 33
3. 内置变量
变量 | 含义 |
---|---|
$0 | 整行记录 |
$1~$n | 当前记录的第几个字段 |
FS | 字段分隔符,默认是空格 |
RS | 记录分隔符,默认是换行符 |
NF | 当前行字段数目,就是有多少列 |
NR | 已经读出的记录数,就是行号 |
OFS | 输出字段分隔符,默认也是空格 |
ORS | 输出记录分隔符,默认也是换行符 |
3.1 字段分隔符FS
-
FS="\t+"
一个或多个TAB分隔➜ ~ cat test awk hello world! ➜ ~ awk 'BEGIN{FS="\t+"}{print $1,$2,$3}' test awk hello world!
-
FS="[[:space:]+]"
一个或多个空格分隔(默认)➜ ~ cat test awk hello world! ➜ ~ awk 'BEGIN{FS="[[:space:]+]"}{print $1,$2,$3}' test awk hello ➜ ~ awk -F [[:space:]+] '{print $1,$2,$3}' test awk hello
-
FS="[ :]+"
一个或多个空格或者:
分隔➜ ~ cat test awk:hello world! ➜ ~ awk -F '[ :]+' '{print $1,$2,$3}' test awk hello world! ➜ ~ awk 'BEGIN {FS="[ :]+"} {print $1,$2,$3}' test awk hello world!
3.2 字段数NF
输出字段数为2的记录
➜ ~ cat test
awk:hello:world
hell:world
➜ ~ awk -F ':' '{if(NF==2) print $0}' test
hell:world
3.3 记录数NR
输出第一条记录
➜ ~ cat test
awk:hello:world
hell:world
➜ ~ awk '{if(NR==1) print $0}' test
awk:hello:world
3.4 输出字段分隔符OFS
➜ ~ awk 'BEGIN {FS=":";OFS="QAQ"} {print $1,$2,$3}' test
awkQAQhelloQAQworld
hellQAQworldQAQ
3.5 输出行分隔符ORS
➜ ~ awk 'BEGIN {FS=":";ORS="\nQAQ\n"} {print $0}' test
awk:hello:world
QAQ
hell:world
QAQ
4. awk的if、循环和数组
4.1 条件语句
上面已经给了很多使用实例了,这里就不多说了
4.2 循环结构
- while循环
➜ ~ vim script #!/bin/awk BEGIN { i=1 } { while (i < 3) { print i i++ } print $0 } ➜ ~ awk -f script test 1 2 awk:hello:world hell:world
- for循环
➜ ~ vim script #!/bin/awk { for (i=1;i<4;i++) { print i } } ➜ ~ cat test awk:hello:world hell:world ➜ ~ awk -f script test 1 2 3 1 2 3 # 因为test有两行,每一行执行一次script,所有输出了两次1 2 3
4.3 数组
awk中的数组都是关联数组,数字索引也会转变为字符串索引
➜ ~ vim script
#!/bin/awk
{
cities[1]="beijing"
cities[2]="shanghai"
cities["three"]="guangzhou"
for( c in cities) {
print cities[c]
}
print "---"
print cities[1]
print cities["1"]
print cities["three"]
}
➜ ~ cat test
awk:hello:world
➜ ~ awk -f script test
guangzhou
beijing
shanghai
---
beijing
beijing
guangzhou
查看网络汇总状态
➜ ~ netstat -an | awk '/^tcp/ {s[$NF]++} END {for (i in s) print i,s[i]}'
LISTEN 20
ESTABLISHED 5
5. 案例
5.1 两个文件求交集
➜ ~ cat a
1,name
2,name
3,name
4,name
5,name
6,name
➜ ~ cat b
3,city
6,city
7,city
➜ ~ vim script
#!/bin/awk
BEGIN {
FS=","
}
{
if (NR==FNR) {
a[$1]++
}
if (NR>FNR) {
if ($1 in a) {
print $0
}
}
}
➜ ~ awk -f script a b
3,city
6,city
脚本解释:
BEGIN{FS=","}
:很简单了,指定分隔符。
if (NR==FNR) {a[$1]++}
:这里是把第一个文件的第一列作为数组的索引,将它出现的次数作为值。
if (NR>FNR) {if ($1 in a) {print $0}}
:这里是查看第二个文件当前第一列是否在上面的数组中,如果存在则输出。
重点解释:FNR
NR
都已经明白是awk已经读出的记录数,就是行号。
FNR
,与NR
类似,但是不同的是每当awk打开一个新文件时,FNR便重新计数;而NR
是继续计数。
举例:
- 只打开一个文件时,两者没有什么区别
➜ ~ awk '{print NR,$0}' t1 1 a 2 b 3 c 4 d 5 e 6 f ➜ ~ awk '{print FNR,$0}' t1 1 a 2 b 3 c 4 d 5 e 6 f
- 但是当打开多个文件呢?
➜ ~ awk '{print NR,$0}' t1 t2 1 a 2 b 3 c 4 d 5 e 6 f 7 a 8 b 9 c ➜ ~ awk '{print FNR,$0}' t1 t2 1 a 2 b 3 c 4 d 5 e 6 f # 这里打开t2文件时,行号重新计数了 1 a 2 b 3 c