保持快乐,善于表达,敢于创新

awk manual-2

上一篇 / 下一篇  2008-06-23 13:55:13 / 个人分类:Shell编程

5.awk中数组

awk程序中允许使用字符串当做数组的下标(index).利用这个特色十分有助于资料统计工作.(使用字符串当下标的数组称为Associative Array)

首先建立一个数据文件,并取名为reg.dat.此为一学生注册的资料文件;第一栏为学生姓名,其后为该生所修课程.

Mary O.S. Arch. Discrete

Steve D.S. Algorithm Arch.

Wang Discrete Graphics O.S.

Lisa Graphics A.I.

Lily Discrete Algorithm

awk中数组的特性

使用字符串当数组的下标(index).

使用数组前不须宣告数组名及其大小.

例如:希望用数组来记录reg.dat中各门课程的修课人数.

这情况,有二项信息必须储存:

(a)课程名称,: "O.S.","Arch.".. ,共有哪些课程事先并不明确.

(b)各课程的修课人数.:有几个人修"O.S."

awk中只要用一个数组就可同时记录上列信息.其方法如下:

使用一个数组Number[ ] :

以课程名称当Number[ ]的下标.

Number[ ]中不同下标所对映的元素代表修课人数.

例如:

2个学生修"O.S.",则以Number["O.S."] = 2表之.

若修"O.S."的人数增加一人,Number["O.S."] = Number["O.S."] + 1Number["O.S."]++ .

如何取出数组中储存的信息

C语言为例,声明int Arr[100];之后,若想得知Arr[ ]中所储存的数据,只须用一个循环,:

for(i=0; i<100; i++) printf("%d\n", Arr[i]);

即可.上式中:

数组Arr[ ]的下标: 0, 1, 2,..., 99

数组Arr[ ]中各下标所对应的值: Arr[0], Arr[1],...Arr[99]

awk中使用数组并不须事先宣告.以刚才使用的Number[ ]而言,程序执行前,并不知将来有哪些课程名称可能被当成Number[ ]的下标.

awk提供了一个指令,藉由该指令awk会自动找寻数组中使用过的所有下标.Number[ ]为例, awk将会找到"O.S.", "Arch.",...

使用该指令时,须指定所要找寻的数组,及一个变量. awk会使用该的变量来记录从数组中找到的每一个下标.例如

for(course in Number){....}

指定用course来记录awkNumber[ ]中所找到的下标. awk每找到一个下标时,就用course记录该下标之值且执行{....}中之指令.藉由这个方式便可取出数组中储存的信息.

(详见下例)

[范例: ]统计各科修课人数,并印出结果.

建立如下程序,并取名为course.awk:

{ for( i=2; i <= NF; i++) Number[$i]++ }

END{for(course in Number) printf("%10s %d\n", course, Number[course] )}

执行下列命令:

$awk -f course.awk reg.dat

执行结果如下:

  Graphics 2

      O.S. 2

  Discrete 3

      A.I. 1

      D.S. 1

     Arch. 2

 Algorithm 2

[: ]

 

这程序包含二个Pattern { Actions }指令.

{ for( i=2; i <= NF; i++) Number[$i]++ }

END{for(course in Number) printf("%10s %d\n", course, Number[course] )}

第一个Pattern { Actions }指令中省略了Pattern部分.故随着

每笔数据行的读入其Actions部分将逐次无条件被执行.

awk读入第一笔资料" Mary O.S. Arch. Discrete"为例,因为该笔数据NF = 4(4个字段),故该Actionfor Loopi = 2,3,4.

i $i最初Number[$i] Number[$i]++之后

i=2$i="O.S." Number["O.S."]的值从默认的0,变成了1 ;

i=3$i="Arch." Number["Arch."]的值从默认的0,变成了1 ;

同理,i=4$i="Discrete" Number["Discrete"]的值从默认的0,变成了1 ;

 

第二个Pattern { Actions }指令中ENDawk之保留字,Pattern的一种.

END成立(其值为true)的条件是: "awk处理完所有数据,即将离开程序时. "

平常读入数据行时, END并不成立,故其后的Actions并不被执行;

唯有当awk读完所有数据时,Actions才会被执行(注意,不管数据行有多少笔, END仅在最后才成立,故该Actions仅被执行一次.)

BEGINEND有点类似,awk中另一个保留的Pattern.

唯一不同的是: "BEGINPatternActions于程序一开始执行时,被执行一次."

NFawk的内建变量,用以表示awk正处理的数据行中,所包含的字段个数.

 

