主要来自对AWK手册(http://phi.sinica.edu.tw/aspac/reports/94/94011/)的阅读实践整理。
AWK的执行方式
方式一 $ awk -f pay1.awk emp.dat
方式二 $ awk '{ print $2, $3 * $4 }' emp.dat
AWK的工作流程
执行 awk时,它会反复进行下列四步骤.
1.自动从指定的数据文件中读取一个数据行.
2.自动更新(Update)相关的内建变量之值.如: NF, NR, $0...
3.依次执行程序中所有的 Pattern { Actions }指令,其中Pattern { Actions }的语法已涵盖这种 " if (条件) {动作} "的架构。
4.当执行完程序中所有 Pattern { Actions }时,若数据文件中还有未读取的数据,则反复执行步骤1到步骤4.
awk会自动重复进行上述4个步骤,使用者不须于程序中编写这个循环 (Loop).
PATTERN{ACTIONS}
PATTERN的含义
BEGIN部分的Actions在程序一开始执行时,仅被执行一次."
END部分的Actions仅在程序最后才成立,仅被执行一次
>大于
<小于
>=大于或等于
<=小于或等于
==等于
!=不等于
~ match
!~ not match
ACTIONS的含义
表达式 ( function calls, assignments..)
print表达式列表
printf(格式化字符串,表达式列表)
if(表达式 )语句 [else语句]
while(表达式 )语句
do语句 while(表达式)
for(表达式;表达式;表达式)语句
for( variable in array)语句
delete
[例]
for( any in X_arr )
delete X_arr[any]
delete指令一次只能释放数组中的一个元素.
break
continue
next
执行 next指令时, awk将掠过位于该指令(next)之后的所有指令(包括其
后的所有Pattern { Actions }),接著读取下一笔数据行,继续从第一
个 Pattern {Actions}执行起.
例子
/^[ \t]*$/ { print "This is a blank line! Do nothinghere !"
next
}
$2 != 0 { print $1, $1/$2 }
exit [表达式]
[例1]
现在需要给组装部门员工调薪5%,(组装部门员工之ID以"A"开头),所有员工最后之薪资率若仍低于100,则以100计.编写awk程序打印新的员工薪资率报表.
编写如下之程序,并取名adjust1.awk
$1 ~ /^A.*/ { $3 *= 1.05 } $3<100 { $3 = 100 }
{ printf("%s %8s %d\n", $1, $2, $3)}
执行:
$awk -f adjust1.awk emp.dat
内建变量
$0,一字符串,其内容为目前 awk所读入的数据行.
$1,$0上第一个字段的数据.
$2,$0上第二个字段的数据.
$n,$0上第n个字段的数据.
$NF(Number of Fields)为一整数,其值表$0上所存在的字段数目.
$NR(Number ofRecords)为一整数,其值表awk已读入的数据行数目.
$FILENAME
$FS 例如,FS ="[ \t:]+" (改变字段分隔符,使它变为空格或者tab或者冒号。)
数组:使用字符串当数组的下标(index),类似关联数组使用数组前不须宣告数组名及其大小.
[例1]
例如 : awk从资料文件 emp.dat中读入第一笔数据行
"A125 Jenny 100 210"之后,程序中:
$0之值将是 "A125 Jenny 100210"
$1之值为 "A125"
$2之值为 "Jenny"
$3之值为 100
$4之值为 210
$NF之值为 4
$NR之值为 1
$FILENAME之值为 "emp.dat"
注意:在awk中任何变量使用之前,并不须事先声明.其初始值为空字符串(Null string)或 0.
awk的 I/O指令
print, printf( ), getline
getline var < file所指定的 file变量 var(var省略时,表示置于$0)
getline var pipe变量变量 var(var省略时,表示置于$0)
getline var见注一变量 var(var省略时,表示置于$0)
当 Pattern为 BEGIN或 END时, getline将由 stdin读取数据,否则由awk正处理的数据文件上读取数据.getline一次读取一行数据,若读取成功则return 1,若读取失败则return-1,若遇到文件结束(EOF),则return 0;
[例]
BEGIN { print " ID Number Arrival Time" >"today_rpt1"
print "===========================" >"today_rpt1"
}
{ printf(" %s %s\n", $1,$2 ) >"today_rpt1" }
]
[说明]
1awk 程序中,文件名称 today_rpt1的前后须以" (双引号)括住,表示 today_rpt1为一字符串常量.若未以"括住,则 today_rpt1将被awk解释为一个变量名称.
2 awk中的重定向和SHELL中的有区别。
AWK与SHELL的交互
[a.语法] awk output指令 | "Shell接受的命令"(如 : print $1,$2 | "sort -k 1" )
[b.语法] "Shell接受的命令" | awk input指令( 如 : "ls " | getline)
[c.语法]使用system命令
[例1 ]写一个awk程序来打印出线上人数.
将下列程序建文件,命名为 count.awk
BEGIN{
while ( "who" | getline ) n++
print n
}
执行:awk -f count.awk
结果:印出目前在线人数
注意:一、不论 print $1, $2被执行几次, "sort -k 1"的执行时间是"awk程序结束时"。[a.语法]程序中, "print$1, $2"可能反复执行很多次,其输出的结果将先暂存于 pipe中,等到该程序结束时,才会一并进行 "sort -k 1".
二、"sort -k 1"的执行次数是 "一次".
[例2]
$ awk'
BEGIN{
system("date > date.dat")
getline < "date.dat"
print "Today is ", $2, $3
}'
但使用 system( "shell命令" )时, awk无法直接将执行中的部分数据输出给Shell命令.且 Shell命令执行的结果也无法直接输入到awk中。
[例3]重定向,一次只能对一个文件进行写操作,否则乱套。
#!/bin/sh
awk '
BEGIN{
FS= "[ \t:]+" #改变字段切割的方式
"date" |getline # Shell执行 "date".getline取得结果以$0记录
print " Today is " ,$2, $3 >"today_rpt3"
print"=========================">"today_rpt3"
print " ID Number Arrival Time" >"today_rpt3"
close( "today_rpt3" )
}
{
#已更改字段切割方式, $2表到达小时数, $3表分钟数
arrival = HM_to_M($2, $3)
printf(" %s %s:%s %s\n", $1, $2, $3, arrival >480 ? "*": " " ) | "sort -k 1 >>
today_rpt3"
total += arrival
}
END {
close("today_rpt3")
close("sort -k 1 >> today_rpt3")
printf(" Average arrival time :%d:%d\n",total/NR/60, (total/NR)%60 ) >>
"today_rpt3"
}
functionHM_to_M( hour, min ){
return hour*60 + min
}
' $*
执行:$ ./reformat3 arr.dat
[说明]
1.awk 中提供的 close( )指令,语法如下(有二种) :
close( filename )
close(置于pipe之前的command )
2.为何本程序使用了两个 close( )指令 :
指令 close( "sort -k 1>> today_rpt3" ),其意思为 close程序中置于 "sort-k 1 >> today_rpt3 "之前的 Pipe ,并立刻调用 Shell来执行"sort -k 1 >>today_rpt3". (若未执行这指令, awk 必须于结束该程序时才会进行上述动作;则这12笔sort后的数据将被 append到文件 today_rpt3中"Average arrivaltime : ..."的后方)。
因为 Shell排序后的数据也要写到 today_rpt3,所以awk必须先关闭使用中的today_rpt3以使 Shell正确将排序后的数据追加到today_rpt3否则2个不同的 process同时打开一个文件进行输出将会产生不可预期的结果.
读者应留心上述两点,才可正确控制数据输出到文件中的顺序.
指令 close("sort -k 1>> today_rpt3")中字符串 "sort +0n >>today_rpt3" 必须与 pipe |后方的 Shell Command名称一字不差,否则awk将视为二个不同的 pipe.读者可于BEGIN{}中先令变量 Sys_call = "sort +0n >> today_rpt3",
程序中再一律以 Sys_call代替该字符串.
读取命令行的参数
建立文件如下,命名为 see_arg :
#!/bin/sh
awk '
BEGIN{
for( i=0; i<ARGC ; i++)
print ARGV[i] #依次印出awk所记录的参数
}
' $*
执行:$ ./see_arg first-argsecond-arg
结果:
awk
first-arg
second-arg
内建函数
index(原字串,找寻的子字串)
若原字串中含有欲找寻的子字串,则返回该子字串在原字串中第一次出现的位置,若未曾出现该子字串则返回0.
执行 :
$ awk 'BEGIN{ print index("8-12-94","-")}'
结果:
2
length(字串 ) :返回该字串的长度
match(原字串,用以找寻比对的正则表达式 )
awk找到该字串后会依此字串为依据进行下列动作:
设定awk内建变量 RSTART, RLENGTH,并返回 RSTART 之值。
RSTART
=合条件的子字串在原字串中的位置.
= 0 ;若未找到合条件的子字串.
RLENGTH
=合条件的子字串长度(贪婪匹配)
= -1 ;若未找到合条件的子字串.
[例1]
awk ' BEGIN {
match( "banana", /(an)+/ )
print RSTART, RLENGTH
} '
结果:2 4
split(原字串,数组名称,分隔字符 )
awk将依所指定的分隔字符(field separator)来分隔原字串成一个个的栏位(field),并以指定的数组记录各个被分隔的栏位.
[例1]:
ArgLst = "5P12p89"
split( ArgLst, Arr, /[Pp]/)
执行结果 : Arr[1]=5, Arr[2]=12, Arr[3]=89
sub(比对用的正则表达式,将替换的新字串,原字串 )
[例1]
A = "a6b12anan212.45an6a"
sub( /(an)+[0-9]*/, "[&]", A)
print A
结果输出
ab12[anan212].45an6a
总结:第二个参数"将替换的新字串"中可用"&"来代表"合於条件的子字串"
[例2]
awk '
BEGIN {
data = "p12-P34 P56-p61"
while( match( data ,/[0-9]+/) > 0) {
print substr(data, RSTART, RLENGTH )
sub(/[0-9]+/,"",data)
}
}'
总结:通过 sub() 与 match()的搭配使用,可逐次取出原字串中合乎指定条件的所有子字串.
结果输出
12
34
56
61
substr(字串,起始位置 [,长度] )
[例1]
$ awk 'BEGIN { print substr("User:Wei-Lin Liu",6)}'
结果印出
Wei-Lin Liu
[例2]从文件的 Fullname 中分离出路径与档名
awk '
BEGIN{
Fullname = "/usr/local/bin/xdvi"
match( Fullname, /.*\//)
path = substr(Fullname, 1, RLENGTH-1)
name = substr(Fullname, RLENGTH+1)
print "path :", path," name :",name
}
' $*
结果印出
path : /usr/local/bin name : xdvi
参考资料
1简体中文版由 bones7456 (bones7456@gmail.com)整理.原文应该是http://phi.sinica.edu.tw/aspac/reports/94/94011/
说