简介
除了使用 sed 命令,Linux 系统中还有一个功能更加强大的文本数据处理工具,就是 awk。它诞生于 20 世纪 70 年代末期,这也许是它影响了众多 Linux 用户的原因之一。曾有人推测 awk 命令的名字来源于 awkward 这个单词。其实不然,此命令的设计者有 3 位,他们的姓分别是 Aho、Weingberger 和 Kernighan,awk 就取自这 3 为大师姓的首字母。awk的最基本功能是在文件或字符串中基于指定规则浏览和抽取信息,awk抽取信息后,才能进行其他文本操作,完整的awk脚本通常用来格式化文本文件中的信息。awk和 sed 命令类似,awk 命令也是逐行扫描文件(从第 1 行到最后一行),寻找含有目标文本的行,如果匹配成功,则会在该行上执行用户想要的操作;反之,则不对行做任何处理。
语法:awk [选项] '脚本命令' 文件名
常用选项
- -F fs:指定以 fs 作为输入行的分隔符,awk 命令默认分隔符为空格或制表符
- -f file:从脚本文件中读取 awk 脚本指令,以取代直接在命令行中输入指令
- -v var=val:在执行处理过程之前,设置一个变量 var,并设置初始值为 val
- -V 或 --version: 显示 awk 的版本信息。
- -h 或 --help: 显示 awk 的帮助信息,包括选项和用法示例。
脚本命令
awk 的强大之处在于脚本命令,如下所示:
语法:'BEGIN{commands} pattern{commands} END{commands}'
说明:
- BEGIN:处理数据前执行的命令
- END:处理数据后执行的命令
- pattern:模式,每一行都执行的命令
- BEGIN和END里的命令只是执行一次
- pattern里的命令会匹配每一行去处理
AWK 工作流程可分为三个部分:
- 执行BEGIN{commands}语句块中的语句(由BEGIN关键字标识)。
- 主循环执行输入文件的语句。
- 从文件或stdin中读取第一行,看有无模式匹配,若无则执行{}中的语句
- 若有,则检查该整行与pattern是否匹配,若匹配,则执行{}中的语句
- 若不匹配,则不执行{}中的语句,接着读取下一行
- 重复这个过程,直到所有行被读取完毕
- 执行BEGIN{commands}语句块中的语句(由END关键字标识)。
这三部分中BEGIN和END部分可选,中间主体部分的pattern{commands}必选,但这部分的pattern或commands也是可选的。
主体块的匹配规则pattern,和 sed 命令中的 address 部分作用相同,用来指定脚本命令可以作用到文本内容中的具体行,可以使用字符串(比如 /demo/,表示查看含有 demo 字符串的行)或者正则表达式指定。另外需要注意的是,整个脚本命令是用单引号(')括起,而其中的执行命令部分需要用大括号({})括起来。在 awk 程序执行时,如果没有指定执行命令,则默认会把匹配的行输出;如果不指定匹配规则,则默认匹配文本中所有的行。
$ awk '/^$/ {print "Blank line"}' test.txt
在此命令中,/^$/ 是一个正则表达式,功能是匹配文本中的空白行,同时可以看到,执行命令使用的是 print 命令,此命令会经常使用,它的作用很简单,就是将指定的文本进行输出。因此,整个命令的功能是,如果 test.txt 有 N 个空白行,那么执行此命令会输出 N 个 Blank line。再次说明, awk 对输入文件中的每一行都执行这个脚本。
BEGIN关键字
通常,对于每个输入行, awk 都会执行脚本代码块一次。然而,在许多编程情况中,可能需要在处理数据前运行一些脚本命令,这就需要使用 BEGIN 关键字。BEGIN 会强制 awk 在读取数据前执行该关键字后指定的脚本命令(BEGIN后面的命令只执行一次),例如:
$ cat data3.txt
Line 1
Line 2
Line 3
$ awk 'BEGIN {print "The data3 File Contents:"}
> {print $0}' data3.txt
The data3 File Contents:
Line 1
Line 2
Line 3
可以看到,这里的脚本命令中分为 2 个部分,BEGIN 部分的脚本指令会在 awk 命令处理数据前运行,而真正用来处理数据的是第二段脚本命令。
注意:开始块部分是可选的,你的程序可以没有开始块部分。
END关键字
和 BEGIN 关键字相对应,END 关键字允许我们指定一些脚本命令,awk 会在读完数据后执行它们,例如:
$ awk 'BEGIN {print "The data3 File Contents:"}
> {print $0}
> END {print "End of File"}' data3.txt
The data3 File Contents:
Line 1
Line 2
Line 3
End of File
注意:与开始块相似,结束块也是可选的。
数据字段变量
awk的主要特性之一是处理文本文件中数据的能力,它会自动给每一行中的每个数据元素分配一个变量。
默认情况下,awk 会将如下变量分配给它在文本行中发现的数据字段(或者叫做域):
- $0 代表整个文本行;
- $1 代表文本行中的第 1 个数据字段(域);
- $2 代表文本行中的第 2 个数据字段(域);
- $n 代表文本行中的第 n 个数据字段(域)。
前面说过,在 awk 中,默认的字段分隔符是任意的空白字符(例如空格或制表符)。 在文本行中,每个数据字段都是通过字段分隔符划分的。awk 在读取一行文本时,会用预定义的字段分隔符划分每个数据字段。所以在下面的例子中,awk 程序读取文本文件,只显示第 1 个数据字段的值:
$ cat data2.txt
One line of test text.
Two lines of test text.
Three lines of test text.
$ awk '{print $1}' data2.txt
One
Two
Three
该程序用 $1 字段变量来表示“仅显示每行文本的第 1 个数据字段”。当然,如果你要读取采用了其他字段分隔符的文件,可以用 -F 选项手动指定。
脚本命令使用多个命令
awk 允许将多条命令组合成一个正常的程序。要在命令行上的程序脚本中使用多条命令,只要在命令之间放个分号即可,例如:
$ echo "My name is Rich" | awk '{$4="Christine"; print $0}'
My name is Christine
第一条命令会给字段变量 $4 赋值。第二条命令会打印整个数据字段。可以看到,awk 程序在输出中已经将原文本中的第四个数据字段替换成了新值。除此之外,也可以一次一行地输入程序脚本命令,比如说:
$ awk '{
> $4="Christine"
> print $0}'
My name is Rich
My name is Christine
在你用了表示起始的单引号后,bash shell 会使用 > 来提示输入更多数据,我们可以每次在每行加一条命令,直到输入了结尾的单引号。注意,此例中因为没有在命令行中指定文件名,awk 程序需要用户输入获得数据,因此当运行这个程序的时候,它会一直等着用户输入文本,此时如果要退出程序,只需按下 Ctrl+C 组合键即可。
从文件中读取程序
跟 sed 一样,awk 允许将脚本命令存储到文件中,然后再在命令行中引用,需使用 -f 选项,比如:
$ cat awk.sh
{print $1 "'s home directory is " $6}
$ awk -F: -f awk.sh /etc/passwd
root's home directory is /root
bin's home directory is /bin
daemon's home directory is /sbin
adm's home directory is /var/adm
lp's home directory is /var/spool/lpd
...
Christine's home directory is /home/Christine
Samantha's home directory is /home/Samantha
Timothy's home directory is /home/Timothy
awk.sh 脚本文件会使用 print 命令打印 /etc/passwd 文件的主目录数据字段(字段变量 $6),以及 userid 数据字段(字段变量 $1)。注意,在程序文件中,也可以指定多条命令,只要一条命令放一行即可,之间不需要用分号。
运算符
运算符 | 描述 |
---|---|
= += -= *= /= %= ^= **= | 赋值 |
?: | C条件表达式 |
|| | 逻辑或 |
&& | 逻辑与 |
~ 和 !~ | 匹配正则表达式和不匹配正则表达式 |
< <= > >= != == | 关系运算符 |
空格 | 连接 |
+ - | 加,减 |
* / % | 乘,除与求余 |
+ - ! | 一元加,减和逻辑非 |
^ *** | 求幂 |
++ -- | 增加或减少,作为前缀或后缀 |
$ | 字段引用 |
in | 数组成员 |
条件操作符
条件操作符有:<、<=、==、!=、>=、~匹配正则表达式、!~不匹配正则表达式
例子:
匹配:awk '{if ($4~/ASIMA/) print $0}' temp.txt 表示如果第四个数据字段包含ASIMA,就打印整行数据
精确匹配:awk '$3=="48" {print $0}' temp.txt 表示只打印第3个数据字段等于"48"的记录
不匹配:awk '$0 !~ /ASIMA/' temp.txt 表示打印整条不包含ASIMA的记录
不等于:awk '$1 != "asima"' temp.txt 表示打印第1个数据字段不等于"asima"的记录
小于:awk '{if ($1<$2) print $1 "is smaller"}' temp.txt 表示如果第一个数据字段小于第二个数据字段,则打印第一个数据字段+"is smaller"
设置大小写:awk '/[Gg]reen/' temp.txt 表示打印整条包含Green,或者green的记录
任意字符:awk '$1 ~/^...a/' temp.txt 表示打印第1个数据字段中第四个字符是a的记录,符号’^’代表行首,符合’.’代表任意字符
或关系匹配:awk '$0~/(abc)|(efg)/' temp.txt 表示打印整行数据匹配“abc”或“efg”的记录,使用|时,语句需要括起来
AND与关系:awk '{if ( $1=="a" && $2=="b" ) print $0}' temp.txt 表示如果第一个数据字段等于“a”并且第二个数据字段等于“b”,则打印该行数据
OR或关系:awk '{if ($1=="a" || $1=="b") print $0}' temp.txt 表示如果第一个数据字段等于“a”或者第二个数据字段等于“b”,则打印该行数据
数组
AWK 可以使用关联数组这种数据结构,索引可以是数字或字符串。
AWK关联数 组也不需要提前声明其大小,因为它在运行时可以自动的增大或减小。
数组使用的语法格式:
语法:array_name[index]=value
说明:
- array_name:数组的名称
- index:数组索引
- value:数组中元素所赋予的值
例子:
➜ test awk 'BEGIN {record="123#456#789";split(record,arr,"#")} END {for (i in arr) {print arr[i]" i="i}}' temp.txt
123 i=1
456 i=2
789 i=3
在上述命令中,在BEGIN中把record分割成数组,保存在arr中,在END中遍历并输出数组arr中的每个元素。i从1开始自动递增,数组的下标也是从1开始。
创建数组
接下来看一下如何创建数组以及如何访问数组元素:
例如:
$ awk 'BEGIN {
sites["runoob"]="www.runoob.com";
sites["google"]="www.google.com"
print sites["runoob"] "\n" sites["google"]
}'
执行以上命令,输出结果为:
www.runoob.com
www.google.com
在上面的例子中,我们定义了一个站点(sites)数组,该数组的索引为网站英文简称,值为网站访问地址。可以使用如下格式访问数组元素:
array_name[index]
删除数组元素
我们可以使用 delete 语句来删除数组元素,语法格式如下:
语法:delete array_name[index]
下面的例子中,数组中的 google 元素被删除(删除命令没有输出):
$ awk 'BEGIN {
sites["runoob"]="www.runoob.com";
sites["google"]="www.google.com"
delete sites["google"];
print sites["google"]
}'
多维数组
AWK 本身不支持多维数组,不过我们可以很容易地使用一维数组模拟实现多维数组。
如下示例为一个 3x3 的二维数组:
100 200 300
400 500 600
700 800 900
以上实例中,array[0][0] 存储 100,array[0][1] 存储 200 ,依次类推。为了在 array[0][0] 处存储 100, 我们可以使用如下语法: array["0,0"] = 100。
我们使用了 0,0 作为索引,但是这并不是两个索引值。事实上,它是一个字符串索引 0,0。
下面是模拟二维数组的例子:
$ awk 'BEGIN {
array["0,0"] = 100;
array["0,1"] = 200;
array["0,2"] = 300;
array["1,0"] = 400;
array["1,1"] = 500;
array["1,2"] = 600;
# 输出数组元素
print "array[0,0] = " array["0,0"];
print "array[0,1] = " array["0,1"];
print "array[0,2] = " array["0,2"];
print "array[1,0] = " array["1,0"];
print "array[1,1] = " array["1,1"];
print "array[1,2] = " array["1,2"];
}'
执行上面的命令可以得到如下结果:
array[0,0] = 100
array[0,1] = 200
array[0,2] = 300
array[1,0] = 400
array[1,1] = 500
array[1,2] = 600
在数组上可以执行很多操作,比如,使用 asort 完成数组元素的排序,或者使用 asorti 实现数组索引的排序等等。
其他用法
-
给变量赋值
以下两种方法都可以实现给变量AGE赋值:
➜ test awk '{if ($6<AGE) print $0}' AGE=10 data2.txt
line1:This is the header line 1.
line4:This is the last line 4.
➜ test awk -v AGE=10 '{if ($6<AGE) print $0}' data2.txt
line1:This is the header line 1.
line4:This is the last line 4.
另外,也可以将环境变量的值赋给变量:
# 输出登录名
➜ test echo $LOGNAME
baichunyu.bcy
# 把登录名赋值给变量user
➜ test who | awk '{if ($1==user) print $1 " are in " $0}' user=$LOGNAME
baichunyu.bcy are in baichunyu.bcy pts/1 Sep 17 09:38 (10.78.232.152)
-
只列出文件名
# 常规情况文件名是第9域
ls -l | awk '{print $9}'
awk正则
元字符 | 功能 | 示例 | 解释 |
^ | 行首定位符 | /^root/ | 匹配所有以root开头的行 |
$ | 行尾定位符 | /root$/ | 匹配所有以root结尾的行 |
. | 匹配任意单个字符 | /r..t/ | 匹配字母r,然后两个任意字符,再以t结尾的单词,比如:root、raat、rabt |
* | 匹配0个或多个前导字符 | /a*ool/ | 匹配0个或多个a之后紧跟着ool的单词,比如:ool、aool、aaaool |
+ | 匹配1个或多个前导字符 | /a+b/ | 匹配1个或多个a加b的单词,比如:ab、aab、aaaab |
? | 匹配0个或1个前导字符 | /a?b/ | 匹配b 或 ab |
[] | 匹配指定字符组内的任意一个字符 | /^[abc]/ | 匹配以字母a或b或c开头的单词 |
[^] | 匹配不在指定字符组内的任意一个字符 | /^[^abc]/ | 匹配不以字母a或b或c开头的单词 |
() | 子表达式组合 | /(root)+/ | 匹配1个或多个root |
| | 或者的意思 | /(root)|B/ | 匹配root或者B |
\ | 转义字符 | /a\/\// | 匹配a// |
~和!~ | 匹配和不匹配的条件语句 | $1~/root/ | 匹配第一个数据字段包含root的行 |
x{m} x{m,} x{m,n} | x重复m次 x重复至少m次 x重复至少m次,但不超过n次 | /(root){3}/ /(root){3,}/ /(root){5,6}/ | 匹配root正好出现3次的情况 匹配root出现至少3次的情况 匹配root出现5到6次的情况 |
举例说明:
#匹配所有包含root的行
$ awk '/root/{print $0}' passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin
#以分号作为分隔符,匹配第5个字段是root的行
$ awk -F: '$5~/root/{print $0}' passwd
root:x:0:0:root:/root:/bin/bash
条件语句
awk中if条件语句的格式为:
if(表达式)
{语句1} ;
else if(表达式)
{语句2} ;
else
{语句3};
每条语句后面要用“;”结尾,例如:
➜ test awk 'BEGIN {
quote> test=70;
quote> if(test>90)
quote> {
quote> print "very good!";
quote> }
quote> else if(test>60)
quote> {
quote> print "good~";
quote> }
quote> else
quote> {
quote> print "no pass!!";
quote> }
quote> }'
good~
循环语句
-
while循环语句
awk中while循环语句的格式为:
while(表达式)
{语句};
举个简单的例子:
➜ test awk 'BEGIN {
border=10;
sum=0;
i=0;
while(i<=border){
sum+=i;
i++;
}
print sum;
}'
55
-
for循环语句
awk中for循环语句有两种格式,先看格式1:
for(变量 in 数组)
{语句};
例子:
➜ test awk 'BEGIN {
for (k in ENVIRON){ # ENVIRON 是awk常量,是个数组
print k"="ENVIRON[k];
}
}'
AWKPATH=.:/usr/share/awk
OLDPWD=/home/web97
SSH_ASKPASS=/usr/libexec/openssh/gnome-ssh-askpass
SELINUX_LEVEL_REQUESTED=
SELINUX_ROLE_REQUESTED=
LANG=zh_CN.GB2312
......
for循环的格式2:
for(变量;条件;表达式)
{语句};
例子:
➜ test awk 'BEGIN {
sum=0;
for(i=0;i<=100;i++){
sum+=i;
}
print sum;
}'
5050
-
do...while循环语句
awk中do...while循环语句的格式为:
do{
语句;
}
while(条件)
例子:
➜ test awk 'BEGIN {
sum=0;
i=0;
do{
sum+=i;
i++;
}while(i<=100)
print sum;
}'
5050
除此之外,还有一些关键字,例如:
break | 当 break 语句用于 while 或 for 语句时,导致退出程序循环 |
continue | 当 continue 语句用于 while 或 for 语句时,使程序循环移动到下一个迭代 |
next | 使程序读入下一个输入行,并返回到脚本的顶部,这可以避免对当前输入行执行其他的操作 |
exit | exit关键字使主输入循环退出并移动到END,如果END存在的话。如果没有定义END规则,或在END中应用exit语句,则终止脚本的执行 |
以上为awk命令的流程控制语句,从语法上面大家可以看到,与大多数语言是一样的。有了这些语句,其实很多shell程序都可以交给awk,而且性能是非常高的。
内置变量
ARGC | 命令行参数个数 | NF | 当前行的数据字段个数 |
AGRV | 命令行参数排列 | NR | 已读的记录数,即行号,从1开始(一行就是一个记录,一个记录有若干个字段/域) |
ARGIND | 命令行中当前文件的位置(从0开始算) | FIELDWIDTHS | 字段宽度列表(用空格键分隔) |
ENVIRON | 支持队列中系统环境变量的使用 | OFS | 输出数据字段分隔符(默认为空格) |
FILENAME | awk当前浏览的文件名 | ORS | 输出记录分隔符(默认为换行符) |
FNR | 当前浏览文件的记录数,即当前文件的行数 | RS | 记录分隔符(默认是换行符) |
FS | 设置输入域分隔符,同- F选项 | IGNORECASE | 如果为真,则进行忽略大小写的匹配 |
RLENGTH | 由match函数所匹配的字符串的长度 | RSTART | 由match函数所匹配的字符串的第一个位置 |
下图说明了几个内置变量的含义:
举几个实例:
#实例1:在最后打印文件的行数
➜ test awk 'END {print NR}' data2.txt
4
#实例2:先输出行号,再输出本行有几个数据字段,再输出本行,最后输出文件名
➜ test awk '{print NR,NF,$0} END {print FILENAME}' data2.txt
1 6 line1:This is the header line 1.
2 7 line2:This is the first data line 2.
3 7 line3:This is the second data line 3.
4 6 line4:This is the last line 4.
data2.txt
#实例3:行数>0,并且第4个数据字段和/last/正则表达式匹配,就输出该行
➜ test awk '{if (NR>0 && $4~/last/) print $0}' data2.txt
line4:This is the last line 4.
#实例4:显示当前目录名
➜ test echo $PWD
/home/baichunyu.bcy/test
➜ test echo $PWD | awk -F / '{print $NF}'
test
#实例5:修改字段分隔符为\t
➜ test cat test.txt
ww CC IDD
➜ test awk 'BEGIN {FS="\t+"} {print $1,$2,$3}' test.txt #把字段分隔符修改为一个或多个制表符
ww CC IDD
#实例6:修改字段分隔符的另一种方式
➜ test cat hello.txt
root:x::0 0:root:/root:/bin/bash
➜ test awk -F '[ :/]+' '{print $1,$2,$3,$4} END{print NF}' hello.txt #把字段分隔符修改为1个或多个空格、冒号、斜杠(采用正则表达式)
root x 0 0
8
FS: 输入字段分隔符变量
FS(Field Separator) 读取并解析输入文件中的每一行时,默认按照空格分隔为字段变量,$1,$2...等。FS 变量被用来设置每一记录的字段分隔符号。FS 可以是任意的字符串或者正则表达式。你可以使用下面两种方式来声名FS:
- 使用 -F 命令选项
- 作为设置为普通变量使用
语法:
$ awk -F 'FS' 'commands' inputfilename
或者
$ awk 'BEGIN{FS="FS";}'
FS 可以是任意字符或者正则表达式。
FS 可以多次改变, 不过会保持不变直到被明确修改。不过如果想要改变字段分隔符, 最好是在读入文本之前就改变 FS, 这样改变才会在你读入的文本生效。
下面是一个使用 FS 读取 /etc/passwd 以 : 作为分隔符的例子:
$ cat etc_passwd.awk
BEGIN{
FS=":";
print "Name\tUserID\tGroupID\tHomeDirectory";
}
{
print $1"\t"$3"\t"$4"\t"$6;
}
END {
print NR,"Records Processed";
}
使用结果:
$ awk -f etc_passwd.awk /etc/passwd
Name UserID GroupID HomeDirectory
gnats 41 41 /var/lib/gnats
libuuid 100 101 /var/lib/libuuid
syslog 101 102 /home/syslog
hplip 103 7 /var/run/hplip
avahi 105 111 /var/run/avahi-daemon
saned 110 116 /home/saned
pulse 111 117 /var/run/pulse
gdm 112 119 /var/lib/gdm
8 Records Processed
OFS: 输出字段分隔符变量
OFS(Output Field Separator) 相当与输出上的 FS, 默认是以一个空格字符作为输出分隔符的,下面是一个 OFS 的例子:
$ awk -F':' '{print $3,$4;}' /etc/passwd
41 41
100 101
101 102
103 7
105 111
110 116
111 117
112 119
注意命令中的 print 语句的, 表示的使用一个空格连接两个参数,也就是默认的OFS的值。因此 OFS 可以像下面那样插入到输出的字段之间:
$ awk -F':' 'BEGIN{OFS="=";} {print $3,$4;}' /etc/passwd
41=41
100=101
101=102
103=7
105=111
110=116
111=117
112=11
RS: 记录分隔符
RS(Record Separator)定义了一行记录。读取文件时,默认将一行作为一条记录。 下面的例子以 student.txt 作为输入文件,记录之间用两行空行分隔,并且每条记录的每个字段用一个换行符分隔:
$ cat student.txt
Jones
2143
78
84
77
Gondrol
2321
56
58
45
RinRao
2122
38
37
65
Edwin
2537
78
67
45
Dayan
2415
30
47
20
然后下面的脚本就会从student.txt输出两项内容:
$ cat student.awk
BEGIN {
RS="\n\n";
FS="\n";
}
{
print $1,$2;
}
$ awk -f student.awk student.txt
Jones 2143
Gondrol 2321
RinRao 2122
Edwin 2537
Dayan 2415
在 student.awk 中,把每个学生的详细信息作为一条记录, 这是因为RS(记录分隔符)是被设置为两个换行符。并且因为 FS (字段分隔符)是一个换行符,所以一行就是一个字段。
ORS: 输出记录分隔符变量
ORS(Output Record Separator)顾名思义就相当与输出的 RS。 每条记录在输出时候会用分隔符隔开,看下面的 ORS 的例子:
$ awk 'BEGIN{ORS="=";} {print;}' student-marks
Jones 2143 78 84 77=Gondrol 2321 56 58 45=RinRao 2122 38 37 65=Edwin 2537 78 67 45=Dayan 2415 30 47 20=
上面的脚本,输入文件的每条记录被 = 分隔开。 附:student-marks 便是上面的输出.
NR: 记录数变量
NR(Number of Record) 表示的是已经处理过的总记录数目,或者说行号(不一定是一个文件,可能是多个)。下面的例子,NR 表示行号,在 END 部分,NR 就是文件中的所有记录数目。
$ awk '{print "Processing Record - ",NR;}END {print NR, "Students Records are processed";}' student-marks
Processing Record - 1
Processing Record - 2
Processing Record - 3
Processing Record - 4
Processing Record - 5
5 Students Records are processed
NF:一条记录的记录数目
NF(Number for Field)表示的是,一条记录的字段的数目. 它在判断某条记录是否所有字段都存在时非常有用。 让我们观察 student-mark 文件如下:
$ cat student-marks
Jones 2143 78 84 77
Gondrol 2321 56 58 45
RinRao 2122 38 37
Edwin 2537 78 67 45
Dayan 2415 30 47
接着下面的Awk程序,打印了记录数(NR),以及该记录的字段数目,因此可以非常容易的发现那些数据丢失了。
$ awk '{print NR,"->",NF}' student-marks
1 -> 5
2 -> 5
3 -> 4
4 -> 5
5 -> 4
FILENAME: 当前输入文件的名字
FILENAME 表示当前正在输入的文件的名字。 AWK 可以接受读取很多个文件去处理。看下面的例子:
$ awk '{print FILENAME}' student-marks
student-marks
student-marks
student-marks
student-marks
student-marks
在输入的文件的每一条记录都会输出该名字。
FNR: 当前输入文件的记录数目
当awk读取多个文件时,NR 代表的是当前输入所有文件的全部记录数,而 FNR 则是当前文件的记录数。如下面的例子:
$ awk '{print FILENAME, "FNR= ", FNR," NR= ", NR}' student-marks bookdetails
student-marks FNR= 1 NR= 1
student-marks FNR= 2 NR= 2
student-marks FNR= 3 NR= 3
student-marks FNR= 4 NR= 4
student-marks FNR= 5 NR= 5
bookdetails FNR= 1 NR= 6
bookdetails FNR= 2 NR= 7
bookdetails FNR= 3 NR= 8
bookdetails FNR= 4 NR= 9
bookdetails FNR= 5 NR= 10
附: bookdetails 与 student-marks 内容一样,作例子. 可以看出来 NR 与 FNR 的区别。
经常使用 NR 与 FNR 结合来处理两个文件,比如有两个文件:
$ cat a.txt
李四|000002
张三|000001
王五|000003
赵六|000004
$ cat b.txt
000001|10
000001|20
000002|30
000002|15
000002|45
000003|40
000003|25
000004|60
如果想作对应的话, 比如张三|000001|10:
$ awk -F '|' 'NR == FNR{a[$2]=$1;} NR>FNR {print a[$1],"|", $0}' a.txt b.txt
张三 | 000001|10
张三 | 000001|20
李四 | 000002|30
李四 | 000002|15
李四 | 000002|45
王五 | 000003|40
王五 | 000003|25
赵六 | 000004|60
内置算数函数
-
atan2( y, x )
含义:返回 y/x 的反正切。
$ awk 'BEGIN {
PI = 3.14159265
x = -10
y = 10
result = atan2 (y,x) * 180 / PI;
printf "The arc tangent for (x=%f, y=%f) is %f degrees\n", x, y, result
}'
# 输出结果为
The arc tangent for (x=-10.000000, y=10.000000) is 135.000000 degrees
-
cos( x )
含义:返回 x 的余弦;x 是弧度。
$ awk 'BEGIN {
PI = 3.14159265
param = 60
result = cos(param * PI / 180.0);
printf "The cosine of %f degrees is %f.\n", param, result
}'
# 输出结果为:
The cosine of 60.000000 degrees is 0.500000.
-
sin( x )
含义:返回 x 的正弦;x 是弧度。
$ awk 'BEGIN {
PI = 3.14159265
param = 30.0
result = sin(param * PI /180)
printf "The sine of %f degrees is %f.\n", param, result
}'
# 输出结果为
The sine of 30.000000 degrees is 0.500000.
-
exp( x )
含义:返回 x 幂函数。
$ awk 'BEGIN {
param = 5
result = exp(param);
printf "The exponential value of %f is %f.\n", param, result
}'
# 输出结果为:
The exponential value of 5.000000 is 148.413159.
-
log( x )
含义:返回 x 的自然对数。
$ awk 'BEGIN {
param = 5.5
result = log (param)
printf "log(%f) = %f\n", param, result
}'
# 输出结果为:
log(5.500000) = 1.704748
-
sqrt( x )
含义:返回 x 平方根。
$ awk 'BEGIN {
param = 1024.0
result = sqrt(param)
printf "sqrt(%f) = %f\n", param, result
}'
# 输出结果为:
log(5.500000) = 1.704748
-
int( x )
含义:返回 x 的截断至整数的值。
$ awk 'BEGIN {
param = 5.12345
result = int(param)
print "Truncated value =", result
}'
# 输出结果为:
Truncated value = 5
-
rand( )
含义:返回任意数字 n,其中 0 <= n < 1。
$ awk 'BEGIN {
print "Random num1 =" , rand()
print "Random num2 =" , rand()
print "Random num3 =" , rand()
}'
# 输出结果为
Random num1 = 0.237788
Random num2 = 0.291066
Random num3 = 0.845814
-
srand( [Expr] )
含义:将 rand 函数的种子值设置为 Expr 参数的值,或如果省略 Expr 参数则使用某天的时间。返回先前的种子值。
$ awk 'BEGIN {
param = 10
printf "srand() = %d\n", srand()
printf "srand(%d) = %d\n", param, srand(param)
}'
# 输出结果为:
srand() = 1
srand(10) = 1417959587
内置字符串函数
-
gsub(r,s)
含义:在整个$0中用s替代r
# 在temp.txt中把每行中的name替换成xiaoming,并打印该行
$ awk 'gsub(/name/,"xiaoming") {print $0}' temp.txt
-
gsub(r,s,t)
含义:在整个t中用s替代r
➜ test cat data2.txt
line1:This is the header line 1.
line2:This is the first data line 2.
line3:This is the second data line 3.
line4:This is the last line 4.
# 在data2.txt文件中,把每行的第5个域中的line替换成hang,如果替换成功则打印该行
➜ test awk 'gsub(/line/,"hang",$5) {print $0}' data2.txt
line1:This is the header hang 1.
line4:This is the last hang 4.
-
index(s,t)
含义:返回s中字符串t的第一位置(下标从1开始计算),如果 t 参数不在 s 参数中出现,则返回 0(零)
➜ test awk 'BEGIN {print index("Sunny","ny")}' temp.txt
4
-
length([s])
含义:返回s的长度,如果未给出 s 参数,则返回整个记录的长度($0 记录变量)
$ awk 'BEGIN {
str = "Hello, World !!!"
print "Length = ", length(str)
}'
# 输出结果为:
Substring "Two" found at 5 location.
-
blength ([s])
含义:返回 String 参数指定的字符串的长度(以字节为单位)。如果未给出 String 参数,则返回整个记录的长度($0 记录变量)。
-
match(s,r)
含义:在 s 参数指定的字符串(r 参数指定的扩展正则表达式出现在其中)中返回位置(字符形式),从 1 开始编号,或如果 r 参数不出现,则返回 0(零)。RSTART 特殊变量设置为返回值。RLENGTH 特殊变量设置为匹配的字符串的长度,或如果未找到任何匹配,则设置为 -1(负一)。
# 因为temp.txt中有两行,遍历每一行时,都把$1赋值为"J.Lulu",都包含"u",因此输出两个4,代表"J.Lulu"中包含"u"
➜ test awk '$1="J.Lulu" {print match($1,"u")}' temp.txt
4
4
-
split(s,a,fs)
含义:用fs把s分割成序列a
➜ test awk 'BEGIN {print split("12#345#6789",arr,"#");print arr[1];print arr[2];print arr[3]}' temp.txt
3
12
345
6789
#上述命令中,print split("12#345#6789",arr,"#"),输出3,即arr的长度,同时arr[1]="12", arr[2]="345", arr[3]="6789" (该序列下标从1开始计算)
➜ test awk 'BEGIN{info="this is a test";split(info,tA," ");print length(tA);for(k in tA){print k,tA[k];}}'
4
1 this
2 is
3 a
4 test
#上述命令中,分割字符串到数组tA中,for循环会自动遍历数组,其中k是数组下标,会自动增加。
-
sprint(fmt,exp)
含义:返回经fmt格式化后的exp
-
sprintf(Format, Expr, Expr, . . . )
含义:根据 Format 参数指定的 printf 子例程格式字符串来格式化 Expr 参数指定的表达式并返回最后生成的字符串。
$ awk 'BEGIN {
str = sprintf("%s", "Hello, World !!!")
print str
}'
# 输出结果为:
Hello, World !!!
-
sub(r,s)
含义:在$0中用s替换第一个r
➜ test cat data2.txt
line1:This is the header line 1.
line2:This is the first data line 2.
line3:This is the second data line 3.
line4:This is the last line 4.
# 把每行中第一个line替换成hang,并打印该行
➜ test awk 'sub(/line/,"hang") {print $0}' data2.txt
hang1:This is the header line 1.
hang2:This is the first data line 2.
hang3:This is the second data line 3.
hang4:This is the last line 4.
-
substr(str,from,[num])
含义:从str中截取子串,该子串从from下标开始截取(str下标从1开始),截取的长度为num,若未指定num参数,则截取到str的末尾
➜ test awk 'BEGIN{print substr("abcdef",4)}' data2.txt
def
➜ test awk 'BEGIN{print substr("abcdef",3,2)}' data2.txt
cd
-
tolower(str)、toupper(str)
含义:把str转为小写、把str转为大写
➜ test awk 'BEGIN{print tolower("abcdefAAA")}' data2.txt
abcdefaaa
➜ test awk 'BEGIN{print toupper("abcdefAAA")}' data2.txt
ABCDEFAAA
-
strtonum(str)
含义:strtonum 将字符串 str 转换为数值。 如果字符串以 0 开始,则将其当作八进制数;如果字符串以 0x 或 0X 开始,则将其当作十六进制数;否则,将其当作浮点数.
$ awk 'BEGIN {
print "十进制数 = " strtonum("123")
print "八进制数 = " strtonum("0123")
print "十六进制数 = " strtonum("0x123")
}'
# 输出结果为:
十进制数 = 123
八进制数 = 83
十六进制数 = 291
例子:
# gsub、sub 使用
$ awk 'BEGIN{info="this is a test2012test!";gsub(/[0-9]+/,"||",info);print info}'
this is a test||test!
# 查找字符串(index 使用)
# 使用了三元运算符: 表达式 ? 动作1 : 动作2
$ awk 'BEGIN{info="this is a test2012test!";print index(info,"11111")?"ok":"no found";}'
no found
$ awk 'BEGIN{info="this is a test2012test!";print index(info,"is")?"ok":"no found";}'
ok
$ awk 'BEGIN{info="this is a test2012test!";print index(info,"test")?"ok":"no found";}'
ok
# 正则表达式匹配查找(match 使用)
$ awk 'BEGIN{info="this is a test2012test!";print match(info,/[0-9]+/)?"ok":"no found";}'
ok
# 从第 4 个 字符开始,截取 10 个长度字符串。
$ awk 'BEGIN{info="this is a test2012test!";print substr(info,4,10);}'
s is a tes
$ awk 'BEGIN{info="this is a test";split(info,tA," ");print length(tA);for(k in tA){print k,tA[k];}}'
4
2 is
3 a
4 test
1 this
# 分割 info,将 info 字符串使用空格切分为动态数组 tA。注意 awk for …in 循环,是一个无序的循环。 并不是从数组下标 1…n ,因此使用时候需要特别注意。
printf函数的使用
其中格式化字符串包括两部分内容:一部分是正常字符,这些字符将按原样输出;另一部分是格式化规定字符, 以 % 开始,后跟一个或几个规定字符,用来确定输出内容格式。 需要特别注意的是使用 printf 时默认是不会换行的,而 print 函数默认会在每行后面加上 \n 换行符。
格式符 | 说明 |
---|---|
%d | 十进制有符号整数 |
%u | 十进制无符号整数 |
%f | 浮点数 |
%s | 字符串 |
%c | 单个字符 |
%p | 指针的值 |
%e | 指数形式的浮点数 |
%x | %X 无符号以十六进制表示的整数 |
%o | 无符号以八进制表示的整数 |
%g | 自动选择合适的表示法 |
# 字符转换:
➜ test echo "65"|awk '{printf "%c\n",$0}'
A
# 格式化输出
➜ test awk 'BEGIN {printf "%f\n",999}' temp.txt
999.000000
# 格式化输出
➜ test cat temp.txt
my name is bcy.
and you?
➜ test awk '{printf "%-15s %s\n",$1,$2}' temp.txt
my name
and you?
$ awk 'BEGIN{n1=124.113;n2=-1.224;n3=1.2345; printf("%.2f,%.2u,%.2g,%X,%o\n",n1,n2,n3,n1,n1);}'
124.11,4294967295,1.2,7C,174
$ awk 'BEGIN{n1=124.113;n2=-1.224;n3=1.2645; printf("%.2f,%.2u,%.2g,%X,%o\n",n1,n2,n3,n1,n1);}'
124.11,4294967295,1.3,7C,174
# 看上面的 n3 输出值会发现,在使用 printf 处理时一个比较智能的功能是可以进行四舍五入保留小数点位的。
内置时间函数
-
mktime( YYYY MM DD HH MM SS[ DST])
含义:生成时间格式
$ awk 'BEGIN {
print "Number of seconds since the Epoch = " mktime("2014 12 14 30 20 10")
}'
# 输出结果为:
Number of seconds since the Epoch = 1418604610
-
strftime([format [, timestamp]])
含义:格式化时间输出,将时间戳转为时间字符串。具体格式,见下表:
$ awk 'BEGIN {
print strftime("Time = %m/%d/%Y %H:%M:%S", systime())
}'
# 输出结果为:
Time = 12/14/2014 22:08:42
strftime 日期和时间格式说明符:
序号 | 描述 |
---|---|
%a | 星期缩写(Mon-Sun)。 |
%A | 星期全称(Monday-Sunday)。 |
%b | 月份缩写(Jan)。 |
%B | 月份全称(January)。 |
%c | 本地日期与时间。 |
%C | 年份中的世纪部分,其值为年份整除100。 |
%d | 十进制日期(01-31) |
%D | 等价于 %m/%d/%y. |
%e | 日期,如果只有一位数字则用空格补齐 |
%F | 等价于 %Y-%m-%d,这也是 ISO 8601 标准日期格式。 |
%g | ISO8610 标准周所在的年份模除 100(00-99)。比如,1993 年 1 月 1 日属于 1992 年的第 53 周。所以,虽然它是 1993 年第 1 天,但是其 ISO8601 标准周所在年份却是 1992。同样,尽管 1973 年 12 月 31 日属于 1973 年但是它却属于 1994 年的第一周。所以 1973 年 12 月 31 日的 ISO8610 标准周所在的年是 1974 而不是 1973。 |
%G | ISO 标准周所在年份的全称。 |
%h | 等价于 %b. |
%H | 用十进制表示的 24 小时格式的小时(00-23) |
%I | 用十进制表示的 12 小时格式的小时(00-12) |
%j | 一年中的第几天(001-366) |
%m | 月份(01-12) |
%M | 分钟数(00-59) |
%n | 换行符 (ASCII LF) |
%p | 十二进制表示法(AM/PM) |
%r | 十二进制表示法的时间(等价于 %I:%M:%S %p)。 |
%R | 等价于 %H:%M。 |
%S | 时间的秒数值(00-60) |
%t | 制表符 (tab) |
%T | 等价于 %H:%M:%S。 |
%u | 以数字表示的星期(1-7),1 表示星期一。 |
%U | 一年中的第几个星期(第一个星期天作为第一周的开始),00-53 |
%V | 一年中的第几个星期(第一个星期一作为第一周的开始),01-53。 |
%w | 以数字表示的星期(0-6),0表示星期日 。 |
%W | 十进制表示的一年中的第几个星期(第一个星期一作为第一周的开始),00-53。 |
%x | 本地日期表示 |
%X | 本地时间表示 |
%y | 年份模除 100。 |
%Y | 十进制表示的完整年份。 |
%z | 时区,表示格式为+HHMM(例如,格式要求生成的 RFC 822或者 RFC 1036 时间头) |
%Z | 时区名称或缩写,如果时区待定则无输出。 |
-
systime()
含义:得到时间戳,返回从1970年1月1日开始到当前时间(不计闰年)的整秒数
awk 'BEGIN{now=systime();print now}'
# 输出结果为:
1343210982
内置位操作函数
-
and
含义:位与操作。
$ awk 'BEGIN {
num1 = 10
num2 = 6
printf "(%d AND %d) = %d\n", num1, num2, and(num1, num2)
}'
# 输出结果为:
(10 AND 6) = 2
-
or
含义:按位或操作
$ awk 'BEGIN {
num1 = 10
num2 = 6
printf "(%d OR %d) = %d\n", num1, num2, or(num1, num2)
}'
# 输出结果为:
(10 OR 6) = 14
-
xor
含义:按位异或操作
$ awk 'BEGIN {
num1 = 10
num2 = 6
printf "(%d XOR %d) = %d\n", num1, num2, xor(num1, num2)
}'
# 输出结果为:
(10 bitwise xor 6) = 12
-
compl
含义:按位求补。
$ awk 'BEGIN {
num1 = 10
printf "compl(%d) = %d\n", num1, compl(num1)
}'
# 输出结果为:
compl(10) = 9007199254740981
-
lshift
含义:左移位操作
$ awk 'BEGIN {
num1 = 10
printf "lshift(%d) by 1 = %d\n", num1, lshift(num1, 1)
}'
# 输出结果为:
lshift(10) by 1 = 20
-
rshift
含义:右移位操作
$ awk 'BEGIN {
num1 = 10
printf "rshift(%d) by 1 = %d\n", num1, rshift(num1, 1)
}'
# 输出结果为:
rshift(10) by 1 = 5
其他内置函数
-
close(expr)
含义:关闭管道的文件
$ awk 'BEGIN {
cmd = "tr [a-z] [A-Z]"
print "hello, world !!!" |& cmd
close(cmd, "to")
cmd |& getline out
print out;
close(cmd);
}'
# 输出结果为:
HELLO, WORLD !!!
- 第一条语句 cmd = "tr [a-z] [A-Z]" 在 AWK 中建立了一个双向的通信通道。
- 第二条语句 print 为 tr 命令提供输入。&| 表示双向通信。
- 第三条语句 close(cmd, "to") 完成执行后关闭 to 进程。
- 第四条语句 cmd |& getline out 使用 getline 函数将输出存储到 out 变量中。
- 接下来的输出语句打印输出的内容,最后 close 函数关闭 cmd。
-
delete
含义:用于从数组中删除元素
$ awk 'BEGIN {
arr[0] = "One"
arr[1] = "Two"
arr[2] = "Three"
arr[3] = "Four"
print "Array elements before delete operation:"
for (i in arr) {
print arr[i]
}
delete arr[0]
delete arr[1]
print "Array elements after delete operation:"
for (i in arr) {
print arr[i]
}
}'
# 输出结果为:
Array elements before delete operation:
One
Two
Three
Four
Array elements after delete operation:
Three
Four
-
exit
含义:终止脚本执行,它可以接受可选的参数 expr 传递 AWK 返回状态。
$ awk 'BEGIN {
print "Hello, World !!!"
exit 10
print "AWK never executes this statement."
}'
# 输出结果为:
Hello, World !!!
-
flush
含义:刷新打开文件或管道的缓冲区
-
getline
含义:读入下一行
使用 getline 从文件 marks.txt 中读入一行并输出:
$ awk '{getline; print $0}' marks.txt
AWK 从文件 marks.txt 中读入一行存储到变量 0 中。在下一条语句中,我们使用 getline 读入下一行。因此AWK读入第二行并存储到 0 中。最后,AWK 使用 print 输出第二行的内容。这个过程一直到文件结束。
-
next
含义:停止处理当前记录,并且进入到下一条记录的处理过程。
当模式串匹配成功后程序并不执行任何操作:
$ awk '{if ($0 ~/Shyam/) next; print $0}' marks.txt
-
nextfile
含义:停止处理当前文件,从下一个文件第一个记录开始处理。
$ cat file1.txt
file1:str1
file1:str2
file1:str3
file1:str4
$ cat file2.txt
file2:str1
file2:str2
file2:str3
file2:str4
$ awk '{ if ($0 ~ /file1:str2/) nextfile; print $0 }' file1.txt file2.txt
# 输出结果为:
file1:str1
file2:str1
file2:str2
file2:str3
file2:str4
-
return
含义:从用户自定义的函数中返回值。请注意,如果没有指定返回值,那么的返回值是未定义的。
$ cat functions.awk
function addition(num1, num2)
{
result = num1 + num2
return result
}
BEGIN {
res = addition(10, 20)
print "10 + 20 = " res
}
# 执行该文件:
$ awk -f functions.awk
10 + 20 = 30
-
system
含义:执行特定的命令然后返回其退出状态。返回值为 0 表示命令执行成功;非 0 表示命令执行失败。
$ awk 'BEGIN { ret = system("date"); print "Return value = " ret }'
# 输出结果为:
Sun Dec 21 23:16:07 IST 2014
Return value = 0
用户自定义函数
一个程序包含有多个功能,每个功能我们可以独立一个函数。函数可以提高代码的复用性。
用户自定义函数的语法格式为:
语法:
function function_name(argument1, argument2, ...)
{
function body
}说明:
- function_name 是用户自定义函数的名称。函数名称应该以字母开头,其后可以是数字、字母或下划线的自由组合。AWK 保留的关键字不能作为用户自定义函数的名称。
- 自定义函数可以接受多个输入参数,这些参数之间通过逗号分隔。参数并不是必须的。我们也可以定义没有任何输入参数的函数。
- function body 是函数体部分,它包含 AWK 程序代码。
以下实例我们实现了两个简单函数,它们分别返回两个数值中的最小值和最大值。我们在主函数 main 中调用了这两个函数。 文件 functions.awk 代码如下:
# 返回最小值
function find_min(num1, num2)
{
if (num1 < num2)
return num1
return num2
}
# 返回最大值
function find_max(num1, num2)
{
if (num1 > num2)
return num1
return num2
}
# 主函数
function main(num1, num2)
{
# 查找最小值
result = find_min(10, 20)
print "Minimum =", result
# 查找最大值
result = find_max(10, 20)
print "Maximum =", result
}
# 脚本从这里开始执行
BEGIN {
main(10, 20)
}
执行 functions.awk 文件,可以得到如下的结果:
$ awk -f functions.awk
Minimum = 10
Maximum = 20
使用实例
-
实例1
只查看test.txt文件(100行)内第20到第30行的内容:
→ test awk '{if(NR>=20 && NR<=30) print $0}' test.txt
20
21
22
23
24
25
26
27
28
29
30
解释: NR代表已读的记录数(行数),NR>=20&&NR<=30 代表文件中的第20行到第30行。
-
实例2
已知test.txt文件内容为:
➜ test cat test.txt
I am Poe,my qq is 33794712
请从该文件中过滤出"Poe"字符串与33794712,最后输出的结果为:Poe 33794712
方法一:
➜ test awk '{split($3,arr,",");print arr[1]" "$6}' test.txt
Poe 33794712
解释:Poe,my 是$3,用split()内置函数把$3分割成arr,其中arr[1]=Poe,33794712是$6,print 后面拼接字符串进行输出。
方法二:
➜ test awk -F '[ ,]+' '{print $3" "$7}' test.txt
Poe 33794712
解释:用-F来指定分隔符为1个或多个空格或逗号(,) ,然后Poe和33794712分别是$3和$7,print 后面拼接字符串进行输出。