- 論壇徽章:
- 1
|
[ 本帖最后由 ziyunfei 于 2012-08-10 09:39 編輯 ]
4.1 輸入如何被分割為記錄
awk會(huì)將你寫的awk程序?qū)⒁幚淼妮斎霐?shù)據(jù)分割為記錄和字段. awk會(huì)記錄從當(dāng)前輸入文件中已經(jīng)讀取的記錄數(shù).該值被存在一個(gè)內(nèi)置變量FNR里.當(dāng)讀取另外一個(gè)輸入文件時(shí),該變量的值會(huì)被重置為0.而另外一個(gè)內(nèi)置的變量,NR,記錄了當(dāng)前從所有輸入文件中讀取的文件記錄總數(shù).該變量的值也從0開始,但永遠(yuǎn)不會(huì)被自動(dòng)重置回0.
記錄是由一個(gè)稱為記錄分隔符的字符分割而成的. 記錄分隔符默認(rèn)是一個(gè)換行符. 因此,在默認(rèn)狀態(tài)下,一行數(shù)據(jù)就是一條記錄. 可以通過(guò)為內(nèi)置變量RS賦值來(lái)使用自定義的記錄分隔符.
和其他的變量一樣,變量RS的值可以在awk程序中通過(guò)賦值運(yùn)算符來(lái)改變, ‘=’ (查看賦值運(yùn)算符). 新的記錄分隔符應(yīng)該用引號(hào)引住,表示為一個(gè)字符串常量.該賦值操作通常在awk代碼剛剛開始被解釋執(zhí)行的時(shí)候,在輸入數(shù)據(jù)還未處理之前,這樣做才能保證第一個(gè)記錄是以指定的分隔符分割而成的.想要達(dá)到這種處理效果,需要使用特殊的BEGIN模式 (查看BEGIN/END).例如:
awk 'BEGIN { RS = "/" }
{ print $0 }' BBS-list
在讀取輸入數(shù)據(jù)之前,改變RS的默認(rèn)值到"/".該分隔符只有一個(gè)"斜杠"字符;因此,每個(gè)記錄會(huì)被斜杠分割而成.當(dāng)BEGIN模式執(zhí)行完畢后,開始讀取輸入文件,然后awk程序中的第二個(gè)規(guī)則開始執(zhí)行(該規(guī)則沒(méi)有模式只有行為),該規(guī)則打印出了每個(gè)記錄的內(nèi)容.由于每個(gè)print語(yǔ)句都會(huì)在它的輸出數(shù)據(jù)之后添加一個(gè)換行符,所以該awk程序的實(shí)際效果是把輸入數(shù)據(jù)中的所有斜杠替換成了換行符. 下面是在用該awk程序處理BBS-list文件時(shí)輸出的結(jié)果:
$ 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
-|
你可能會(huì)注意到,‘camelot’ BBS所在的條目沒(méi)有被分割.在原始數(shù)據(jù)文件中, (查看原始數(shù)據(jù)文件), 該行的內(nèi)容如下:
camelot 555-0542 300 C
它只有一個(gè)波特率,所以數(shù)據(jù)中并沒(méi)有斜杠, 不像其它的有兩個(gè)或者更多波特率的條目. 實(shí)際上,該"記錄"是被當(dāng)作‘core’ BBS記錄的一部分來(lái)看待的;輸出數(shù)據(jù)中分割兩行數(shù)據(jù)的換行符是原始數(shù)據(jù)文件中的換行符,并不是在打印記錄時(shí),由awk程序中的print語(yǔ)句添加的!
另外一種改變記錄分隔符的方法是在命令行里,使用變量賦值特性(查看其他參數(shù)):
awk '{ print $0 }' RS="/" BBS-list
這樣也可以在開始處理BBS-list文件之前,將RS賦值為‘/’.
使用一個(gè)不常用的字符,比如‘/’來(lái)作為記錄分隔符,在絕大多數(shù)情況下都會(huì)產(chǎn)生正確的行為.但是,下面的(極端示例)管道卻輸出了一個(gè)令人費(fèi)解的‘1’(譯者注:下面的示例有誤):
$ echo | awk 'BEGIN { RS = "a" } ; { print NF }'
-| 1
該記錄中只有一個(gè)字段,該字段僅有一個(gè)換行符組成.內(nèi)置變量NF的值為當(dāng)前記錄中包含的字段總數(shù).
當(dāng)讀取到輸入文件的結(jié)尾時(shí),會(huì)終止當(dāng)前記錄中數(shù)據(jù)的繼續(xù)添加, 即使文件內(nèi)容的最后一個(gè)字符并不是RS指定的記錄分隔符. (d.c.)
空字符串"" (不包含任何字符的字符串)對(duì)于變量RS有著特殊的意義.使用該記錄分隔符意味著所有記錄會(huì)被輸入數(shù)據(jù)中的一個(gè)或多個(gè)空行分割而成. 查看 多行數(shù)據(jù),了解詳情.
如果你在awk程序運(yùn)行中間(已經(jīng)處理了部分記錄的時(shí)候)改變RS的值,則新的記錄分隔符會(huì)應(yīng)用在將要處理的記錄中,而正在處理的記錄,以及那些已經(jīng)處理完的記錄并不會(huì)受新的記錄分隔符的影響.
每當(dāng)一個(gè)記錄讀取完畢之后,gawk 會(huì)將變量RT的值設(shè)置為輸入數(shù)據(jù)中上一個(gè)匹配變量RS的值的字符串.
在gawk中, 變量RS的值并不限制為僅能是包含一個(gè)字符的字符串.它可以是任意的正則表達(dá)式(查看正則表達(dá)式). (c.e.) 通常情況下,每個(gè)記錄會(huì)在下一個(gè)匹配RS指定的正則表達(dá)式的字符串之前結(jié)尾;下一個(gè)記錄會(huì)在該匹配的字符串之后開始.這個(gè)通用規(guī)則也同樣適用于更常見的情況,當(dāng)RS僅包含一個(gè)換行符的時(shí)候:一個(gè)記錄會(huì)在下一個(gè)匹配字符串(下一個(gè)換行符)之前結(jié)尾,并且緊接著下一個(gè)記錄會(huì)在該換行符之后開始(也就是下一行的首字母之前的位置). 換行符本身,因?yàn)槠ヅ淞薘S,所以它不屬于任何一個(gè)記錄.
當(dāng)RS為單個(gè)字符時(shí),RT的值會(huì)是和RS相同的單個(gè)字符.但是,如果RS為一個(gè)正則表達(dá)式,則RT的值會(huì)是輸入數(shù)據(jù)中匹配這個(gè)正則表達(dá)式的字符串.
如果輸入文件結(jié)尾處沒(méi)有任何匹配RS正則的文本,則gawk會(huì)將RT設(shè)置為空字符串.
下面的例子演示了所有這些特性.首先將RS設(shè)置為一個(gè)可以匹配換行符或者一系列由一個(gè)或者多個(gè)大寫字母組成的可以帶有可選的前后空格的字符串的正則表達(dá)式:
$ 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 =
-|
輸出數(shù)據(jù)的結(jié)尾有一個(gè)多余的空行.這是因?yàn)镽T最后一次的匹配值就是換行符,并且print語(yǔ)句在打印RT時(shí)又輸出一個(gè)換行符. 查看Simple Sed,一個(gè)更有用的關(guān)于正則表達(dá)式類型的RS以及RT的示例 .
如果你將RS設(shè)置為一個(gè)以可選字符串結(jié)尾的正則表達(dá)式,比如‘RS = "abc(XYZ)?"’,則由于實(shí)現(xiàn)的限制,gawk有可能只會(huì)匹配正則表達(dá)式開頭的非可選的部分,而會(huì)忽略結(jié)尾的可選部分,尤其是當(dāng)輸入文本中匹配正則表達(dá)式可選部分的文本內(nèi)容很長(zhǎng)很長(zhǎng)的時(shí)候. gawk也嘗試避免該問(wèn)題,但目前,并不能保證該問(wèn)題永遠(yuǎn)不會(huì)發(fā)生.
注意:在awk中,'^’和‘$’錨點(diǎn)元字符僅匹配一個(gè)字符串的開始和結(jié)尾,并不匹配一行的開始和結(jié)尾.類似 ‘RS = "^[[:upper:]]"’這樣的正則表達(dá)式,僅可能在一個(gè)文件的開始處發(fā)生匹配.這是因?yàn)間awk會(huì)把整個(gè)輸入文件看作一個(gè)長(zhǎng)字符串,只不過(guò)該字符串中可能會(huì)包含換行符. 所以,應(yīng)當(dāng)盡量避險(xiǎn)為RS設(shè)置包含錨點(diǎn)的正則表達(dá)式.
正則表達(dá)式類型的RS和RT變量都是gawk的擴(kuò)展;在兼容模式中(查看選項(xiàng)),他們都不可用(譯者注:經(jīng)測(cè)試,發(fā)現(xiàn)在兼容模式下,RT不可用,但正則類型的RS仍然可用).在兼容模式中,只有RS的第一個(gè)字符會(huì)被當(dāng)作記錄分隔符.
高級(jí)知識(shí):RS = "\0" 不具有兼容性
有時(shí)候,你可能需要將整個(gè)數(shù)據(jù)文件作為一個(gè)單獨(dú)的記錄來(lái)處理.達(dá)到這種效果的唯一的方法就是將RS設(shè)置為一個(gè)你確定不可能出現(xiàn)在輸入文件中的值.以常規(guī)方法是很難做到的,因?yàn)橐粋(gè)程序通?赡軙(huì)處理很多未知的文件.
你可能會(huì)認(rèn)為,對(duì)于文本文件來(lái)說(shuō),nul字符(ascii碼為0的字符),是最適合用在此處作為RS的值了:
BEGIN { RS = "\0" } # 整個(gè)文件將作為一個(gè)記錄?
gawk實(shí)際上支持這樣做,它會(huì)把nul字符作為記錄分隔符.但是,其他版本的awk實(shí)現(xiàn)并不支持這種用法.
所有其他版本的awk實(shí)現(xiàn)19在內(nèi)部都以C風(fēng)格存儲(chǔ)字符串.C風(fēng)格的字符串使用nul字符作為字符串的結(jié)尾標(biāo)識(shí)符.實(shí)際上,這意味著‘RS = "\0"’等同于‘RS = ""’. (d.c.)
將整個(gè)文件作為單獨(dú)一個(gè)記錄的最好方法是:像通常一樣,簡(jiǎn)單的讀取文件,每次讀取一個(gè)記錄,然后和上次讀取的記錄合并,一直到讀取完畢,最后合并成的字符串也就是整個(gè)文件的內(nèi)容了.
4.2 匹配字段
當(dāng)awk從輸入數(shù)據(jù)中讀取一個(gè)記錄之后,該記錄會(huì)被awk自動(dòng)解析或者說(shuō)分割成為一個(gè)個(gè)稱為 字段的區(qū)塊.默認(rèn)狀態(tài)下,字段是由空白字符串分割的,就像一句話中的單詞一樣(指英文).awk中的空白字符串指的是任何只包含一個(gè)或多個(gè)空格,制表符,或者換行符的字符串;20其他的字符,比如換頁(yè)符,垂直制表符,等.,這些被其他一些語(yǔ)言當(dāng)作空白符的字符,在awk中不被認(rèn)為是空白符.
字段的作用是能夠讓你更方便的使用一個(gè)記錄的部分片段.雖然使用字段并不是必須的(如果你愿意的話,你可以直接操作整個(gè)記錄),但使用字段能讓awk程序變的既小巧又強(qiáng)大.
在awk程序中,一個(gè)美元符(‘$’)后跟一個(gè)字段序號(hào),用來(lái)代表一個(gè)字段.也就是說(shuō),$1指的是第一個(gè)字段,$2指的是第二個(gè)字段,等等. (不像Unix shell一樣,字段數(shù)目被限制為只能是單個(gè)數(shù)字.awk中,$127也可以表示記錄中的第127個(gè)字段.) 例如, 假設(shè)下面是個(gè)單行的輸入數(shù)據(jù):
This seems like a pretty nice example.
這里的第一個(gè)字段,或者說(shuō)$1的值,就是‘This’,等二個(gè)字段,或者說(shuō)$2的值,就是‘seems’,等等.注意最后一個(gè)字段,$7的值為‘example.’.因?yàn)樵凇甧’和‘.’之間并沒(méi)有任何空白,所以最后的句號(hào)也被認(rèn)為是屬于第七個(gè)字段的.
內(nèi)置變量NF用來(lái)表示當(dāng)前記錄中包含的字段總數(shù).每當(dāng)讀取一個(gè)記錄后,awk會(huì)自動(dòng)更新NF的值.無(wú)論有多個(gè)字段,一個(gè)記錄中的最后一個(gè)字段總可以通過(guò)$NF來(lái)訪問(wèn)到. 所以,$NF等同于$7,它的值也為‘example.’. 如果你嘗試引用一個(gè)字段索引大于字段總數(shù)的字段(例如,當(dāng)該條記錄只有七個(gè)字段的時(shí)候,引用$8),你會(huì)得到一個(gè)空字符串. (如果你在它上面進(jìn)行了數(shù)學(xué)運(yùn)算,則它的值會(huì)首先轉(zhuǎn)換為0.)
$0,看起來(lái)像是第"0"個(gè)字段的引用,其實(shí)是個(gè)特例,它表示當(dāng)前整個(gè)記錄.當(dāng)你不對(duì)某個(gè)特定的字段感興趣時(shí),你可以使用它. 下面有一些示例:
$ 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
這個(gè)例子打印出了BBS-list文件中,所有第一個(gè)字段包含字符串‘foo’的記錄.操作符‘~’稱為 匹配操作符 (查看正則表達(dá)式用法); 該操作符檢測(cè)一個(gè)字符串(在這里,就是檢測(cè)$1)是否匹配一個(gè)給定的正則表達(dá)式.
和上面的例子對(duì)比,下面的例子在整條記錄中查找‘foo’字符串,打印出每個(gè)匹配記錄的第一個(gè)和最后一個(gè)字段:
$ awk '/foo/ { print $1, $NF }' BBS-list
-| fooey B
-| foot B
-| macfoo A
-| sabafoo C
|
|