awk命令

简介

除了使用 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关键字标识)。
  • 主循环执行输入文件的语句。
  1. 从文件或stdin中读取第一行,看有无模式匹配,若无则执行{}中的语句
  2. 若有,则检查该整行与pattern是否匹配,若匹配,则执行{}中的语句
  3. 若不匹配,则不执行{}中的语句,接着读取下一行
  4. 重复这个过程,直到所有行被读取完毕
  • 执行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使程序读入下一个输入行,并返回到脚本的顶部,这可以避免对当前输入行执行其他的操作
exitexit关键字使主输入循环退出并移动到END,如果END存在的话。如果没有定义END规则,或在END中应用exit语句,则终止脚本的执行

以上为awk命令的流程控制语句,从语法上面大家可以看到,与大多数语言是一样的。有了这些语句,其实很多shell程序都可以交给awk,而且性能是非常高的。

内置变量

ARGC命令行参数个数NF当前行的数据字段个数
AGRV命令行参数排列NR已读的记录数,即行号,从1开始(一行就是一个记录,一个记录有若干个字段/域)
ARGIND命令行中当前文件的位置(从0开始算)FIELDWIDTHS字段宽度列表(用空格键分隔)
ENVIRON支持队列中系统环境变量的使用OFS输出数据字段分隔符(默认为空格)
FILENAMEawk当前浏览的文件名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 标准日期格式。
%gISO8610 标准周所在的年份模除 100(00-99)。比如,1993 年 1 月 1 日属于 1992 年的第 53 周。所以,虽然它是 1993 年第 1 天,但是其 ISO8601 标准周所在年份却是 1992。同样,尽管 1973 年 12 月 31 日属于 1973 年但是它却属于 1994 年的第一周。所以 1973 年 12 月 31 日的 ISO8610 标准周所在的年是 1974 而不是 1973。
%GISO 标准周所在年份的全称。
%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 !!!
  1. 第一条语句 cmd = "tr [a-z] [A-Z]" 在 AWK 中建立了一个双向的通信通道。
  2. 第二条语句 print 为 tr 命令提供输入。&| 表示双向通信。
  3. 第三条语句 close(cmd, "to") 完成执行后关闭 to 进程。
  4. 第四条语句 cmd |& getline out 使用 getline 函数将输出存储到 out 变量中。
  5. 接下来的输出语句打印输出的内容,最后 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 后面拼接字符串进行输出。

参考

https://www.cnblogs.com/baichunyu/p/15257904.html ​​​​​​​

Linux awk 命令 | 菜鸟教程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值