awk 用法小结 - - - by ruson 2006.4 NTU
1. awk 非常适合于结构化的文本文件(行、列数据)复杂处理。相对于 sed 而言,它可进
行复杂的编程处理,并且可以产生复杂的报表输出。
2. awk 通常有三个版本,旧 awk、nawk(新) 、gawk。Solaris 下建议用 nawk,因为旧 awk
有很多功能不支持,例如数学运算幂 ^ ,函数getline,system等。
3. 基本语法:awk ‘pattern{action}’ filename 其中 action 内容可扩充,也可以有多个 action。
执行顺序:awk 一行行读入输入文件,顺序执行‘’内内容,按模式匹配来采取动作。
其他调用:awk 可用内部变量和函数,条件与循环语句,也可执行数学运算和字符串操
作。 此外, 可以使用 BEGIN和 END来执行处理前预操作和处理后后继操作。
A. 常用内部变量:NR(当前行数)NF(字段总数)$0(当前整行)$1(第一个记录)
FS(字段分隔符)OFS(输出字段分隔符)ORS(输出记录分隔符)
B.模式 pattern可以是/ /包含的匹配形式,也是条件语句如$3<10
C.BEGIN(处理文件前的 action,常包含 FS、OFS 等) 、END(处理文件后的 action)
D.条件与循环:if else(next,exit) ,for do while (continue,break)
E.数学运算符 + - * / % ^;数学函数 sin int;字符串函数 length index gsub substr等
F.数组与关联数组:a[1]; a[$1]; a[$0]; a[b[i]]
F.输出重定向和管道:> >> | ;awk 内部命令:getline、system等
4.awk 的三种调用方式: X awk ‘pattern{action}’ filename1,filename2
Y awk –f myscript.awk filename1,filename2
Z #!/bin/nawk –f … … 执行:myscript.awk filename
特别注意事项:
----------------------------------------------------------------------------------------------------------------------
A.从 window拷贝语法句到 Unix 中,行结尾可能有不识别的字符会导致语法错误
B.写 script 文件时,BEGIN或 END后必须紧跟{
C.所有 action语句必须放在{ }中,否则提示语法错误。例如不可直接写 print
D.单独的赋值或 pattern条件句没有{ }则默认 print $0;例如{ }外面的 i=1
E.if条件块必须放在 action 中,也就是必须要有{ }来包含
F.条件的判断“相等”须用= =,而不是=(赋值) ;例如,if($1= =100)
G.输出多列须用“, ” (逗号)隔开;for 循环中间隔符为“; ”而不是“, ”
实例演练: (filename 为以下文件,注意:第二列用“-”隔开而非空格,以避免与 FS冲突)
ID Name Age City Country Tel Salary Children
1001 Steven 25 NY U.S.A +01-02-323222 $4900 2
1002 Huang-Yu 30 BJ CHN +86-10-36789966 ¥6000 1
1003 Fish-Mad 27 SG SG +65-67456632 $3000 3
1004 Vale-Kiss 46 LD ENG +44-20-87634321 $6280 3
----------------------------------------------------------------------------------------------------------------------
以下分基础实例:1.无 pattern 的 action 实例 2.有 pattern的 action 实例
3.BEGIN,END,FS,OFS 实例 4.if else; for while 循环实例
5.数学运算与字符串操作实例
高级实例:6.数组与关联数组运用实例 7.多文件运用 NR FNR实例
8.输出重定向和 getline实例
1
基 础 篇
1.无 pattern 的 action 实例
a.awk ‘{print NR $1 $NF}’ data.txt 打印行号,第一列和最后一列,中间无分隔符
b.awk ‘{print $1,$NF}’ data.txt 打印第一列和最后一列,并且中间有分隔符
c.awk ‘{print $0,$NF+10}’ data.txt 打印整行,并打印 最后一行加上 10 的结果
2.有 pattern 的 action 实例
a.awk ‘/[0-9]/’ data.txt 打印记录中任意列包含数字 0-9 的行
b.awk ‘/01/||/02/’ data.txt 打印包含 01 或者 02 的行
c.awk ‘/01/,/02/’ data.txt 打印既包含01又包含02的行;等同awk ‘/01/&&/02’
d.awk ‘$1= =1001{print $2}’ data.txt 打印符合第一列等于 1001 的第二列
e.awk ‘$2= =”Steven”{print}’data.txt 打印符合第二列等于 Steven 的那些行
f.awk ‘$3>20&&$3<30’ data.txt 打印第三列在 20 到30 之间的那些行
g.nawk ‘$3*$NF<100’ data.txt 打印第三列和最后一列乘积小于 100 的那些行
h.awk ‘$6~/01/{print $2}’ data.txt 打印符合仅仅第六列里包含 01那些行的第二列
i.awk ‘NR>3{print $1,$2}’ data.txt 从第四行才开始打印第一列和第二列
3.有 BEGIN、END 并包含 FS、OFS 的实例
a.awk –F”+” ‘{print $1}’ data.txt 按+为分隔符来分隔列,并打印第一列
b.nawk –F’[+/t$]’ ‘{print $6,$7,$8,$9}’data.txt 按+或/t 或$都可作分隔符,并打印指定列
c.nawk ‘BEGIN{FS=”[/t+$]”}{print $6,$8,$9}’data.txt
按+,/t,$(顺序无所谓)为分割符,并打印指定列
d.nawk ‘BEGIN{FS=”[/t+$]”;OFS=”%”}{print $6,$7,$8,$9}’ data.txt
按+,/t,$为分割符, 用%作输出分隔符打印指定列
e.nawk –F‘[ [ ] ]’ ‘{print $1}’ data.txt 按 [ 或 ] 为分隔符,并打印第一列
f.nawk –F‘[ ][ ]’ ‘{print $1}’ data.txt 同上,按[ 或 ] 为分隔符,并打印第一列
关于-F 和 BEGIN 内的 FS 定义特别注意如下:
A.-F 参数后紧跟单个分隔符,则用双引号“” ,例如 –F”+”
B.-F 参数后紧跟多个分隔符,则用单引号‘ ’并用[ ],中间顺序无所谓,例如-F’[+$]’
C.BEGIN 中的 FS无论是单个还是多个分隔,均用双引号“” ,多个则需加[ ]
例如:nawk ‘BEGIN{FS=”[/t+$]”}{print $6,$8,$9}’data.txt
D.BEGIN 中若同时有 FS和 OFS,则建议中间用分号“; ”来分隔开,否则提示出错
例如:nawk ‘BEGIN{FS=”[/t+$]”;OFS=”%”}{print $6,$7,$8,$9}’ data.txt
E.半方括号[ ]也可以作为分隔符,顺序无所谓。例如:-F‘[[]]’ 或 –F‘[][]’
F.在用多个分隔符的时候,为避免语法错误或想得到正确的结果,最好用 nawk
4.条件 if else 和 for while 循环实例
a.nawk ‘{print($3>25 ? $2“ Old” : $2“ Young”)}’ data.txt
若第三列大于 25 就打印某某 Old;否则打印某某 Young
b.awk ‘{if($1>1002)print $2; else print $3}’ data.txt
若第二列大于 1002 就打印第二列,否则打印第三列
c.awk ‘NR>1{if($3<30)print$2;else if($3<40) print$2”m”;else exit;}END{print 10}’data.txt
从第二行开始判断第三列的值,如果小于 30 就打印$2,如果大于 30 而又小于
240,打印$2并后跟 m,若是都不符合就退出处理行,但是 END后的操作仍执行
d.awk ‘BEGIN{ORS=””}{for(i=1;i<NF-2;i++) print $i”/t”}{print “/n”}’ data.txt
打印出每行的前面几列,最后几列不打印。特别注意:默认情况下,print 每执
行一次时,另起一行打印。因此为避免每打印一列就重新一行,设置 ORS为空,
但是在每打印完符合条件的所有列后, 需要手工换行 (循环外的 print 起此作用) 。
e.awk ‘BEGIN{ORS=””}{i=1; while(i<NF-2){print $i”/t”; i++}}{print “/n”}’ data.txt
功能同上,打印出每行的前面几列,最后几列不打印。
5.数学运算和字符串操作实例
a.nawk ‘{print 2^5+sin(2.1)+int(0.9)}’ data.txt 在每一行都打印运算值(32.8632)
b.awk ‘END{print length(“How are you?”)}’ data.txt 打印出字符串的长度(12)
c.awk ‘END{print index(“How are you?”,”you”)}’ data.txt 返回 you在字符串中的开始位置(9)
d.nawk ‘{gsub(/ /$/,””);print $0}’ data.txt 把每行的$符号用空替掉,并打印行
e.awk ‘END{print substr(“How are you?”,9,3)}’ data.txt
从字符串的第九个位置开始截出后面 3 个长度的子字符串
f.nawk ‘{print toupper($2), tolower($2)}’ data.txt
分别打印第二列大写和小写
g.nawk ‘END{print match(“How are you you?”,/you/),RSTART,RLENGTH}’ data.txt
打印 you 在字符串中第一个匹配的位置以及长度(9,9,3)
h.nawk ‘END{str=”How are you doing?”;sub(/o/,”0”,str);print str}’ data.txt
将字符串变量指定字符中第一个符合含有 o 的用 0 替换调
特别注意:sub 函数的第三个参数不可直接用字符串,而必须用
字符串变量。
i.awk ‘END{print split(“Jan,Feb,Mar,Apr,May”,mymonths,”,”),mymonths[2]}’ data.txt
将字符串用第三个参数分隔符号进行分隔,把分隔的各个元素放
在第二个参数的数组中,函数返回分隔得到的元素数目。结果:
(5,Feb)
高 级 篇
6.数组与关联数组(a[1],a[$1] a[$0],a[b[i]])
a.awk ‘{for(i=1;i<NF;i++) {a[i]=$i; print a[i]}}’ data.txt 把文件每一列按行打印
b.awk ‘{a[$1]=$2}END{for(x in a) print a[x]}’ data.txt
数组 a 关联到第一列,并将第二列值赋给 a,最后打印 a 中的所有内容。
注意:但不是顺序打印。for中的 x是随意变量,awk 自动确定。
c.awk ‘{b[i]=$1;a[$1]=$2;i++}END{for(j=1;j<i;j++) print b[j],a[b[j]]}’ data.txt
实现从第二行开始打印每一行的第一列和第二列的功能。
注意:i 从0 开始取值,而 j 从1 开始。a 为关联数组,b 为顺序数组。
7.多输入文件和 NR、FNR 的实例
a. awk ‘BEGIN{OFS=FS=”:”} NR==FNR{a[$1]=$2} NR>FNR{$2=a[$1];print}’
/etc/shadow /etc/passwd
该语句中第一个模式匹配到第一个输入文件 shadow, 第二个模式匹配到第二个输入
文件 passwd,并使用关联数组,实现不同文件关联列的其他列值相互替换。
3
A.awk 对多输入文件的执行顺序是,先将代码作用于第一个文件(一行行读入) ,然
后该重复的代码又作用于第二个文件,再作用于第三个文件。
B.awk 对多输入文件的执行顺序产生了行序号的问题。当第一个文件执行完,下次读
入第二个文件,那么第二个文件的第一行怎么算呢?如果又计为 1 的话,那不就两
个 1 了么?(因为第一个文件也有第一行) 。这就是 NR 和 FNR 的问题。
NR :全局行数(第二个文件的第一行接着第一个文件尾行数顺序计数)
FNR:当前文件自身的行数(不考虑前几个输入文件的自身行数及总数)
例如:data1.txt 中有40 行,data2.txt 中有 50 行,那么 awk ‘{}’ data1.txt data2.txt
NR 的值依次为:1,2……40,41,42……90
FNR 的值依次为:1,2……40, 1, 2……50
8.重定向输出和特别函数 getline 实例
a.awk ‘{print FILENAME,$0}’ data1.txt data2.txt >data_all.txt
把第一个文件和第二个文件合并到 data_all.txt 中,新文件第一列为原始文件名,后
面列为原始文件内容。
b.awk ‘$1!=fd{close(fd);fd=$1} {print substr($0,index($0,“ ”)+1)>$1}’ data_all.txt
把合并后的新文件 data.txt 重新拆开成原来的两个子文件,依照新文件的第一列
来产生新文件名。生成一个完整子文件后,关闭之;再生成下一个子文件。
A.getline 从整体上来说,应这么理解它的用法:
当其左右无重定向符 | 或 < 时,getline 作用于当前文件,读入当前文件的第一行给
其后跟的变量 var 或$0(无变量) ;应该注意到,由于 awk 在处理 getline
之前已经读入了一行,所以 getline得到的返回结果是隔行的。
当其左右有重定向符 | 或 < 时,getline 则作用于定向输入文件,由于该文件是刚打
开,并没有被 awk 读入一行,只是 getline 读入,那么 getline返回的是该文
件的第一行,而不是隔行。
B.getline 用法大致可分为三大类(每大类又分两小类) ,即总共有 6 种用法。代码如下:
nawk ‘BEGIN{“cat data.txt”|getline d; print d}’ data2.txt
nawk ‘BEGIN{“cat data.txt”|getline; print $0}’ data2.txt
nawk ‘BEGIN{getline d < “data.txt”; print d}’ data2.txt
nawk ‘BEGIN{getline < “data.txt”; print $0}’ data2.txt
以上四行代码均实现“只打印 data.txt文件的第一行” (若打印全部行,用循环)
eg. nawk ‘BEGIN{FS=”:”;while(getline<”/etc/passwd”>0){print $1}}’ data.txt
nawk ‘{getline d; print d”#”$3}’ data.txt
awk首先读入第一行,接着处理getline函数,然后把下一行指定给变量d,再先打印
d,由于d后面有换行符,所以后面紧跟的#会覆盖d,后面的$3同样也会覆盖d。
nawk ‘{getline; print $0”#”$3}’ data.txt
awk 首先读入第一行接着处理 getline 函数,然后把下一行指定给$0,现在的$0 已经
是下一行内容,后面的#和$3(从$0中取)会覆盖$0的内容。
c.nawk ‘BEGIN{system(“echo /”input your name:/” ”);getline var; print “/nYour name is”,d,”/n”}’
系统提示输入名字,然后将输入的名字打印出来。特别注意:该 awk 语句后没
有输入文件名,而是利用键盘输入作为文件名。
4
经 典 实 例
1.问题描述
有如下银行帐单部分文本,
200000000000007|shi yan city qi pei bu
202111320000018|hospital
200000000000007|shiyan city renmen road qi bei bu
201602520002941|middle school
200000000000007|mingfeng road qi pei
201602120000113|zhuanghuang factory
201602320000115|liangyou factory
要求:将第一列中重复的合并为一行,其第二列填入最长地址的那列
得到的结果应为:
200000000000007|shiyan city renmen road qi bei bu
202111320000018|hospital
201602520002941|middle school
201602120000113|zhuanghuang factory
201602320000115|liangyou factory
代码: (假设下面代码在文件 myawk..sh 中)
#!/usr/bin/nawk -f
BEGIN{FS=OFS="|"; i=1;}
{ if(a[$1]==0){b[i]=$1;a[$1]=$2;i++}
if(length(a[$1])<length($2)){a[$1]=$2}
}
END{for(j=1;j<i;j++) print b[j],a[b[j]]
}
执行:myawk.sh data.txt
比较代码: (实现不全)
awk 'BEGIN{FS=OFS="|"} !(length(a[$1])>length($2)){a[$1]=$2}
END{for(i in a)print i,a[i]}' data.txt
解释:此例中,用了两个数组,a 用来与$1 关联,b 用来顺序记录,使得在最后打印时
是完全按照$1 顺序打印。条件句首先判断数组元素是否是第一次写入,若非,则
比较当前$2值和以前储存的值长度。
功能不全代码不能顺序打印。当$1 有重复,而$2 长度第一次、第二次、第三次是
以递减的方式时,该代码应用得较好。但是,当$2 各次得到的长度不确定时,代
码不能实现上述功能。例如:本例中第 5 行的第二列若比第 3 行的第二列长度长
时,功能不全代码就不能实现要求。
5
2.问题描述:
有两个文件如下:
data1 文件内容:
1 0.5 100
10 15 36.5
data2 文件内容:
50 10 19
3.2 1 5
要求:得到一个新的文件,内容是 data1 和 data2 对应的列数字相加的和,新文件内容:
51 10.5 119
13.2 16 41.5
代码: (假设下面代码在文件 myawk..sh 中)
#!/usr/bin/nawk -f
{ for(i=1;i<=NF;i++) a[i]=$i;
getline < “data2.txt”
for(j=1;j<NF;j++) printf $j+a[j] “/t”
printf $NF+a[NF]“/n”
}
执行:myawk.sh data1.txt
解释: 上述代码用到了getline同时读入第二个文件的行内容并存入到$0中, 最后用printf
来实现相加后的打印。注意后面同时紧跟打印了/t 和/n,以用来列之间的分隔和
行之间的分隔。此外,也可以用 print来实现,但是 print 每执行完一次都要换行,
(当然可以用 OFS 来进一步处理) 。
实际上也可以用另外一种办法来实现,就是用 NR、FNR 比较,然后分别将两个
文件赋值给不同的数组,然后相加。当然代码就没有上述代码简练。
3.问题描述
有两个如下文件:
data2.txt 文件内容如下:
01 1111 AAA WW001 $$$$ 1000.00
02 2222 BBB GG001 %%%% 2000.00
03 3333 CCC JJ001 **** 3000.00
04 4444 DDD FF002 &&&& 4000.00
05 5555 EEE RR002 @@@@ 5000.00
06 666 FFF UU003 JJJJ 6000.00
07 777 III II005 PPPP 7000.00
08 8888 TTT TT008 TTTT 8000.00
data1.txt 文件内容如下:
AAA 001 1000.00
BBB 001 2000.00
DDD 002 4000.00
EEE 002 5000.00
FFF 003 6000.00
data1 的第 1列、最后一列和 data2 的第 3 列、最后一列是相同的,
data1 的第 2列和 data2 的第 4 列的后3 位是相同的。
要求:data2中第三列、第四列后 3位、第六列完全和 data1相同的取出来得到新文件如
下内容:
6
01 1111 AAA WW001 $$$$ 1000.00
02 2222 BBB GG001 %%%% 2000.00
04 4444 DDD FF002 &&&& 4000.00
05 5555 EEE RR002 @@@@ 5000.00
06 666 FFF UU003 JJJJ 6000.00
代码:
nawk 'NR<=FNR {a[$1]=$1"x"$2"x"$3}
NR>FNR { b=substr($4,3);
c=$3"x"b"x"$6;
if(c==a[$3]) print}' data1.txt data2.txt
执行:直接执行上述代码
解释:本例巧妙地利用了组合字符串(用 x 来组合) ,然后用来比较。
当 NR<=FNR 时,处理第一个文件,当 NR>FNR 时,处理第二个文件。
-----------------------------------------------------------------------------------------------------------------
后记:
花了约有一个星期的时间来整理这些有关 awk 的材料。 希望这些小小的总结实例
能给初学者带来抛砖引玉的作用。上述总结期望能概括 awk的大部分内容,足以
应付日常的维护工作。其他,例如自定义函数等未能包括在其中。
本总结的主要目的是以代码带动理解,每行代码均在 Solaris 下运行通过。当然某
些地方有打字上的疏漏,请指正。至于基础文字上的说明请参阅其他网页。
上述语法实例材料均取自于 chinaunix 论坛,本人只是文字整理和重新排版。在
此特别感谢那些付出努力的网友们。