awk程序中若含有以$开头的自定变量,都将以如下方式解释:

i= 2为例, $i = $2表第二个字段数据. (实际上, $awk中为一运算符(Operator),用以取得字段数据.)

 

6.awk程序中使用Shell命令

awk程序中允许呼叫Shell指令.并提供管道解决awk与系统间数据传递的问题.所以awk很容易使用系统资源.读者可利用这个特点来编写某些适用的系统工具.

[范例: ]写一个awk程序来打印出线上人数.

将下列程序建文件,命名为count.awk

BEGIN {

while ( "who" | getline ) n++

print n

}

并执行下列命令:

awk -f count.awk

执行结果将会印出目前在线人数

[: ]

awk程序并不一定要处理数据文件.以本例而言,仅输入程序文件count.awk,未输入任何数据文件.

BEGINEND同为awk中的一种Pattern.BEGINPatternActions ,只有在awk开始执行程序,尚未开启任何输入文件前,被执行一次.(注意:只被执行一次)

"|"awk中表示管道的符号. awk|之前的字符串"who"当成Shell上的命令,并将该命令送往Shell执行,执行的结果(原先应于屏幕印出者)则藉由pipe送进awk程序中.

getlineawk所提供的输入指令.

其语法如下:

语法

由何处读取数据

数据读入后置于

getline var < file

所指定的file

变量var(var省略时,表示置于$0)

getline var

pipe变量

变量var(var省略时,表示置于$0)

getline var

注一

变量var(var省略时,表示置于$0)

 

注一:PatternBEGINEND, getline将由stdin读取数据,否则由awk正处理的数据文件上读取数据.

getline一次读取一行数据,若读取成功则return 1,若读取失败则return -1,若遇到文件结束(EOF),return 0;

本程序使用getlinereturn的数据来做为while判断循环停止的条件,某些awk版本较旧,并不容许使用者改变$0之值.这种版的awk执行本程序时会产生Error,读者可于getline之后置上一个变量(如此, getline读进来的数据便不会被置于$0 ),或直接改用gawk便可解决.

 

7.awk程序的应用实例

本节将示范一个统计上班到达时间及迟到次数的程序.

这程序每日被执行时将读入二个文件:

员工当日到班时间的数据文件(如下列之arr.dat )

存放员工当月迟到累计次数的文件.

当程序执行执完毕后将更新第二个文件的数据(迟到次数),并打印当日的报表.这程序将分成下列数小节逐步完成,其大纲如下:

 

[7.1]在到班资料文件arr.dat之前增加一行抬头

"ID Number Arrvial Time",并产生报表输出到文件today_rpt1.

<思考:awk中如何将数据输出到文件>

[7.2]today_rpt1上的数据按员工代号排序,并加注执行当日日期;产生文件today_rpt2

<思考awk中如何运用系统资源及awkPipe之特性>

[7.3]awk程序包含在一个shell scrīpt文件中

[7.4]today_rpt2每日报表上,迟到者之前加上"*",并加注当日平均到班时间;

产生文件today_rpt3

[7.5]从文件中读取当月迟到次数,并根据当日出勤状况更新迟到累计数.

<思考使用者在awk中如何读取文件数据>

 

某公司其员工到勤时间档如下,取名为arr.dat.文件中第一栏为员工代号,第二栏为到达时间.本范例中,将使用该文件为数据文件.

1034 7:26

1025 7:27

1101 7:32

1006 7:45

1012 7:46

1028 7:49

1051 7:51

1029 7:57

1042 7:59

1008 8:01

1052 8:05

1005 8:12

 

Ø重定向输出到文件

awk中并未提供如C语言中之fopen()指令,也未有fprintf()文件输出这样的指令.awk中任何输出函数之后皆可借助使用与UNIX中类似的I/O重定向符,将输出的数据重定向到指定的文件;其符号仍为> (输出到一个新产生的文件)>> (添加输出的数据到文件末尾).

[:]在到班数据文件arr.dat之前增加一行抬头如下:

"ID Number Arrival Time",并产生报表输出到文件today_rpt1

建立如下文件并取名为reformat1.awk

BEGIN { print " ID Number Arrival Time" > "today_rpt1"

print "===========================" > "today_rpt1"

}

{ printf(" %s %s\n", $1,$2 ) > "today_rpt1" }

执行:

$awk -f reformat1.awk arr.dat

执行后将产生文件today_rpt1,其内容如下:

ID Number Arrival Time

============================

1034 7:26

1025 7:27

