awk的使用 第六部分 awk编程的几个实例

在这里举个例子,统计上班到达时间及迟到次数的程序。这程序每日被执行时将读入二个文件:员工当日上班时间的数据文件 ( arrive.dat ) 存放员工当月迟到累计次数的文件当程序执行执完毕后将更新第二个文件的数据(迟到次数), 并打印当日的报表。
    此程序的步骤分析如下:
    [6.1] 在上班数据文件arrive.dat之前增加一行标题 “ID Number Arrvial Time”,并产生报表输出到文件today_result1中。   
    [6.2]将today_result1上的数据按员工代号排序, 并加注执行当日日期;  产生文件today_result2
    [6.3] 将awk程序包含在一个shell script文件中
    [6.4] 在today_result2 每日报表上,迟到者之前加上”*”,并加注当日平均上班时间;产生文件today_result3
    [6.5] 从文件中读取当月迟到次数, 并根据当日出勤状况更新迟到累计数。


    上班时间数据文件arrive.dat的格式是:第一列是员工代号,第二列是到达时间,内容如下:
[root@myfreelinux pub]# cat arrive.dat
A034 7:26
A025 7:27
A101 7:32
A006 7:45
A012 7:46
A028 7:49
A051 7:51
A029 7:57
A042 7:59
A008 8:01
A052 8:05
A005 8:12

    说 明:awk程序中,文件名称today_result1的前后需要用” (双引号)括起来,表示today_result1是一个字符串常量。如果没有用双括号括起来,today_result1将被awk解释为一个变量名称。在awk中,变量不需要事先声明,变量的初始值为空字符串(Null string) 或0。因此awk程序中如果没有将today_result1用双括号括起来,那么awk将today_result1作为一个变量来使用,它的值是空字符串,那么在执行时造成错误(Unix无法开启一个以空字符串为文件名的文件)。因此在编写awk程序时,一定要将文件名用双括号括起来。BEGIN是awk 的保留字,是Pattern的一种。以BEGIN为Pattern的Actions在awk程序刚被执行尚未读取数据文件时被执行一次,此后便不再被执行。本程序中若使用”>” 将数据重定向到today_result1,awk第一次执行该指令时会产生一个新文件today_result1,再执行该指令时则把数据追加到today_result1的文件结尾,而不是每执行一次就打开一次该文件。而”>>”和“>”的差异仅在执行该指令时,如果已存在today_result1则awk将直接把数据append在原文件的末尾。

    awk 中如何利用系统资源
    awk程序中可以很容易的使用系统资源。比如1、在程序中途调用Shell命令来处理程序中的部分数据;2、在调用Shell命令后将其产生的结果交回awk 程序(不需将结果暂存于某个文件)。这一过程是使用awk 所提供的管道(类似于Unix中的管道,但又有些不同),和一个从awk 中调用Unix的Shell命令的语法来实现的。
    例:承上题,将数据按员工ID排序后再输出到文件today_result2, 并在表头上添加执行时的日期。
    分 析
    awk 提供了和UNIX用法类似的管道命令”|”。在awk中管道的用法和含意如下:awk程序中可接受下列两种语法:
     [a语法] awk output 指令| “Shell 接受的命令” ( 如:print $1,$2 | “sort -k 1″ )  
     [b语法] “Shell 接受的命令” | awk input 指令 ( 如:”ls ” | getline)  。
    awk的input指令只有getline 这一个输入指令。awk的output 指令有print,printf() 二个
    在a语法中,awk所输出的数据将转送往Shell ,由Shell的命令进行处理。以上例而言,print所输出的数据将经由Shell 命令”sort – k 1″排序后再输出到屏幕(stdout)。上例awk程序中,”print $1,$2″ 可能反复执行很多次,输出的结果将先暂存在pipe中,等程序结束后,才会一并进行排序”sort -k 1″。须注意二点:不论print $1,$2被执行几次,”sort -k 1″的执行时间是”awk程序结束时”,”sort -k 1″ 的执行次数是”一次”。  
    在b语法中,awk将先调用Shell命令,执行结果将通过pipe传送到awk程序,以上例而言,awk先让Shell执行”ls”,Shell执行后将结果保存在pipe中,awk的输入指令getline再从pipe中读取数据。使用本语法时应注意:在上例中,awk”立刻”调用Shell 来执行 ”ls”,执行次数是一次。getline则可能执行多次(如果pipe 中存在多行数据)。
    除以上a,b两种语法外,awk程序中其它地方如出现像”date”,”ls”…这样的字符串,awk只把它当成一般字符串处理。  
   根据以上分析,建立awk程序脚本内容如下:
    [root@myfreelinux pub]# cat reformat2.awk
