# #
# 转载声明 #
# #
##############################
原摘处: http://blog.chinaunix.net/u3/109820/article.html
英文远处: http://www.catonmat.net/blog/awk-one-liners-explained-part-one/
########################################
# #
# 第一个部分:行距,编号和运算 #
# #
#######################################
###############
#
# 行间隔
#
###############
####################
#
# 1 输出两倍行距文件
#
####################
# more file |
这个是如何运行的呢?每一个awk程序都是由一系列的 “模式—动作”语句组成,即“模式{动作}”,在这个例子中有两个这样的语句,即"1" 和 "{ print "" }",每个语句中无论是模式还是动作都可能不存在。如果模式部分不存在,默认匹配所有行,那么就对每一行都执行动作。如果动作不存在,默认执行动作{print}。
因此这个例子也可以写成这样:
# awk '1 { print } { print "" }' file |
只有当模式正确匹配,动作才会执行,由于“1”始终是正确的,所以这个例子可以写成两条打印的语句:
# awk '{ print } { print "" }' file |
在awk中每一条打印命令中后面都跟了一个输出记录分隔符(ORS),默认是换行。第一个打印命令后面没有加参数,等同于{print $0},$0是相应的每一条记录,是可的,通过设置输出记录分隔符(ORS)可以得到不同类型的记录。第二个打印命令后面接的是“”(空),我们知道每个打印命令后面都跟了一个输出记录分隔符,实际上打印了一个新行,因此打印出来以后每行之间有双倍的行距。
PS:例子中的分号的作用是将两条语句区分开来,如果不用分号awk会认为是一个语句。其他两种写法不加分号是因为检测到有动作{print}存在,后面的内容会自动认为是另一个语句。所以在awk中,当前一个语句只有模式而没有动作时,后面要在加语句的话,必须要用分号区分开来,有动作时分号可有可无。
##############################
#
# 2 另一种方法输出两倍行距文件
#
##############################
# awk 'BEGIN { ORS="\n\n" }; 1' file |
BEGIN是一种特殊的语句,这种语句不检测输入文件。也就是在读输入文件之前运行。通过设置输出记录分隔符(ORS)为两次换行的方法实现输出两倍行距文件。根据之前提到过的,语句“1”等同于{print},打印出来的每条记录之间的分隔符都为前面设置的ORS。
PS:一个完整的awk语句应该是这种形式:
awk ‘BEGIN{动作};模式{动作};模式{动作}。。。;END{动作}’ file
其中BEGIN{动作}和END{动作}分别是在读输入文件之前和读输入文件之后执行,通常用来制表和统计数据,很多时候都不必用到。只有中间的语句模式{动作}才会读输入文件并对其执行动作。
####################################################
#
# 3 输出两倍行距文件,并且任意两行之间只有一个空行存在
#
####################################################
首先修改一下file,在里面加一个空行
# more file |
这个命令使用了另一个AWK变量NF(域的个数),意思是当前行被分割的数量,比如234532 23456 555555
这一行被分割成3个部分,因此得NF的值就是3,空行的无法被分割,因此NF的大小就是0.在模式中使用NF可以有效的过滤空行。这个命令的意思是:只要行中存在域,就在此行的后面打印一个空行。
PS:当模式为数值(-1,0,1,1.1)时,只要数值不为0,即为匹配所有行。
#############
#
# 4 三倍行距
#
#############
# awk '1; { print "\n" }' file |
这个命令跟之前的很是相似,语句“1”等同于{print},因此也可以写成
# awk '{ print; print "\n" }' file
先打印一行,然后是打印输出记录分隔符(ORS),默认是换行。
PS:有些初学者在这里可能会有点疑惑(我刚开始也想了很久O(∩_∩)O),不过仔细想想就容易理解了。之前我们提到过,每个打印命令后面都跟了一个输出记录分隔符(默认是换行),因此在这个命令中,先执行第一个语句:首先打印文件的第一行,然后跟一个ORS,也就是换行了,接着执行第二个语句{print “\n”},\n就是换行,这时候后面又跟一个ORS,还是换行,因此出现了在每行之间出现了2个空行。
######################
# #
# 编号和运算 #
# #
######################
##########################
#
# 5 给每个文件的行单独编号
#
##########################
# awk '{ print FNR "\t" $0 }' file |
这个awk程序在每行之前附加了一个文件行号(FNR)file line number和一个tab(\t),FNR包含了每一个文件当前行的行号。比如说,awk针对两个文件做操作:
# awk '{ print FNR "\t" $0 }' file file |
可以看到,结果是分别给两个文件的每一行之前加上该行在文件中的行号。FNR给文件的行编号时,如果有多个文件会重新开始编号。
##########################
#
# 6 给所有文件的行一起编号
#
##########################
# awk '{ print NR "\t" $0 }' file file |
这个命令和第五个例子几乎一样,唯一不同的地方是使用了参数(行号)NR-Line Number。NR与FNR不同的地方就在于NR在给多个文件的行编号的时候不会根据文件重新编号,而是按照读取顺序统一编号。
#############
#
# 7 花式编号
#
#############
# awk '{ printf("%5d : %s\n", NR, $0) }' file |
这个命令用了通常格式printf()函数来给给行编号,像普通的printf()函数一样格式化参数。这里需要特别注意的是在printf()函数后面不会附加一个输出记录分隔符(ORS)。因此我们需要在每一行的后面明确的打印出一个换行符(\n)。这个命令的结果是在每行之前打印行号和一个冒号。
##################
#
# 8 只给非空行编号
#
##################
# more file |
Awk参数都是动态的,在第一次使用的时候建立。这个命令指定a这个变量随着行数的增加和不断自增长,空行除外(NF=0)。然后将这个参数的值和冒号附加在每一行的开头并打印出来。
PS:在这个例子中,第一个语句是NF{$0=++a”:”$0},模式是NF,动作是一个赋值语句$0=++a”:”$0,即当NF不为0的时候,在每一行的开头都加上一个变量a和一个冒号,然后通过第二个语句打印出来。记住,在这里“=”不是等于的意思,而是给$0重新赋值,“==”才是等于的意思。
#####################################
#
# 9 计算文件行数(与wc –l的作用类似)
#
#####################################
# awk 'END { print NR }' file |
前面提到过,END{}是一种不测试文件的特殊语句,它是在所有行遍历完以后执行。在所有行遍历完以后,NR就等于最后一行的行号,再出变量NR的值,就是输出文件的行数了。
#########################
#
# 10 打印每行中域值的总和
#
#########################
# awk '{ s = 0; for (i = 1; i <= NF; i++) s = s+$i; print s }' file |
awk有很多地方借鉴了c的风格,比如这个for(;;){…}循环。这个命令使用for循环遍历了每一行中的每个域(NF即为每行中域的个数)。然后将每个域的值累加给变量s,最后打印出s的值,即为所有域相加后的值。
PS:如果把s=s+$i写成s=s+i,则结果大相径庭
# awk '{ s = 0; for (i = 1; i <= NF; i++) s = s+i; print s }' file |
因为前面的s=$1+$2+$3,而后面的s=1+2+3
###########################
#
# 11 打印所有行中域值的总和
#
###########################
# awk '{ for (i = 1; i <= NF; i++) s = s+$i }; END { print s+0 }' file |
这个命令基本上和#10的一致,不同的是打印出来的结果是所有域值的累加。注意到开始没有初始化变量s的值为0.这是因为要将所有行的每一个域相加的话,就不能在遍历每一行的时候将s初始化为0,否则最后得到的s就是最后一行的所有域相加的值,而不是所有行。还需要注意的是最后是{print s+0}而不是{print s}。当文件file中没有域(都是空行)的时候这是很有必要的。因为如果没有域的话,那么变量s就无法建立也没有被定义,输出一个没有定义的变量就等于什么都不输出(ORS还是要跟的)。加上一个0的话就可以从数值上体现s的大小即为0.
###############################
#
# 12 将所有域都替换成它的绝对值
#
###############################
# awk '{ for (i = 1; i <= NF; i++) if ($i < 0) $i = -$i; print }' file |
这个命令也借鉴了c的两个特征,if(..){…}语句和省略了大括号。这个命令遍历了所有行中的每一个域,检查是否有小于0的域,如果有,将域值取反,变成整数。域值可以使用参数间接的赋值,比如i=5;$i=”hello”,就是将第五个域赋值为”hello”
下面是该命令的另一种比较完整的写法,在每行中所有的域都检查过并且重新赋值以后,再执行打印的命令。
awk '{ |
###############################
#
# 13 计算一个文件中所有域的数量
#
###############################
# awk '{ total = total + NF }; END { print total+0 }' file |
这个awk程序遍历文件file所有的行,并将每一行的域的数量累加起来,并将累加的数量赋给变量total,一旦文件遍历完,开始执行END{},也就是打印出变量total的值,通过第十一个例子我们可以知道为什么打印的是total+0
#################################
#
# 14 打印包含“55”内容的行的个数
#
#################################
# awk '/55/ { n++ }; END { print n+0 }' file |
这个命令包含两个模式{动作}语句。第一个是/55/{n++}。模式中两个斜杠之间的是一个正则表达式。表示匹配所有包含数字“55”的行(不一定精确匹配55这个数字,也匹配像555,553,255,55d这种)。当匹配了一行时,变量就自动+1,第二个语句是是END{print n+0},表示当文件遍历完后,打印出变量n的值。注意到是print n+0,因为当没有行匹配55的时候,n就没有被创建和被定义,n的值也就打印不出来,加上0可以避免无输出。
###########################
#
# 15 找出第一个域中最大的数
#
###########################
# awk '$1 > max { max=$1; maxline=$0 }; END { print max"\n"maxline }' file |
这个命令将通过比较将第一个域中的最大数保存在变量max中,并且把相对应的行赋给变量maxline,当所有行遍历完以后,把它们都打印出来。需要注意的是,当第一个域中所有的域值都是负数时,这个程序无法工作。
PS:为什么第一个域都是负数的时候,打印不出来任何东西呢,是因为当第一个域都是负数的时候,没有行能够匹配模式$1>max,因此后面的动作也就不能执行,变量max和maxline没有被定义,因此打印不出任何东西了。
利用下面的命令可以弥补这一缺陷:
# awk 'NR == 1 { max = $1; maxline = $0; next; } $1 > max { max=$1; maxline=$0 }; END { print max”\n” maxline }' file |
这个命令在前一个命令的基础上加了一个命令NR==1{max=$1;maxline=$0;next},我们来看一下,模式部分是行号NR==1,也就是说动作只对第一行做操作,把第一个域的值赋给变量max,第一行赋予变量maxline。注意后面的next函数。next意思就是:匹配NR==1的行执行完动作{max=$1;maxline=$0}后,后面的语句通通不执行(END{}除外),也就是说后面的语句从NR=2开始执行。通过后面语句来和第一行的$1做比较,最后选出最大的$1和相应的行并打印出来。
PS:新加的语句主要是用来初始化变量max和maxline,即不管$1的大小,直接把$1赋给max,然后再比较。因此不会出现max没有创建和被定义的现象。
#############################
#
# 16 在每行之前打印出域的个数
#
############################
# awk '{ print NF ":" $0 } ' file |
这个命令还是很简单了,先打印出预先确定的NF-number of fields(域的数量),后面加一个冒号和行记录。
##########################
#
# 17 打印每行的最后一个域
#
##########################
# awk '{ print $NF }' file |
每一行域的数量NF不会总是一样,$NF就是每行的最后一个域。
##############################
#
# 18 打印最后一行的最后一个域
#
##############################
这个命令将记录中的最后一个域赋给变量field,所有行遍历完以后,变量field的值就是最后一行记录的最后一个域了,然后打印出变量field
有一个更好,更常用的写法:
# awk 'END { print $NF}' file |
#######################
#
# 19 打印超过4个域的行
#
#######################
# awk 'NF > 4' file |
这个命令省略了动作只有模式,缺省的动作就是{print $0},因此如果匹配到有超过4个域的行,打印出来,如果没有,则无输出。
##############################
#
# 20 打印最后一个域值大于4的行
#
##############################
# awk '$NF > 4' file |
和前一个例子不同的地方在于,这个模式匹配的是最后一个域值大于4的行。并打印相关的行。
########################################
# #
# 第二个部分:文本编辑替换 #
# #
########################################
##############################################################
#
# 21 将windows/dos下的回车换行转换为unix下的换行。(unix下运行)
#
##############################################################
# awk '{ sub(/\r$/,""); print }' file |
这个命令使用了sub{regex,repl,[string]}函数,这个函数在字符串[string]中查找第一次匹配正则表达式regex的字符(串),找到后用字符(串)repl将这个匹配的字符(串)替换。如果函数中没有[string],那么默认将使用$0(也就是整行)作为要查找的对象。
这个命令将每行结尾的回车符/r替换成“”,也就是将每行结尾的回车符删除。打印命令将每行打印出来并在后面附加了一个ORS(默认就是换行\n),这样结尾的回车换行就变成了直接换行。
############################################################
#
# 22 将unix下的换行转换为windos/dos下的回车换行。(unix下运行)
#
############################################################
# awk '{ sub(/$/,"\r"); print }' file |
这个命令也同样使用了sub()函数,这次将每行中的零字符的$(代表每行的末尾)替换成回车\r,实际上就是在每行的结尾附加了一个回车符。打印记录的后面也要加上ORS(默认\n).
####################################################################
#
# 23 将unix下的换行转换为windos/dos下的回车换行。(windows/dos下运行)
#
####################################################################
# awk 1 file |
这个命令可能执行,也可能不执行,取决于执行过程,如果在执行中从读取的文件中发现了unix的换行,程序会一行行的读取文件然后把换行LF替换成windows下的CRLF(回车换行),如果没有检测到unix的换行符,程序会打印出整个文件然后再以CRLF结束。
####################################################################
#
# 24 将windows/dos下的回车换行转换为unix下的换行。(windows/dos下运行)
#
####################################################################
# gawk -v BINMODE="w" '1' file |
理论上来说,这个命令应该会在dos上将CRLF转换为LF,在GNU awk的说明中这么一段话:在dos命令行下,gawk会默认将输入文件的每行结尾的“\r\n”转换成“\n”或者输出的行结尾“\n”转换为“\r\n”。特殊参数BINMODE
允许用以下的值控制这些转换,如果BINMODE的值为W,二进制模式为写(写的时候不转换)。
建立在windows下用下面的命令实现该功能:
# tr -d \r |
tr程序用来转换字符,-d选项是用来删除不需要的字符,而“\r”正是每行结尾的回车符,因此把输入文件的每行结尾“\r\n”变成了“\n”
###########################################
#
# 25 删除每行开头的空白(空格或者制表符tabs)
#
###########################################
# more file |
这个例子同样使用了sub()函数,将开头的空白部分替换成“”,也就是空。正则表达式^[ \t]+ 的意思是行的开头匹配一个或多个空格或者tabs,+就是指的一个或多个字符的意思。
###########################################
#
# 26 删除每行结尾的空白(空格或者制表符tabs)
#
###########################################
# awk '{ sub(/[ \t]+$/, ""); print }' file |
跟上面的例子如出一辙,不过删除的是末尾的空格或tabs,^和$都是一个空字符,表示位置,一个是行的开头,一个是行的结尾.
#############################
#
# 27 同时删除开头和结尾的空白
#
#############################
# awk '{ gsub(/^[ \t]+|[ \t]+$/, ""); print }' file |
这回用的是gsub()函数,与sub()一样,也是替换的作用,不一样的地方在于,sub()替换的是一个匹配的字符,而gsub()是全局替换,也就是替换所有,比如说有一个变量f=foo,sub("o", "x", f)替换后的结果是fxo,而gsub("o", "x", f)替换后的结果是fxx。
这个命令的正则表达式部分同时匹配每行的开头和结尾的空白,|的意思是或者,因此无论是开头还是结尾的空白都将被删除。
要删除每个域之间的空白可以用下面的命令实现:
# more file |
这个命令有点复杂,乍一看$1=$1好像什么都没做是吗,其实不然,在awk里面,当你改变一个域的时候(比如给它赋值),awk会重置$0,把所有的域拼接起来,然后print,而默认使用输出域分隔符OFS就是空格,这样所有域之间的空白都将删除。
############################
#
# 28 在每行的开头增加5个空白
#
############################
# awk '{ sub(/^/, " "); print }' file |
前面提到过,^只是代表每行的开头的一个空字符,即在每行的开头加上5个空格。
################
#
# 29 右对齐文本
#
################
# awk '{ printf "%79s\n", $0 }' file |
这个命令在$0前添加空格直到整行的长度达到79个字节。更多关于printf()的使用方法请参考
################
#
# 30 使文本居中
#
################
# awk '{ l=length(); s=int((79-l)/2); printf "%"(s+l)"s\n", $0 }' file |
这个程序开始计算整行的长度,并把值赋给变量l,length(var)函数返回字符串var的长度,如果不指定字符串var,返回的是整行$0的长度。然后计算在每行之前有多少个空格,也把这个值赋给变量s,最后打印出文本,使得文本刚好居中在79个字符中间,前面用空白填充。
##############
#
# 31 替换文本
#
##############
# awk '{ sub(/55/,"xx"); print }' file |
这个替换命令把每行中的55替换成xx,记住,这个命令只是替换第一次匹配的字符。如果要把所有的55都替换成xx,则用下面的命令:
# awk '{ gsub(/55/,"xx"); print }' file |
可以看到所有的55都换成了xx,还可以使用另一种替换的函数:
# awk '{$0= gensub(/55/,"xx",2); print }' file |
这个程序只是替换第二次匹配55为xx,使用的是之前没有见过的gensub()函数,这个函数的基本形式是
gensub(regex,s,h[,t]),这个函数查找在字符串t中第h次匹配正则表达式regex的字符,并用字符串s来替换。如果t没有指定,默认使用$0。
gensub()是一个非标准的函数,GNU版本的awk或者netbsd系统中的awk有此函数。
PS:这里需要注意的是,大家可以看到用gensub()函数的时候是给$0赋值,而前面的sub()和gsub()并没有这样做,让我们看看不给$0赋值是什么情况:
# awk '{gensub(/55/,"xx",2); print }' file |
可以看到print出来的东西并不没有变化,这就是gensub()和sub(),gsub()不同的地方之一,也就是gensub虽然对$0做了替换,但是只保存在函数中,并不对$0做实际修改。因此如果想要把替换的内容输出,可以把gensub()函数赋值给$0。或者直接用{print gensub()}打印出结果。
################################
#
# 32 在包含特定字符的行中替换字符
#
################################
# awk '/34/ { gsub(/5/, "x") }; { print }' file |
在第一部分提到过,所有的awk程序都是由一个个的 模式{动作}语句组成,只有当模式匹配的时候才执行动作。第一个语句的模式部分是/34/,即匹配包含34的行,如果有行包含34,执行动作全局替换,将所有的数字5替换成字符x,第二个语句把所有行打印出来。如果你只想替换一次,用sub()或者gensub()函数即可。
###################################
#
# 33 在不包含特定字符的行中替换字符
#
###################################
# awk '!/34/ { gsub(/5/, "x") }; { print }' file |
这个例子只是模式部分与前一个不一样,!就是非,也就是匹配不包含34的行对其做替换操作并打印出所有的行。
##############################
#
# 34 将不同字符替换为同一个字符
#
##############################
# awk '{ gsub(/34|55|12/, "xx"); print}' file |
以上这3个例子讲的都是正则表达式,这个程序用管道符|实现匹配多个字符并替换,最后打印整行。
################################
#
# 35 倒序输出记录(类似tac的作用)
#
################################
# awk '{ a[i++] = $0 } END { for (j=i-1; j>=0;) print a[j--] }' file |
这个程序时目前来说最复杂的一个了,首先把每行的记录都赋值给数组a,比如有两行记录“aa”和“aaa”,则分别是a[0]=aa,a[1]=aaa。当文件的所有行都赋值给了数组a以后,awk执行END{}版块,查找数组a中的元素并打印出来。
在这里有4行记录,因此END
# for (j = 4-1; j >= 0; ) print a[j--]
或者
# for (j = 4-1; j >= 0;j-- ) print a[j]
首先打印a[3],然后是a[2],a[1],a[0]。我们可以看到正好把所有行倒过来排列了。
#########################################
#
# 36 把行结尾是反斜杠\的记录与下一行连起来
#
#########################################
# more file |
这个awk程序先利用正则表达式“/\\$/”找出匹配每行结尾是\的记录,如果匹配,首先用sub{}函数将行尾的\删除,然后getline函数开始执行,这个函数的作用是读取下一行的记录$0并把$0赋值给变量t,Print $0 t
也就是打印当前的行(行尾的\已删除)和下一行(变量t即是下一行记录),然后继续往下读取(已经赋值给t的那行不读取),第二个语句1等同与{print},打印出结果。
很遗憾这个程序连接的记录不能超过两行(如果连续的第二行还是以\结尾,程序并不会把下一行连接到这一行的结尾,这是因为getline以后,该行就不再读取和操作了。)
#############################
#
# 37 打印出所有的用户名并排序
#
#############################
# awk -F ":" '{ print $1 | "sort" }' /etc/passwd |
这是我们在程序中第一次看到-F参数,这个参数的作用是自定义一个字符、字符串、或者正则表达式把记录$0分割成一个个的域$1、$2、$3.例如,如果有一行记录为“foo-bar-baz” 同时 -F 设置为 “-”,这行就被分割为3个域$1=foo,$2=bar,$3=baz.如果不设置-F参数的话,默认的FS(记录分隔符)是空格,因此这一行只有一个域,那就是$1=foo-bar-baz。
定义-F参数和在BEGIN{}模块中定义FS参数是一样的:
# awk -F ":" |
等同于
awk 'BEGIN { FS=":" }' |
因为/etc/passwd包含了一系列系统用户,除此之外还有用户名,用户id,组id等等,他们之间使用:分割开来,因此需要定义域分割符FS为冒号。
sort命令的作用是排序,打印出用户名以后,使用sort将这些用户名按照英文字母a,b,c。。。的先后顺序排列。
####################################
#
# 38 打印第一第二个域并把它们反向排列
#
####################################
# awk '{ print $2, $1 }' file |
这个程序比较简单,就是打印出每行的第二和第一个域。
#############################
#
# 39 把第一个域和第二个域互换
#
#############################
# awk '{ temp = $1; $1 = $2; $2 = temp; print }' file |
这个程序使用了一个临时的变量temp,首先把$1的值赋给temp,然后把$2的值赋给$1,最后把temp的值(也就是之前$1的值)赋给$2,正好把$1和$2的值相互交换了,比如说输入是“foo bar baz”,那么输出即将是“bar foo baz”。
################
#
# 40 删除某个域
#
################
# awk '{ $2 = ""; print }' file |
首先把空值“”赋给$2,在打印记录,相当于删除了$2。
##################################
#
# 41 在每行中以倒序的方式排列每个域
#
##################################
# awk '{ for (i=NF; i>0; i--) printf("%s ", $i); printf ("\n") }' |
在第一部分我们知道NF变量是指每行中域的个数,在读取了记录以后,awk把每行域的数量指定给变量NF,这个程序利用for语句将域反向打印出来,即$NF,$(NF-1),..$1,然后再打印出一个换行符“\n”。
PS:有些朋友可能会有疑问,为什么打印函数不用print而是printf{},我们先来看用print的结果:
# awk '{ for (i=NF; i>0; i--) print $i}' file |
可以看到,打印出来的结果是每一个域后面都自动换行。前面提到过,因为print{}函数在打印一个域$i后,会在后面自动附加一个记录分隔符ORS,也就是换行,这样就会导致没有安装记录输出。而printf{}函数则不会再后面附加ORS,第一个命令是当一行中打印完所有的$i后,再附加一个ORS“\n”,这样就不会把原来的记录打乱了。
###############################################
#
# 42 删除连续出现的多余相同行。(类似uniq的作用)
#
###############################################
#more file |
我们前面提到过。Awk中变量在被使用之前不需要初始化和公布。它们在第一使用的时候创建。这个程序使用变量a保存上一行记录$0,然后再读取下一行,将上一行的记录与下一行作比较.a!~$0指的是,如果当前行不匹配上一行的记录,表达式的值就是真,默认的动作就是{print},反之当匹配的时候,就不打印相关的行,第二个语句又重新把当前行赋给变量a,依次执行知道记录读取完。
这个程序其实是有问题的,问题就出现在模式中的~符号上,~是匹配的意思,也就是说,如果当前行是“aa”,
而上一行是“aaa”,同样也匹配,这样就不会打印出“aa”,实际是应该要被打印出来的:
# more file |
可以看到,记录“aa”,被程序认为和“aaa”一样而不被打印出来。
要解决这个问题也不是没有办法,请看:
# more file |
这里比较的是整个记录是否相同,因此不会出现正则表达式错误匹配的问题。
#####################################
#
# 43 删除多余相同的行(不连续的也删除)
#
#####################################
# more file |
这个程序非常常用。注意到这里使用了关联数组a,同时测试当前行在之前是否出现过,如果之前出现过相同的行,那么a[$0]>0,!a[$0]等于0,即模式不匹配,默认的{print}也就不执行,反之如果之前没有出现过该行,则打印出来。
我们来看一下执行过程。读取了第一行后,awk开始查看模式!a[foo]++,因为foo第一次出现,因此a[foo]=0为假,但是!a[foo]为真,因此awk打印出“foo”,然后通过++,a[foo]增加了1,这个数组a就有了第一个元素a[foo]=1.
同样,读取第二行的时候a[bar]也为假,同样打印出“bar”,同时a[bar]增长为a[bar]=1。
第三行,foo第二次出现,因为这时候a[foo]=1为真,所以模式!a[foo]为假,不匹配模式,不执行动作。a[foo]又增加了1变成了a[foo]=2,这样数组a中就有了两个元素a[foo]=2,a[bar]=1。同理,最后一行也打印出来。
这里有另一种方法可以得到相同的效果,据说这是效率最高的方法:
# awk '!($0 in a) { a[$0]; print }' file
这个跟之前的命令差不了太多,不同的是使用了in操作符,这个程序,同样判断当前记录是否已经存在。存在则不打印,反之打印。
############################
#
# 44 把每5行记录用逗号连起来
#
############################
# more file |
ORS我们知道是输出的记录分隔符,输出记录分割符附加在每行记录的结尾,在这个程序中,平时使用的换行符是逗号,当行数是5的倍数的时候,把ORS换成换行符“\n”,依次类推。请注意,这里的输出虽然只有两行,但是实际的记录数还是7个。
PS:awk中的ORS=NR%5?",":"\n"是从c语言中借鉴过来的,实际上是一个赋值语句,是这样运行的,前面提到过NR是行号。当NR%5为真,也就是NR%5不等于0(NR的值不是5的倍数)时,ORS取值为“,”也就是冒号前的字符,反之当NR的值是5的倍数的时候,ORS取值为“\n”,也就是换行了。
########################################
# #
# 第三篇:有选择的打印行 #
# #
########################################
####################################
#
# 45 打印文件的前5行(相当于head -5)
#
####################################
# more file |
前面也提到过,NR的意思是只当前文件的行号,每读一行,awk会自动给NR增加1。这个命令只有一个模式NR <6,而没有{动作},默认为{print $0}。模式中指的是匹配行号NR<6(即第一到第五行),即把文件file的第一到第五行打印出来。从第六行开始,awk检测到NR>=6,因此不执行动作{print}。
这个程序的效率不高,因为当NR>=6时,awk会继续遍历文件直到结束,但其实后面做的都是无用功,因为不执行任何动作。以下是改进的程序:
# awk '1; NR == 5 { exit }' file |
语句NR==5{exit}能够确保文件遍历到第五行的时候立刻停止程序。当文件的NR小于5的时候,语句“1”相当于{print},因此当NR<=5的时候,awk打印出所有匹配的行,然后程序停止。当文件的行数非常多的时候,提高执行的效率。
###############################
#
# 46 打印文件的第一行(head -1)
#
###############################
# awk 'NR > 1 { exit }; 1' file |
和前面的例子一样,只有当NR==1的时候模式才匹配,读第一行的时候,模式不匹配,直接到第二个语句“1”,也就是{print},打印第一行,读到第二行的时候NR=2,匹配模式,执行{exit},退出了awk,完毕。
################################
#
# 47 打印文件的倒数2行(tail -2)
#
################################
# awk '{ y=x "\n" $0; x=$0 }; END { print y }' file |
这个awk程序时怎么样工作的呢,首先看第一个语句{ y=x "\n" $0; x=$0 },这个语句只有动作没有模式,因此对所有行都执行动作。读取第一行的时候,变量y被赋值为”\nline1”(因为x没有被定义),然后定义变量x为”line1”. 读取第二行的时候,变量y被赋值为”line1\nline2”,变量x=line2。依次类推,读取第三行的时候y=”line2\nline3”,x=”line3”,一直到读取最后一行的时候,y=”lineN-1\nlineN”,x=”lineN”。这样变量y就包含了最后两行的内容,通过END{print y}将其打印出来。
仔细想想这个程序,大家能够看得出来这个程序的效率也是非常低的。Awk程序读取了整个文件的所有行,但是最后的动作无非是打印出来最后两行。不行的是在awk中没有像seek()这样的函数,因此无法在文件中找出最后两行(用tail可以找出),因此如果要打印出文件最的最后几行,还是把这个任务交给tail吧。
#################################
#
# 48 打印文件的最后一行(tail -1)
#
#################################
# awk 'END { print }' file |
这个程序有可能会生效也有可能不生效。它只有在文件遍历完以后,包含最后一行记录的$0没有被初始化的情况下才能生效。END{}模块是在所有记录都读取完以后执行,这里的{print}原意是想打印出文件结尾的$0,而这个变量是有可能被重置的。
而这个程序生效与否取决于你的awk的版本和执行过程,GNU的awk是可以执行成功的,但是在nawk 或者xpg4/bin/awk下貌似不能执行成功。
####################################################
#
# 49 打印匹配正则表达式/regex/的行(相当于grep的作用)
#
####################################################
# awk '/f/' file |
这个程序使用正则表达式/f/作为模式部分,匹配正则表达式则执行默认的动作{print},即打印匹配f的所有行。
###################################################
#
# 50 打印不匹配正则表达式/regex/的行(相当于grep -v)
#
###################################################
# awk '!/f/' file |
正则表达式可以在表达式前面加上!来取反,匹配f的行在!的作用下变成了假,而不执行{print}的动作,相反不匹配f的行在!的作用下为真,执行动作{print},打印记录。
###########################################
#
# 51 打印匹配正则表达式/regex/所在行的前一行
#
###########################################
# awk '/f/ { print x }; { x=$0 }' file |
这个程序总是把当前的行赋值给变量x,直到有行匹配f,这时候打印变量x,而这个x正好是上一行的$0.因此打印出上一行。
如果当正则表达式匹配第一行的时候,这个程序就不起作用,因为没有前一行,x没有被定义。这时候我们可能会想要打印出”match on line1”(匹配第一行),可以用下面程序实现
# awk '/a/ { print (x=="" ? "match on line 1" : x) }; { x=$0 }' file
match on line 1
这个语句测试变量x是否被定义,只有第一次使用x的时候x才为空,这样的话讲打印出”match on line1”。否则将打印出变量x的值,也就是读取上一行的时候被赋值给x的$0。
注意到awk中使用了一个三元运算符(x==””?”match on line1”:”x”)。它的意思是if(x==””),前面print的就是”match on line1”,不为空则print x
PS:三元运算符(a?b:c)是从c中借鉴过来的,意思是if a 为真,则b,else c。
###########################################
#
# 52 打印匹配正则表达式/regex/所在行的下一行
#
###########################################
# awk '/a/ { getline; print }' file |
这个awk程序在匹配/a/的所有行调用了getline()函数,getline获取的是当前行的下一行记录$0(同时更行变量NF,NR,FNR等),然后{print}打印出来的也是下一行的$0,这样就实现了打印匹配行的下一行。
如果刚好是最后一行匹配/a/,那么getline应该会返回一个错误代码,因为已经没有下一行了,但是实际上getline得到就是最后一行并且print出来
############################
#
# 53 打印匹配/a/,/f/,/k/的行
#
############################
# awk '/a|f|k/' file |
这个语句的特点在于使用了扩展的正则表达式,意思是任意行只要匹配a,或f,或k,都将执行默认的{print}动作,打印出相应的行。
#############################################
#
# 54 打印出包含a,b,c的行(顺序必须是a,b,c)
#
#############################################
# awk '/a.*b.*c/' file |
这个语句同样是在正则表达式上做手脚,模式是这样理解的,匹配的行中包含一个a,然后是任意字符(也可以是空),然后是b,然后是任意字符,然后是c,最后打印出这样的行。
#############################
#
# 55 打印长度大于64个字符的行
#
#############################
# awk 'length > 64' file |
模式部分使用的是length()函数,完整的模式应该是length(var),函数的作用是返回字符串var的长度,如果不指定var,默认将使用$0。因此这个语句在模式部分判断是否有超过64个字符的行,如果有,打印出来,没有则不执行任何动作。
#############################
#
# 56 打印长度小于64个字符的行
#
#############################
# awk 'length < 64' file |
这个没什么好讲的,大家肯定都能理解。
##################################################
#
# 57 打印出从匹配正则表达式的行到文件末尾的部分区域
#
##################################################
# awk '/s/,0' file |
这个awk程序使用的匹配模式是”模式1,模式2”这种形式,意思是从模式1匹配的行开始,一直到模式2匹配的行结束,对区间内所有的行执行{动作}。在这个例子中,模式1,是指包含s的行,模式2是0,也就是假,所以这例子也就是说从包含s的行开始,一直到文件结束,把这些行打印出来。
PS:由于0是假,不可能被匹配,因此模式匹配的行是从包含s的那行直到文件结尾。
#########################
#
# 58 打印从第三行到第六行
#
#########################
# awk 'NR==3,NR==6' file |
这个例子同样使用了多个模式匹配的形式,模式1和模式2分别为NR==3,NR==6,即第三行和第六行,因此匹配3到6行的记录为3,4,5,6行,打印出来。
################
#
# 59 打印第五行
#
################
# awk 'NR==5' file |
这个例子测试当前行是否是第五行,如果是第五行则打印出来。
其实正确的方法应该是匹配到第五行以后要立即停止awk程序,提高运行效率。
# awk 'NR==5{print;exit}' file |
这个程序在遍历到第5行的时候,把第五行打印出来,然后强制退出awk,省去了读取其他行的时间,提高了效率。
##########################################################
#
# 60 打印从匹配正则表达式1的行到匹配正则表达式2的行内的所有行
#
##########################################################
# awk '/a/,/k/' file |
前面讲过这个模式匹配的是从包含a的行到包含k的行,之间所有的行。然后打印出来。
PS:有个地方需要注意,文件中可能有多个区间都匹配这个模式,比如:
e |
这种情况下的输出为:
a |
如果文件为:
e |
输出为 :
a |
这是因为当模式第二次匹配到a的时候,下面没有行再匹配k,默认匹配到文件结尾。
#########################
#
# 61 删除文件中所有的空行
#
#########################
# more file |
这个例子使用了域的个数(NF)作为模式部分,因为空行中域的个数为0,因此不执行默认的{print}动作,还有一个命令可以实现这个功能:
# awk '/./' file |
这里使用的是正则表达式/./,它匹配的是任何包含字符的行,空行不包含字符,所以不被打印。