awk manual-3
上一篇 / 下一篇 2008-06-23 13:58:25 / 个人分类:Shell编程
Ø执行awk程序的几种方式
本小节中描述如何将awk程序直接写在shell scrīpt之中.此后使用者执行awk程序时,就不需要每次都键入" awk -f program datafile" .
scrīpt中还可包含其它Shell命令,如此更可增加执行过程的自动化.
建立一个简单的awk程序mydump.awk,如下:
{print}
这个程序执行时会把数据文件的内容print到屏幕上(与cat功用类似).
print之后未接任何参数时,表示"print $0".
若欲执行该awk程序,来印出文件today_rpt1及today_rpt2的内容时,
必须于UNIX的命令行上执行下列命令:
方式一awk -f mydump.awk today_rpt1 today_rpt2
方式二awk '{print}' today_rpt1 today_rpt2第二种方式系将awk程序直接写在Shell的命令行上,这种方式仅适合较短的awk程序.
方式三建立如下之shell scrīpt,并取名为mydisplay,
#!/bin/sh
#注意以下的awk与'之间须有空白隔开
awk '
{print}
' $*
#注意以上的'与$*之间须有空白隔开
执行mydisplay之前,须先将它改成可执行的文件(此步骤往后不再赘述).请执行如下命令:
$ chmod +x mydisplay
往后使用者就可直接把mydisplay当成指令,来display任何文件.
例如:
$ ./mydisplay today_rpt1 today_rpt2
[说明: ]
在scrīpt文件mydisplay中,指令"awk"与第一个' 之间须有空格(Shell中并无" awk' "指令).
第一个'用以通知Shell其后为awk程序.
第二个'则表示awk程序结束.
故awk程序中一律以"括住字符串或字符,而不使用' ,以免Shell混淆.
$*为shell scrīpt中的用法,它可用来代表命令行上"mydisplay之后的所有参数".
例如执行:
$ mydisplay today_rpt1 today_rpt2
事实上Shell已先把该指令转换成:
awk '
{ print}
' today_rpt1 today_rpt2
本例中, $*用以代表"today_rpt1 today_rpt2".在Shell的语法中,可用$1代表第一个参数, $2代表第二个参数.当不确定命令行上的参数个数时,可使用$*表之.
awk命令行上可同时指定多个数据文件.
以awk -f dump.awk today_rpt1 today_rpt2hf为例,awk会先处理today_rpt1,再处理today_rpt2.此时若文件无法打开,将造成错误.
例如:不存在文件"file_no_exist",则执行:
$ awk -f dump.awk file_no_exit
将产生运行时错误(无法打开文件).
但某些awk程序"仅"包含以BEGIN为Pattern的指令.执行这种awk程序时, awk并不须开启任何数据文件.此时命令行上若指定一个不存在的数据文件,并不会产生"无法打开文件"的错误.(事实上awk并未打开该文件)
例如执行:
$ awk 'BEGIN {print "Hello,World!!"} ' file_no_exist
该程序中仅包含以BEGIN为Pattern的Pattern {actions}, awk执行时并不会开启任何数据文件;所以不会因不存在文件file_no_exit而产生"无法打开文件"的错误.
awk会将Shell命令行上awk程序(或-f程序文件名)之后的所有字符串,视为将输入awk进行处理的数据文件文件名.
若执行awk的命令行上"未指定任何数据文件文件名",则将stdin视为输入之数据来源,直到输入end of file( Ctrl-D )为止.
读者可以用下列程序自行测试,执行如下命令:
$ awk -f mydump.awk #(未接任何数据文件文件名)
或
$ ./mydisplay #(未接任何数据文件文件名)
将会发现:此后键入的任何数据将逐行复印一份于屏幕上.这情况不是机器当机!是因为awk程序正处于执行中.它正按程序指示,将读取数据并重新dump一次;只因执行时未指定数据文件文件名,故awk便以stdin(键盘上的输入)为数据来源.读者可利用这个特点,设计可与awk即时聊天的程序.
Ø改变awk切割字段的方式&自定义函数
awk不仅能自动分割字段,也允许使用者改变其字段切割方式以适应各种格式之需要.使用者也可自定义函数,若有需要可将该函数单独写成一个文件,以供其它awk程序调用.
[范例: ]承接6.2的例子,若八点为上班时间,请加注"*"于迟到记录之前,并计算平均上班时间.
[分析: ]
因八点整到达者,不为迟到,故仅以到达的小时数做判断是不够的;仍应参考到达时的分钟数.若"将到达时间转换成以分钟为单位",不仅易于判断是否迟到,同时也易于计算到达平均时间.
到达时间($2)的格式为dd:dd或d:dd;数字当中含有一个":".但文本数字交杂的数据awk无法直接做数学运算. (注: awk中字符串"26"与数字26,并无差异,可直接做字符串或数学运算,这是awk重要特色之一.但awk对文本数字交杂的字符串无法正确进行数学运算).
解决之方法:
[方法一]
对到达时间($2) d:dd或dd:dd进行字符串运算,分别取出到达的小时数及分钟数.
首先判断到达小时数为一位或两位字符,再调用函数分别截取分钟数及小时数.
此解法需使用下列awk字符串函数:
length(字符串) :返回该字符串的长度.
substr(字符串,起始位置,长度) :返回从起始位置起,指定长度之子字符串.若未指定长度,则返回从起始位置到字符串末尾的子字符串.
所以:
小时数= substr( $2, 1, length($2) - 3 )
分钟数= substr( $2, length($2) - 2 )
[方法二]
改变输入列字段的切割方式,使awk切割字段后分别将小时数及分钟数隔开于二个不同的字段.
字段分隔字符FS (field seperator)是awk的内建变量,其默认值是空白及tab. awk每次切割字段时都会先参考FS的内容.若把":"也当成分隔字符,则awk便能自动把小时数及分钟数分隔成不同的字段.故令FS = "[ \t:]+" (注: [ \t:]+为一Regular Expression )
Regular Expression中使用中括号[ ... ]表示一个字符集合,用以表示任意一个位于两中括号间的字符.故可用"[ \t:]"表示一个空白, tab或":"
Regular Expression中使用"+"形容其前方的字符可出现一次
或一次以上.
故"[ \t:]+"表示由一个或多个"空白, tab或: "所组成的字符串.
设定FS ="[ \t:]+"后,数据行如: "1034 7:26"将被分割成3个字段
第一栏第二栏第三栏
$1 $2 $3
1034 7 26
明显地, awk程序中使用方法二比方法一更简洁方便.本例子中采用方法二,也借此示范改变字段切割方式的用途.
编写awk程序reformat3,如下:
#!/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"
}
function HM_to_M( hour, min ){
return hour*60 + min
}
' $*
并执行如下指令:
$ ./reformat3 arr.dat
执行后,文件today_rpt3的内容如下:
Today is 09月21日
=========================
ID Number Arrival Time
1005 8:12 *
1006 7:45
1008 8:01 *
1012 7:46
1025 7:27
1028 7:49
1029 7:57
1034 7:26
1042 7:59
1051 7:51
1052 8:05 *
1101 7:32
Average arrival time : 7:49
[说明: ]
awk中亦允许使用者自定函数.函数定义方式请参考本程序, function为awk的保留字.
HM_to_M( )这函数负责将所传入之小时及分钟数转换成以分钟为单位.使用者自定函数时,还有许多细节须留心,如data scope,.. (请参考第十节Recursive Program)
awk中亦提供与C语言中相同的Conditional Operator.上式printf()中使用arrival >480 ? "*" : " "即为一例若arrival大于480则return "*" ,否则return " ".
%为awk的运算符(operator),其作用与C语言中之%相同(取余数).
NR(Number of Record)为awk的内建变量.表示awk执行该程序后所读入的记录笔数.
awk中提供的close( )指令,语法如下(有二种) :
close( filename )
close(置于pipe之前的command )
为何本程序使用了两个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 arrival time : ..."的后方)
因为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代替该字符串.
Ø使用getline来读取数据
[范例: ]承上题,从文件中读取当月迟到次数,并根据当日出勤状况更新迟到累计数.(按不同的月份累计于不同的文件)
[分析: ]
程序中自动抓取系统日期的月份名称,连接上"late.dat",形成累计迟到次数的文件名称(如: 09月late.dat,...),并以变量late_file记录该文件名.
累计迟到次数的文件中的数据格式为:员工代号(ID)迟到次数
例如,执行本程序前文件09月late.dat的内容为:
1012 0
1006 1
1052 2
1034 0
1005 0
1029 2
1042 0
1051 0
1008 0
1101 0
1025 1
1028 0
编写程序reformat4如下:
#!/bin/sh
awk '
BEGIN {
Sys_Sort = "sort -k 1 >> today_rpt4"
Result = "today_rpt4"
#改变字段切割的方式
FS = "[ \t:]+"
#令Shell执行"date"; getline读取结果,并以$0记录
"date" | getline
print " Today is " , $2, $3 >Result
print "=========================" > Result
print " ID Number Arrival Time" > Result
close( Result )
#从文件按中读取迟到数据,并用数组cnt[ ]记录.数组cnt[ ]中以
#员工代号为下标,所对应的值为该员工之迟到次数.
late_file = $2"late.dat"
while( getline < late_file >0 ) cnt[$1] = $2
close( late_file )
}
{
#已更改字段切割方式, $2表小时数,$3表分钟数
arrival = HM_to_M($2, $3)
if( arrival > 480 ){
mark = "*" #若当天迟到,应再增加其迟到次数,且令mark为"*".
cnt[$1]++ }
else mark = " "
# message用以显示该员工的迟到累计数,若未曾迟到message为空字符串
message = cnt[$1] ? cnt[$1] " times" : ""
printf("%s %2d:%2d %5s %s\n", $1, $2, $3, mark, message ) | Sys_Sort
total += arrival
}
END {
close( Result )
close( Sys_Sort )
printf(" Average arrival time : %d:%d\n", total/NR/60, (total/NR)%60 ) >> Result
#将数组cnt[ ]中新的迟到数据写回文件中
for( any in cnt )
print any, cnt[any] > late_file
}
function HM_to_M( hour, min ){
return hour*60 + min
}
' $*
执行后, today_rpt4之内容如下:
Today is 09月21日
=========================
ID Number Arrival Time
1005 8:12 * 1 times
1006 7:45 1 times
1008 8: 1 * 1 times
1012 7:46
1025 7:27 1 times
1028 7:49
1029 7:57 2 times
1034 7:26
1042 7:59
1051 7:51
1052 8: 5 * 3 times
1101 7:32
Average arrival time : 7:49
09月late.dat文件被修改为如下:
1005 1
1012 0
1006 1
1008 1
1101 0
1025 1
1034 0
1042 0
1028 0
1029 2
1051 0
1052 3
说明:
late_file是一变量,用以记录迟到次数的文件的文件名.
late_file之值由两部分构成,前半部是当月月份名称(由调用"date"取得)后半部固定为"late.dat"如: 09月late.dat.
指令getline < late_file表示从late_file所代表的文件中读取一笔记录,并存放于$0.
若使用者可自行把数据放入$0, awk会自动对这新置入$0的数据进行字段分割.之后程序中可用$1, $2,..来表示该笔资料的第一栏,第二栏,..,
(注:有少数awk版本不容许使用者自行将数据置于$0,遇此情况可改用gawk或nawk)
执行getline指令时,若成功读取记录,它会返回1.若遇到文件结束,它返回0;无法打开文件则返回-1.
利用while( getline < filename >0 ) {....}可读入文件中的每一笔数据并予处理.这是awk中用户自行读取数据文件的一个重要模式.
数组cnt[ ]以员工ID.当下标(index),其对应值表示其迟到的次数.
执行结束后,利用for(Variable in array ){...}的语法
for( any in cnt ) print any, cnt[any] > late_file
将更新过的迟到数据重新写回记录迟到次数的文件.该语法在前面曾有说明.
TAG:
标题搜索
日历
|
|||||||||
日 | 一 | 二 | 三 | 四 | 五 | 六 | |||
1 | 2 | 3 | 4 | ||||||
5 | 6 | 7 | 8 | 9 | 10 | 11 | |||
12 | 13 | 14 | 15 | 16 | 17 | 18 | |||
19 | 20 | 21 | 22 | 23 | 24 | 25 | |||
26 | 27 | 28 | 29 | 30 | 31 |
我的存档
数据统计
- 访问量: 47625
- 日志数: 70
- 建立时间: 2007-07-05
- 更新时间: 2011-11-08