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的分隔符.显然awk对RS = ""另有解释方式,简略描述如下,当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}'都不会列入ARGC及ARGV[ ]中.
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( ) :返回介于0与1之间的(近似)随机数值.
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)
#又呼叫本身.因为i是global 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." #变量A与B之间应留空白,否则"AB"将代表另一新变量.
print C
} '
结果将印出
This is a key.
awk使用者所编写的函数可再重用,并不需要每个awk式中都重新编写.
将函数部分单独编写于一文件中,当需要用到该函数时再以下列方式include进来.
$ awk -f函数文件名-f awk主程序文件名数据文件文件名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 |
我的存档
数据统计
- 访问量: 48024
- 日志数: 70
- 建立时间: 2007-07-05
- 更新时间: 2011-11-08