awk
时间:2009-03-03 来源:huanghaojie
12. 附录 A ── Pattern
awk 通过判断 Pattern 之值来决定是否执行其后所对应的Actions.这里列出几种常见的Pattern :
ØBEGIN
BEGIN 为 awk 的保留字, 是一种特殊的 Pattern.
BEGIN 成立(其值为true)的时机是: "awk 程序一开始执行, 尚未读取任何数据之前." 所以在 BEGIN { Actions } 语法中, 其 Actions 部份仅于程序一开始执行时被执行一次. 当 awk 从数据文件读入数据行后, BEGIN 便不再成立, 故不论有多少数据行, 该 Actions 部份仅被执行
一次.
一般常把 "与数据文件内容无关" 与 "只需执行ㄧ次" 的部分置于该Actions(以 BEGIN 为 Pattern)中.
例如:
BEGIN {
FS = "[ \t:]" # 于程序一开始时, 改变awk切割字段的方式
RS = "" # 于程序一开始时, 改变awk分隔数据行的方式
count = 100 # 设定变量 count 的起始值
print " This is a title line " # 印出一行 title
}
....... # 其它 Pattern { Actions } .....
有些awk程序甚至"不需要读入任何数据行". 遇到这情况可把整个程序置于以 BEGIN 为 Pattern的 Actions 中.
例如 :
BEGIN { print " Hello ! the Word ! " }
注意 :执行该类仅含 BEGIN { Actions } 的程序时, awk 并不会开启任何数据文件进行处理.
ØEND
END 为 awk 的保留字, 是另一种特殊的 Pattern.
END 成立(其值为true)的时机与 BEGIN 恰好相反, 为:"awk 处理完所有数据, 即将离开程序时"平常读入数据行时, END并不成立, 故其对应的 Actions 并不被执行; 唯有当awk读完所有数据时, 该 Actions 才会被执行
注意 : 不管数据行有多少笔, 该 Actions 仅被执行一次.
Ø关系表达式
使用像 " A 关系运算符 B" 的表达式当成 Pattern.
当 A 与 B 存在所指定的关系(Relation)时, 该 Pattern 就算成立(true).
例如 :
length($0) <= 80 { print $0 }
上式中 length($0)<= 80 是一个 Pattern, 当 $0(数据行)之长度小于等于80时该 Pattern 之值为true, 将执行其后的 Action (打印该数据行).
awk 中提供下列 关系运算符(Relation Operator)
运算符 含意
> 大于
< 小于
>= 大于或等于
<= 小于或等于
== 等于
!= 不等于
~ match
!~ not match
上列关系运算符除~(match)与!~(not match)外与 C 语言中之含意一致.
~(match) 与!~(match) 在 awk 之含意简述如下 :
若 A 为一字符串, B 为一正则表达式.
A ~B 判断 字符串A 中是否 包含 能匹配(match)B式样的子字符串.
A !~B 判断 字符串A 中是否 未包含 能匹配(match)B式样的子字符串.
例如 :
$0 ~ /program[0-9]+\.c/ { print $0 }
$0 ~ /program[0-9]+\.c/ 整个是一个 Pattern, 用来判断$0(数据行)中是否含有可 match /program[0-9]+\.c/ 的子字符串, 若$0 中含有该类字符串, 则执行 print (打印该行数据).
Pattern 中被用来比对的字符串为$0 时(如本例), 可仅以正则表达式部分表示整个Pattern.
故本例的 Pattern 部分$0 ~/program[0-9]+\.c/ 可仅用/program[0-9]+\.c/表之(有关匹配及正则表达式请参考 附录 E )
Ø正则表达式
直接使用正则表达式当成 Pattern; 此为 $0 ~ 正则表达式 的简写.
该 Pattern 用以判断 $0(数据行) 中是否含有匹配该正则表达式的子字符串; 若含有该成立(true) 则执行其对应的 Actions.
例如 :
/^[0-9]*$/ { print "This line is a integer !" }
与 $0 ~/^[0-9]*$/ { print "This line is a integer !" } 相同
Ø混合 Pattern
之 前所介绍的各种 Patterns, 其计算后结果为一逻辑值(True or False).awk 中逻辑值彼此间可通过&&(and), ||(or), !(not) 结合成一个新的逻辑值.故不同 Patterns 彼此可通过上述结合符号来结合成一个新的 Pattern. 如此可进行复杂的条件判断.
例 如 :
FNR >= 23 && FNR <= 28 { print " " $0 }
上式利用&& (and) 将两个 Pattern 求值的结果合并成一个逻辑值.
该式将数据文件中 第23行 到 28行 向右移5格(先输出5个空白字符)后输出.
( FNR 为awk的内建变量, 请参考 附录 D )
ØPattern1 , Pattern2
遇到这种 Pattern, awk 会帮您设立一个 switch(或flag).
当awk读入的数据行使得 Pattern1 成立时, awk 会打开(turn on)这 switch.
当awk读入的数据行使得 Pattern2 成立时, awk 会关上(turn off)这个 switch.
该 Pattern 成立的条件是 :
当这个 switch 被打开(turn on)时 (包括 Pattern1, 或 Pattern2 成立的情况)
例 如 :
FNR >= 23 && FNR <= 28 { print " " $0 }
可改写为
FNR == 23 , FNR == 28 { print " " $0 }
说 明 :
当 FNR >= 23 时, awk 就 turn on 这个 switch; 因为随着数据行的读入, awk不停的累加 FNR. 当 FNR = 28 时, Pattern2 (FNR == 28) 便成立, 这时 awk 会关上这个 switch.
当 switch 打开的期间, awk 会执行 print " " $0
( FNR 为awk的内建变量, 请参考 附录 D )
13. 附录 B ── Actions
Actions 是由下列指令(statement)所组成 :
* 表达式 ( function calls, assignments..)
* print 表达式列表
* printf( 格式化字符串, 表达式列表)
* if( 表达式 ) 语句 [else 语句]
* while( 表达式 ) 语句
* do 语句 while( 表达式)
* for( 表达式; 表达式; 表达式) 语句
* for( variable in array) 语句
* delete
* break
* continue
* next
* exit [表达式]
* 语句
awk 中大部分指令与 C 语言中的用法一致, 此处仅介绍较为常用或容易混淆的指令的用法.
Ø流程控制指令
l if 指令
语法
if (表达式) 语句1 [else 语句2 ]
范例 :
if( $1 > 25 )
print "The 1st field is larger than 25"
else print "The 1st field is not larger than 25"
(a)与 C 语言中相同, 若 表达式 计算(evaluate)后之值不为 0 或空字符串, 则执行 语句1; 否则执行 语句2.
(b)进行逻辑判断的表达式所返回的值有两种, 若最后的逻辑值为true, 则返回1, 否则返回0.
(c)语法中else 语句2 以[ ] 前后括住表示该部分可视需要而予加入或省略.
l while 指令
语法 :
while( 表达式 ) 语句
范例 :
while( match(buffer,/[0-9]+\.c/ ) ){
print "Find :" substr( buffer,RSTART, RLENGTH)
buff = substr( buffer, RSTART + RLENGTH)
}
上列范例找出 buffer 中所有能匹配 /[0-9]+.c/(数字之后接上 ".c"的所有子字符串).
范例中 while 以函数 match( )所返回的值做为判断条件. 若buffer 中还含有匹配指定条件的子字符串(match成功), 则 match()函数返回1,while 将持续进行其后的语句.
l do-while 指令
语法 :
do 语句 while(表达式)
范例 :
do{
print "Enter y or n ! "
getline data
} while( data !~ /^[YyNn]$/)
(a) 上例要求用户从键盘上输入一个字符, 若该字符不是Y, y, N, 或 n则会不停执行该循环, 直到读取正确字符为止.
(b)do-while 指令与 while 指令 最大的差异是 : do-while 指令会先执行statement而后再判断是否应继续执行. 所以, 无论如何其 statement 部分至少会执行一次.
l for Statement 指令(一)
语法 :
for(variable in array ) statement
范例 : 执行下列命令
awk '
BEGIN{
X[1]= 50; X[2]= 60; X["last"]= 70
for( any in X )
printf("X[%s] = %d\n", any, X[any] )
}'
结果输出 :
X[last] = 70
X[1] = 50
X[2] = 60
(a)这个 for 指令, 专用以查找数组中所有的下标值, 并依次使用所指定的变量予以记录. 以本例而言, 变量 any 将逐次代表 "last", 1 及2 .
(b)以这个 for 指令, 所查找出的下标之值彼此间并无任何次续关系.
(c)第5节中有该指令的使用范例, 及解说.
l for Statement 指令(二)
语法 :
for(expression1; expression2; expression3) statement
范例 :
for(i=1; i< =10; i++) sum = sum + i
说明 :
(a)上列范例用以计算 1 加到 10 的总和.
(b)expression1 常用于设定该 for 循环的起始条件, 如上例中的 i=1
expression2 用于设定该循环的停止条件, 如上例中的 i <= 10
expression3 常用于改变 counter 之值, 如上例中的 i++
l break 指令
break 指令用以强迫中断(跳离) for, while, do-while 等循环.
范例 :
while( getline < "datafile" > 0 )
{
if( $1 == 0 )
break
else
print $2 / $1
}
上例中, awk 不断地从文件 datafile 中读取资料, 当$1等于0时,就停止该执行循环.
l continue 指令
循环中的 statement 进行到一半时, 执行 continue 指令来略过循环中尚未执行的statement.
范例 :
for( index in X_array)
{
if( index !~ /[0-9]+/ ) continue
print "There is a digital index", index
}
上例中若 index 不为数字则执行 continue, 故将略过(不执行)其后的指令.
需留心 continue 与 break 的差异 : 执行 continue 只是掠过其后未执行的statement, 但并未跳离开该循环.
l next 指令
执行 next 指令时, awk 将掠过位于该指令(next)之后的所有指令(包括其后的所有Pattern { Actions }), 接著读取下一笔数据行,继续从第一个 Pattern {Actions} 执行起.
范例 :
/^[ \t]*$/ { print "This is a blank line! Do nothing here !"
next
}
$2 != 0 { print $1, $1/$2 }
上例中, 当 awk 读入的数据行为空白行时( match /^[ \]*$/ ),除打印消息外只执行 next, 故 awk 将略过其后的指令, 继续读取下一笔资料, 从头(第一个 Pattern { Actions })执行起.
l exit 指令
执行 exit 指令时, awk将立刻跳离(停止执行)该awk程序.
Øawk 中的 I/O 指令
l printf 指令
该指令与 C 语言中的用法相同, 可借由该指令控制资料输出时的格式.
语法 :
printf("format", item1, item2,.. )
范 例 :
id = "BE-2647"; ave = 89
printf("ID# : %s Ave Score : %d\n", id, ave)
(a)结果印出 :
ID# : BE-2647 Ave Score : 89
(b)format 部分是由 一般的字串(String Constant) 及 格式控制字符(Formatcontrol letter, 其前会加上一个%字符)所构成. 以上式为例"ID# : " 及 " Ave Score : " 为一般字串. %s 及 %d 为格式控制字符.
(c)打印时, 一般字串将被原封不动地打印出来. 遇到格式控制字符时,则依序把 format后方之 item 转换成所指定的格式后进行打印.
(d)有关的细节, 读者可从介绍 C 语言的书籍上得到较完整的介绍.
(e)print 及 printf 两个指令, 其后可使用 > 或 >> 将输出到stdout 的数据重定向到其它文件, 7.1 节中有完整的
l print 指令
范 例 :
id = "BE-267"; ave = 89
print "ID# :", id, "Ave Score :"ave
(a)结果印出 :
ID# : BE-267 Ave Score :89
(b)print 之后可接上字串常数(Constant String)或变量. 它们彼此间可用"," 隔开.
(c)上式中, 字串 "ID# :" 与变量 id 之间使用","隔开, 打印时两者之间会以自动 OFS(请参考 附录D 內建变量 OFS) 隔开. OFS 之值一般內定为 "一个空格"
(d)上式中, 字串 "Ave Score :" 与变量ave之间并未以","隔开, awk会将这两者先当成字串concate在一起(变成"Ave Score :89")后,再予打印
l getline 指令
语法
语法
由何处读取数据
数据读入后置于
getline var < file
所指定的 file
变量 var(var省略时,表示置于$0)
getline var
pipe 变量
变量 var(var省略时,表示置于$0)
getline var
见 注一
变量 var(var省略时,表示置于$0)
getline 一次读取一行资料, 若读取成功则return 1,若读取失败则return -1, 若遇到文件结束(EOF), 则return 0
l close 指令
该指令用以关闭一个打开的文件, 或 pipe (见下例)
范 例 :
BEGIN { print "ID # Salary" > "data.rpt" }
{ print $1 , $2 * $3 | "sort -k 1 > data.rpt" }
END{ close( "data.rpt" )
close( "sort -k 1 > data.rpt" )
print " There are", NR, "records processed."
}
说 明 :
(a) 上例中, 一开始执行 print "ID # Salary" > "data.rpt" 指令来输出一行抬头. 它使用 I/O Redirection ( > )将数据转输出到data.rpt,故此时文件 data.rpt 是处於 Open 状态.
(b) 指令 print $1, $2 * $3 不停的将输出的资料送往 pipe(|), awk在程序将结束时才会呼叫 shell 使用指令 "sort -k 1 > data.rpt" 来处理 pipe 中的数据; 并未立即执行, 这点与 Unix 中pipe的用法不尽相同.
(c) 最后希望於文件 data.rpt 的末尾处加上一行 "There are.....".但此时, Shell尚未执行 "sort -k 1 > data.rpt" 故各数据行排序后的 ID 及 Salary 等数据尚未写入data.rpt. 所以得命令 awk 提前先通知 Shell 执行命令 "sort -k 1 > data.rpt" 来处理 pipe 中的资料. awk中这个动作称为 close pipe. 是由执行 close ( "shell command" )来完成. 需留心 close( )指令中的 shell command
需与"|"后方的 shell command 完全相同(一字不差), 较佳的方法是先以该字串定义一个简短的变量, 程序中再以此变量代替该shell command
(d) 为什么执行 close("data.rpt") ? 因为 sort 完后的资料也将写到data.rpt,而该文件正为awk所打开使用(write)中, 故awk程式中应先关闭data.rpt. 以免造成因二个 processes 同时打开一个文件进行输出(write)所产生的错误.
l system 指令
该指令用以执行 Shell上的 command.
范 例 :
DataFile = "invent.rpt"
system( "rm " DataFile )
说明 :
(a) system("字符串")指令接受一个字符串当成Shell的命令. 上例中, 使用一个字串常数"rm " 连接(concate)一个变量 DataFile 形成要求 Shell 执行的命令.Shell 实际执行的命令为 "rm invent.rpt".
l "|" pipe指令
"|" 配合 awk 输出指令, 可把 output 到 stdout 的资料继续转送给Shell 上的某一命令当成input的资料.
"|" 配合 awk getline 指令, 可呼叫 Shell 执行某一命令, 再以 awk 的 getline 指令将该命令的所产生的资料读进 awk 程序中.
范 例 :
{ print $1, $2 * $3 | "sort -k 1 > result" }
"date" | getline Date_data
读者请参考7.2 节,其中有完整的范例说明.
Øawk 释放所占用的记忆体的指令
awk 程式中常使用数组(Array)来记忆大量数据, delete 指令便是用来释放数组中的元素所占用的内存空间.
范 例 :
for( any in X_arr )
delete X_arr[any]
读者请留心, delete 指令一次只能释放数组中的一个元素.
Øawk 中的数学运算符(Arithmetic Operators)
+(加), -(減), *(乘), /(除), %(求余数), ^(指数) 与 C 语言中用法相同
Øawk 中的赋值运算符(Assignment Operators)
=, +=, -=, *= , /=, %=, ^=
x += 5 的意思为 x = x + 5, 其余类推.
Øawk 中的条件运算符(Conditional Operator)
语 法 :
判断条件 ? value1 : value2
若 判断条件 成立(true) 则返回 value1, 否则返回 value2.
Øawk 中的逻辑运算符(Logical Operators)
&&( and ), ||(or), !(not)
Extended Regular Expression 中使用 "|" 表示 or 请勿混淆.
Øawk 中的关系运算符(Relational Operators)
>, >=, <, < =, ==, !=, ~, !~
Øawk 中其它的运算符
+(正号), -(负号), ++(Increment Operator), - -(Decrement Operator)
Øawk 中各运算符的运算级
按优先高低排列:
$ (栏位运算元, 例如 : i=3; $i表示第3栏)
^ (指数运算)
+ ,- ,! (正,负号,及逻辑上的 not)
* ,/ ,% (乘,除,余数)
+ ,- (加,減)
>, > =,< , < =, ==, != (关系运算符)
~, !~ (match, not match)
&& (逻辑上的 and)
|| (逻辑上的 or )
? : (条件运算符)
= , +=, -=,*=, /=, %=, ^= (赋值运算符)
14. 附录C ── awk 的內建函数(Built-in Functions)
Ø(一). 字串函数
l index( 原字串, 找寻的子字串 ):
若原字串中含有欲找寻的子字串,则返回该子字串在原字串中第一次出现的位置,若未曾出现该子字串则返回0.
例如执行 :
$ awk 'BEGIN{ print index("8-12-94","-") }'
结果印出
2
l length( 字串 ) : 返回该字串的长度.
例如执行 :
$ awk 'BEGIN { print length("John") '}
结果印出
4
l match( 原字串, 用以找寻比对的正则表达式 ):
awk会在原字串中找寻合乎正则表达式的子字串. 若合乎条件的子字串有多个, 则以原字串中最左方的子字串为准.
awk找到该字串后会依此字串为依据进行下列动作:
设定awk內建变量 RSTART, RLENGTH :
RSTART = 合条件的子字串在原字串中的位置.
= 0 ; 若未找到合条件的子字串.
RLENGTH = 合条件的子字串长度.
= -1 ; 若未找到合条件的子字串.
返回 RSTART 之值.
例如执行 :
awk ' BEGIN {
match( "banana", /(an)+/ )
print RSTART, RLENGTH
} '
执行结果输出
2 4
l split( 原字串, 数组名称, 分隔字符 ):
awk将依所指定的分隔字符(field separator)来分隔原字串成一个个的栏位(field),并以指定的数组记录各个被分隔的栏位.
例如 :
ArgLst = "5P12p89"
split( ArgLst, Arr, /[Pp]/)
执行后 : Arr[1]=5, Arr[2]=12, Arr[3]=89
l sprintf(格式字符串, 项1, 项2, ...)
该函数的用法与awk或C的输出函数printf()相同. 所不同的是sprintf()会将要求印出的结果当成一个字串返回. 一般最常使用sprintf()来改变资料格式. 如: x 为一数值资料, 若欲将其变成一个含二位小数的资料,可执行如下指令:
x = 28
x = sprintf("%.2f",x)
执行后 x = "28.00"
l sub( 比对用的正则表达式, 将替換的新字串, 原字串 )
sub( )将原字串中第一个(最左边)合乎所指定的正则表达式的子字串改以新字串取代.
第二个参数"将替換的新字串"中可用"&"来代表"合於条件的子字串"
承上例,执行下列指令:
A = "a6b12anan212.45an6a"
sub( /(an)+[0-9]*/, "[&]", A)
print A
结果输出
ab12[anan212].45an6a
sub()不仅可执行替换(replacement)的功用,当第二个参数为空字串("")时,sub()所执行的是"去除指定字串"的功用.
通过 sub() 与 match() 的搭配使用,可逐次取出原字串中合乎指定条件的所有子字串.
例如执行下列程式:
awk '
BEGIN {
data = "p12-P34 P56-p61"
while( match( data ,/[0-9]+/) > 0) {
print substr(data, RSTART, RLENGTH )
sub(/[0-9]+/,"",data)
}
}'
结果输出 :
12
34
56
61
sub( )中第三个参数(原字串)若未指定,则其预设值为$0.
可用 sub( /[9-0]+/,"digital" ) 表示 sub(/[0-9]+/,"digital",$0 )
l gsub( 比对用的正则表达式, 将替換的新字串, 原字串 )
这个函数与 sub()一样,同样是进行字串取代的函数. 唯一不同点是
gsub()会取代所有合条件的子字串.
gsub()会返回被取代的子字串个数.
请参考 sub().
l substr( 字串,起始位置 [,长度] ):
返回从起始位置起,指定长度的子字串. 若未指定长度,则返回起始位置到字串末尾的子字串.
执行下例
$ awk 'BEGIN { print substr("User:Wei-Lin Liu", 6)}'
结果印出
Wei-Lin Liu
Ø(二). 数学函数
l int(x) : 返回x的整数部分(去掉小数).
例如 :
int(7.8) 将返回 7
int(-7.8) 将返回 -7
l sqrt(x) : 返回x的平方根.
例如 :
sqrt(9) 将返回 3
若 x 为负数,则执行 sqrt(x)时将造成 Run Time Error [译者注: 我这里没有发生错误,返回的是"nan"]
l exp(x) : 将返回e的x次方.
例如 :
exp(1) 将返回 2.71828
l log(x) : 将返回x以e为底的对数值.
例如 :
log(exp(1)) 将返回 1
若 x< 0 ,则执行 sqrt(x)时将造成 Run Time Error. [译者注: 我这里也没有发生错误,返回的是"nan"]
l sin(x) : x 须以弧度为单位,sin(x)将返回x的sin函数值.
l cos(x) : x 须以弧度为单位,cos(x)将返回x的cos函数值
l atan2(y,x) : 返回 y/x 的tan反函数之值,返回值系以弧度为单位.
l rand() : 返回介于 0与1之间的(近似)随机数值; 0 < rand()<1.
除非使用者自行指定rand()函数起始的种子,否则每次执行awk程式时, rand()函数都将使用同一个內定的种子,来产生随机数.
l srand([x]) : 指定以x为rand( )函数起始的种子.
若省略了x,则awk会以执行时的日期与时间为rand()函数起始的种子.
15. 附录D ── awk 的內建变量 Built-in Variables
因內建变量的个数不多, 此处按其相关性分类说明, 并未按其字母顺序排列.
l ARGC
ARGC表示命令行上除了选项 -F, -v, -f 及其所对应的参数之外的所有参数的个数.若将"awk程式"直接写於命令列上, 则 ARGC 亦不将该"程式部分"列入计算.
l ARGV
ARGV数组用以记录命令列上的参数.
例 : 执行下列命令
$ awk -F\t -v a=8 -f prg.awk file1.dat file2.dat
或
$ awk -F\t -v a=8 '{ print $1 * a }' file1.dat file2.dat
执行上列任一程式后
ARGC = 3
ARGV[0] = "awk"
ARGV[1] = "file1.dat"
ARGV[2] = "file2.dat"
读者请留心 : 当 ARGC = 3 时, 命令列上仅指定了 2 个文件.
注 :
-F\t 表示以 tab 为栏位分隔字符 FS(field seporator).
-v a=8 是用以初始化程序中的变量 a.
l FILENAME
FILENAME用以表示目前正在处理的文件档名.
l FS
栏位分隔字符.
l $0
表示目前awk所读入的数据行.
l $1,$2..
分別表示所读入的数据行之第一栏, 第二栏,..
说明:
当awk读入一笔数据行 "A123 8:15" 时,会先以$0 记录.
故 $0 = "A123 8:15"
若程序中进一步使用了 $1, $2.. 或 NF 等內建变量时, awk才会自动分割 $0.
以便取得栏位相关的资料. 切割后各个栏位的资料会分別以$1, $2, $3...予以记录.
awk內定(default)的 栏位分隔字符(FS) 为 空白字符(空格及tab).
以本例而言, 读者若未改变 FS, 则分割后:
第一栏($1)="A123", 第二栏($2)="8:15".
使用者可用正则表达式自行定义 FS. awk每次需要分割数据行时, 会参考目前FS的值.
例如 :
令 FS = "[ :]+" 表示任何由 空白" " 或 冒号":" 所组成的字串都可当成分隔字符, 则分割后 :
第一栏($1) = "A123", 第二栏($2) = "8", 第三栏($3) = "15"
l NR
NR 表从 awk 开始执行该程序后所读取的数据行数.
l FNR
FNR 与 NR 功用类似. 不同的是awk每打开一个新的文件,FNR 便从 0 重新累计
l NF
NF表目前的数据行所被切分的栏位数.
awk 每读入一笔资料后, 在程序中可以 NF 来得知该行数据包含的栏位个数.在下一笔资料被读入之前, NF 并不会改变. 但使用者若自行使用$0来记录数据,例如: 使用 getline , 此时 NF 将代表新的 $0 上所记载的资料的栏位个数.
l OFS
OFS输出时的栏位分隔字符. 预设值 " "(一个空白), 详见下面说明.
l ORS
ORS输出时数据行的分隔字符. 预设值 "\n"(跳行), 见下面说明.
l OFMT
OFMT数值资料的输出格式. 预设值 "%.6g"(若须要时最多印出6位小数)
当使用 print 指令一次印出多项资料时,
例如 : print $1, $2
输出时, awk会自动在 $1 与 $2 之间补上一个 OFS 之值
每次使用 print 输出后, awk会自动补上 ORS 之值.
使用 print 输出数值数据时, awk将采用 OFMT 之值为输出格式.
例如 :
$ awk 'BEGIN { print 2/3,1; OFS=":"; OFMT="%.2g"; print 2/3,1 }'
输出:
0.666667 1
0.67:1
程序中通过改变OFS和OFMT的值, 改变了指令 print 的输出格式.
l RS
RS( Record Separator) : awk从文件上读取资料时, 将根据 RS 的定义把资料切割成许多Records,而awk一次仅读入一个Record,以进行处理.
RS 的预设值是 "\n". 所以一般 awk一次仅读入一行资料.
有时一个Record含括了几行资料(Multi-line Record). 这情況下不能再以"\n"
来分隔相邻的Records, 可改用 空白行 来分隔.
在awk程式中,令 RS = "" 表示以 空白行 来分隔相邻的Records.
l RSTART
RSTART与使用字串函数 match( )有关的变量,详见下面说明.
l RLENGTH
RLENGTH与使用字串函数match( )有关之变量.
当使用者使用 match(...) 函数后, awk会将 match(...) 执行的结果以RSTART,RLENGTH 记录.
请参考 附录 C awk的內建函数 match().
l SUBSEP
SUBSEP(Subscript Separator) 数组下标的分隔字符,
预设值为"\034"实际上, awk中的 数组 只接受 字串 当它的下标,如: Arr["John"].
但使用者在 awk 中仍可使用 数字 当阵列的下标, 甚至可使用多维的数组(Multi-dimenisional Array) 如: Arr[2,79]
事实上, awk在接受 Arr[2,79] 之前, 就已先把其下标转换成字串"2\03479", 之后便以Arr["2\03479"] 代替 Arr[2,79].
可参考下例 :
awk 'BEGIN {
Arr[2,79] = 78
print Arr[2,79]
print Arr[ 2 , 79 ]
print Arr["2\03479"]
idx = 2 SUBSEP 79
print Arr[idx]
}
' $*
执行结果输出:
78
78
78
78
16. 附录E ── 正则表达式(Regular Expression) 简介
l 为什么要使用正则表达式
UNIX 中提供了许多 指令 和 tools, 它们具有在文件中 查找(Search)字串或替换(Replace)字串 的功能. 像 grep, vi , sed, awk,...
不论是查找字串或替换字串, 都得先告诉这些指令所要查找(被替换)的字串为何.若未能预先明确知道所要查找(被替换)的字串为何, 只知该字串存在的范围或特征时,例如 :
(一)找寻 "T0.c", "T1.c", "T2.c".... "T9.c" 当中的任一字串.
(二)找寻至少存在一个 "A"的任意字串.
这情況下, 如何告知执行查找字串的指令所要查找的字串为何.
例 (一) 中, 要查找任一在 "T" 与 ".c" 之间存在一个阿拉伯数字的字串;当然您可以列举的方式, 一一把所要找寻的字串告诉执行命令的指令.但例 (二) 中合乎该条件的字串有无限种可能, 势必无法一一列举.此时,便需要另一种字串表示的方法(协定).
l 什么是正则表达式
正则表达式(以下简称 Regexp)是一种字串表达的方式. 可用以指定具有某特征的所有字串.
注: 为区別于一般字串, 本附录中代表 Regexp 的字串之前皆加 "Regexp". awk 程式中常以/..../括住 Regexp; 以区別于一般字串.
l 组成正则表达式的元素
普通字符 除了 . * [ ] + ? ( ) \ ^ $ 外之所有字符.
由普通字符所组成的Regexp其意义与原字串字面意义相同.
例如: Regexp "the" 与一般字串的 "the" 代表相同的意义.
. (Meta character) : 用以代表任意一字符.
须留心 UNIX Shell 中使用 "*"表示 Wild card, 可用以代表任意长度的字串.而 Regexp 中使用 "." 来代表一个任意字符.(注意: 并非任意长度的字串)Regexp 中 "*" 另有其它涵意, 并不代表任意长度的字串.
^ 表示该字串必须出现于行首.
$ 表示该字串必须出现于行末.
例如 :
Regexp /^The/ 用以表示所有出现于行首的字串 "The".
Regexp /The$/ 用以表示所有出现于行末字串 "The".
\ 将特殊字符还原成字面意义的字符(Escape character)
Regexp 中特殊字符将被解释成特定的意义. 若要表示特殊字符的字面(literal meaning)意义时,在特殊字符之前加上"\"即可.
例如 :
使用Regexp来表示字串 "a.out"时, 不可写成 /a.out/.
因 为 "."是特殊字符, 表任一字符. 可符合 Regexp / a.out/ 的字串将不只 "a.out" 一个; 字串 "a2out", "a3out", "aaout" ...都符合 Regexp /a.out/ 正确的用法为: / a\.out/
[...]字符集合, 用以表示两中括号间所有的字符当中的任一个.
例如:
Regexp /[Tt]/ 可用以表示字符 "T" 或 "t".故 Regexp /[Tt]he/ 表示 字串 "The" 或 "the".
字符集合 [...] 內不可随意留空白.
例如: Regexp /[ Tt ]/ 其中括号內有空白字符, 除表示"T", "t" 中任一个字符, 也可代表一个 " "(空白字符)
- 字符集合中可使用 "-" 来指定字符的区间, 其用法如下:
Regexp /[0-9]/ 等于 /[0123456789]/ 用以表示任意一个阿拉伯数字.
同理 Regexp /[A-Z]/ 用以表示任意一个大写英文字母.
但应留心:
Regexp /[0-9a-z]/ 并不等于 /[0-9][a-z]/; 前者表示一个字符,后者表示二个字符.
Regexp /[-9]/ 或 /[9-]/ 只代表字符 "9"或 "-".
[^...]使用[^..] 产生字符集合的补集(complement set).
其用法如下 :
例如: 要指定 "T" 或 "t" 之外的任一个字符, 可用 /[^Tt]/ 表之.
同理 Regexp /[^a-zA-Z]/ 表示英文字母之外的任一个字符.
须留心 "^" 的位置 : "^"必须紧接於"["之后, 才代表字符集合的补集
例如 :Regexp /[0-9\^]/ 只是用以表示一个阿拉伯数字或字符"^".
* 形容字符重复次数的特殊字符.
"*" 形容它前方之字符可出现 1 次或多次, 或不出现(0次).
例如:
Regexp /T[0-9]*\.c/ 中 * 形容其前 [0-9] (一个阿拉伯数字)出现的次数可为 0次或 多次.故Regexp /T[0-9]*\.c/ 可用以表示"T.c", "T0.c", "T1.c"..."T19.c"
+形容其前的字符出现一次或一次以上.
例如:
Regexp /[0-9]+/ 用以表示一位或一位以上的数字.
? 形容其前的字符可出现一次或不出现.
例如:
Regexp /[+-]?[0-9]+/ 表示数字(一位以上)之前可出现正负号或不出现正负号.
(...)用以括住一群字符,且将之视成一个group(见下面说明)
例如 :
Regexp /12+/ 表示字串 "12", "122", "1222", "12222",...
Regexp /(12)+/ 表示字串 "12", "1212", "121212", "12121212"....
上式中 12 以( )括住, 故 "+" 所形容的是 12, 重复出现的也是 12.
| 表示逻辑上的"或"(or)
例如:
Regexp / Oranges? | apples? | water/ 可用以表示 : 字串 "Orange", "Oranges" 或 "apple", "apples" 或 "water"
l match是什么?
讨论 Regexp 时, 经常遇到 "某字串匹配( match )某 Regexp"的字眼. 其意思为 : "这个 Regexp 可被解释成该字串".
[ 例如] :
字串 "the" 匹配(match) Regexp /[Tt]he/.
因为 Regexp /[Tt]he/ 可解释成字串 "the" 或 "The", 故字串 "the" 或 "The"都匹配(match) Regexp /[Th]he/.
l awk 中提供二个关系运算符(Relational Operator,见注一) ~ !~,
它们也称之为 match, not match.但函义与一般常称的 match 略有不同.
其定义如下:
A 表一字串, B 表一 Regular Expression
只要 A 字串中存在有子字串可 match( 一般定义的 match) Regexp B , 则 A ~B 就算成立, 其值为 true, 反之则为 false.
! ~ 的定义与~恰好相反.
例 如 :
"another" 中含有子字串 "the" 可 match Regexp /[Tt]he/ , 所以
"another" ~ /[Tt]he/ 之值为 true.
[注 一] : 有些论著不把这两个运算符( ~, !~)与 Relational Operators 归为一类.
l 应用 Regular Expression 解题的简例
下面列出一些应用 Regular Expression 的简例, 部分范例中会更改$0 之值, 若您使用的 awk不允许用户更改 $0时, 请改用 gawk.
例1:
将文件中所有的字串 "Regular Expression" 或 "Regular expression" 换成 "Regexp"
awk '
{ gsub( /Regular[ \t]+[Ee]xpression/, "Regexp")
print
}
' $*
例2:
去除文件中的空白行(或仅含空白字符或tab的行)
awk '$0 !~ /^[ \t]*$/ { print }' $*
例3:
在文件中具有 ddd-dddd (电话号码型态, d 表digital)的字串前加上"TEL : "
awk '
{ gsub( /[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]/, "TEL : &" )
print
}
' $*
例4:
从文件的 Fullname 中分离出 路径 与 档名
awk '
BEGIN{
Fullname = "/usr/local/bin/xdvi"
match( Fullname, /.*\//)
path = substr(Fullname, 1, RLENGTH-1)
name = substr(Fullname, RLENGTH+1)
print "path :", path," name :",name
}
' $*
结果印出
path : /usr/local/bin name : xdvi
例5:
将某一数值改以现金表示法表示(整数部分每三位加一撇,且含二位小数)
awk '
BEGIN {
Number = 123456789
Number = sprintf("$%.2f",Number)
while( match(Number,/[0-9][0-9][0-9][0-9]/ ) )
sub(/[0-9][0-9][0-9][.,]/, ",&", Number)
print Number
}
' $*
结果输出
$123,456,789.00
例6:
把文件中所有具 "program数字.f"形态的字串改为"[Ref : program数字.c]"
awk '
{
while( match( $0, /program[0-9]+\.f/ ) ){
Replace = "[Ref : " substr( $0, RSTART, RLENGTH-2) ".c]"
sub( /program[0-9]+\.f/, Replace)
}
print
}
' $*
awk 通过判断 Pattern 之值来决定是否执行其后所对应的Actions.这里列出几种常见的Pattern :
ØBEGIN
BEGIN 为 awk 的保留字, 是一种特殊的 Pattern.
BEGIN 成立(其值为true)的时机是: "awk 程序一开始执行, 尚未读取任何数据之前." 所以在 BEGIN { Actions } 语法中, 其 Actions 部份仅于程序一开始执行时被执行一次. 当 awk 从数据文件读入数据行后, BEGIN 便不再成立, 故不论有多少数据行, 该 Actions 部份仅被执行
一次.
一般常把 "与数据文件内容无关" 与 "只需执行ㄧ次" 的部分置于该Actions(以 BEGIN 为 Pattern)中.
例如:
BEGIN {
FS = "[ \t:]" # 于程序一开始时, 改变awk切割字段的方式
RS = "" # 于程序一开始时, 改变awk分隔数据行的方式
count = 100 # 设定变量 count 的起始值
print " This is a title line " # 印出一行 title
}
....... # 其它 Pattern { Actions } .....
有些awk程序甚至"不需要读入任何数据行". 遇到这情况可把整个程序置于以 BEGIN 为 Pattern的 Actions 中.
例如 :
BEGIN { print " Hello ! the Word ! " }
注意 :执行该类仅含 BEGIN { Actions } 的程序时, awk 并不会开启任何数据文件进行处理.
ØEND
END 为 awk 的保留字, 是另一种特殊的 Pattern.
END 成立(其值为true)的时机与 BEGIN 恰好相反, 为:"awk 处理完所有数据, 即将离开程序时"平常读入数据行时, END并不成立, 故其对应的 Actions 并不被执行; 唯有当awk读完所有数据时, 该 Actions 才会被执行
注意 : 不管数据行有多少笔, 该 Actions 仅被执行一次.
Ø关系表达式
使用像 " A 关系运算符 B" 的表达式当成 Pattern.
当 A 与 B 存在所指定的关系(Relation)时, 该 Pattern 就算成立(true).
例如 :
length($0) <= 80 { print $0 }
上式中 length($0)<= 80 是一个 Pattern, 当 $0(数据行)之长度小于等于80时该 Pattern 之值为true, 将执行其后的 Action (打印该数据行).
awk 中提供下列 关系运算符(Relation Operator)
运算符 含意
> 大于
< 小于
>= 大于或等于
<= 小于或等于
== 等于
!= 不等于
~ match
!~ not match
上列关系运算符除~(match)与!~(not match)外与 C 语言中之含意一致.
~(match) 与!~(match) 在 awk 之含意简述如下 :
若 A 为一字符串, B 为一正则表达式.
A ~B 判断 字符串A 中是否 包含 能匹配(match)B式样的子字符串.
A !~B 判断 字符串A 中是否 未包含 能匹配(match)B式样的子字符串.
例如 :
$0 ~ /program[0-9]+\.c/ { print $0 }
$0 ~ /program[0-9]+\.c/ 整个是一个 Pattern, 用来判断$0(数据行)中是否含有可 match /program[0-9]+\.c/ 的子字符串, 若$0 中含有该类字符串, 则执行 print (打印该行数据).
Pattern 中被用来比对的字符串为$0 时(如本例), 可仅以正则表达式部分表示整个Pattern.
故本例的 Pattern 部分$0 ~/program[0-9]+\.c/ 可仅用/program[0-9]+\.c/表之(有关匹配及正则表达式请参考 附录 E )
Ø正则表达式
直接使用正则表达式当成 Pattern; 此为 $0 ~ 正则表达式 的简写.
该 Pattern 用以判断 $0(数据行) 中是否含有匹配该正则表达式的子字符串; 若含有该成立(true) 则执行其对应的 Actions.
例如 :
/^[0-9]*$/ { print "This line is a integer !" }
与 $0 ~/^[0-9]*$/ { print "This line is a integer !" } 相同
Ø混合 Pattern
之 前所介绍的各种 Patterns, 其计算后结果为一逻辑值(True or False).awk 中逻辑值彼此间可通过&&(and), ||(or), !(not) 结合成一个新的逻辑值.故不同 Patterns 彼此可通过上述结合符号来结合成一个新的 Pattern. 如此可进行复杂的条件判断.
例 如 :
FNR >= 23 && FNR <= 28 { print " " $0 }
上式利用&& (and) 将两个 Pattern 求值的结果合并成一个逻辑值.
该式将数据文件中 第23行 到 28行 向右移5格(先输出5个空白字符)后输出.
( FNR 为awk的内建变量, 请参考 附录 D )
ØPattern1 , Pattern2
遇到这种 Pattern, awk 会帮您设立一个 switch(或flag).
当awk读入的数据行使得 Pattern1 成立时, awk 会打开(turn on)这 switch.
当awk读入的数据行使得 Pattern2 成立时, awk 会关上(turn off)这个 switch.
该 Pattern 成立的条件是 :
当这个 switch 被打开(turn on)时 (包括 Pattern1, 或 Pattern2 成立的情况)
例 如 :
FNR >= 23 && FNR <= 28 { print " " $0 }
可改写为
FNR == 23 , FNR == 28 { print " " $0 }
说 明 :
当 FNR >= 23 时, awk 就 turn on 这个 switch; 因为随着数据行的读入, awk不停的累加 FNR. 当 FNR = 28 时, Pattern2 (FNR == 28) 便成立, 这时 awk 会关上这个 switch.
当 switch 打开的期间, awk 会执行 print " " $0
( FNR 为awk的内建变量, 请参考 附录 D )
13. 附录 B ── Actions
Actions 是由下列指令(statement)所组成 :
* 表达式 ( function calls, assignments..)
* print 表达式列表
* printf( 格式化字符串, 表达式列表)
* if( 表达式 ) 语句 [else 语句]
* while( 表达式 ) 语句
* do 语句 while( 表达式)
* for( 表达式; 表达式; 表达式) 语句
* for( variable in array) 语句
* delete
* break
* continue
* next
* exit [表达式]
* 语句
awk 中大部分指令与 C 语言中的用法一致, 此处仅介绍较为常用或容易混淆的指令的用法.
Ø流程控制指令
l if 指令
语法
if (表达式) 语句1 [else 语句2 ]
范例 :
if( $1 > 25 )
print "The 1st field is larger than 25"
else print "The 1st field is not larger than 25"
(a)与 C 语言中相同, 若 表达式 计算(evaluate)后之值不为 0 或空字符串, 则执行 语句1; 否则执行 语句2.
(b)进行逻辑判断的表达式所返回的值有两种, 若最后的逻辑值为true, 则返回1, 否则返回0.
(c)语法中else 语句2 以[ ] 前后括住表示该部分可视需要而予加入或省略.
l while 指令
语法 :
while( 表达式 ) 语句
范例 :
while( match(buffer,/[0-9]+\.c/ ) ){
print "Find :" substr( buffer,RSTART, RLENGTH)
buff = substr( buffer, RSTART + RLENGTH)
}
上列范例找出 buffer 中所有能匹配 /[0-9]+.c/(数字之后接上 ".c"的所有子字符串).
范例中 while 以函数 match( )所返回的值做为判断条件. 若buffer 中还含有匹配指定条件的子字符串(match成功), 则 match()函数返回1,while 将持续进行其后的语句.
l do-while 指令
语法 :
do 语句 while(表达式)
范例 :
do{
print "Enter y or n ! "
getline data
} while( data !~ /^[YyNn]$/)
(a) 上例要求用户从键盘上输入一个字符, 若该字符不是Y, y, N, 或 n则会不停执行该循环, 直到读取正确字符为止.
(b)do-while 指令与 while 指令 最大的差异是 : do-while 指令会先执行statement而后再判断是否应继续执行. 所以, 无论如何其 statement 部分至少会执行一次.
l for Statement 指令(一)
语法 :
for(variable in array ) statement
范例 : 执行下列命令
awk '
BEGIN{
X[1]= 50; X[2]= 60; X["last"]= 70
for( any in X )
printf("X[%s] = %d\n", any, X[any] )
}'
结果输出 :
X[last] = 70
X[1] = 50
X[2] = 60
(a)这个 for 指令, 专用以查找数组中所有的下标值, 并依次使用所指定的变量予以记录. 以本例而言, 变量 any 将逐次代表 "last", 1 及2 .
(b)以这个 for 指令, 所查找出的下标之值彼此间并无任何次续关系.
(c)第5节中有该指令的使用范例, 及解说.
l for Statement 指令(二)
语法 :
for(expression1; expression2; expression3) statement
范例 :
for(i=1; i< =10; i++) sum = sum + i
说明 :
(a)上列范例用以计算 1 加到 10 的总和.
(b)expression1 常用于设定该 for 循环的起始条件, 如上例中的 i=1
expression2 用于设定该循环的停止条件, 如上例中的 i <= 10
expression3 常用于改变 counter 之值, 如上例中的 i++
l break 指令
break 指令用以强迫中断(跳离) for, while, do-while 等循环.
范例 :
while( getline < "datafile" > 0 )
{
if( $1 == 0 )
break
else
print $2 / $1
}
上例中, awk 不断地从文件 datafile 中读取资料, 当$1等于0时,就停止该执行循环.
l continue 指令
循环中的 statement 进行到一半时, 执行 continue 指令来略过循环中尚未执行的statement.
范例 :
for( index in X_array)
{
if( index !~ /[0-9]+/ ) continue
print "There is a digital index", index
}
上例中若 index 不为数字则执行 continue, 故将略过(不执行)其后的指令.
需留心 continue 与 break 的差异 : 执行 continue 只是掠过其后未执行的statement, 但并未跳离开该循环.
l next 指令
执行 next 指令时, awk 将掠过位于该指令(next)之后的所有指令(包括其后的所有Pattern { Actions }), 接著读取下一笔数据行,继续从第一个 Pattern {Actions} 执行起.
范例 :
/^[ \t]*$/ { print "This is a blank line! Do nothing here !"
next
}
$2 != 0 { print $1, $1/$2 }
上例中, 当 awk 读入的数据行为空白行时( match /^[ \]*$/ ),除打印消息外只执行 next, 故 awk 将略过其后的指令, 继续读取下一笔资料, 从头(第一个 Pattern { Actions })执行起.
l exit 指令
执行 exit 指令时, awk将立刻跳离(停止执行)该awk程序.
Øawk 中的 I/O 指令
l printf 指令
该指令与 C 语言中的用法相同, 可借由该指令控制资料输出时的格式.
语法 :
printf("format", item1, item2,.. )
范 例 :
id = "BE-2647"; ave = 89
printf("ID# : %s Ave Score : %d\n", id, ave)
(a)结果印出 :
ID# : BE-2647 Ave Score : 89
(b)format 部分是由 一般的字串(String Constant) 及 格式控制字符(Formatcontrol letter, 其前会加上一个%字符)所构成. 以上式为例"ID# : " 及 " Ave Score : " 为一般字串. %s 及 %d 为格式控制字符.
(c)打印时, 一般字串将被原封不动地打印出来. 遇到格式控制字符时,则依序把 format后方之 item 转换成所指定的格式后进行打印.
(d)有关的细节, 读者可从介绍 C 语言的书籍上得到较完整的介绍.
(e)print 及 printf 两个指令, 其后可使用 > 或 >> 将输出到stdout 的数据重定向到其它文件, 7.1 节中有完整的
l print 指令
范 例 :
id = "BE-267"; ave = 89
print "ID# :", id, "Ave Score :"ave
(a)结果印出 :
ID# : BE-267 Ave Score :89
(b)print 之后可接上字串常数(Constant String)或变量. 它们彼此间可用"," 隔开.
(c)上式中, 字串 "ID# :" 与变量 id 之间使用","隔开, 打印时两者之间会以自动 OFS(请参考 附录D 內建变量 OFS) 隔开. OFS 之值一般內定为 "一个空格"
(d)上式中, 字串 "Ave Score :" 与变量ave之间并未以","隔开, awk会将这两者先当成字串concate在一起(变成"Ave Score :89")后,再予打印
l getline 指令
语法
语法
由何处读取数据
数据读入后置于
getline var < file
所指定的 file
变量 var(var省略时,表示置于$0)
getline var
pipe 变量
变量 var(var省略时,表示置于$0)
getline var
见 注一
变量 var(var省略时,表示置于$0)
getline 一次读取一行资料, 若读取成功则return 1,若读取失败则return -1, 若遇到文件结束(EOF), 则return 0
l close 指令
该指令用以关闭一个打开的文件, 或 pipe (见下例)
范 例 :
BEGIN { print "ID # Salary" > "data.rpt" }
{ print $1 , $2 * $3 | "sort -k 1 > data.rpt" }
END{ close( "data.rpt" )
close( "sort -k 1 > data.rpt" )
print " There are", NR, "records processed."
}
说 明 :
(a) 上例中, 一开始执行 print "ID # Salary" > "data.rpt" 指令来输出一行抬头. 它使用 I/O Redirection ( > )将数据转输出到data.rpt,故此时文件 data.rpt 是处於 Open 状态.
(b) 指令 print $1, $2 * $3 不停的将输出的资料送往 pipe(|), awk在程序将结束时才会呼叫 shell 使用指令 "sort -k 1 > data.rpt" 来处理 pipe 中的数据; 并未立即执行, 这点与 Unix 中pipe的用法不尽相同.
(c) 最后希望於文件 data.rpt 的末尾处加上一行 "There are.....".但此时, Shell尚未执行 "sort -k 1 > data.rpt" 故各数据行排序后的 ID 及 Salary 等数据尚未写入data.rpt. 所以得命令 awk 提前先通知 Shell 执行命令 "sort -k 1 > data.rpt" 来处理 pipe 中的资料. awk中这个动作称为 close pipe. 是由执行 close ( "shell command" )来完成. 需留心 close( )指令中的 shell command
需与"|"后方的 shell command 完全相同(一字不差), 较佳的方法是先以该字串定义一个简短的变量, 程序中再以此变量代替该shell command
(d) 为什么执行 close("data.rpt") ? 因为 sort 完后的资料也将写到data.rpt,而该文件正为awk所打开使用(write)中, 故awk程式中应先关闭data.rpt. 以免造成因二个 processes 同时打开一个文件进行输出(write)所产生的错误.
l system 指令
该指令用以执行 Shell上的 command.
范 例 :
DataFile = "invent.rpt"
system( "rm " DataFile )
说明 :
(a) system("字符串")指令接受一个字符串当成Shell的命令. 上例中, 使用一个字串常数"rm " 连接(concate)一个变量 DataFile 形成要求 Shell 执行的命令.Shell 实际执行的命令为 "rm invent.rpt".
l "|" pipe指令
"|" 配合 awk 输出指令, 可把 output 到 stdout 的资料继续转送给Shell 上的某一命令当成input的资料.
"|" 配合 awk getline 指令, 可呼叫 Shell 执行某一命令, 再以 awk 的 getline 指令将该命令的所产生的资料读进 awk 程序中.
范 例 :
{ print $1, $2 * $3 | "sort -k 1 > result" }
"date" | getline Date_data
读者请参考7.2 节,其中有完整的范例说明.
Øawk 释放所占用的记忆体的指令
awk 程式中常使用数组(Array)来记忆大量数据, delete 指令便是用来释放数组中的元素所占用的内存空间.
范 例 :
for( any in X_arr )
delete X_arr[any]
读者请留心, delete 指令一次只能释放数组中的一个元素.
Øawk 中的数学运算符(Arithmetic Operators)
+(加), -(減), *(乘), /(除), %(求余数), ^(指数) 与 C 语言中用法相同
Øawk 中的赋值运算符(Assignment Operators)
=, +=, -=, *= , /=, %=, ^=
x += 5 的意思为 x = x + 5, 其余类推.
Øawk 中的条件运算符(Conditional Operator)
语 法 :
判断条件 ? value1 : value2
若 判断条件 成立(true) 则返回 value1, 否则返回 value2.
Øawk 中的逻辑运算符(Logical Operators)
&&( and ), ||(or), !(not)
Extended Regular Expression 中使用 "|" 表示 or 请勿混淆.
Øawk 中的关系运算符(Relational Operators)
>, >=, <, < =, ==, !=, ~, !~
Øawk 中其它的运算符
+(正号), -(负号), ++(Increment Operator), - -(Decrement Operator)
Øawk 中各运算符的运算级
按优先高低排列:
$ (栏位运算元, 例如 : i=3; $i表示第3栏)
^ (指数运算)
+ ,- ,! (正,负号,及逻辑上的 not)
* ,/ ,% (乘,除,余数)
+ ,- (加,減)
>, > =,< , < =, ==, != (关系运算符)
~, !~ (match, not match)
&& (逻辑上的 and)
|| (逻辑上的 or )
? : (条件运算符)
= , +=, -=,*=, /=, %=, ^= (赋值运算符)
14. 附录C ── awk 的內建函数(Built-in Functions)
Ø(一). 字串函数
l index( 原字串, 找寻的子字串 ):
若原字串中含有欲找寻的子字串,则返回该子字串在原字串中第一次出现的位置,若未曾出现该子字串则返回0.
例如执行 :
$ awk 'BEGIN{ print index("8-12-94","-") }'
结果印出
2
l length( 字串 ) : 返回该字串的长度.
例如执行 :
$ awk 'BEGIN { print length("John") '}
结果印出
4
l match( 原字串, 用以找寻比对的正则表达式 ):
awk会在原字串中找寻合乎正则表达式的子字串. 若合乎条件的子字串有多个, 则以原字串中最左方的子字串为准.
awk找到该字串后会依此字串为依据进行下列动作:
设定awk內建变量 RSTART, RLENGTH :
RSTART = 合条件的子字串在原字串中的位置.
= 0 ; 若未找到合条件的子字串.
RLENGTH = 合条件的子字串长度.
= -1 ; 若未找到合条件的子字串.
返回 RSTART 之值.
例如执行 :
awk ' BEGIN {
match( "banana", /(an)+/ )
print RSTART, RLENGTH
} '
执行结果输出
2 4
l split( 原字串, 数组名称, 分隔字符 ):
awk将依所指定的分隔字符(field separator)来分隔原字串成一个个的栏位(field),并以指定的数组记录各个被分隔的栏位.
例如 :
ArgLst = "5P12p89"
split( ArgLst, Arr, /[Pp]/)
执行后 : Arr[1]=5, Arr[2]=12, Arr[3]=89
l sprintf(格式字符串, 项1, 项2, ...)
该函数的用法与awk或C的输出函数printf()相同. 所不同的是sprintf()会将要求印出的结果当成一个字串返回. 一般最常使用sprintf()来改变资料格式. 如: x 为一数值资料, 若欲将其变成一个含二位小数的资料,可执行如下指令:
x = 28
x = sprintf("%.2f",x)
执行后 x = "28.00"
l sub( 比对用的正则表达式, 将替換的新字串, 原字串 )
sub( )将原字串中第一个(最左边)合乎所指定的正则表达式的子字串改以新字串取代.
第二个参数"将替換的新字串"中可用"&"来代表"合於条件的子字串"
承上例,执行下列指令:
A = "a6b12anan212.45an6a"
sub( /(an)+[0-9]*/, "[&]", A)
print A
结果输出
ab12[anan212].45an6a
sub()不仅可执行替换(replacement)的功用,当第二个参数为空字串("")时,sub()所执行的是"去除指定字串"的功用.
通过 sub() 与 match() 的搭配使用,可逐次取出原字串中合乎指定条件的所有子字串.
例如执行下列程式:
awk '
BEGIN {
data = "p12-P34 P56-p61"
while( match( data ,/[0-9]+/) > 0) {
print substr(data, RSTART, RLENGTH )
sub(/[0-9]+/,"",data)
}
}'
结果输出 :
12
34
56
61
sub( )中第三个参数(原字串)若未指定,则其预设值为$0.
可用 sub( /[9-0]+/,"digital" ) 表示 sub(/[0-9]+/,"digital",$0 )
l gsub( 比对用的正则表达式, 将替換的新字串, 原字串 )
这个函数与 sub()一样,同样是进行字串取代的函数. 唯一不同点是
gsub()会取代所有合条件的子字串.
gsub()会返回被取代的子字串个数.
请参考 sub().
l substr( 字串,起始位置 [,长度] ):
返回从起始位置起,指定长度的子字串. 若未指定长度,则返回起始位置到字串末尾的子字串.
执行下例
$ awk 'BEGIN { print substr("User:Wei-Lin Liu", 6)}'
结果印出
Wei-Lin Liu
Ø(二). 数学函数
l int(x) : 返回x的整数部分(去掉小数).
例如 :
int(7.8) 将返回 7
int(-7.8) 将返回 -7
l sqrt(x) : 返回x的平方根.
例如 :
sqrt(9) 将返回 3
若 x 为负数,则执行 sqrt(x)时将造成 Run Time Error [译者注: 我这里没有发生错误,返回的是"nan"]
l exp(x) : 将返回e的x次方.
例如 :
exp(1) 将返回 2.71828
l log(x) : 将返回x以e为底的对数值.
例如 :
log(exp(1)) 将返回 1
若 x< 0 ,则执行 sqrt(x)时将造成 Run Time Error. [译者注: 我这里也没有发生错误,返回的是"nan"]
l sin(x) : x 须以弧度为单位,sin(x)将返回x的sin函数值.
l cos(x) : x 须以弧度为单位,cos(x)将返回x的cos函数值
l atan2(y,x) : 返回 y/x 的tan反函数之值,返回值系以弧度为单位.
l rand() : 返回介于 0与1之间的(近似)随机数值; 0 < rand()<1.
除非使用者自行指定rand()函数起始的种子,否则每次执行awk程式时, rand()函数都将使用同一个內定的种子,来产生随机数.
l srand([x]) : 指定以x为rand( )函数起始的种子.
若省略了x,则awk会以执行时的日期与时间为rand()函数起始的种子.
15. 附录D ── awk 的內建变量 Built-in Variables
因內建变量的个数不多, 此处按其相关性分类说明, 并未按其字母顺序排列.
l ARGC
ARGC表示命令行上除了选项 -F, -v, -f 及其所对应的参数之外的所有参数的个数.若将"awk程式"直接写於命令列上, 则 ARGC 亦不将该"程式部分"列入计算.
l ARGV
ARGV数组用以记录命令列上的参数.
例 : 执行下列命令
$ awk -F\t -v a=8 -f prg.awk file1.dat file2.dat
或
$ awk -F\t -v a=8 '{ print $1 * a }' file1.dat file2.dat
执行上列任一程式后
ARGC = 3
ARGV[0] = "awk"
ARGV[1] = "file1.dat"
ARGV[2] = "file2.dat"
读者请留心 : 当 ARGC = 3 时, 命令列上仅指定了 2 个文件.
注 :
-F\t 表示以 tab 为栏位分隔字符 FS(field seporator).
-v a=8 是用以初始化程序中的变量 a.
l FILENAME
FILENAME用以表示目前正在处理的文件档名.
l FS
栏位分隔字符.
l $0
表示目前awk所读入的数据行.
l $1,$2..
分別表示所读入的数据行之第一栏, 第二栏,..
说明:
当awk读入一笔数据行 "A123 8:15" 时,会先以$0 记录.
故 $0 = "A123 8:15"
若程序中进一步使用了 $1, $2.. 或 NF 等內建变量时, awk才会自动分割 $0.
以便取得栏位相关的资料. 切割后各个栏位的资料会分別以$1, $2, $3...予以记录.
awk內定(default)的 栏位分隔字符(FS) 为 空白字符(空格及tab).
以本例而言, 读者若未改变 FS, 则分割后:
第一栏($1)="A123", 第二栏($2)="8:15".
使用者可用正则表达式自行定义 FS. awk每次需要分割数据行时, 会参考目前FS的值.
例如 :
令 FS = "[ :]+" 表示任何由 空白" " 或 冒号":" 所组成的字串都可当成分隔字符, 则分割后 :
第一栏($1) = "A123", 第二栏($2) = "8", 第三栏($3) = "15"
l NR
NR 表从 awk 开始执行该程序后所读取的数据行数.
l FNR
FNR 与 NR 功用类似. 不同的是awk每打开一个新的文件,FNR 便从 0 重新累计
l NF
NF表目前的数据行所被切分的栏位数.
awk 每读入一笔资料后, 在程序中可以 NF 来得知该行数据包含的栏位个数.在下一笔资料被读入之前, NF 并不会改变. 但使用者若自行使用$0来记录数据,例如: 使用 getline , 此时 NF 将代表新的 $0 上所记载的资料的栏位个数.
l OFS
OFS输出时的栏位分隔字符. 预设值 " "(一个空白), 详见下面说明.
l ORS
ORS输出时数据行的分隔字符. 预设值 "\n"(跳行), 见下面说明.
l OFMT
OFMT数值资料的输出格式. 预设值 "%.6g"(若须要时最多印出6位小数)
当使用 print 指令一次印出多项资料时,
例如 : print $1, $2
输出时, awk会自动在 $1 与 $2 之间补上一个 OFS 之值
每次使用 print 输出后, awk会自动补上 ORS 之值.
使用 print 输出数值数据时, awk将采用 OFMT 之值为输出格式.
例如 :
$ awk 'BEGIN { print 2/3,1; OFS=":"; OFMT="%.2g"; print 2/3,1 }'
输出:
0.666667 1
0.67:1
程序中通过改变OFS和OFMT的值, 改变了指令 print 的输出格式.
l RS
RS( Record Separator) : awk从文件上读取资料时, 将根据 RS 的定义把资料切割成许多Records,而awk一次仅读入一个Record,以进行处理.
RS 的预设值是 "\n". 所以一般 awk一次仅读入一行资料.
有时一个Record含括了几行资料(Multi-line Record). 这情況下不能再以"\n"
来分隔相邻的Records, 可改用 空白行 来分隔.
在awk程式中,令 RS = "" 表示以 空白行 来分隔相邻的Records.
l RSTART
RSTART与使用字串函数 match( )有关的变量,详见下面说明.
l RLENGTH
RLENGTH与使用字串函数match( )有关之变量.
当使用者使用 match(...) 函数后, awk会将 match(...) 执行的结果以RSTART,RLENGTH 记录.
请参考 附录 C awk的內建函数 match().
l SUBSEP
SUBSEP(Subscript Separator) 数组下标的分隔字符,
预设值为"\034"实际上, awk中的 数组 只接受 字串 当它的下标,如: Arr["John"].
但使用者在 awk 中仍可使用 数字 当阵列的下标, 甚至可使用多维的数组(Multi-dimenisional Array) 如: Arr[2,79]
事实上, awk在接受 Arr[2,79] 之前, 就已先把其下标转换成字串"2\03479", 之后便以Arr["2\03479"] 代替 Arr[2,79].
可参考下例 :
awk 'BEGIN {
Arr[2,79] = 78
print Arr[2,79]
print Arr[ 2 , 79 ]
print Arr["2\03479"]
idx = 2 SUBSEP 79
print Arr[idx]
}
' $*
执行结果输出:
78
78
78
78
16. 附录E ── 正则表达式(Regular Expression) 简介
l 为什么要使用正则表达式
UNIX 中提供了许多 指令 和 tools, 它们具有在文件中 查找(Search)字串或替换(Replace)字串 的功能. 像 grep, vi , sed, awk,...
不论是查找字串或替换字串, 都得先告诉这些指令所要查找(被替换)的字串为何.若未能预先明确知道所要查找(被替换)的字串为何, 只知该字串存在的范围或特征时,例如 :
(一)找寻 "T0.c", "T1.c", "T2.c".... "T9.c" 当中的任一字串.
(二)找寻至少存在一个 "A"的任意字串.
这情況下, 如何告知执行查找字串的指令所要查找的字串为何.
例 (一) 中, 要查找任一在 "T" 与 ".c" 之间存在一个阿拉伯数字的字串;当然您可以列举的方式, 一一把所要找寻的字串告诉执行命令的指令.但例 (二) 中合乎该条件的字串有无限种可能, 势必无法一一列举.此时,便需要另一种字串表示的方法(协定).
l 什么是正则表达式
正则表达式(以下简称 Regexp)是一种字串表达的方式. 可用以指定具有某特征的所有字串.
注: 为区別于一般字串, 本附录中代表 Regexp 的字串之前皆加 "Regexp". awk 程式中常以/..../括住 Regexp; 以区別于一般字串.
l 组成正则表达式的元素
普通字符 除了 . * [ ] + ? ( ) \ ^ $ 外之所有字符.
由普通字符所组成的Regexp其意义与原字串字面意义相同.
例如: Regexp "the" 与一般字串的 "the" 代表相同的意义.
. (Meta character) : 用以代表任意一字符.
须留心 UNIX Shell 中使用 "*"表示 Wild card, 可用以代表任意长度的字串.而 Regexp 中使用 "." 来代表一个任意字符.(注意: 并非任意长度的字串)Regexp 中 "*" 另有其它涵意, 并不代表任意长度的字串.
^ 表示该字串必须出现于行首.
$ 表示该字串必须出现于行末.
例如 :
Regexp /^The/ 用以表示所有出现于行首的字串 "The".
Regexp /The$/ 用以表示所有出现于行末字串 "The".
\ 将特殊字符还原成字面意义的字符(Escape character)
Regexp 中特殊字符将被解释成特定的意义. 若要表示特殊字符的字面(literal meaning)意义时,在特殊字符之前加上"\"即可.
例如 :
使用Regexp来表示字串 "a.out"时, 不可写成 /a.out/.
因 为 "."是特殊字符, 表任一字符. 可符合 Regexp / a.out/ 的字串将不只 "a.out" 一个; 字串 "a2out", "a3out", "aaout" ...都符合 Regexp /a.out/ 正确的用法为: / a\.out/
[...]字符集合, 用以表示两中括号间所有的字符当中的任一个.
例如:
Regexp /[Tt]/ 可用以表示字符 "T" 或 "t".故 Regexp /[Tt]he/ 表示 字串 "The" 或 "the".
字符集合 [...] 內不可随意留空白.
例如: Regexp /[ Tt ]/ 其中括号內有空白字符, 除表示"T", "t" 中任一个字符, 也可代表一个 " "(空白字符)
- 字符集合中可使用 "-" 来指定字符的区间, 其用法如下:
Regexp /[0-9]/ 等于 /[0123456789]/ 用以表示任意一个阿拉伯数字.
同理 Regexp /[A-Z]/ 用以表示任意一个大写英文字母.
但应留心:
Regexp /[0-9a-z]/ 并不等于 /[0-9][a-z]/; 前者表示一个字符,后者表示二个字符.
Regexp /[-9]/ 或 /[9-]/ 只代表字符 "9"或 "-".
[^...]使用[^..] 产生字符集合的补集(complement set).
其用法如下 :
例如: 要指定 "T" 或 "t" 之外的任一个字符, 可用 /[^Tt]/ 表之.
同理 Regexp /[^a-zA-Z]/ 表示英文字母之外的任一个字符.
须留心 "^" 的位置 : "^"必须紧接於"["之后, 才代表字符集合的补集
例如 :Regexp /[0-9\^]/ 只是用以表示一个阿拉伯数字或字符"^".
* 形容字符重复次数的特殊字符.
"*" 形容它前方之字符可出现 1 次或多次, 或不出现(0次).
例如:
Regexp /T[0-9]*\.c/ 中 * 形容其前 [0-9] (一个阿拉伯数字)出现的次数可为 0次或 多次.故Regexp /T[0-9]*\.c/ 可用以表示"T.c", "T0.c", "T1.c"..."T19.c"
+形容其前的字符出现一次或一次以上.
例如:
Regexp /[0-9]+/ 用以表示一位或一位以上的数字.
? 形容其前的字符可出现一次或不出现.
例如:
Regexp /[+-]?[0-9]+/ 表示数字(一位以上)之前可出现正负号或不出现正负号.
(...)用以括住一群字符,且将之视成一个group(见下面说明)
例如 :
Regexp /12+/ 表示字串 "12", "122", "1222", "12222",...
Regexp /(12)+/ 表示字串 "12", "1212", "121212", "12121212"....
上式中 12 以( )括住, 故 "+" 所形容的是 12, 重复出现的也是 12.
| 表示逻辑上的"或"(or)
例如:
Regexp / Oranges? | apples? | water/ 可用以表示 : 字串 "Orange", "Oranges" 或 "apple", "apples" 或 "water"
l match是什么?
讨论 Regexp 时, 经常遇到 "某字串匹配( match )某 Regexp"的字眼. 其意思为 : "这个 Regexp 可被解释成该字串".
[ 例如] :
字串 "the" 匹配(match) Regexp /[Tt]he/.
因为 Regexp /[Tt]he/ 可解释成字串 "the" 或 "The", 故字串 "the" 或 "The"都匹配(match) Regexp /[Th]he/.
l awk 中提供二个关系运算符(Relational Operator,见注一) ~ !~,
它们也称之为 match, not match.但函义与一般常称的 match 略有不同.
其定义如下:
A 表一字串, B 表一 Regular Expression
只要 A 字串中存在有子字串可 match( 一般定义的 match) Regexp B , 则 A ~B 就算成立, 其值为 true, 反之则为 false.
! ~ 的定义与~恰好相反.
例 如 :
"another" 中含有子字串 "the" 可 match Regexp /[Tt]he/ , 所以
"another" ~ /[Tt]he/ 之值为 true.
[注 一] : 有些论著不把这两个运算符( ~, !~)与 Relational Operators 归为一类.
l 应用 Regular Expression 解题的简例
下面列出一些应用 Regular Expression 的简例, 部分范例中会更改$0 之值, 若您使用的 awk不允许用户更改 $0时, 请改用 gawk.
例1:
将文件中所有的字串 "Regular Expression" 或 "Regular expression" 换成 "Regexp"
awk '
{ gsub( /Regular[ \t]+[Ee]xpression/, "Regexp")
}
' $*
例2:
去除文件中的空白行(或仅含空白字符或tab的行)
awk '$0 !~ /^[ \t]*$/ { print }' $*
例3:
在文件中具有 ddd-dddd (电话号码型态, d 表digital)的字串前加上"TEL : "
awk '
{ gsub( /[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]/, "TEL : &" )
}
' $*
例4:
从文件的 Fullname 中分离出 路径 与 档名
awk '
BEGIN{
Fullname = "/usr/local/bin/xdvi"
match( Fullname, /.*\//)
path = substr(Fullname, 1, RLENGTH-1)
name = substr(Fullname, RLENGTH+1)
print "path :", path," name :",name
}
' $*
结果印出
path : /usr/local/bin name : xdvi
例5:
将某一数值改以现金表示法表示(整数部分每三位加一撇,且含二位小数)
awk '
BEGIN {
Number = 123456789
Number = sprintf("$%.2f",Number)
while( match(Number,/[0-9][0-9][0-9][0-9]/ ) )
sub(/[0-9][0-9][0-9][.,]/, ",&", Number)
print Number
}
' $*
结果输出
$123,456,789.00
例6:
把文件中所有具 "program数字.f"形态的字串改为"[Ref : program数字.c]"
awk '
{
while( match( $0, /program[0-9]+\.f/ ) ){
Replace = "[Ref : " substr( $0, RSTART, RLENGTH-2) ".c]"
sub( /program[0-9]+\.f/, Replace)
}
}
' $*
相关阅读 更多 +