第一章 简介 awk是Unix操作系统提供的一个程序化语句,是为了使信息和文本的处理更易于表达和完成而设计的。它对于资料的处理具有很强的功能:对于文本文件里的内容做修改、对比、抽取等的处理,可以以很短的语句轻易完成。而象 C 或 Pascal 等高级语言要完成上述的动作,相对要麻烦得多。 awk 是在一组输入信息或文件上运行的,它浏览输入行,按照行次顺序,一次一行地进行处理。在一行中寻找程序中描述的模式,发现该模式,则进行相应的操作,处理完一行后,再进行下一行的处理,直到文件结束。 从awk在操作系统中所处的位置(/usr/bin/awk)来看,awk是Unix的一个命令;从它提供的内部语句来看,awk又象是一个程序语言。在以后的讨论中称它为语句或程序,但这都没什么区别。 awk 这个名字是由它的几个设计者姓氏的第一个字母而命名:Alfred V. Aho, Peter J. Weinberger, Brian W. Kernighan。awk最初在1977年完成,后来又经过多次修改,这里讨论的awk 实例都是在SCO UNIX 3.0 上通过的。
第二章 基础知识 2.1.基本程序结构 awk 程序是由若干个“模式”与“动作”所组成的,其执行方式是针对文件中的每一行(line)搜索指定的模式 (patterns),当一行中有符合指定的模式,awk 就会在此一行执行被指定的动作(actions)。 awk 依此方式处理输入文件的每一行直到输入文件结束。 “动作”一般写在大括号 “{ }” 里面,一个模式后面就跟着一个动作,一个动作可以是一条或多条语句。整个 awk 程式就象下面的形式: awk ‘ 模式 { 动作 } ................. 模式 { 动作 }’ filename
这种形式是从filename文件中读入数据 或者 `命令|awk ‘模式 { 动作 } .............. 模式 { 动作 }’` 是通过管道将命令的输出传递给awk语句。 例:awk ‘$1=”127.0.0.1” { print $2 }’ /etc/hosts #从/etc/hosts中读入数据 而:cat /etc/hosts| awk ‘$1=”127.0.0.1” { print $2 }’ #则是接收cat /etc/hosts的结果 两个例子都是将/etc/hosts文件中所有第一个字段为127.0.0.1的行的第二个字段打印出来。 其运行结果为:localhost
在 awk 中,“模式”或“动作”能够被省略,但通常是省略模式。模式被省略后,对于输入文件里面的每一行,动作都会被执行。而如果“动作”被省略,awk语句只会去寻找你所指定的模式,但即使找到了也不执行其它任何操作(因为没有指定动作),就象在Unix系统中你用find命令去寻找一个文件,但是没有用 –print 参数来说明要显示一样,find只是找到了这个文件,但并不显示出来。 如:awk ‘{ print $2 }’ /etc/hosts 的含义为:从/etc/hosts文件中读入数据,将每个记录(行)的第二个字段打印出来 结果为:localhost peixun1 而:awk ‘$1=”127.0.0.1”’ /etc/hosts 只是在/etc/hosts文件中寻找,看看哪一行的第一个字段为127.0.0.1,不管找到与否,一概不显示。 所以结果是:什么都没有
2.2.如何执行awk语句 基本上,有三种方法可以执行awk语句。 1) 如果 awk 程序很短,则 awk 可以直接写在命令行上(command line),如下 所示: #awk 'program' input-file1 input-file2 ... 其中 program 包括一些模式和动作,就象前面的例子一样。input-file1 input-file2 ...为数据文件名,程序将从中读取数据。文件名可以多于一个,当同时存在多个文件名时,程序将顺序处理这些文件。
2) 如果 awk 语句较长,较为方便也是通常的做法是将整个 awk语句存放在一个文本文件中,通过文件名来执行这段程序。 如一个awk命令的所有语句都放在了一个叫mypro的文件中,那么可以通过下面的方式来执行:#sh mypro
3)也可以只将 'program' 部分即“模式与动作”部分写在文件中,通过以下的方式执行: #awk -f program-file1 -f program-file2 ...input-file1 input-file2 ... 其中,program-file1 ,program-file2是存放“模式与动作”的文件名称,他通过 -f 参数被调用,命令文件的个数也可以有多个,在使用时,每个文件名前面都必须要有-f 参数。 如:文件file1的内容为:‘{ print $1,$2,$3 }’ file2为:‘{ print $2,$3,$4 }’ file3为: I am a student file4为: You are a teacher 则:执行 awk -f ./file1 -f ./file2 ./file3 ./file4 的结果为:I am a am a student You are a are a student 我们建议使用第二种方法,简单而又方便。
2.3 变量 awk中的变量和Shell中的变量相似,都不用事先定义,也没有类型的区别(都是字符串型),在使用时可随时进行定义。awk中的变量分以下几种: 1)字段变量 awk处理数据时是逐行进行处理的,字段变量就是在处理过程中表示当前记录各字段的变量,其形式和含义如下: $0: $0就是所处理的当前行(字符串) $1: 当前行(记录)的第一个字段 $2: 当前行(记录)的第二个字段 $3: 当前行(记录)的第三个字段 ……… 如: awk ‘{ print $1,”|”,$2 }’ /tmp/test 该语句将/tmp目录下的test文件的第一和第二个字段打印出来,字段间的分隔符为”|”.
3) 内部变量 awk的每次执行,都建立了一些缺省的变量,也叫做内部变量,这些变量有固定的名字和固定的含义,它们在程序运行期间可以随时被引用。其具体定义如下: FS: 输入记录字段间的分隔符 RS: 输入记录的分隔符 OFS: 输出记录字段间的分隔符 ORS: 输出记录的分隔符 NR: 当前行数 NF: 当前记录字段数 ARGC: 命令行变元数
前四个内部变量在使用时一般用于设定你所需要的分隔副符,具体的使用方法如下例所示: awk ‘BEGIN { FS=”|” ;OFS=”|” } #指明输入输出时都以“|”为字段间的分隔符 { print $1,$2,…… }’ 文件名
后面三个内部变量用于模式部分较多,在动作部分也有使用。 awk ‘ NR=10 { print $1,$2,…… }’ 文件名 #用于限定只对第十行进行处理。 awk ‘ BEGIN { SUM=0 } #对SUM变量赋初值 { SUM=SUM+NF } #利用NF变量获得每行字段数的总和 END { print “本文总共” SUM “个字段” }’ 文件名 关于BEGIN和END的用法将在第三章介绍。
3)自定义变量 在awk语言的任意动作区间,即在{}之内,均可随时定义变量,无须事先说明。但一般情况下是在BEGIN中定义变量并赋以初值,在动作区域内使用。 如:awk ‘BEGIN { sum=“0” } #定义变量sum,其初值为0 { sum=sum+1 } #对于每一条记录变量sum加1 END { print sum }’ file0 整条语句将计算出文件总的字段数。 第三章 模式(pattern) 在一条“模式-动作”语句中,模式是一个表达式,用来确定执行相关操作的那些记录(行)。也就是在awk处理数据时,只有数据行与模式(pattern)相匹配,程序中相对应的动作(action)才会被执行。
3.1 BEGIN和END BEGIN和END是两个特殊的模式。 BEGIN的作用是在处理第一条记录之前将BEGIN后面大括号之内的动作运行且只运行一次,也就是BEGIN匹配第一个输入记录之前。 END的作用是在处理完最后一条记录之后将END后面大括号之内的动作运行且只运行一次,也就是说,END匹配最后一个输入记录之后。
如:awk ‘BEGIN { print “my program begin !”;i=0 } { i=i+$1 print $1 } END { print “total :”,i }’ file0 如文件file0的内容为: 3 4 2 8 则运行结果为: my program begin !#在处理文件file0第一条之前,由BEGIN的print动作显示 3 # 2 #这两行是print $1语句的结果,直接将第一个字段打印出来。 total :5 #在处理完文件file0最后一条记录之后,由END的print动作显示
3.2 用表达式(Expression)作为模式 一个awk模式可以是一个表达式,用这个表达式来测试两个数字或字串的诸如大于、等于、小于等关系。awk有六个关系操作符和两个匹配符: xx<=y 如果 x 小于、等于 y,则结果为真。 x>y 如果 x 大于 y,则结果为真。 x>=y 如果 x 大于、等于 y,则结果为真。 x==y 如果 x 等于 y,则结果为真。 x!=y 如果 x 不等于 y,则结果为真。 x~y 如果 x 符合 regular expression y,则结果为真。 x!~y 如果 x 不符合 regular expression y,则结果为真。
上面所提到的 x 与 y,如果二者皆是数字则视为数字之间的比较,否则它们会被转换成字符串且以字符串的形式做比较。两个字符串的比较,会先比较第一个字符,然后比较第二个字符,依此类推,直到有不同的地方出现为止。如果两个字符串在较短的一个结束之前是相等,则视为
长的字符串比短的字符串大。例如 "10" 比 "9" 小,"abc" 比 "abcd" 小。 例:awk ‘$1>5{ print $0 }’ file0 其结果是把文件file0中第一个字段大于5的所有行打印出来。
3.3 用正则表达式(Regular Expressions)作为模式 awk还提供了比前面介绍的功能更强的字符串查找模式。这些模式称做正则表达式,最简单的正则表达式是以斜杠“/”括起来字符串,例如:/Asia/ 一个regular expression (正则表达式)可简写为 regexp,当输入记录含有 regexp 时就视为符合。如上例中的/Asia/,对于任何输入记录,若含有'Asia'则视为符合。 例如:awk '/Asia/ { print $2 }' earth 结果将把文件earth中所有含'Asia'的行的第二个字段打印出来,而不论'Asia'这个字符串在一行中的那一部分。 通常,为了将一个匹配限制在一个特定的域上,可以使用匹配操作符~(匹配)和!~(不匹配)。 如:awk '$2 ~ /Asia/ { print $0 }' file0 将把文件file0中第二个字段是字符串Asia的行全部打印出来。
而:awk '$2 !~ /Asia/ { print $0 }' file0 将把文件file0中第二个字段不是字符串Asia的行全部打印出来。 在正则表达式中,符号 \ ^ $ . [ ] * ? ( ) 是元字符,这些元字符象UNIX Shell中的元字符一样有特殊的含义。例如,元字符“^”和“$”分别匹配字符串的开始和结束,而元字符“.”匹配任意单个字符。如 /^.$/ 将匹配所有只包含一个字符的输入行。 由括号括起来的一组字符,表示匹配其中的任何一个字符。如/{ABC}/与那些无论在什么地方包含A,B,C中任一字符的记录相匹配。 多个字符或数字可以在括号内采用缩写形式:/[a-z]/,它与所有纯小写字母字符串相匹配。 /^[0-9]+$/匹配所有以数字开头的字符串。 在元字符的前面加上一个“\”,就可以关闭元字符的特殊含义。这样,/b\$/匹配所有包含b$这两个字符的行。
3.4 模式组合 一个组合的模式是由较简单的模式加上圆括号和逻辑操作符&&"与",||"或",!"非"来构成。 例如: awk '$1==”Asia” && $3>500 { print $0 }’earth 将文件earth中所有第一个字段为Asia,且第三个字段大于500的行打印出来。 awk '$1==”Asia”||$1==”Africa” { print $0 }’earth awk '$1~/^(Asia|Africa)$/ { print $0 }’earth 以上两句都是将文件earth中所有第一个字段为Asia或者为Africa的行打印出来。 awk '$1!=”Asia” && $1~/^[As]/ { print $0 }’earth 将文件earth中所有第一个字段包含As但不为Asia的行打印出来。 非操作符!的优先级最高,其次是&&,最后是||。操作符&&和||从左到右判断它们的操作数,当得到真或假值时,这种判断即停止。 第四章 动 作 4.1.1 输入 awk所处理的数据可以从指定的文件中读取或者接收某些命令的执行结果,再或者可以直接从标准输入设备上输入。 1)从文件中输入 格式为: awk ’{ action }’ filename1 filename2 ... 执行时,awk将依次从filename1 filename2 ...文件中读取数据并执行action操作. 如: awk ’{ print $1 }’ file1 从文件file1中读取数据,并将打印每行的第一个字段 2)从其它命令输入 格式为: Unix命令| awk ’{ action }’ 执行时,awk将接收Unix命令的输出,并对输出执行action操作. 如: who| awk ’{ print $2 }’ 如果who命令结果为: root ttyp0 Jul 6 14:19
则上面awk语句的执行结果为:ttyp0
3) 从标准输入设备上输入 在命令行上输入awk ’{ action }’ 并回车后 awk语句便处于等待状态,此时可以输入一条记录,当打回车确认后,awk语句将对这一行进行处理。如果符合处理的条件,awk就会将处理结果显示到屏幕上。接着,再次进入等待输入的状态。如此循环下去,一直到打Ctrl-d退出或被del键中断。
4) 读入awk以外的Shell变量 有时需要将awk以外的变量读入到awk之中,通常有两种方法: a. 直接引用外部变量 aa="字符串" awk ' { print "'$aa'" }' 次种方法,如果变量aa的值是一个汉字字符串,则在SCO Unix3.0上运行将显示不出来,在5.0上没有问题。 b. 间接引用外部变量 aa="string" awk '{ print bb }' bb=$aa 执行后每一次回车都将显示一个字符串string,该字符串是由awk中的print语句显示的,其中变量bb的值通过后面的bb=$aa引入。对于汉字字符串的赋值在SCO Unix3.0上运行时还需注意,多个赋值语句,使用汉字字符串的语句应放在后面(在5.0上没有这种情况): aa=“计算机使用指南” bb=“computer guide” awk '{ print cc dd }' cc=$bb dd=$aa filename
数据的处理是以“记录”(recoder)为单位的,也就是awk 在做处理时,是一个记录一个记录地处理。每个记录的缺省值是一行(line),一个记录又被分为多个字段(fields)。
如何将输入分解成记录(records) awk 语言会把输入分解成记录(record)。记录与记录之间是以记录分隔符(record separator)隔开的,其缺省值是文件中的换行符(newline character),因此在缺省状态下输入数据的每一行是一个记录。 记录分隔符随着内部变量 RS 的改变而改变。RS 是一个字串,如“|”。它的缺省值是"\n"。仅有 RS 的第一个字符是有效的,而其它字符会被忽略。 内部变量 FNR 会储存目前的输入文件已经被读取的记录的个数。内部变量 NR 会储存目前为止所有的输入文件已经被读取记录的个数。
字段(field) awk 会自动将每个记录按缺省值分解成多个字段 (field)。awk 的缺省动作会认为字段之间是以 whitespace 为分隔的。这里,whitespace 的意思是一个或多个空格或 tabs。 在 awk 程序里面,以'$1'表示第一个字段,'$2'表示第二个字段,依此类推。举个例子,假设输入的一行如下所示: This seems like a pretty nice example. 第一个字段或 $1 是'This',第二个字段或 $2 是 'seems',依此类推。有个地方需要特别注意,第七个字段或 $7 是'example.'而非'example'。 不论有多少字段,$NF 可用来表示一个记录的最后一个字段。以上面的例子为例,$NF 与 $7 相同,也就是'example.'。
NF 是一个内部变量,它的值表示目前这个记录之字段的个数。 $0,看起来好像是第零个字段,它是一个特例,它表示整个记录。 下面是一个较复杂的例子: awk '$1~/foo/ { print $0 }' Earth 这个例子是把输入文件'Earth'的每个记录的第一个字段作检查,如果它含有子字串'foo',则这一个记录会被输出。
如何将记录分解成字段 awk 根据 field separator(字段分隔符)将一个记录分解成若干字段。field separator 以内部变量 FS 表示。 举个例子,假如 field separator 是'oo',则下面的行: moo goo gai pan 会被分成三个字段:'m'、' g'、' gai pan'。 在 awk 程序里,可以使用'='来改变 FS 的值。 例如: awk 'BEGIN { FS="," }; { print $2 }' 输入行如下: John Q. Smith, 29 Oak St., Walamazoo, MI 42139 执行awk的结果将输出字符串 ' 29 Oak St.'(将第二个字段打印出)。BEGIN 后面的 action 会在第一个记录被读取之前执行一次。
4.1.2 输出 在awk程序里,actions 最常做的事就是输出(printing)。简单的输出,使用 print描述。复杂格式的输出,使用 printf 描述。 print 语句 print语句用在简单、标准的输出格式。 格式:print var1,[ var2,……],str1,[str2,……,strn ] 说明:在print 语句后面,可以跟变量或字符串,变量或字符串可以有多个,语句执行后,跟在print 语句后面的变量值或字符串将被显示到屏幕上。多个变量或字符串之间可以是逗号“,” 或空格,使用逗号时,显示出的各个字符串之间会有空格分隔;使用空格时,显示出的各个字符串将连接起来,形成一个长的字符串。 如:echo “fir sec thir four five”|awk ‘{ print $1,$2,$3,$4 }’ 其执行结果为fir sec thir four 而:echo “fir sec thir four five”|awk ‘{ print $1 $2 $3 $4 }’ 的结果为firsecthirfour print语句输出后自动换行,如有第二个显示语句,将从下一行输出。
如果 'print'描述之后没有跟着任何东西,它与'print $0'的效果一样,它会输出现在的记录(record)。要输出空白行可使用'print ""'。 输出一段固定的文字,可用双引号将文字的两边括起来,例如'print "Hello there"'。 下面的例子,会把每个输入记录的前二个字段输出: awk '{ print $1,$2 }' shipped
2) 输出分隔符 前面我们已提过print语句后面多个输出字段(变量)之间可以用逗点使输出分隔开来,事实上,你可以任何设定你所需要的输出分隔符,OFS 就是用来设定输出分隔符的内部变量。其缺省值为空格。 下面这个例子会输出每个记录的第一个字段和第二个字段,此二个字段之间在输出时是以分号';'分开的,每行输出之后会加入一个空白行。 awk 'BEGIN { OFS=";" ORS="\n\n" } {print $1, $2}' earth
3) printf语句 printf 语句会使得输出格式较容易精确地控制。printf 语句可以指定每个 item 输出的宽度,也可以指定数字的各种格式。 printf 语句的格式如下: printf format, item1, item2, ... 如:printf "%4s%10s%10s\n","str1","str2","str3" 或:printf ("%4s%10s%10s\n","str1","str2","str3") print 与 printf 的差别是在于 format, printf 的参数比 print多了字符串 format。format 的格式与 C语言 中的 printf语句格式相同。
printf 并不会做自动换行的动作。内部变量 OFS 与 ORS 对 printf 语句没有任何影响。 格式的指定以字符'%'开始,后面接着格式控制字母。
格式控制字母如下所示: 'c' 将数字以 ASCII 字符输出。 例如 'printf "%C",65'会输出字符'A'。 'd' 输出十进位的整数。 'e' 将数字以科学记数的形式输出。 例如 printf "%4.3e",1950 结果会输出'1.950e+03'。 'f' 将数字以浮点的形式输出。 'g' 将数字以科学记数的形式或浮点的形式输出。数字的绝对值如果大于 等于0.0001则以浮点的形式输出,否则以科学符号的形式输出。 'o' 输出无符号的八进位整数。 's' 输出一个字串。 'x' 输出无符号的十六进位整数。10至15以'a'至'f'表示。 'X' 输出无符号的十六进位整数。10至15以'A'至'F"表示。 '%' 它并不是真正的格式控制字母,”%%"将输出‘%'。 在 % 与格式控制字母之间可加入 modifier,是用来进一步控制输出的格式。可能的 modifier 如下所示: 'width' 这一个数字指示相对应的字段输出时的宽度。例如: '-' 使用在 width 之前,指明是向左靠齐。如果'-'没有出现,则会在被指定 的宽度向右靠齐。 例如:printf "%-6s", "foo" 会输出'foo '。 printf "%6s","foo" 会输出' foo'。 width 的值是一个最小宽度而非最大宽度。如果一个 item 的值需要的宽度比 width 大,则不受 width 的影响。例如 printf "%4s","foobar" 将输出'foobar'。
'.prec' 此数字指定输出时的精确度。它指定小数点右边的位数。如果是要输出一个字串,它指定此字串最多会被输出多少个字符。
4.2 控制语句 在 awk 程序里面,控制语句诸如 if、while 等的使用与 C语言类似。 很多的控制语句会包括其它的语句,被包括的语句称为程序体(body)。假如body 里面包括一个以上的语句,必须以大括弧 { } 将这些语句括起来(这与Shell的语法有所区别),而各个语句之间需以换行(newline)或分号隔开。
1) if 语句 语法: if ( 条件 ) { body1 } [ else { body2 } ] 其中,if,else 是关键字,如果使用必须保留,程序体中可以有多条语句。 如果条件(condition)为真(true),则执行程序体1中的语句,否则执行程序体2。 例1: if ( x == 0) #变量x是否为0 print "x equal zero" #为0则打印"x equal zero" else #否则 print "x not equal zero" #则打印"x not equal zero"
当然也可以只要“if部分”而不使用“else部分”如例2: if ( x == 0 ) { print "x equal zero" x=x+1 } 说明:if中的程序体通常要用大括号括起来,但在有些情况下可以省略。如在例1中,不论在if后面还是else后面,过程体中总共只有一句,在这中情况下就不需要使用大括号。而在例2中,if后面过程体中的语句多于一句,就必须使用大括号,否则意义会有所区别。
2)while 语句 语法: while ( 条件 ) { body }
while 语句执行后所做的第一件事就是测试‘条件’是否为真。为真则执行 body 中的语句,执行完后,再进行测试,如‘条件’仍为真,则 body 会被再度执行。这个过程会一直被重复直到‘条件’不再为真。如果‘条件’的第一次测试就是假(false),则body 会被跳过去,程序将执行后面的语句。 通常body中的语句都会改变条件中所使用变量的值,或当达到某个条件后跳出循环,否则,while语句将会无限执行下去,出现所谓的死循环。 下面的例子会输出每个输入记录(record)的前三个字段。 awk '{ i=1 while (i <= 3) { printf(“%s”,$i) i++ } print “” #换行 }' 3)do-while 语句 语法:do body while ( 条件 )
do-while 语句执行时,不管条件成立与否,都首先执行程序体(body)一次,然后再判断是否为真,为真则会重复执行 body,否则退出do-while循环语句。 下面的例子会将每个输入记录输出十次。 awk '{ i= 1 do { print $0 i++ } while (i <= 10) }' 文件名
4)for 语句 语法: for (赋初值;条件;动作) { body } 此语句开始时会执行初始赋值(initialization),然后比较条件是否为真,只要条件(condition)为真,就执行‘动作’和body ,然后重新判断条件,重新执行,直到条件为假为止。 下面的例子会输出每个输入记录的前三个字段。 awk '{ for (i=1; i<=3; i++) printf("%s ",$i) print "" #换行 }' 文件名 5)break 语句 break 语句用于跳出包含它的 for、while、do-while 语句的当前循环。 下面的例子能够对任意输入的自然数做出以下判断: a. 求出其最小除数 b. 判断其是否为质数。 awk '{ num=$1 for (div=2; div*div <=num; div++) { if ( num % div == 0 ) break #当整除时跳出for循环 } if ( num % div == 0 ) printf "%d 最小的除数是 %d\n", num, div else printf "%d 是质数\n", num }'
6)continue 语句 continue 语句使用于 for、while、do-while 循环内部,它会跳过循环 body 的剩余部分,使得它立刻进行下一次循环的执行。 下面的例子会输出 0 至 20 的全部数字,但是 5 并不会被输出。 awk 'BEGIN { for (x=0; x<=20; x++) { if (x==5) continue #当x为5时跳过下面的printf语句,进入下次循环 printf ("%d ",x) } }'
7) next 语句、next file 语句、exit 语句 next 语句强迫 awk 立刻停止处理目前的记录(record)而继续下一个记录的处理。 next file类似 next。然而,它强迫 awk 立刻停止处理目前的文件。 exit 语句会使得 awk 程序停止执行而跳出。然而,如果END 出现,它会去执行 END 中的 actions(动作)。
第五章 内部函数(Built-in Functions) 内部函数是 awk 提供的函数,可在 awk 程序的任何地方调用。
5.1 数值方面的内部函数 int(x) 求出 x 的整数部份,朝向 0 的方向做舍去。例如:int(3.9) 是 3,int(-3.9) 是 -3。 sqrt(x) 求出 x 正的平方根值。例 sqrt(4)=2 exp(x) 求出 x 的次方。例 exp(2) 即是求 e*e 。 log(x) 求出 x 的自然对数。 sin(x) 求出 x 的 sin 值。 cos(x) 求出 x 的 cos 值。 atan2(y,x) 求 y/x 的 arctangent 值,所求出的值其单位是弧度量。 rand() 得出一个随机数值。此随机数值平均分布在 0 和 1 之间。这个值不会是 0,也不会是 1。每次执行awk,rand 产生的数字都相同。 如:awk '{ for (i=1;i<10;i++) { x=rand() print x } }' 执行后,每打一次回车便产生10个0-1之间的随机数。但第一次执行产生 的若干数字如100个和第二次执行产生的100个数字相同。
srand(x) 设定产生随机数的开始点为 x。如果在第二次你设定相同的 seed 值,你将再度得到相同序列的随机数值。如果省略引数 x,例如 srand(),则现在的日期、时间会被当成初始值。这个方法可使得随机数值是真正不可预测的。
2)字符串方面的内部函数 index(in, find) 它会在字符串 in 里面,寻找字符串 find 第一次出现的地方,返回值是字符串 find 出现在字符串 in 里面的位置。如果在字符串 in 里面找不到字符串 find,则返回值为 0。 例如: print index("peanut","an") 会输出 3。
length(string) 求出 string 字符串的长度。 例如: length("abcde") 的返回值是 5。
match(string,regexp) match 函数会在字符串 string 里面,寻找符合 regexp 的最长、最靠左边的子字符串。返回值是 regexp 在 string 的开始位置,即 index 值。 match 函数会设定内在变量 RSTART 等于 index,它也会设定内在变量 RLENGTH 等于符合的字符个数。如果不符合,则会设定 RSTART 为0、RLENGTH 为 -1。 如:awk '{ match("sfsdfgertertrtra","tert") print RSTART,RLENGTH }' 执行后,每次回车都会显示9 4,意为第9位,长度为4
sprintf(format,expression1,...) 与 printf 类似,但是 sprintf 并不输出,而是返回字符串。 例如: pi=sprintf(“%.2f (approx.)”,22/7)
print pi 将会显示 3.14 (approx.)
sub(regexp, replacement,target) 在字符串 target 里面,寻找符合 regexp 的最长、最靠左边的地方,以字符串 replacement 代替最左边的 regexp。 例如: awk '{ str = "water, water, everywhere" sub(/at/, "ith",str) print str }' 结果字符串str会变成 "wither, water, everywhere"
gsub(regexp, replacement, target) gsub 与前面的 sub 类似。在字符串 target 里面,寻找符合 regexp 的所有地方,以字符串 replacement 代替所有的 regexp。 例如: str="water, water, everywhere" gsub(/at/, "ith",str) 结果字符串str会变成 wither, wither, everywhere
substr(string, start, length) 返回字符串 string 的子字符串,这个子字符串的长度为 length 个字符,从第 start 个位置开始。 例如:str=substr("washington",5,3) 返回值为"ing"
如果 length 没有出现,则返回的子字符串是从第 start 个位置开始至结束。 例如: awk 'BEGIN { oldstr="washington" } { newstr=substr(oldstr,5) print newstr }’ 执行后将显示"ington" tolower(string) 将字符串string的大写字母改为小写字母。 例如: awk 'BEGIN { oldstr="WASHINGTON" } { newstr= tolower(oldstr) print newstr }’ 执行后显示: washington
toupper(string) 将字符串string的小写字母改为大写字母。 例如: toupper("MiXeD cAsE 123") 返回值为"MIXED CASE 123"
3) 输入输出的内部函数 close(filename) 将输入或输出的文件 filename 关闭。 如: awk '{ print "new line">"new" }'
前面的语句的作用是将”new line”这个字符串放入文件new中,注意语句中使用的是”>”符号,其作用将显示内容转向,在转向的同时将文件中原有的内容清除掉,所以不论print语句执行几遍,文件new中的内容都应该只有一行:”new line” 。 但执行上例awk语句,敲5次回车后发现文件new中有5行new line,而不是一行。其原因是因为文件new被打开后,没有被关闭,导致再次操作时发生错误。如果需要完成上例所设想的功能,应该使用下面的语句: awk '{ print "new line">"new" close(new) }' 另外,在一个awk中如使用追加符号“>>”可以不使用close语句,但为了避免多个awk语句可能存在对同一个文件的操作,在对文件操作完成后,最好都使用close关闭文件。
system(command) 此函数允许在awk中使用系统提供的Shell命令,命令执行完毕后将回到 awk 程序。 例如: awk '{ str=sprintf("head -%d filename",$1 ) system(str)}' 该语句将按输入数字的值取出文件filename的前若干行。 注意,在使用格式化语句的时候,首先要将格式化的字符串赋予某个变量,如例子中的str,然后再在system中使用。
如果希望将命令执行的结果截获下来,供awk中的语句使用,可以使用管道。如: awk ' { "cat filename|wc -l" | getline var #将文件filename的长度赋予变量var print var }' 管道线前面的语句可以不包括在system中。 getline函数的在这里的作用是将通过管道获得的值存储于var变量。
getline awk提供将输入数据分解为记录的功能,但对于某些任务来说,仍不能满足数据处理的要求。比比如,在下列数据文件当中数据是以若干行为单位存放的: more data: begin zhangqiang 24 nan 96 wanggang 23 nan 97 zhaoyu 22 nu 98 end …… 如上所示,begin和end之间的数据是一个整体,在文件中的数据全部是以这种方式存放,当然在处理时也需要读入一个单位后统一进行,在这种情况下,就可以用到getline函数。 getline函数用以读取下一行的数据并进行字段分解操作,设置NF,NR和FNR。如果有一个记录,getline返回1,如果遇到文件结束,返回0,如果出现错误,返回-1。 举例: awk '/^begin/ { N=0 #在找到开始标志begin时,将变量N置0 while (getline && $0 !~ /^end/) #取下一行,并且下一行不是end时 { f[++N]=$0 } #将读入的数据存入数组f中 for (i=1;i<=N;i++) #对begin与end之间的数据进行处理(已 { printf("%s " , f) } #存入到数组f中。 print "" #换行 }' data getline函数还有其它用法: getline x #将下一个记录读入变量x中,不作分解,不设置NF getline < “filename” #从指定文件filename中读取一行(存入$0中),而不是从当前文件中,对NR或FNR没有影响,但对字段进行分解并且设置NF。 再或者,如上页例题中所示,通过管道截取命令执行后的结果。
第六章 自定义函数(User-defined Functions) 复杂的 awk 程序常常要通过自定义函数来简化。自定义的函数的使用与内部函数的使用方法一样。 1) 函数定义的格式 函数的定义可以放在 awk 程序的任何地方。 一个自定义的函数其格式如下: function name (parameter-list) { body-of-function } name 是所定义的函数的名称。一个正确的函数名称可包括一序列的字母、数字、下标线 (underscores),但是不能用数字做开头。 parameter-list 是函数的参数列表(argument),各个参数之间以逗点隔开。 body-of-function 包含 awk 的描述 (statement)。它是函数定义里最重要的部份,它决定函数实际要做何种事。
2) 函数定义的例子 下面这个例子,会将每个记录的第一个字段值的平方与第二个字段之值的平方加起来。 awk ‘{ print $1,$2,"sum =",SquareSum($1,$2) } function SquareSum(x,y) { sum=x*x+y*y return sum }’ filename 在定义了函数SquareSum之后,对每一行数据的处理只需经过一次调用就可以。但定义自定义函数最大的好处还是可以在不同的地方调用,而不必每次都写上一堆,此外还使程序结构清楚,易读,易维护。
第七章 示例 awk '{ if (NF > max) max = NF } END { print max }' filename 此程序会输出所有输入行中,包含字段最多的行的字段数。 awk 'length($0) > 80' filename 此程序会输出一行超过 80 个字符的每一行。此处只有 pattern 被列出,action 是采用缺省的 print。
awk 'NF > 0' filename 对于拥有至少一个字段的所有行,此程序皆会输出。这是一个简单的方法,将一个文件里的所有空白行删除。
awk '{if (NF > 0) print}' filename 对于拥有至少一个字段的所有行,此程序皆会输出。这是一个简单的方法,将一个文件里的所有空白行删除。
awk 'BEGIN { for (i = 1; i <= 7; i++) print int(101 * rand()) }' filename 此程序会输出 0 到 100 之间的 7 个乱数值。
ls -l files| awk 'BEGIN { x=0 } { x += $4 } END { print "total bytes: " x }' filename 此程序会输出所有指定的文件之bytes数目的总和。
expand file | awk '{if (x < length()) x = length()}
END {print "maximum line length is " x}' filename 此程序会将指定文件里最长一行的长度输出。expand 会将 tab 改成 space,所以是用实际的右边界来做长度的比较。
awk 'BEGIN {FS = ":"} {print $1 | "sort"}' /etc/passwd 此程序会将所有用户的login名称,依照字母的顺序输出。
awk '{nlines++} END {print nlines}' filename 此程序会将一个文件的总行数输出。
awk 'END {print NR}' filename 此程序也会将一个文件的总行数输出,但是计算行数的工作由awk来做。
awk '{print NR,$0}' filename 在输出文件的内容时,会在每行的最前面输出行号,它的功能与 'cat -n' 类似。
□ 附录 例程分析 1. 关机帐号 大家都知道在UNIX系统中,关机命令都必须在超级用户执行(其它用户没有关机权限),而在有些情况,我们并不希望直接使用UNIX系统的人知道根用户的口令(如网点端的操作员),而又想让他可以直接正常关闭机器, 并且在关机时,系统中的其它注册用户最好都退到login:状态. 下面是一个运行在UNIX系统上的关机程序,主要是针对操作员设计的,使用它可以在不知道超级用户口令的情况下,通过系统提供的关机命令正常关闭计算机,并且它在关机之前检测其它用户是否都已退出,避免系统数据不必要的损失。 一般情况下,在UNIX系统中建立一个关机帐号有以下两个步骤: 1. 在系统中建立一个普通用户,如halts,其用户主为halts,用户组为group.将关机权限赋予该用户. 2. 在该用户目录下编辑一个关机程序,即下例所示(程序名为halts),赋予其可执行权限.在该用户的.profile文件的第一行增加trap 'clear;exit' 0 1 2 3 5 15,在最后一行增加一行exec halts #halts: 关机帐号程序,用以关闭计算机 1999/3/21 trap 'clear;exit' 0 1 2 3 5 15 #截获中断信号,当后面所列信号出现时执行单 #引号中的命令,即先清屏然后退出 #===============================================================================# testscreen() #函数,用于测试哪些屏幕还没有退出 { #函数体开始符号 she=`tty` #变量she被赋予当前所在屏幕的设备名 if [ "$she" != "/dev/tty01" ] #如果变量she的值不等于第一个屏幕的设备名 then #则 echo "\n\t\t请在第一屏关机 !\c" #显示“请在第一屏关机 !”字样 sleep 2 #等待2秒钟后 exit 0 #退出程序 fi #if语句结束 } #函数体结束符号 #===============================================================================# yesyn() #函数,用于测试用户的输入是‘y’还是‘n’ { #函数体开始符号 while echo "\n$* (y/n) \c">&2 #当正常显示提示信息($*)后进入循环 do #作以下操作 read yn rest #读用户输入并将输入放入yn变量中 case $yn in #当变量yn的值为 [Yy]) echo return 0 ; #大写字母Y或小写字母y时,函数返回0 [Nn]) echo return 1 ; #大写字母N或小写字母n时,函数返回1 *) echo "\n\t\t稍后请回答 y 或 n !\c">&2 #其它输入时,显示“稍后请回答 y 或 n !” sleep 1 #等待1秒钟 clear ; #清屏 esac #case语句结束 done #while语句结束 } #函数体结束符号 #===============================================================================# menu() #函数,用于显示如下菜单 { #函数体开始符号 echo #换行 echo "\t\t ┏━━━━━━━━━━━━┓" #显示菜单框 echo "\t\t ┃ 1) 强 行 关 机 ┃" #显示选项 echo "\t\t ┃ 2) 重 新 测 试 ┃" #显示选项 echo "\t\t ┃ 3) 退 出 ┃" #显示选项 echo "\t\t ┗━━━━━━━━━━━━┛" #显示菜单框 echo "\n\t\t 请输入选择: \c" #显示“请输入选择” } #函数体结束符号 #===============================================================================# exam() #函数,用于检测有哪些屏幕还没有退出 { #函数体开始符号 clear #清屏 status=0 #状态变量stutes被赋初值0(表示全部退出) sum=`who|wc -l` #变量sum被赋于尚未退出的用户数 if [ $sum -ne 1 ] #如果该数目不等于1 then #那么 status=1 #状态变量stutes被赋初值1(表示有用户尚未退出) echo "^G^G" #响铃,注意^G是一个组合键Ctrl-g echo "\n\n" #换行 for i in `who|awk ' $2!="tty01" { which=substr($2,4,2) print which }'` #该语句首先将who命令的输出传递给awk语句,awk语句检测输入的 #每一行的第二个字段,当这个字段的值不是"tty01"时,取出该字段 #的第三四位,并将其依此赋予变量which,然后将变量which的值输出 #当以上语句有输出时,将输出依次赋于变量i,进入for循环语句 do echo "\t\t\t第 $i 屏尚未退出 !" #显示“第 n 屏尚未退出 !”,n的值取自变量i done echo "\n\t\t\t请退出上述屏幕后再进行关机 !" #显示字符串 echo "\n\t\t\t按回车键继续 \c" #显示字符串 read key #读用户输入,并将其放入变量key中。其作用是 #等回车键被按下后,和序继续执行。 fi return $status #将变量status的值作为函数的返回值 } #===============================================================================# downs() #函数,用于关闭计算机 { echo "请等待......" #显示"请等待......" 字样 /tcb/bin/asroot shutdown -g0 -y #执行关机命令 exit 0 #退出程序 (此句亦可省略) } #===============================================================================# main() #主函数,控制整个程序的流程 { clear #清屏 testscreen #检测是否在第一屏 echo "^G\c" #响铃 yesyn "\n\n\t\t本程序用于关闭计算机 是否要关闭(Y/N): \c" #显示"是否关机(Y/N)"的提示,接收用户输入 if [ $? -eq 1 ] #当函数yesyn的返回值为1时 then #就 clear #先清屏 exit 0 #然后退出程序 fi while true #无条件进入while循环语句 do exam #检测是否还有用户没有退出 if [ $? -eq 0 ] #当函数exam的返回值为0时 then #那么 downs #执行关机函数 else #否则 menu #执行菜单显示函数 read key #读用户输入,并将其放入变量key中 case $key in #当变量key的值为 1) downs ; #1时,执行关机函数downs 3) exit 0 ; #3时,退出程序 *) echo "请回答y或n">&2;; #其它输入时, 先显示"请回答y或n"后无条件进入下次循环 esac #case语句结束 fi #if语句结束 done #while语句结束 } #===============================================================================# main #执行主函数
2. 临测程序 下面是一个网络连通检测程序,它可以在屏幕上直观地反映出网络中各目标网点的连通状况。程序循环检测,直到被中断为止。 从简单易读的角度出发,该段程序对屏幕显示的格式只作了简单处理,在网点数较多的情况下,需要稍作修改。 #本程序用以检测客户端连通情况 1999/3/21 #============================================================================# tests() #函数,用以检测一个网点端的连通状况,连通返回0,否则返回1。 #网点名由位置参数确定 { num=`ping -c 1 $1|awk '$2=="packets" { data=substr($7,1,1) print data }'` #上面一句是将“ping -c 1 $1”命令的输出转向给awk语句,awk将第二个 #字段为“packets”的那一行的第七个字段的第一个字符赋予变量num。此数 #是“ping -c 1 $1”命令返回的丢包率,0为通,非0为不通 if [ $num -eq 0 ] #如果变量num的值为何0 then #则 return 0 #返回0值 else #否则 return 1 #返回1 fi #if语句结束 } #============================================================================# all() #函数,用以检测所有网点端的网络连通性 { hang=3 #设置行变量的初始值为3 lie=10 #设置列变量的初始值为10 for i in lan0 serial0 zongying yiying erying sanying siying wuying liuying #当i变量为“lan0 serial0 ......wuying liuying”等网点端机器名时 do #作如下工作 tput cup $hang $lie #将光标定位在初始位置 echo "\t\t^[[36m →^[[37m${i}: \c" #显示当前网点机器名 tests $i #用tests函数测试该网点是否为通 tput cup $hang $lie #光标定位于$hang $lie if [ $? -eq 0 ] #如果tests函数的返回值等于0 then #则 echo "\t\t ^[[33m→^[[37m ${i}\t ^[[32m ● ^[[37m\c" #显示绿色● sleep 1 #等待1秒钟 else #否则 echo "\t\t ^[[33m→^[[37m ${i}\t ^[[31m ● ^[[37m\c" #显示红色● fi hang=`expr $hang + 2` #行变量加二 sleep 1 done tput cup 22 10 #光标定位于 22 10 echo " 呼市育财信用社线路连通状况表 ^[[32m●^[[37m: 通 ^[[31m●^[[37m: 不通" #显示 ” 呼市育财信用社线路连通状况表 ●(绿色): 通 ●(红色): 不通” } #============================================================================# main() #主函数 { clear #清屏 tput cup 22 10 #光标定位于 22 10 echo " 呼市育财信用社线路连通状况表 ^[[32m●^[[37m: 通 ^[[31m●^[[37m: 不通" while true #无条件进入while循环 do all #调用all函数 done } #============================================================================# TERM=ansi #给终端类型变量赋值ansi export TERM #声明TERM变量 main #执行主函数 3. 数据查询程序 相信大家都有同感,在需要查寻所有储种户数和金额的时候,在菜单中或数据库中一项一项地查找是一件很繁琐的事情,特别是在初始建帐,需要核对录入数据正确性的时候。下面一段程序就是为自动查询数据设计的。当然它十分简单,比如它没有检测输入所号的否合法性等,但比较易读和实用。
#本程序用于查询Informix数据库中某个储蓄所活期、定期、零整储种的记录数 #及总金额,查询结果显示在屏幕上 #==========================================================================# zzbsum() #过程,用于获得某所定期储种的记录数及总金额 { echo "\c" #换行 dbaccess bankstar</dev/null 2>/tmp/error unload to /tmp/jieguo0 select sum(jce) from zzb where zh[1,7]="$1"; unload to /tmp/jieguo1 select count(*) from zzb where zh[1,7]="$1"; END #以上语句以Here文本的方式,调用dbaccess工具中的SQL语句将某所 #定期储种的总金额及记录数分别下载到/tmp/jieguo0,/tmp/jieguo1文件中 #其中,储蓄所号是由该过程的位置参数(即$1)确定的 sum0=`awk -F '|' '{ print $1 }' /tmp/jieguo0` sum1=`awk -F '|' '{ print $1 }' /tmp/jieguo1` #以上两句分别取出/tmp/jieguo0,/tmp/jieguo1文件中的第一个字段,并 #将其分别赋予变量sum0及sum1 echo "\t整整簿总结存额: $sum0 \c" echo "\t整整簿总户数: $sum1" #以上两句将字符串及变量的值分别显示到屏幕上 } #==========================================================================# zzb() #过程,用于获得某所定期储种某一档次(存期)的记录数及总金额 { #过程中的语句用法同hzbsum(),存期由该过程的位置参数(即$2)确定 echo "\c" dbaccess bankstar</dev/null 2>/tmp/error unload to /tmp/jieguo0 select sum(jce) from zzb where zh[1,7]="$1" and qx="$2"; unload to /tmp/jieguo1 select count(*) from zzb where zh[1,7]="$1" and qx="$2"; END sum0=`awk -F '|' '{ print $1 }' /tmp/jieguo0` sum1=`awk -F '|' '{ print $1 }' /tmp/jieguo1` echo "\t整整簿${2}总结存: $sum0 \c" echo "\t整整簿${2}总户数: $sum1" } #==========================================================================# #依照上面的形式很容易写出活期、零整的过程:hzbsum()、lzbsum()、lzb() main() #主过程,询问储蓄所号及调用子过程 { clear #清屏 echo "\n\n\t\t请输入储蓄所号 : \c" #显示提示字符串 read cxsh #读取输入 echo #换行 hzbsum $cxsh #调用过程hzbsum,变量cxsh的值为位置参数 echo #换行 zzbsum $cxsh #调用过程zzbsum,变量cxsh的值为位置参数 for i in 03 12 24 36 60 96 do zzb $cxsh $I done #以for语句循环调用过程zzb,第二个位置参数 #分别为 03 12 24 36 60 96 echo #换行 lzbsum $cxsh #调用过程lzbsum,变量cxsh的值为位置参数 lzb $cxsh 12 #调用过程lzb,“03”作为第二个位置参数,以下用法相同 lzb $cxsh 36 lzb $cxsh 60 rm /tmp/jieguo0 /tmp/jieguo1>/dev/null 2>/dev/null #删除临时文件 } #===========================================================================# main #调用main主过程
| | |