awk 是一种样式扫描和处理语言,使用 Linux 的 awk 命令能够高效快捷地进行文本处理。awk 扫描文本的每一行并执行指定的命令。html
awk 诞生于 1977 年,借鉴了 C 语言等编程语言,名字取自三位设计者 Alfred Aho、Peter Weinberger 和 Brian Kernighan 的姓氏。awk 的版本众多,本文中使用的是 Ubuntu 上的 GNU Awk,在 MacOS 上可使用 HomeBrew 安装 gawk。node
awk 能够直接在命令行中执行,也能够编写.awk
后缀的文件而后执行。awk 以行为单位进行文本处理,对于接收到的每一行都会执行指定的行为。正则表达式
$ awk [ -F fs ] [ -v var=value ] 'pattern {action}' [ file ... ]
复制代码
其中 -F
指定分隔符,-v
指定 awk 的内置变量。编程
例如对于 /etc/passwd
文件中的内容:bash
root:x:0:0:root:/root:/usr/bin/zsh
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
复制代码
若是要输出每一行的内容,可使用编程语言
$ awk '{print $0}' /etc/passwd
复制代码
其中 $0
表示扫描到的文本行。函数
.awk
文件能够分红三个部分来写,以下:ui
# passwd.awk
BEGIN{
FS="\n";
print "Before action";
}
{
print $0;
}
END{
print "After action";
}
复制代码
BEGIN 块用于定义处理每一行以前的行为,能够用来设置 awk 的内置变量,设置好后在后面处理每一行时都会生效。spa
END 块用于定义处理完文本以后的行为,能够用来输出一些总结信息。命令行
BEGIN 和 END 中间的块是对于每一行的操做。直接在命令行中执行 awk 时也能够用 BEGIN 和 END 块。
写好文件后在命令行中执行:
Before action
root:x:0:0:root:/root:/usr/bin/zsh
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
After action
复制代码
$0
表示扫描到的行,$1
表示将该行分隔后的第 1 项,$2
表示将该行分隔后的第 2 项,以此类推。
为了输出 /etc/passwd
的用户名(第一项),能够执行下面语句:
$ awk -F ':' '{print $1}' /etc/passwd
root
daemon
bin
sys
复制代码
这里以处理第一行 root:x:0:0:root:/root:/usr/bin/zsh
为例,awk 处理是会先按照 -F
设置的分隔符 :
将这行分割为 root x 0 0 root /root /usr/bin/zsh
而后输出第一项 root
FS(field separator)
FS 是输入字段分隔符,例如上面设置的 :
,其默认值是空格,能够在命令行中使用 -F
设置,也能够在 BEGIN 块中经过 FS=
设置为某个字符串或者一个正则表达式。例如:
$ awk -F ":" '{print $1,$2,$3}' /etc/passwd
root x 0
daemon x 1
bin x 2
sys x 3
复制代码
OFS(output field separator)
OFS 是输出字段链接符,上面例子中的输出默认使用空格做为输出字段链接符,经过设置 OFS 变量进行修改:
$ awk -F ":" -v OFS="-" '{print $1,$2,$3}' /etc/passwd
root-x-0
daemon-x-1
bin-x-2
sys-x-3
复制代码
RS(record separator)
前面提到的例子中,awk 都默认以行为单位处理文本,每一行保存了一条记录,这是由于默认的记录分隔符 RS 为 "\n"。还有一些文本在存储时并非想 csv 等文件同样以行单位,例如:
# people.txt
P1
male
15
p2
female
20
p3
male
19
复制代码
上面的文件中使用 "\n\n" 分隔记录,每一个记录中又使用 "\n" 分隔字段,能够这样处理:
$ awk -F "\n" -v RS="\n\n" '{print $1,$2,$3}' people.txt
P1 male 15
p2 female 20
p3 male 19
复制代码
ORS(output field separator)
与 RS 相似,ORS 设置输出的记录分隔符。
$ awk -F "\n" -v RS="\n\n" -v ORS="\n***\n" '{print $1,$2,$3}' people.txt
P1 male 15
***
p2 female 20
***
p3 male 19
***
复制代码
NR(number of records)
NR 表示当前正在处理的记录的是第几项,若是 NR 出如今 END 块中则表示已处理的记录数
$ awk -F ":" '{print "line" NR ":" $1,$2,$3}' /etc/passwd
line1:root x 0
line2:daemon x 1
line3:bin x 2
line4:sys x 3
复制代码
若是同时处理多个文件,那么这个项数会累加
$ awk -F ":" '{print "record" NR ":" $1,$2,$3}' people.txt /etc/passwd
record1:P1
record2:male
record3:15
record4:
record5:p2
record6:female
record7:20
record8:
record9:p3
record10:male
record11:19
record12:root x 0
record13:daemon x 1
record14:bin x 2
record15:sys x 3
复制代码
NF(number of fields)
NF 表示一条记录中分隔后的字段数,所以这个值与设置的 FS 有关:
# 以 ":" 为分隔符
$ awk -F ":" '{print "record" NR " with " NF " fields:" $1,$2,$3}' /etc/passwd
record1 with 7 fields:root x 0
record2 with 7 fields:daemon x 1
record3 with 7 fields:bin x 2
record4 with 7 fields:sys x 3
# 以 "o" 为分隔符
$ awk -F "o" '{print "record" NR " with " NF " fields:" $1,$2,$3}' /etc/passwd
record1 with 7 fields:r t:x:0:0:r
record2 with 5 fields:daem n:x:1:1:daem n:/usr/sbin:/usr/sbin/n
record3 with 3 fields:bin:x:2:2:bin:/bin:/usr/sbin/n l gin
record4 with 3 fields:sys:x:3:3:sys:/dev:/usr/sbin/n l gin
复制代码
FILENAME
FILENAME 是当前正在处理的文件的名字
$ awk -F ":" '{print FILENAME}' /etc/passwd people.txt
/etc/passwd
/etc/passwd
/etc/passwd
/etc/passwd
people.txt
people.txt
people.txt
people.txt
people.txt
people.txt
people.txt
people.txt
people.txt
people.txt
people.txt
复制代码
这个值在开始处理记录后才有意义,所以在 BEGIN 块中尝试输出 FILENAME 将获得空值
FNR
前面的 NR 表示的项数在处理多个文件时会累加,而FNR 表示记录位于当前文件的第几项
awk -F ":" '{print "record" FNR ":" $1,$2,$3}' people.txt /etc/passwd
record1:P1
record2:male
record3:15
record4:
record5:p2
record6:female
record7:20
record8:
record9:p3
record10:male
record11:19
record1:root x 0
record2:daemon x 1
record3:bin x 2
record4:sys x 3
复制代码
awk 提供了一些内置函数,便于文本和运算的处理,包括获取字符串长度的 length()
,获取随机数的 rand()
,计算正余弦的 sin()
和 cos()
。
这些函数能够在官方手册中查询。
上面的全部的例子都对每一条记录进行了操做,事实上还能够用条件进行筛选。
使用正则表达式能够对记录进行模式匹配:
$ awk -F ':' '/root/ {print $1,$2,$3}' /etc/passwd
root x 0
复制代码
这里筛选出了包含 root
的记录。
结合 awk 的内置变量和函数也能够进行筛选:
# 输出第一个字段长度大于 2 且在 第 1 条记录以后的记录
$ awk -F ':' 'length($1)>3 && NR>1 {print $0}' /etc/passwd
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
复制代码
awk 也提供了 if 语句:
# 输出第 3 个字段为 0 的记录
$ awk -F ':' '{if ($3==0)print $0}' /etc/passwd
root:x:0:0:root:/root:/usr/bin/zsh
复制代码
awk 还有 for 语句,与 C 语言中形式相似:
$ awk -v ORS="," 'BEGIN{ for(i=1;i<5;i++) print i}'
1,2,3,4,
复制代码
awk 提供了对数学运算符和逻辑运算符的支持的支持,在 awk 中字符串与数字间还能够直接进行强制类型转换,+0
能够强制转换为数字,与空格拼接能够转换为字符串:
awk 'BEGIN{print "origin\tnumber\tstring"}{print $0,"\t",$0+0,"\t",$0 ""}' people.txt
origin number string
P1 0 P1
male 0 male
15 15 15
0
p2 0 p2
female 0 female
20 20 20
0
p3 0 p3
male 0 male
19 19 1
复制代码
这里使用了 $0 ""
的表示方式将原记录与空格进行了拼接,但在某些状况下会出现问题,考虑下面的语句:
$ awk 'BEGIN { print -12 " " -24 }'
-| -12-24
复制代码
这里想要在 -12 和 -24 中间加一个空格,但没有获得想要的结果,这是因为数学运算符的优先级高于拼接操做,因此解析顺序以下:
-12 (" " - 24)
⇒ -12 (0 - 24)
⇒ -12 (-24)
⇒ -12-24
复制代码
要获得正确的正确的结果须要使用括号结合:
$ awk 'BEGIN { print -12 " " (-24) }'
-| -12 -24
复制代码