1. 程式人生 > >陳錦亮的專欄

陳錦亮的專欄

詳解著名的awk oneliner,第一部分:空行、行號和計算

偶然在網上看到有人註釋過的《awk oneliner》,而且註釋的人聲稱“全部弄懂這 些就已經是awk高手了”,而我也一直想學下awk又懶得花時間,然後就覺得這個東 西一定很適合我。為了督促自己看完這個系列,決定整個給翻譯一遍。說實話看了 第一篇之後覺得awk的語句也不是那麼天書了。原作者是Peteris Krumins,一個很 酷的傢伙,你可以在titter上follow他(Peteris Krumins)。

原文請見:http://www.catonmat.net/blog/awk-one-liners-explained-part-one/

將每行後面都新增一個空行

awk '1; { print "" }'

這是怎麼意思呢?一個單行awk命令,其實也是一個用awk語言寫的程式,每個awk程式,都是由一系列的“匹配模式 { 執行動作 }”語句所組成的。在這個例子裡面,有兩個語句,“1”和“{print ""}”。在每個“匹配模式——執行動作”語句中,模式和動作都是可以被省略的。如果匹配模式被省略,那麼預定的動作將會對輸入檔案的每一行執行。如果動作被省略,那麼就預設會執行{print }。所以,這個單行awk語句等同於下面的語句

awk '1 {print } {print ""}'

動作只有在匹配模式的值為真的時候才會執行。因為“1”永遠為真,所以,這個例子也可以寫成下面的形式

awk '{print } {print ""}'

awk中每條print語句後都預設會輸出一個ORS變數(Output Record Separator,即輸出行分隔符,預設為換行符)。第一個不帶引數的print語句,等同於print $0,其中$0是代表整行內容的變數。第二個print語句什麼也不輸出,但是鑑於print語句後都會被自動加上ORS變數,這句的作用就是輸出一個新行。於是每行後面加空行的目的就達到了。

新增空行的另一種方法

awk 'BEGIN { ORS="/n/n" }; 1'

BEGIN是一個特殊的模式,後面所接的內容,會在檔案被讀入前執行。這裡,對ORS變數進行了重新定義,將一個換行符改成了兩個。後面的“1”,同樣等價於{print }

,這樣就達到了在每行後用新的ORS新增空行的目的。

在每個非空的行後面新增空行

awk 'NF {print $0 "/n"}'

這個語句裡面用到了一個新的變數,NF(number of fields),即本行被分割成的欄位的數目。例如,“this is a test”,會被awk分割成4個詞語,NF的值就為4。當遇到空行,分割後的欄位數為0,NF為0,後面的匹配動作就不會被執行。這條語句,可以理解成“如果這一行可以分割成任意大於0的部分,那麼輸出當前行以及一個換行符”。

在每行後新增兩個空行

awk '1; {print "/n"}'

這一語句與前面的很相似。“1”可以理解為{print },所以整個句子可以改寫為

awk '{print ; print "/n"}'

它首先輸出當前行,然後再輸出一個換行符以及一個結束print語句的ORS,也就是另外一個換行符。

為每個檔案的內容新增行號

awk '{ print FNR "/t" $0 }'

這個awk程式在每行的內容前添加了一個變數FNR的輸出,並用一個製表符進行分隔。FNR(File Number of Row)這個變數記錄了當前行在當前檔案中的行數。在處理下一個檔案時,這個變數會被重置為0。

為所有檔案的所有行統一新增行號

awk '{print NR "/t" $0}'

這一句與上一例基本一樣,除了使用的行號變數是NR(Number of Row),這個變數不會在處理新檔案的時候被重置。所以說,如果你有2個檔案,一個10行一個12行,那這個變數會從1一直變到22。

用更漂亮的樣式新增行號

awk '{printf("%5d : %s/n", FNR, $0)}'

這個例子用了printf函式來自定義輸出樣式,它所接受的引數與標準C語言的printf函式基本一致。需要注意的是,printf後不會被自動新增ORS,所以你需要自己指定換行。這個語句指定了行號會右對齊,然後是一個空格和冒號,接著是當前行的內容。

為檔案中的非空行新增行號

awk 'NF { $0=++a " :" $0}; {print }'