#!/bin/awk -f
BEGIN{
“date”|getline;#在shell中执行date指令,并将结果存储到$0中。
print “Today is”,$2,$3>”today_result2″;
print “ID Number    Arrival Time”>”today_result2″;
print “=========================”>”today_result2″;
close(“today_result2″);
}
{printf(“%s/t/t%s/n”,$1,$2)|”sort -k 1 >> today_result2″;}
执行该程序脚本:
[root@myfreelinux pub]# awk -f reformat2.awk arrive.dat
    执行后,程序将sort后的数据追加(Append)到文件today_result2最末端。today_result2内容如下:
[root@myfreelinux pub]# cat today_result2
Today is 06月 17日
ID Number    Arrival Time
=========================
A005  8:12
A006  7:45
A008  8:01
A012  7:46
A025  7:27
A028  7:49
A029  7:57
A034  7:26
A042  7:59
A051  7:51
A052  8:05
A101  7:32
    解释说明:awk程序由三个主要部分构成:1、Pattern { Action} 指令,2、函数主体。例如:function double( x ){ return 2*x },3、注释Comment ( 以# 开头识别之 )。
     awk 的输入指令getline,每次读取一列数据。如果getline之后没接任何变量,读入的数据将存储到$0中,否则以所指定的变量储存储。
    比如在执行”date”|getline后,$0 的值是2010年 06月 17日 星期四 10:43:16 CST,当$0被更新时,awk将自动更新相关的内建变量,如:$1,$2,…,NF。所以$2的值是”06月”,$3的值是“17日”。
    本程序中printf() 指令会被执行12次( 因为有arrive.dat中有12行数据),在awk结束该程序时会close这个pipe ,此时才将12行数据一次送往系统,并由”sort -k 1 >> today_result2″进行处理,注意”>>”的左侧一定要有空格,否则在执行的时候会报错“sort:选项需要一个参数 -k”,awk还提供了另一种调用Shell命令的方法,即使用awk 函数system(“shell 命令”) ,例如:
[root@myfreelinux pub]# awk ‘BEGIN{system(“date>date.dat”);getline<”date.dat”;print “Today is “,$2,$3}’,但使用 system( “shell 命令” ) 时,awk无法直接将执行中的数据输出给Shell 命令。且Shell命令执行的结果也无法直接输入到awk 中。

    执行awk 程序的几种方式 
    一下部分将介绍如何将awk程序直接写在shell script 之中,这样在执行的时候就不需要在命令行每次都输入” awk -f program  datafile” 了。script中还可包含其它Shell 命令,这样可增加执行过程的自动化。建立一个简单的awk程序mydump.awk,如下: {print}这个程序执行时会把数据文件的内容print到屏幕上( 与cat功用类似  )。print 之后未接任何参数时,表示 “print $0″。如果要执行这个awk程序输出today_result1和today_result2 的内容时,在unix命令行上,有以下几种方式:
    一、awk -f mydump.awk today_result1 today_result2 
    二、awk ‘{print}’ today_result1 today_result2
    第二种方法将awk 程序直接写在Shell的命令行上,这种方法仅适合awk程序较短的时候。
    三、使用shell脚本。建立一个shell脚本mydisplay,内容如下:
[root@myfreelinux pub]# cat mydisplay
#!/bin/bash  # 注意awk 与’ 之间须有空白隔开
awk ‘{print}’ $*# 注意’ 与$* 之间须有空白隔开
     在执行mydisplay 之前,需要给它添加可执行权限,首先执行如下命令: [root@myfreelinux pub]# chmod +x mydisplay,这样mydisplay才有可执行权限,[root@myfreelinux pub]# ./mydisplay today_result1 today_result2就可以执行了,当然,如果没有给他可执行权限的话,也可以使用一下方法来执行:[root@myfreelinux pub]# bash mydisplay today_result1 today_result2
     说明:在script文件mydisplay 中,指令”awk”与第一个’ 之间须有空格(Shell中并无” awk’ “指令)。第一个’用来通知Shell其后为awk程序,第二个’ 则表示awk 程序结束。所以awk程序中一律以”括住字符串或字符,而不能使用’括住字符串或字符,以免引起Shell混淆。$* 是shell script 中的用法,可用来代表命令行上”mydisplay之后的所有参数”。例如执行:[root@myfreelinux pub]# ./mydisplay today_result1 today_result2事实上Shell 已先把该指令转换成:awk ‘ { print} ‘ today_result1 today_result2 。本例中,$*代表”today_result1 today_result2″。在Shell的语法中, $1代表第一个参数,$2 代表第二个参数。当不确定命令行上的参数个数时,可使用$*代表。awk命令行上可同时指定多个数据文件。以awk -f dump.awk today_result1 today_result2hf 为例,awk会先处理today_result1,再处理today_result2,如果文件不存在或无法打开,会提示相应的错误,比如awk: (FILENAME=today_result1 FNR=14) fatal: cannot open file `today_result’ for reading (没有那个文件或目录)。
    有些awk程序”仅” 包含以BEGIN为Pattern的指令,这种awk 程序执行时不须要数据文件,如果在命令行上指定一个不存在的数据文件,awk不会产生”无法打开文件”的错误(事实上awk并未打开该文件) 。例如执行:[root@myfreelinux pub]#awk ‘BEGIN {print “Hello,World!!”} ‘ file_no_exist ,该程序中仅包含以BEGIN 为Pattern的指令,awk 执行时并不会开启任何数据文件; 所以不会因不存在文件file_no_exit 产生” 无法打开文件”的错误。
    awk会将Shell 命令行上awk程序(或 -f 程序文件名)之后的所有字符串,视为将输入awk进行处理的数据文件文件名。如果执行awk 的命令行上”未指定任何数据文件文件名”,则将stdin作为数据源,直到输入end of file( Ctrl-D )为止。比如执行如下命令:
[root@myfreelinux pub]#awk -f mydump.awk  #(未接任何数据文件文件名)

[root@myfreelinux pub]#./mydisplay  #(未接任何数据文件文件名) 
    会发现:此后键入的任何数据将逐行复印一份显示到屏幕上,这种情况是因为执行时没有指定数据文件文件名,awk 便以stdin(键盘上的输入)做为数据来源。那么我们可利用这个特点,设计与awk即时聊天的程序:mrgreen: 。  

    改变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)
