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

awk manual-4

上一篇 / 下一篇  2008-06-23 14:01:21 / 个人分类:Shell编程

8.处理多行的数据

awk每次从数据文件中只读取一数据进行处理.

awk是依照其内建变量RS(Record Separator)的定义将文件中的数据分隔成一行一行的Record. RS的默认值是"\n"(跳行符号),故平常awk中一行数据就是一笔Record.但有些文件中一笔Record涵盖了多行数据,这种情况下不能再以"\n"来分隔Records.最常使用的方法是相邻的Records之间改以一个空白行来隔开.awk程序中,RS = ""(空字符串), awk把会空白行当成来文件中Record的分隔符.显然awkRS = ""另有解释方式,简略描述如下,RS = "":数个并邻的空白行, awk仅视成一个单一的Record Saparator. (awk不会于两个紧并的空白行之间读取一笔空的Record)

awk会略过(skip)文件头或文件尾的空白行.故不会因为这样的空白行,造成awk多读入了二笔空的数据.

请观察下例,首先建立一个数据文件week.rpt如下:

 

 

张长弓

GNUPLOT入门

 

 

 

吴国强

Latex简介

VAST-2使用手册

mathematic入门

 

李小华

awk Tutorial Guide

Regular Expression

 

 

该文件的开头有数行空白行,各笔Record之间使用一个或数个空白行隔开.读者请细心观察,RS = "", awk读取该数据文件之方式.

编辑一个awk程序文件make_report如下:

#!/bin/sh

 

awk '

BEGIN {

FS = "\n"

RS = ""

split( ".........", C_Number, " " )

}

{

printf("\n%s报告人: %s \n",C_Number[NR],$1)

for( i=2; i <= NF; i++) printf(" %d. %s\n", i-1, $i)

} ' $*

执行

$ make_report week.rpt

屏幕产生结果如下:

 

.报告人:张长弓

 1. GNUPLOT入门

 

.报告人:吴国强 

 1. Latex简介

 2. VAST-2使用手册

 3. mathematic入门

 

.报告人:李小华 

 1. awk Tutorial Guide

 2. Regular Expression

[: ]

本程序同时也改变字段分隔字符( FS= "\n" ),如此一笔数据中的每一行都是一个field.例如: awk读入的第一笔Record

张长弓

GNUPLOT入门

其中$1指的是"张长弓", $2指的是"GNUPLOT入门"

上式中的C_Number[ ]是一个数组(array),用以记录中文数字.例如: C_Number[1] = ".", C_Number[2] = "."这过程使用awk字符串函数split( )来把中文数字放进数组C_Number[ ].

函数split( )用法如下:

split(原字符串,数组名,分隔字符(field separator) ) : awk将依所指定的分隔字符(field separator)分隔原字符串成一个个的字段(field),并以指定的数组记录各个被分隔的字段

9.如何读取命令行上的参数

大部分的应用程序都允许使用者在命令之后增加一些选择性的参数.执行awk时这些参数大部分用于指定数据文件文件名,有时希望在程序中能从命令行上得到一些其它用途的数据.本小节中将叙述如何在awk程序中取用这些参数.

建立文件如下,命名为see_arg :

#!/bin/sh

 

awk '

BEGIN {

for( i=0; i<ARGC ; i++)

print ARGV[i] #依次印出awk所记录的参数

}

' $*

执行如下命令:

$ ./see_arg first-arg second-arg

结果屏幕出现:

awk

first-arg

second-arg

[说明: ]

ARGC, ARGV[ ]awk所提供的内建变量.

 

ARGC :为一整数.代表命令行上,除了选项-v, -f及其对应的参数之外所有参数的数目.

ARGV[ ] :为一字符串数组. ARGV[0],ARGV[1],...ARGV[ARGC-1].

分别代表命令行上相对应的参数.

 

例如,当命令行为:

$ awk -vx=36 -f program1 data1 data2

$ awk '{ print $1 ,$2 }' data1 data2

ARGC之值为3

ARGV[0]之值为"awk"

ARGV[1]之值为"data1"

ARGV[2]之值为"data2"

命令行上的"-f program1", " -vx=36",或程序部分'{ print $1, $2}'都不会列入ARGCARGV[ ].

awk利用ARGC来判断应开启的数据文件个数.