Awk的變數都是自動定義的:你第一次用到某個變數的時候它就自動被定義了。這個語句在每次遇到一個非空行的時候先把一個變數a加1,然後把a的數值新增到行首,然後輸出當前行的內容。

計算檔案行數(模擬 wc -l)

awk 'END {print NR}'

END是另外一個不會被檢驗是否為真的模式,後面的動作會在整個檔案被讀完後進行。這裡是輸出最終的行號,即檔案的總行數。

對每行求和

awk '{s=0;for (i=0;i<NF;i++) s=s+$i; print s}'

Awk有些類似C語言的語法,比如這裡的for (;;;){ ... }迴圈。這句命令會讓程式遍歷所有NF個欄位,並把欄位的總和存在變數s中,最後輸出s的數值並處理下一行。

對所有行所有欄位求和

awk '{for (i=0;i<NF;i++) s=s+$i; END {print s+0}'

這個例子與上一個基本一致,除了輸出的是所有行所有欄位的和。由於變數會被自動定義,s只需要定義一次,故而不需要把s定義成0。另外需要注意的是,它輸出{print s+0}而非{print s},這是因為如果檔案為空,s不會被定義就不會有任何輸出了,輸出s+0可以保證在這種情況下也會輸出更有意義的0。

將所有欄位替換為其絕對值

awk '{ for (i = 1; i <= NF; i++) if ($i < 0) $i = -$i; print }'

這條語句用了C語言的另外兩個特性,一個是if (...) {...}結構,另外就是省略了大括號。它檢查對每一行,檢查每個欄位的值是否小於0,如果值小於0,則將其改為正數。欄位名可以間接地用變數的形式引用,如i=5;$i='hello'會將第5個欄位的內容置為hello。

下面的是將這條語句完整的寫出來的形式。print語句會在行中所有欄位被改為正數後執行。

awk '{ for (i = 1; i <= NF; i++) { if ($i < 0) { $i = -$i; } } print }'

計算檔案中的總欄位(單詞)數

awk '{total=total+NF};END {print total+0}'

這個命令匹配所有的行,並不斷的把行中的欄位數累加到變數total。執行完成上述動作後,輸出total的數值。

輸出含有單詞Beth的行的數目

awk '/Beth/ {n++}; END {print n+0}'

這個例子含有兩個語句。第一句找出匹配/Beth/的行,並對變數n進行累加。在/.../之間的內容為正則表示式,/Beth/匹配所有含有“Beth”的單詞(它不僅匹配Beth,同樣也匹配Bethe)。第二句在檔案處理完成後輸出n的數值。這裡用n+0是為了讓n為0 的情況下輸出0而不是一個空行。

尋找第一個欄位為數字且最大的行

awk '$1 > max { max=$1; maxline=$0 }; END { print max, maxline }'

這個例子用變數max記錄第一個欄位的最大值,並把第一個欄位最大的行的內容存在變數maxline中。在迴圈終止後,輸出max和maxline的內容。注意:如果在數字都為負數的情況下,這個例子就不能用了,下面的是修改過的版本

awk 'NR == 1 { max = $1; maxline = $0; next; } $1 > max { max=$1; maxline=$0 }; END { print max, maxline }'

在每一行前新增輸出該行的欄位數

awk '{print NF ":" $0}'

這個例子僅僅是在逐行輸出欄位數NF,一個冒號,以及該行的內容。

輸出每行的最後一個欄位

awk '{print $NF}'

awk裡面的欄位可以用變數的形式引用。這一句輸出第NF個欄位的內容,而NF就是該行的欄位數。

列印最後一行的最後一個欄位

awk '{ field = $NF };END {print field}'

這個例子用field記錄最後一個欄位的內容,並在迴圈後輸出field的內容。

這裡是一個更好的版本。它更常用、更簡潔也更高效:

awk 'END {print $NF}'

輸出所有欄位數大於4的行

awk 'NF > 4'

這個例子省略了要執行的動作。如前所述,省略動作等價於{print}

輸出所有最後一個欄位大於4的行

awk '$NF > 4'

這個例子用$NF引用最後一個欄位,如果它的數值大於4,那麼就輸出。