[root@myfreelinux pub]# awk ‘BEGIN{“date”|getline;print substr($5,1,2)*60+substr($5,4,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是1034,$2是7,$3是26,显然,awk程序中使用方法二要比方法一更简洁方便。所以本例中使用方法二,来介绍改变字段切割方式的用法。
     编写awk程序reformat3,如下:
[root@myfreelinux pub]# cat reformat3.awk
#!/bin/bash
awk ‘BEGIN{
FS=”[ /t:]+”;
“date”|getline;
print “Today is”,$2,$3 > “today_result3″;
print “==================” > “today_result3″;
print “ID Number  Arrival Time” > “today_result3″;
close(“today_result3″);
}
{
arrival=HM_TO_M($2,$3);
printf(“%s/t/t%s:%s %s/n”,$1,$2,$3,arrival>”480″?”*”:” “)|”sort -k 1 >> today_result3″;
total+=arrival;
}
END{
close(“sort -k 1 >> today_result3″);
printf(“Average arrival time:  %d:%d/n”,total/NR/60, total/NR%60) >> “today_result3″;
close(“today_result3″);
 }
function HM_TO_M(hour,min){ return hour*60 + min }’ $*
     并执行如下指令 :
[root@myfreelinux pub]# bash reformat3.awk arrive.dat
     执行后,文件 today_result3 的内容如下: 