但使用者可强行改变ARGC;ARGC之值被使用者设为1;

awk将被蒙骗,误以为命令行上并无数据文件文件名,故不会以ARGV[1], ARGV[2],..为文件名来打开文件读取数据;但在程序中仍可通过ARGV[1], ARGV[2],..来取得命令行上的数据.

 

 

某一程序test1.awk如下:

BEGIN{

number = ARGC #先用number记住实际的参数个数.

ARGC = 2 #自行更改ARGC=2, awk将以为只有一个资料文件

#仍可藉由ARGV[ ]取得命令行上的资料.

for( i=2; i<number; i++) data[i] = ARGV[i]

}

........

于命令行上键入

$ awk -f test1.awk data_file apple orange

执行时awk会打开数据文件data_file以进行处理.但不会打开以apple,orange为档名的文件(因为ARGC被改成2).但仍可通过ARGV[2], ARGV[3]取得命令行上的参数apple, orange

 

也可以用下列命令来达成上例的效果.

$awk -f test2.awk -v data[2]="apple" -v data[3]="orange" data_file

 

10.        编写可与用户交互的awk程序

执行awk程序时, awk会自动从文件中读取数据来进行处理,直到文件结束.只要将awk读取数据的来源改成键盘输入,便可设计与awk交互的程序了.

本节将提供一个该类程序的范例.

[范例:]本节将编写一个英语生字测验的程序,它将印出中文字意,再由使用者回答其英语生字.

首先编辑一个数据挡test.dat (内容不限,格式如下)

apple苹果

orange柳橙

banana香蕉

pear梨子

starfruit杨桃

bellfruit莲雾

kiwi奇异果

pineapple菠萝

watermelon西瓜

 

编辑awk程序"c2e"如下:

#!/bin/sh

 

awk '

BEGIN {

while( getline < ARGV[1] ){ #由指定的文件中读取测验数据

English[++n] = $1 #最后, n将表示题目之题数

Chinese[n] = $2

}

ARGV[1] = "-" # "-"表示由stdin(键盘输入)

srand() #以系统时间为随机数启始的种子

question() #产生考题

}

 

{# awk自动读入由键盘上输入的数据(使用者回答的答案)

if($1 != English[ind] )

print "Try again!"

else{

        print "\nYou are right !! Press Enter to Continue --- "

        getline

        question()#产生考题

}

}

function question(){

ind = int(rand()* n) + 1 #以随机数选取考题

system("clear")

print " Press \"ctrl-d\" to exit"

printf("\n%s ", Chinese[ind] "的英文生字是: ")

}

' $*

执行时键入如下指令:

$./c2e test.dat

屏幕将产生如下的画面:

 Press "ctrl-d " to exit

 

莲雾的英文生字是:

若输入bellfruit

程序将产生

You are right !! Press Enter to Continue ---

 

[: ]

参数test.dat (ARGV[1])表示储存考题的数据文件文件名. awk由该文件上取得考题资料后,ARGV[1]改成"-".

"-"表示由stdin(键盘输入)数据.键盘输入数据的结束符号(End of file)ctrl-d.awk读到ctrl-d时就停止由stdin读取数据.

awk的数学函数中提供两个与随机数有关的函数.

srand( ) : 以当前的系统时间作为随机数的种子

rand( ) :返回介于01之间的(近似)随机数值.

 

11.        使用awk编写递归程序

awk中除了函数的参数列(Argument List)上的参数(Arguments),所有变量不管于何处出现,全被视为全局变量.其生命持续至程序结束---该变量不论在function外或function内皆可使用,只要变量名称相同所使用的就是同一个变量,直到程序结束.

因递归函数内部的变量,会因它调用子函数(本身)而重复使用,故编写该类函数时,应特别留心.

[例如: ]执行

awk '

BEGIN {

x = 35

y = 45

test_variable( x )

printf("Return to main : arg1= %d, x= %d, y= %d, z= %d\n", arg1, x, y, z)

}

function test_variable( arg1 )

{

arg1++ # arg1为参数列上的参数,local variable.离开此函数后将消失.

y ++ #会改变主式中的变量y

z = 55 # z为该函数中新使用的变量,主程序中变量z仍可被使用.

printf("Inside the function: arg1=%d,x=%d, y=%d, z=%d\n", arg1, x, y, z)

} '

