- 论坛徽章:
- 1
|
[ 本帖最后由 ziyunfei 于 2012-08-10 09:39 编辑 ]
4.1 输入如何被分割为记录
awk会将你写的awk程序将要处理的输入数据分割为记录和字段. awk会记录从当前输入文件中已经读取的记录数.该值被存在一个内置变量FNR里.当读取另外一个输入文件时,该变量的值会被重置为0.而另外一个内置的变量,NR,记录了当前从所有输入文件中读取的文件记录总数.该变量的值也从0开始,但永远不会被自动重置回0.
记录是由一个称为记录分隔符的字符分割而成的. 记录分隔符默认是一个换行符. 因此,在默认状态下,一行数据就是一条记录. 可以通过为内置变量RS赋值来使用自定义的记录分隔符.
和其他的变量一样,变量RS的值可以在awk程序中通过赋值运算符来改变, ‘=’ (查看赋值运算符). 新的记录分隔符应该用引号引住,表示为一个字符串常量.该赋值操作通常在awk代码刚刚开始被解释执行的时候,在输入数据还未处理之前,这样做才能保证第一个记录是以指定的分隔符分割而成的.想要达到这种处理效果,需要使用特殊的BEGIN模式 (查看BEGIN/END).例如:
awk 'BEGIN { RS = "/" }
{ print $0 }' BBS-list
在读取输入数据之前,改变RS的默认值到"/".该分隔符只有一个"斜杠"字符;因此,每个记录会被斜杠分割而成.当BEGIN模式执行完毕后,开始读取输入文件,然后awk程序中的第二个规则开始执行(该规则没有模式只有行为),该规则打印出了每个记录的内容.由于每个print语句都会在它的输出数据之后添加一个换行符,所以该awk程序的实际效果是把输入数据中的所有斜杠替换成了换行符. 下面是在用该awk程序处理BBS-list文件时输出的结果:
$ awk 'BEGIN { RS = "/" }
> { print $0 }' BBS-list
-| aardvark 555-5553 1200
-| 300 B
-| alpo-net 555-3412 2400
-| 1200
-| 300 A
-| barfly 555-7685 1200
-| 300 A
-| bites 555-1675 2400
-| 1200
-| 300 A
-| camelot 555-0542 300 C
-| core 555-2912 1200
-| 300 C
-| fooey 555-1234 2400
-| 1200
-| 300 B
-| foot 555-6699 1200
-| 300 B
-| macfoo 555-6480 1200
-| 300 A
-| sdace 555-3430 2400
-| 1200
-| 300 A
-| sabafoo 555-2127 1200
-| 300 C
-|
你可能会注意到,‘camelot’ BBS所在的条目没有被分割.在原始数据文件中, (查看原始数据文件), 该行的内容如下:
camelot 555-0542 300 C
它只有一个波特率,所以数据中并没有斜杠, 不像其它的有两个或者更多波特率的条目. 实际上,该"记录"是被当作‘core’ BBS记录的一部分来看待的;输出数据中分割两行数据的换行符是原始数据文件中的换行符,并不是在打印记录时,由awk程序中的print语句添加的!
另外一种改变记录分隔符的方法是在命令行里,使用变量赋值特性(查看其他参数):
awk '{ print $0 }' RS="/" BBS-list
这样也可以在开始处理BBS-list文件之前,将RS赋值为‘/’.
使用一个不常用的字符,比如‘/’来作为记录分隔符,在绝大多数情况下都会产生正确的行为.但是,下面的(极端示例)管道却输出了一个令人费解的‘1’(译者注:下面的示例有误):
$ echo | awk 'BEGIN { RS = "a" } ; { print NF }'
-| 1
该记录中只有一个字段,该字段仅有一个换行符组成.内置变量NF的值为当前记录中包含的字段总数.
当读取到输入文件的结尾时,会终止当前记录中数据的继续添加, 即使文件内容的最后一个字符并不是RS指定的记录分隔符. (d.c.)
空字符串"" (不包含任何字符的字符串)对于变量RS有着特殊的意义.使用该记录分隔符意味着所有记录会被输入数据中的一个或多个空行分割而成. 查看 多行数据,了解详情.
如果你在awk程序运行中间(已经处理了部分记录的时候)改变RS的值,则新的记录分隔符会应用在将要处理的记录中,而正在处理的记录,以及那些已经处理完的记录并不会受新的记录分隔符的影响.
每当一个记录读取完毕之后,gawk 会将变量RT的值设置为输入数据中上一个匹配变量RS的值的字符串.
在gawk中, 变量RS的值并不限制为仅能是包含一个字符的字符串.它可以是任意的正则表达式(查看正则表达式). (c.e.) 通常情况下,每个记录会在下一个匹配RS指定的正则表达式的字符串之前结尾;下一个记录会在该匹配的字符串之后开始.这个通用规则也同样适用于更常见的情况,当RS仅包含一个换行符的时候:一个记录会在下一个匹配字符串(下一个换行符)之前结尾,并且紧接着下一个记录会在该换行符之后开始(也就是下一行的首字母之前的位置). 换行符本身,因为匹配了RS,所以它不属于任何一个记录.
当RS为单个字符时,RT的值会是和RS相同的单个字符.但是,如果RS为一个正则表达式,则RT的值会是输入数据中匹配这个正则表达式的字符串.
如果输入文件结尾处没有任何匹配RS正则的文本,则gawk会将RT设置为空字符串.
下面的例子演示了所有这些特性.首先将RS设置为一个可以匹配换行符或者一系列由一个或者多个大写字母组成的可以带有可选的前后空格的字符串的正则表达式:
$ echo record 1 AAAA record 2 BBBB record 3 |
> gawk 'BEGIN { RS = "\n|( *[[:upper:]]+ *)" }
> { print "Record =", $0, "and RT =", RT }'
-| Record = record 1 and RT = AAAA
-| Record = record 2 and RT = BBBB
-| Record = record 3 and RT =
-|
输出数据的结尾有一个多余的空行.这是因为RT最后一次的匹配值就是换行符,并且print语句在打印RT时又输出一个换行符. 查看Simple Sed,一个更有用的关于正则表达式类型的RS以及RT的示例 .
如果你将RS设置为一个以可选字符串结尾的正则表达式,比如‘RS = "abc(XYZ)?"’,则由于实现的限制,gawk有可能只会匹配正则表达式开头的非可选的部分,而会忽略结尾的可选部分,尤其是当输入文本中匹配正则表达式可选部分的文本内容很长很长的时候. gawk也尝试避免该问题,但目前,并不能保证该问题永远不会发生.
注意:在awk中,'^’和‘$’锚点元字符仅匹配一个字符串的开始和结尾,并不匹配一行的开始和结尾.类似 ‘RS = "^[[:upper:]]"’这样的正则表达式,仅可能在一个文件的开始处发生匹配.这是因为gawk会把整个输入文件看作一个长字符串,只不过该字符串中可能会包含换行符. 所以,应当尽量避险为RS设置包含锚点的正则表达式.
正则表达式类型的RS和RT变量都是gawk的扩展;在兼容模式中(查看选项),他们都不可用(译者注:经测试,发现在兼容模式下,RT不可用,但正则类型的RS仍然可用).在兼容模式中,只有RS的第一个字符会被当作记录分隔符.
高级知识:RS = "\0" 不具有兼容性
有时候,你可能需要将整个数据文件作为一个单独的记录来处理.达到这种效果的唯一的方法就是将RS设置为一个你确定不可能出现在输入文件中的值.以常规方法是很难做到的,因为一个程序通常可能会处理很多未知的文件.
你可能会认为,对于文本文件来说,nul字符(ascii码为0的字符),是最适合用在此处作为RS的值了:
BEGIN { RS = "\0" } # 整个文件将作为一个记录?
gawk实际上支持这样做,它会把nul字符作为记录分隔符.但是,其他版本的awk实现并不支持这种用法.
所有其他版本的awk实现19在内部都以C风格存储字符串.C风格的字符串使用nul字符作为字符串的结尾标识符.实际上,这意味着‘RS = "\0"’等同于‘RS = ""’. (d.c.)
将整个文件作为单独一个记录的最好方法是:像通常一样,简单的读取文件,每次读取一个记录,然后和上次读取的记录合并,一直到读取完毕,最后合并成的字符串也就是整个文件的内容了.
4.2 匹配字段
当awk从输入数据中读取一个记录之后,该记录会被awk自动解析或者说分割成为一个个称为 字段的区块.默认状态下,字段是由空白字符串分割的,就像一句话中的单词一样(指英文).awk中的空白字符串指的是任何只包含一个或多个空格,制表符,或者换行符的字符串;20其他的字符,比如换页符,垂直制表符,等.,这些被其他一些语言当作空白符的字符,在awk中不被认为是空白符.
字段的作用是能够让你更方便的使用一个记录的部分片段.虽然使用字段并不是必须的(如果你愿意的话,你可以直接操作整个记录),但使用字段能让awk程序变的既小巧又强大.
在awk程序中,一个美元符(‘$’)后跟一个字段序号,用来代表一个字段.也就是说,$1指的是第一个字段,$2指的是第二个字段,等等. (不像Unix shell一样,字段数目被限制为只能是单个数字.awk中,$127也可以表示记录中的第127个字段.) 例如, 假设下面是个单行的输入数据:
This seems like a pretty nice example.
这里的第一个字段,或者说$1的值,就是‘This’,等二个字段,或者说$2的值,就是‘seems’,等等.注意最后一个字段,$7的值为‘example.’.因为在‘e’和‘.’之间并没有任何空白,所以最后的句号也被认为是属于第七个字段的.
内置变量NF用来表示当前记录中包含的字段总数.每当读取一个记录后,awk会自动更新NF的值.无论有多个字段,一个记录中的最后一个字段总可以通过$NF来访问到. 所以,$NF等同于$7,它的值也为‘example.’. 如果你尝试引用一个字段索引大于字段总数的字段(例如,当该条记录只有七个字段的时候,引用$8),你会得到一个空字符串. (如果你在它上面进行了数学运算,则它的值会首先转换为0.)
$0,看起来像是第"0"个字段的引用,其实是个特例,它表示当前整个记录.当你不对某个特定的字段感兴趣时,你可以使用它. 下面有一些示例:
$ awk '$1 ~ /foo/ { print $0 }' BBS-list
-| fooey 555-1234 2400/1200/300 B
-| foot 555-6699 1200/300 B
-| macfoo 555-6480 1200/300 A
-| sabafoo 555-2127 1200/300 C
这个例子打印出了BBS-list文件中,所有第一个字段包含字符串‘foo’的记录.操作符‘~’称为 匹配操作符 (查看正则表达式用法); 该操作符检测一个字符串(在这里,就是检测$1)是否匹配一个给定的正则表达式.
和上面的例子对比,下面的例子在整条记录中查找‘foo’字符串,打印出每个匹配记录的第一个和最后一个字段:
$ awk '/foo/ { print $1, $NF }' BBS-list
-| fooey B
-| foot B
-| macfoo A
-| sabafoo C
|
|