Awk one-liners explained 中文版
http://bbs.chinaunix.net/thread-1640657-1-1.html
首先声明,这篇文章并不是原创,这是我在学习 awk 的过程中,经 CU 的朋
友推荐,看到了 Peteris Krumin 关于 awk 的非常精彩的讲解,由于原文是英文版
的,英语水平稍差的朋友可能学习起来会有点困难,为了能够给正在学习 awk
的朋友们提供一点点帮助,也锻炼一下自己的英语水平,我将这些 awk 讲解翻
译了一下,加上了一点个人的看法,由于本人水平有限,错误肯定不少,欢迎高
手们批评指正,共同进步。谢谢。
http://www.catonmat.net/blog/awk-one-liners-explained-part-one/这是原文的链
接。。
废话不说,开始进入 awk 第一个部分:行距,编号和运算。
第一部分:行距,编号和运算
1、输出两倍行距文件
#more file
12 32423 -2354235
234532 23456 555555
-432346 45435 4462
#awk '1; { print "" }' file
12 32423 -2354235
234532 23456 555555
-432346 45435 4462
这个是如何运行的呢?每一个 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
12 32423 -2354235
234532 23456 555555
-432346 45435 4462
BEGIN 是一种特殊的语句,这种语句不检测输入文件。也就是在读输入文件之前
运行。通过设置输出记录分隔符(ORS)为两次换行的方法实现输出两倍行距文
件。根据之前提到过的,语句“1”等同于{print},打印出来的每条记录之间的分
隔符都为前面设置的 ORS。
PS:一个完整的 awk 语句应该是这种形式
awk ‘BEGIN{动作};模式{动作};模式{动作}。。。;END{动作}’ file
其中 BEGIN{动作}和 END{动作}分别是在读输入文件之前和读输入文件之后执行,
通常用来制表和统计数据,很多时候都不必用到。只有中间的语句模式{动作}才
会读输入文件并对其执行动作。
3、输出两倍行距文件,并且任意两行之间只有一个空行存在。
首先修改一下 file,在里面加一个空行
#more file
12 32423 -2354235
234532 23456 555555
-432346 45435 4462
#awk 'NF { print $0 "\n" }' file
12 32423 -2354235
234532 23456 555555
-432346 45435 4462
这个命令使用了另一个 AWK 变量 NF(域的个数),意思是当前行被分割的数量,
比如 234532 23456 555555
这一行被分割成 3 个部分,因此得 NF 的值就是 3,空行的无法被分割,因此 NF
的大小就是 0.在模式中使用 NF 可以有效的过滤空行。这个命令的意思是:只要
行中存在域,就在此行的后面打印一个空行。
PS:当模式为数值(-1,0,1,1.1)时,只要数值不为 0,即为匹配所有行。
4、 三倍行距
#awk '1; { print "\n" }' file
12 32423 -2354235
234532 23456 555555
-432346 45435 4462
这个命令跟之前的很是相似,语句“1”等同于{print},因此也可以写成
#awk '{ print; print "\n" }' file
先打印一行,然后是打印输出记录分隔符(ORS),默认是换行。
PS:有些初学者在这里可能会有点疑惑(我刚开始也想了很久 O(∩_∩)O),不
过仔细想想就容易理解了。之前我们提到过,每个打印命令后面都跟了一个输出
记录分隔符(默认是换行),因此在这个命令中,先执行第一个语句:首先打印
文件的第一行,然后跟一个 ORS,也就是换行了,接着执行第二个语句{print “\n”},
\n 就是换行,这时候后面又跟一个 ORS,还是换行,因此出现了在每行之间出现
了 2 个空行。
5、给每个文件的行单独编号
#awk '{ print FNR "\t" $0 }' file
1 12 32423 -2354235
2 234532 23456 555555
3 -432346 45435 4462
这个 awk 程序在每行之前附加了一个文件行号(FNR)file line number 和一个 tab
(\t),FNR 包含了每一个文件当前行的行号。比如说,awk 针对两个文件做操作:
#awk '{ print FNR "\t" $0 }' file file
1 12 32423 -2354235
2 234532 23456 555555
3 -432346 45435 4462
1 12 32423 -2354235
2 234532 23456 555555
3 -432346 45435 4462
可以看到,结果是分别给两个文件的每一行之前加上该行在文件中的行号。FNR
给文件的行编号时,如果有多个文件会重新开始编号。
6、给所有文件的行一起编号
#awk '{ print NR "\t" $0 }' file file
1 12 32423 -2354235
2 234532 23456 555555
3 -432346 45435 4462
4 12 32423 -2354235
5 234532 23456 555555
6 -432346 45435 4462
这个命令和第五个例子几乎一样,唯一不同的地方是使用了参数(行号)NR-Line
Number。NR 与 FNR 不同的地方就在于 NR 在给多个文件的行编号的时候不会根
据文件重新编号,而是按照读取顺序统一编号。
7、花式编号
#awk '{ printf("%5d : %s\n", NR, $0) }' file
1 : 12 32423 -2354235
2 : 234532 23456 555555
3 : -432346 45435 4462
这个命令用了通常格式 printf()函数来给给行编号,像普通的 printf()函数一
样格式化参数。这里需要特别注意的是在 printf()函数后面不会附加一个输出
记录分隔符(ORS)。因此我们需要在每一行的后面明确的打印出一个换行符(\n)。
这个命令的结果是在每行之前打印行号和一个冒号。
8、只给非空行编号
#more file
12 32423 -2354235
234532 23456 555555
432346 45435 4462
#awk 'NF { $0=++a " :" $0 }; { print }'
1 :12 32423 -2354235
2 :234532 23456 555555
3 :-432346 45435 4462
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
4
前面提到过,END{}是一种不测试文件的特殊语句,它是在所有行遍历完以后执
行。在所有行遍历完以后,NR 就等于最后一行的行号,再出变量 NR 的值,就
是输出文件的行数了。
10、打印每行中域值的总和
#awk '{ s = 0; for (i = 1; i <= NF; i++) s = s+$i; print s }' file
-2321800
813543
-382449
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
6
6
6
因为前面的 s=$1+$2+$3,而后面的 s=1+2+3。
11、打印所有行中域值的总和
#awk '{ for (i = 1; i <= NF; i++) s = s+$i }; END { print s+0 }' file
-1890706
这个命令基本上和#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
12 32423 2354235
234532 23456 555555
432346 45435 4462
这个命令也借鉴了 c 的两个特征,if(..){„}语句和省略了大括号。这个命令遍历
了所有行中的每一个域,检查是否有小于 0 的域,如果有,将域值取反,变成整
数。域值可以使用参数间接的赋值,比如 i=5;$i=”hello”,就是将第五个域赋值
为”hello”
下面是该命令的另一种比较完整的写法,在每行中所有的域都检查过并且重新赋
值以后,再执行打印的命令。
awk '{
for (i = 1; i <= NF; i++) {
if ($i < 0) {
$i = -$i;
}
}
print
}'
13、计算一个文件中所有域的数量
#awk '{ total = total + NF }; END { print total+0 }' file
9
这个 awk 程序遍历文件 file 所有的行,并将每一行的域的数量累加起来,并将累
加的数量赋给变量 total,一旦文件遍历完,开始执行 END{},也就是打印出变量
total 的值,通过第十一个例子我们可以知道为什么打印的是 total+0。
14、打印包含“55”内容的行的个数
#awk '/55/ { n++ }; END { print n+0 }' file
1
这个命令包含两个模式{动作}语句。第一个是/55/{n++}。模式中两个斜杠之间的
是一个正则表达式。表示匹配所有包含数字“55”的行(不一定精确匹配 55 这
个数字,也匹配像 555,553,255,55d 这种)。当匹配了一行时,变量就自动+1,
第二个语句是是 END{print n+0},表示当文件遍历完后,打印出变量 n 的值。注
意到是 print n+0,因为当没有行匹配 55 的时候,n 就没有被创建和被定义,n
的值也就打印不出来,加上 0 可以避免无输出。
PS:关于正则表达式有一篇文章介绍的不错:
http://bbs.chinaunix.net/viewthread.php?tid=63273 可以参考一下。
15、找出第一个域中最大的数
#awk '$1 > max { max=$1; maxline=$0 }; END { print max"\n"maxline }' file
234532
234532 23456 555555
这个命令将通过比较将第一个域中的最大数保存在变量 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
-12
-12 32423 -2354235
这个命令在前一个命令的基础上加了一个命令 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
3:-12 32423 -2354235
3:234532 23456 555555
3:432346 45435 4462
这个命令还是很简单了,先打印出预先确定的 NF-number of fields(域的数量),后
面加一个冒号和行记录。
17、打印每行的最后一个域
#awk '{ print $NF }' file
-2354235
555555
4462
每一行域的数量 NF 不会总是一样,$NF 就是每行的最后一个域。
18、打印最后一行的最后一个域
#awk '{ field = $NF }; END { print field }' file
4462
这个命令将记录中的最后一个域赋给变量 field,所有行遍历完以后,变量 field
的值就是最后一行记录的最后一个域了,然后打印出变量 field。
有一个更好,更常用的写法
#awk 'END { print $NF}' file
4462
19、打印超过 4 个域的行
#awk 'NF > 4' file
这个命令省略了动作只有模式,缺省的动作就是{print $0},因此如果匹配到有超
过 4 个域的行,打印出来,如果没有,则无输出。
20、打印最后一个域值大于 4 的行
#awk '$NF > 4' file
234532 23456 555555
432346 45435 4462
和前一个例子不同的地方在于,这个模式匹配的是最后一个域值大于 4 的行。并
打印相关的行。
好了,第一部分内容写完了,比较基础的东西。
第二部分是文本转换和替代,下次继续!
第一部分的还是比较简单的,从第二部分开始,就要接触到很多函数和数组
的东西,由于本人只有一点点 c 的基础,所以为了不误人子弟,希望大家能够多
提
。谢谢。
第二部分:文本编辑替换
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”
PS:23,24 都是在 windows/dos 下运行,我没有试过,翻译的也很勉强,而且也不
懂,为了不误导别人,请高手们不吝赐教!!
25、删除每行开头的空白(空格或者制表符 tabs)
#more file
aaaa
aa
234532 23456 555555
-12 32423 -2354235
# awk '{ sub(/^[ \t]+/, ""); print }' file
aaaa
aa
234532 23456 555555
-12 32423 -2354235
这个例子同样使用了 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
aaaa
aa
234532 3456 555555
-12 32423 -2354235
# awk '{ $1=$1; print }' file
aaaa
aa
234532 3456 555555
-12 32423 -2354235
这个命令有点复杂,乍一看$1=$1 好像什么都没做是吗,其实不然,在 awk 里面,
当你改变一个域的时候(比如给它赋值),awk 会重置$0,把所有的域拼接起来,
然后 print,而默认使用输出域分隔符 OFS 就是空格,这样所有域之间的空白都
将删除。
28、在每行的开头增加 5 个空白
# awk '{ sub(/^/, " "); print }' file
aaaa
aa
234532 3456 555555
-12 32423 -2354235
前面提到过,^只是代表每行的开头的一个空字符,即在每行的开头加上 5 个空
格。
29、右对齐文本
#awk '{ printf "%79s\n", $0 }' file
aaaa
aa
234532 3456 555555
-12 32423 -2354235
这个命令在$0 前添加空格直到整行的长度达到 79 个字节。更多关于 printf()
的使用方法请参考:
http://www.gnu.org/manual/gawk/html_node/Basic-Printf.html
30、使文本居中
#awk '{ l=length(); s=int((79-l)/2); printf "%"(s+l)"s\n", $0 }' file
aaaa
aa
234532 3456 555555
-12 32423 -2354235
这个程序开始计算整行的长度,并把值赋给变量 l,length(var)函数返回字符
串 var 的长度,如果不指定字符串 var,返回的是整行$0 的长度。然后计算在每
行之前有多少个空格,也把这个值赋给变量 s,最后打印出文本,使得文本刚好
居中在 79 个字符中间,前面用空白填充。
31、替换文本
#awk '{ sub(/55/,"xx"); print }' file
aaaa
aa
234532 3456 xx5555
-12 32423 -2354235
这个替换命令把每行中的 55 替换成 xx,记住,这个命令只是替换第一次匹配的
字符。如果要把所有的 55 都替换成 xx,则用下面的命令
#awk '{ gsub(/55/,"xx"); print }' file
aaaa
aa
234532 3456 xxxxxx
-12 32423 -2354235
可以看到所有的 55 都换成了 xx,还可以使用另一种替换的函数
#awk '{$0= gensub(/55/,"xx",2); print }' file
aaaa
aa
234532 3456 55xx55
-12 32423 -2354235
这个程序只是替换第二次匹配 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
aa
aaaa
234532 3456 555555
-12 32423 -2354235
可以看到 print 出来的东西并不没有变化,这就是 gensub()和 sub(),gsub()
不同的地方之一,也就是 gensub 虽然对$0 做了替换,但是只保存在函数中,并
不对$0 做实际修改。因此如果想要把替换的内容输出,可以把 gensub()函数
赋值给$0。或者直接用{print gensub()}打印出结果。
32、在包含特定字符的行中替换字符
#awk '/34/ { gsub(/5/, "x") }; { print }' file
aa
aaaa
234x32 34x6 xxxxxx
-12 32423 -2354235
在第一部分提到过,所有的 awk 程序都是由一个个的 模式{动作}语句组成,只
有当模式匹配的时候才执行动作。第一个语句的模式部分是/34/,即匹配包含 34
的行,如果有行包含 34,执行动作全局替换,将所有的数字 5 替换成字符 x,第
二个语句把所有行打印出来。如果你只想替换一次,用 sub()或者 gensub()
函数即可。
33、在不包含特定字符的行中替换字符
# awk '!/34/ { gsub(/5/, "x") }; { print }' file
aa
aaaa
234532 3456 555555
-12 32423 -23x423x
这个例子只是模式部分与前一个不一样,!就是非,也就是匹配不包含 34 的行对
其做替换操作并打印出所有的行。
34、将不同字符替换为同一个字符
# awk '{ gsub(/34|55|12/, "xx"); print}' file
aa
aaaa
2xx532 xx56 xxxxxx
-xx 32423 -2354235
以上这 3 个例子讲的都是正则表达式,这个程序用管道符|实现匹配多个字符并
替换,最后打印整行。
35、倒序输出记录(类似 tac 的作用)
# awk '{ a[i++] = $0 } END { for (j=i-1; j>=0;) print a[j--] }' file
-12 32423 -2354235
234532 3456 555555
aaaa
aa
这个程序时目前来说最复杂的一个了,首先把每行的记录都赋值给数组 a,比如
有两行记录“aa”和“aaa”,则分别是 a[0]=aa,a[1]=aaa。当文件的所有行都赋值
给了数组 a 以后,awk 执行 END{}版块,查找数组 a 中的元素并打印出来。在这
里有 4 行记录,因此 END
# for (j = 4-1; j >= 0; ) print a[j--]
首先打印 a[3],然后是 a[2],a[1],a[0]。我们可以看到正好把所有行倒过来排列了。
36、把行结尾是反斜杠\的记录与下一行连起来
#more file
aa
aaaa
234532 3456 555555 \
-12 32423 -2354235
# awk '/\\$/ { sub(/\\$/,""); getline t; print $0 t; next }; 1'
aa
aaaa
234532 3456 555555 -12 32423 -2354235
这个 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
aa
aaaa
3456 234532
32423 -12
这个程序比较简单,就是打印出每行的第二和第一个域。
39、把第一个域和第二个域互换
# awk '{ temp = $1; $1 = $2; $2 = temp; print }' file
aa
aaaa
3456 234532 555555 \
32423 -12 -2354235
这个程序使用了一个临时的变量 temp,首先把$1 的值赋给 temp,然后把$2 的
值赋给$1,最后把 temp 的值(也就是之前$1 的值)赋给$2,正好把$1 和$2 的值
相互交换了,比如说输入是“foo bar baz”,那么输出即将是“bar foo baz”。
40、删除某个域
# awk '{ $2 = ""; print }' file
aa
aaaa
234532 555555 \
-12 -2354235
首先把空值“”赋给$2,在打印记录,相当于删除了$2。
41、在每行中以倒序的方式排列每个域
# awk '{ for (i=NF; i>0; i--) printf("%s ", $i); printf ("\n") }'
aa
aaaa
555555 3456 234532
-2354235 32423 -12
在第一部分我们知道 NF 变量是指每行中域的个数,在读取了记录以后,awk 把
每行域的数量指定给变量 NF,这个程序利用 for 语句将域反向打印出来,即
$NF,$(NF-1),..$1,然后再打印出一个换行符“\n”。
PS:有些朋友可能会有疑问,为什么打印函数不用 print 而是 printf{},我们先来
看用 print 的结果
# awk '{ for (i=NF; i>0; i--) print $i}' file
aa
aaaa
555555
3456
234532
-2354235
32423
-12
可以看到,打印出来的结果是每一个域后面都自动换行。前面提到过,因为 print{}
函数在打印一个域$i 后,会在后面自动附加一个记录分隔符 ORS,也就是换行,
这样就会导致没有安装记录输出。而 printf{}函数则不会再后面附加 ORS,第一个
命令是当一行中打印完所有的$i 后,再附加一个 ORS“\n”,这样就不会把原来的
记录打乱了。
42、删除连续出现的多余相同行(类似 uniq 的作用)
#more file
aa
aaaa
aaaa
234532 3456 555555
-12 32423 -2354235
# awk 'a !~ $0; { a = $0 }' file
aa
aaaa
234532 3456 555555
-12 32423 -2354235
我们前面提到过。Awk 中变量在被使用之前不需要初始化和公布。它们在第一使
用的时候创建。这个程序使用变量 a 保存上一行记录$0,然后再读取下一行,将
上一行的记录与下一行作比较.a!~$0 指的是,如果当前行不匹配上一行的记录,
表达式的值就是真,默认的动作就是{print},反之当匹配的时候,就不打印相关
的行,第二个语句又重新把当前行赋给变量 a,依次执行知道记录读取完。
这个程序其实是有问题的,问题就出现在模式中的~符号上,~是匹配的意思,
也就是说,如果当前行是“aa”,而上一行是“aaa”,同样也匹配,这样就不会
打印出“aa”,实际是应该要被打印出来的
# more file
aaa
aa
234532 3456 555555
-12 32423 -2354235
# awk 'a !~ $0; { a = $0 }' file
aaa
234532 3456 555555
-12 32423 -2354235
可以看到,记录“aa”,被程序认为和“aaa”一样而不被打印出来。
要解决这个问题也不是没有办法,请看
# more file
aaa
aa
234532 3456 555555
234532 3456 555555
-12 32423 -2354235
# awk 'a != $0; { a = $0 }' file
aaa
aa
234532 3456 555555
-12 32423 -2354235
这里比较的是整个记录是否相同,因此不会出现正则表达式错误匹配的问题。
43、删除多余相同的行(不连续的也删除)
#more file
foo
bar
foo
baz
# awk '!a[$0]++' file
foo
bar
baz
这个程序非常常用。注意到这里使用了关联数组 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
1
2
3
4
5
6
7
#awk 'ORS=NR%5?",":"\n"' file
1,2,3,4,5
6,7,
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
a b c
d e f
g h i
j k l
m n o
p q r
s t u
v w x
y z
# awk 'NR < 6' file
a b c
d e f
g h i
j k l
m n o
前面也提到过,NR 的意思是只当前文件的行号,每读一行,awk 会自动给 NR
增加 1。这个命令只有一个模式 NR <6,而没有{动作},默认为{print $0}。模式中
指的是匹配行号 NR<6(即第一到第五行),即把文件 file 的第一到第五行打印出来。
从第六行开始,awk 检测到 NR>=6,因此不执行动作{print}。
这个程序的效率不高,因为当 NR>=6 时,awk 会继续遍历文件直到结束,但其实
后面做的都是无用功,因为不执行任何动作。以下是改进的程序
# awk '1; NR == 5 { exit }' file
a b c
d e f
g h i
j k l
m n o
语句 NR==5{exit}能够确保文件遍历到第五行的时候立刻停止程序。当文件的 NR
小于 5 的时候,语句“1”相当于{print},因此当 NR<=5 的时候,awk 打印出所
有匹配的行,然后程序停止。当文件的行数非常多的时候,提高执行的效率。
46、打印文件的第一行(head -1)
# awk 'NR > 1 { exit }; 1' file
a b c
和前面的例子一样,只有当 NR==1 的时候模式才匹配,读第一行的时候,模式
不匹配,直接到第二个语句“1”,也就是{print},打印第一行,读到第二行的时
候 NR=2,匹配模式,执行{exit},退出了 awk,完毕。
47、打印文件的倒数 2 行(tail -2)
# awk '{ y=x "\n" $0; x=$0 }; END { print y }' file
v w x
y z
这个 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)