结果屏幕印出

Inside the function: arg1=36,x=35, y=46, z=55

Return to main : arg1= 0, x= 35, y= 46, z= 55

由上可知:

函数内可任意使用主程序中的任何变量.函数内所启用的任何变量(除参数外),于该函数之外依然可以使用.此特性优劣参半,最大的坏处是式中的变量不易被保护,特别是递归调用本身,执行子函数时会破坏父函数内的变量.

一个变通的方法是:在函数的参数列中虚列一些参数.函数执行中使用这些虚列的参数来记录不想被破坏的数据,如此执行子函数时就不会破坏到这些数据.此外awk并不会检查调用函数时所传递的参数个数是否一致.

例如:定义递归函数如下:

function demo( arg1 ) { #最常见的错误例子

........

for(i=1; i< 20 ; i++){

demo(x)

#又呼叫本身.因为iglobal variable,故执行完该子函数后

#原函数中的i已经被坏,故本函数无法正确执行.

.......

}

..........

}

可将上列函数中的i虚列在该函数的参数列上,如此i便是一个局部变量,不会因执行子函数而被破坏.

将上列函数修改如下:

function demo( arg1, i )

{

......

for(i=1; i< 20; i++)

{

demo(x)#awk不会检查呼叫函数时,所传递的参数个数是否一致

.....

}

}

$0, $1,.., NF, NR,..也都是global variable,读者于递归函数中若有使用这些内建变量,也应另外设立一些局部变量来保存,以免被破坏.

[范例:]以下是一个常见的递归调用范例.它要求使用者输入一串元素(各元素间用空白隔开)然后印出这些元素所有可能的排列.

编辑如下的awk,取名为permu

#!/bin/sh

 

awk '

BEGIN {

print "请输入排列的元素,各元素间请用空白隔开"

getline

permutation($0, "")

printf("\n%d种排列方式\n", counter)

}

function permutation( main_lst, buffer,     new_main_lst, nf, i, j )

{

        $0 = main_lst #main_lst指定给$0之后awk将自动进行字段分割.

        nf = NF #故可用NF表示main_lst上存在的元素个数.

        # BASE CASE :main_lst只有一个元素时.

        if( nf == 1){

                print buffer main_lst #buffer的内容再加上main_lst就是完成一次排列的结果

                counter++

                return

        }

        # General Case :每次从main_lst中取出一个元素放到buffer

        #再用main_lst中剩下的元素(new_main_lst)往下进行排列

        else for( i=1; i<=nf ;i++)

        {

                $0 = main_lst # $0为全局变量已被破坏,故重新把main_lst赋给$0,awk再做一次字段分割

                new_main_lst = ""

                for(j=1; j<=nf; j++) #连接new_main_lst

                if( j != i ) new_main_lst = new_main_lst " " $j

                permutation( new_main_lst, buffer " " $i )

        }

}

' $*

执行

$ ./permu

屏幕上出现

请输入排列的元素,各元素间请用空白隔开

 

若输入1 2 3回车,结果印出

 1 2 3

 1 3 2

 2 1 3

 2 3 1

 3 1 2

 3 2 1

 

6种排列方式

 

[: ]

有些较旧版的awk,并不容许使用者指定$0之值.此时可改用gawk,nawk.否则也可自行使用split()函数来分割main_lst.

为避免执行子函数时破坏new_main_lst, nf, i, j故把这些变量也列于参数列上.如此,new_main_lst, nf, i, j将被当成局部变量,而不会受到子函数中同名的变量影响.读者声明函数时,参数列上不妨将这些"虚列的参数"与真正用于传递信息的参数间以较长的空白隔开,以便于区别.

awk中欲将字符串concatenation(连接),直接将两字符串并置即可(Implicit Operator).

例如:

awk '

BEGIN{

A = "This "

B = "is a "

C = A B "key." #变量AB之间应留空白,否则"AB"将代表另一新变量.

print C

} '

结果将印出

This is a key.

awk使用者所编写的函数可再重用,并不需要每个awk式中都重新编写.

将函数部分单独编写于一文件中,当需要用到该函数时再以下列方式include进来.

$ awk -f函数文件名-f awk主程序文件名数据文件文件名

TAG:

 

评分:0

我来说两句

Open Toolbar