[root@myfreelinux pub]# cat today_result3
Today is 06月 17日
==================
ID Number  Arrival Time
A005  8:12 *
A006  7:45 
A008  8:01 *
A012  7:46 
A025  7:27 
A028  7:49 
A029  7:57 
A034  7:26 
A042  7:59 
A051  7:51 
A052  8:05 *
A101  7:32 
Average arrival time:  7:49
    说明:awk 中允许自定义函数,函数定义方式可参考本程序,function是awk的保留字。HM_TO_M( ) 这函数负责将传入的小时和分钟数转换成以分钟为单位的时间。在printf()中使用的arrival >480 ? “*” :” “是一个三元运算符,如果arrival 大于480则return “*” ,否则return ” “。   
    % 是awk的运算符(operator),作用与C 语言中的% 相同(取余数)。
    NR(Number of Record) 为awk 的内建变量,表示awk执行该程序后所读入的记录笔数。  
    close的语法有二种:close( filename ) 和close( 置于pipe之前的command ) 。本程序使用了两个close( ) 指令:指令close( “sort -k 1 >> today_result3″ ),意思是close程序置于”sort -k 1 >> today_result3 ” 之前的Pipe , 并立刻调用Shell 来执行”sort -k 1 >> today_result3″。 因为 Shell 排序后的数据也要写到 today_result3, 所以awk必须先关闭 使用中的today_result3 以使 Shell 正确将排序后的数据追加到 today_result3否则2个不同的 process 同时打开一个文件进行输出将会 产生不可预期的结果。 读者应留心上述两点,才可正确控制数据输出到文件中的顺序。指令close(“sort -k 1 >> today_result3″)中字符串 “sort +0n >>  today_result3″ 必须与pipe |后方的Shell Comman名称一字不差,否则awk将视为二个不同的pipe。

    ?使用getline 来读取数据 
    范例: 承上题,从文件中读取当月迟到次数,并根据当日出勤状况更新迟到累计数。(按不同的月份累计于不同的文件) 
    分析: 程序中自动抓取系统日期的月份名称,连接上”late.dat”, 形成累计迟到次数的文件名称(如:09月late.dat,。。。), 并以变量late_file记录该文件名。累计迟到次数的文件中的数据格式为:员工代号(ID) 迟到次数。例如,执行本程序前文件09月late.dat 的内容如下:
[root@myfreelinux pub]# cat late.dat
A005  2
A006  1 
A008  2
A012  0 
A025  0 
A028  1 
A029  2 
A034  0 
A042  0 
A051  0 
A052  3
A101  0
     编写程序reformat4 如下:[root@myfreelinux pub]# cat reformat4.awk
#!/bin/bash
awk ‘BEGIN{
sys_sort=”sort -k 1 >> today_result4″;
result=”today_result4″;
FS=”[ /t:]+”;#改变字段切割的方式
“date”|getline;#令Shell执行”date”;getline读取结果,并以$0记录结果
print “Today is”,$2,$3 > result;
print “=======================”>result;
print “ID Number  Arrival Time”> result;
close(result);
late_file=$2″late.dat”;
while(getline<late_file >0) #从文件按中读取迟到数据,并用数组cnt[]记录。
 cnt[$1]=$2 #数组cnt[]中以员工代号为下标,所对应的值为该员工之迟到次数
close(late_file)
}
{arrival=HM_TO_M($2,$3);#已更改字段切割方式,$2表小时数,$3表分钟数
if(arrival>480)
 {mark=”*”; #若当天迟到,应再增加其迟到次数,令mark为”*”
 cnt[$1]++;}
else
 mark=” “;
message=cnt[$1]?cnt[$1]“times”:” “;# message用以显示该员工的迟到累计数,若未曾迟到message为空字符串
printf(“%s/t/t%2s:%2s %5s %s/n”,$1,$2,$3,mark,message)|sys_sort;
total+=arrival;
}
END{
close(result);
close(sys_sort);
printf(“Arrivage arrival time: %d:%d/n”,total/NR/60,total/NR%60) >> result;
for(any in cnt) #将数组cnt[]中新的迟到数据写回文件中
 print any,cnt[any] > late_file;
}
function HM_TO_M(hour,minute){return hour*60+minute;}
‘ $*
执行后结果如下:
[root@myfreelinux pub]# bash reformat4.awk arrive.dat
[root@myfreelinux pub]# cat today_result4
Today is 06月 18日
=======================
ID Number  Arrival Time
A005   8:12     * 1times
A006   7:45       
A008   8:01     * 1times
A012   7:46       
A025   7:27       
A028   7:49       
A029   7:57       
A034   7:26       
A042   7:59       
A051   7:51       
A052   8:05     * 1times
A101   7:32       
Arrivage arrival time: 7:49
06月late.dat的内容如下:
[root@myfreelinux pub]# cat 06月late.dat
A028
A029
A012
A005 1
A042
A051
A006
A101
A052 1
A025
A034
A008 1
说 明:由于文件06月late.dat中保存了一些数据内容,在这里我没有做更多的修改,所以每次执行[root@myfreelinux pub]# bash reformat4.awk arrive.dat的时候,迟到次数都会增加,您可以根据实际情况,再做一下修改,做的更完美。
    late_file是一变量,记录迟到次数的文件的文件名。late_file值由两部分构成,前半部是当月月份名称(由调用”date”取得)后半部固定为”late.dat” 如:06月late.dat。指令getline < late_file 表示从late_file所代表的文件中读取一笔记录,并存放于$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 将更新过的迟到数据重新写回记录迟到次数的文件。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值