1101 7:32

1006 7:45

1012 7:46

1028 7:49

1051 7:51

1029 7:57

1042 7:59

1008 8:01

1052 8:05

1005 8:12

[:  ]

awk程序中,文件名称today_rpt1的前后须以" (双引号)括住,表示today_rpt1为一字符串常量.若未以"括住,today_rpt1将被awk解释为一个变量名称.

awk中任何变量使用之前,并不须事先声明.其初始值为空字符串(Null string)0.因此程序中若未以"today_rpt1括住,today_rpt1将是一变量,其值将是空字符串,这会在执行时造成错误(Unix无法帮您开启一个以空字符串为文件名的文件).

因此在编辑awk程序时,须格外留心.因为若敲错变量名称,awk在编译程序时会认为是一新的变量,并不会察觉.因此往往会造成运行时错误.

BEGINawk的保留字,Pattern的一种.

BEGINPatternActionsawk程序刚被执行尚未读取数据文件时被执行一次,此后便不再被执行.

读者或许觉得本程序中的I/O重定向符号应使用" >>" (append)而非" >".

本程序中若使用">"将数据重导到today_rpt1, awk第一次执行该指令时会产生一个新档today_rpt1,其后再执行该指令时则把数据追加到today_rpt1文件末,并非每执行一次就重开一个新文件.

若采用">>"其差异仅在第一次执行该指令时,若已存在today_rpt1awk将直接把数据append在原文件之末尾.这一点,UNIX中的用法不同.

Øawk中如何利用系统资源

awk程序中很容易使用系统资源.这包括在程序中途调用Shell命令来处理程序中的部分数据;或在调用Shell命令后将其产生的结果交回awk程序(不需将结果暂存于某个文件).这一过程是借助awk所提供的管道(虽然有些类似Unix中的管道,但特性有些不同),及一个从awk中呼叫UnixShell命令的语法来达成的.

[:]承上题,将数据按员工ID排序后再输出到文件today_rpt2 ,并于表头附加执行时的日期.

[: ]

awk提供与UNIX用法近似的pipe,其记号亦为"|".其用法及含意如下:

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", "cls", "ls"...这样的字符串, awk只把它当成一般字符串处理.

 

建立如下文件并取名为reformat2.awk

#程序reformat2.awk

#这程序用以练习awk中的pipe

BEGIN {

"date" | getline # Shell执行"date". getline取得结果并以$0记录

print " Today is " , $2, $3 >"today_rpt2"

print "=========================" > "today_rpt2"

print " ID Number Arrival Time" >"today_rpt2"

close( "today_rpt2" )

}

{printf( "%s %s\n", $1 ,$2 ) | "sort -k 1 >>today_rpt2"}

执行如下命令:

awk -f reformat2.awk arr.dat

执行后,系统会自动将sort后的数据追加( Append;因为使用" >>")到文件today_rpt2末端. today_rpt2内容如下:

 Today is  0921

=========================

 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

 

[: ]

awk程序由三个主要部分构成:

[ i.] Pattern { Action}指令

[ ii.]函数主体.例如: function double( x ){ return 2*x } (参考第11Recursive Program )

[ iii.] Comment (#开头识别之)

awk的输入指令getline,每次读取一列数据.getline之后

未接任何变量,则所读入之资料将以$0记录,否则以所指定的变量储存之.

[以本例而言] :

执行"date" | getline, $0之值为"20070921星期五14:28:02 CST",$0之值被更新时, awk将自动更新相关的内建变量,: $1,$2,..,NF.$2之值将为"09", $3之值将为"21".

(有少数旧版的awk不允许即使用者自行更新(update)$0的值,或者更新$0,它不会自动更新$1,$2,..NF.这情况下,可改用gawknawk.否则使用者也可自行以awk字符串函数split()来分隔$0上的数据)

本程序中printf()指令会被执行12(因为有arr.dat中有12行数据),但读者不用担心数据被重复sort12.awk结束该程序时才会close这个pipe ,此时才将这12行数据一次送往系统,并呼叫"sort -k 1 >> today_rpt2"处理之.

awk提供另一个调用Shell命令的方法,即使用awk函数system("shell命令")

例如:

$ awk '

BEGIN{

system("date > date.dat")

getline < "date.dat"

print "Today is ", $2, $3

}

'

但使用system( "shell命令" ), awk无法直接将执行中的部分数据输出给Shell命令.Shell命令执行的结果也无法直接输入到awk.


TAG:

 

评分:0

我来说两句

Open Toolbar