一、使用規則 awk 適合於文本處理和報表生成,它還有許多精心設計的特性,容許進行須要特殊技巧程序設計。 awk 的語法較爲常見。它借鑑了某些語言的一些精華部分,如C 語言、python 和 bash。 第一個 awk 讓咱們繼續,開始使用 awk,以瞭解其工做原理。在命令行中輸入如下命令: $ awk '{ print }' /etc/passwd 您將會見到 /etc/passwd 文件的內容出如今眼前。如今,解釋 awk 作了些什麼。調用 awk 時,咱們指定 /etc/passwd 做爲輸入文件。執行 awk 時,它依次對 /etc/passwd 中的每一行執行 print 命令。全部輸出都發送到 stdout,所獲得的結果與與執行catting /etc/passwd徹底相同。 如今,解釋 { print } 代碼塊。在 awk 中,花括號用於將幾塊代碼組合到一塊兒,這一點相似於 C 語言。在代碼塊中只有一條 print 命令。在 awk 中,若是隻出現 print 命令,那麼將打印當前行的所有內容。 這裏是另外一個awk 示例,做用與上例徹底相同: $ awk '{ print $0 }' /etc/passwd 在 awk 中,$0 變量表示整個當前行,因此 print 和 print $0 的做用徹底同樣。 建立一個 awk 程序,讓它輸出與輸入數據徹底無關的數據。 示例1: $ awk '{ print "" }' /etc/passwd 只要將 "" 字符串傳遞給 print 命令,它就會打印空白行。測試該腳本,將會發現對於/etc/passwd文件中的每一行,awk 都輸出一個空白行。由此可知,awk對輸入文件中的每一行都執行這個腳本。 示例2: $ awk '{ print "hiya" }' /etc/passwd 運行此腳本將在您的屏幕上寫滿 hiya。 二、處理多個字段 awk 很是善於處理分紅多個邏輯字段的文本,還能夠引用 awk 腳本中每一個獨立的字段。 打印系統上全部用戶賬戶的列表: $ awk -F":" '{ print $1 }' /etc/passwd 上例中,調用awk時,使用 -F 選項來指定 ":" 做爲字段分隔符。awk 處理 print $1 命令時,它會打印出在輸入文件中每一行中出現的第一個字段。 如下是另外一示例: $ awk -F":" '{ print $1 $3 }' /etc/passwd 如下是該腳本輸出的摘錄: halt7 operator11 root0 shutdown6 sync5 bin1 ....etc. 如您所見,awk 打印出 /etc/passwd 文件的第一和第三個字段,它們正好分別是用戶名和用戶標識字段。如今,當腳本運行時,它並不理想--在兩個輸出字段之間沒有空格!若是習慣於使用 bash 或 python 進行編程,那麼您會期望 print $1 $3 命令在兩個字段之間插入空格。然而,當兩個字符串在 awk 程序中彼此相鄰時,awk 會鏈接它們但不在它們之間添加空格。如下命令會在這兩個字段中插入空格: $ awk -F":" '{ print $1 " " $3 }' /etc/passwd 以這種方式調用 print 時,它將鏈接 $一、" " 和 $3,建立可讀的輸出。 還能夠插入一些文本標籤: $ awk -F":" '{ print "username: " $1 "ttuid:" $3" }' /etc/passwd 這將產生如下輸出: username: halt uid:7 username: operator uid:11 username: root uid:0 username: shutdown uid:6 username: sync uid:5 username: bin uid:1 ....etc. 三、調用外部腳本 將腳本做爲命令行自變量傳遞給awk對於小的單行程序來講很簡單。 而對於多行程序,則能夠在外部文件中撰寫腳本,而後向awk傳遞-f選項,以向它提供外部腳本文件的調用: $ awk -f myscript.awk myfile.in 將腳本放入文本文件還可使用附加awk功能。例如: BEGIN { FS=":" } { print $1 } 打印出 /etc/passwd 中每一行的第一個字段 在這個腳本中,字段分隔符在代碼自身中指定(經過設置 FS 變量)。 在腳本自身中設置字段分隔符,能夠少輸入一個命令行自變量。 四、begin和end塊 BEGIN 和 END 塊 一般,對於每一個輸入行,awk 都會執行每一個腳本代碼塊一次。然而,可能須要在 awk 開始處理輸入文件中的文本以前執行初始化代碼。對於這種狀況,awk 容許您定義一個 BEGIN 塊。咱們在前一個示例中使用了 BEGIN 塊。由於 awk 在開始處理輸入文件以前會執行 BEGIN 塊,所以它是初始化 FS(字段分隔符)變量、打印頁眉或初始化其它在程序中之後會引用的全局變量的極佳位置。 awk 還提供了另外一個特殊塊,叫做 END 塊。awk 在處理了輸入文件中的全部行以後執行這個塊。一般,END 塊用於執行最終計算或打印應該出如今輸出流結尾的摘要信息。 五、正則表達式 awk 容許使用正則表達式,根據正則表達式是否匹配當前行來選擇執行獨立代碼塊。 輸出包含字符序列foo的行: /foo/ { print } 複雜點的,只打印包含浮點數的行: /[0-9]+.[0-9]*/ { print } 能夠將任意一種布爾表達式放在一個代碼塊以前,以控制什麼時候執行某特定塊。僅當對前面的布爾表達式求值爲真時,awk 才執行代碼塊。如下示例腳本輸出將輸出其第一個字段等於 fred 的全部行中的第三個字段。若是當前行的第一個字段不等於 fred,awk 將繼續處理文件而不對當前行執行 print 語句: $1 == "fred" { print $3 } awk 提供了完整的比較運算符集合,包括 "=="、"<"、">"、"<="、">=" 和 "!="。另外,awk 還提供了 "~" 和 "!~" 運算符,它們分別表示「匹配」和「不匹配」。 它們的用法是在運算符左邊指定變量,在右邊指定正則表達式。若是某一行的第五個字段包含字符序列 root,如下示例只打印這一行中的第三個字段: $5 ~ /root/ { print $3 } 六、條件語句 awk 還提供了很是好的相似於 C 語言的 if 語句。if 語句示例: { if ( $5 ~ /root/ ) { print $3 } } 對每個輸入行執行代碼塊,使用 if 語句來選擇執行 print 命令。 更復雜的 awk if 語句示例。 { if ( $1 == "foo" ) { if ( $2 == "foo" ) { print "uno" } else { print "one" } } else if ($1 == "bar" ) { print "two" } else { print "three" } } 使用 if 語句還能夠將代碼: ! /matchme/ { print $1 $3 $4 } 轉換成: { if ( $0 !~ /matchme/ ) { print $1 $3 $4 } } 這兩個腳本都只輸出不包含 matchme 字符序列的那些行。 awk 還容許使用布爾運算符 "||"(邏輯與)和 "&&"(邏輯或),以便建立更復雜的布爾表達式: ( $1 == "foo" ) && ( $2 == "bar" ) { print } 這個示例只打印第一個字段等於 foo 且第二個字段等於 bar 的行。 七、變量 awk的變量,數值變量與字符串變量。 數值變量 至今,咱們不是打印字符串、整行就是特定字段。然而,awk還能夠執行整數和浮點運算。使用數學表達式,能夠很方便地編寫計算文件中空白行數量的腳本。 BEGIN { x=0 } /^$/ { x=x+1 } END { print "I found " x " blank lines. :}" } 在 BEGIN 塊中,將整數變量 x 初始化成零。而後,awk 每次遇到空白行時,awk 將執行 x=x+1 語句,遞增 x。 處理完全部行以後,執行 END 塊,awk 將打印出最終摘要,指出它找到的空白行數量。 字符串化變量 awk 的優勢之一就是「簡單和字符串化」。我認爲 awk 變量「字符串化」是由於全部 awk 變量在內部都是按字符串形式存儲的。同時,awk 變量是「簡單的」,由於能夠對它執行數學操做,且只要變量包含有效數字字符串,awk 會自動處理字符串到數字的轉換步驟。要理解個人觀點,請研究如下示例: x="1.01" # We just set x to contain the *string* "1.01" x=x+1 # We just added one to a *string* print x # Incidentally, these are comments :) awk 將輸出: 2.01 雖然將字符串值 1.01 賦值給變量 x,仍然能夠對它加一。但在 bash 和 python 中卻不能這樣作。 首先,bash 不支持浮點運算。並且,若是 bash 有「字符串化」變量,它們並不「簡單」;要執行任何數學操做,bash 要求咱們將數字放到醜陋的 $( ) ) 結構中。 若是使用 python,則必須在對 1.01 字符串執行任何數學運算以前,將它轉換成浮點值。雖然這並不困難,但它還是附加的步驟。 若是使用 awk,它是全自動的,而那會使咱們的代碼又好又整潔。若是想要對每一個輸入行的第一個字段乘方並加一,可使用如下腳本: { print ($1^2)+1 } 若是作一個小實驗,就能夠發現若是某個特定變量不包含有效數字,awk 在對數學表達式求值時會將該變量看成數字零處理。 八、運算符 awk 有完整的數學運算符集合。除了標準的加、減、乘、除,awk 還容許使用前面演示過的指數運算符 "^"、模(餘數)運算符 "%" 和其它許多從 C 語言中借入的易於使用的賦值操做符。 這些運算符包括先後加減(i++、--foo)、加/減/乘/除賦值運算符( a+=三、b*=二、c/=2.二、d-=6.2)。不只如此 -- 咱們還有易於使用的模/指數賦值運算符(a^=二、b%=4)。 字段分隔符 awk 有它本身的特殊變量集合。其中一些容許調整 awk 的運行方式,而其它變量能夠被讀取以收集關於輸入的有用信息。咱們已經接觸過這些特殊變量中的一個,FS。前面已經提到過,這個變量讓您能夠設置 awk 要查找的字段之間的字符序列。咱們使用 /etc/passwd 做爲輸入時,將 FS 設置成 ":"。當這樣作有問題時,咱們還能夠更靈活地使用 FS。 FS 值並無被限制爲單一字符;能夠經過指定任意長度的字符模式,將它設置成規則表達式。若是正在處理由一個或多個 tab 分隔的字段,您可能但願按如下方式設置 FS: FS="t+" 以上示例中,咱們使用特殊 "+" 規則表達式字符,它表示「一個或多個前一字符」。 若是字段由空格分隔(一個或多個空格或 tab),您可能想要將 FS 設置成如下規則表達式: FS="[[:space:]+]" 這個賦值表達式也有問題,它並不是必要。爲何?由於缺省狀況下,FS 設置成單一空格字符,awk 將這解釋成表示「一個或多個空格或 tab」。在這個特殊示例中,缺省 FS 設置偏偏是您最想要的! 複雜的規則表達式也不成問題。即便您的記錄由單詞 "foo" 分隔,後面跟着三個數字,如下規則表達式仍容許對數據進行正確的分析: FS="foo[0-9][0-9][0-9]" 字段數量 接着咱們要討論的兩個變量一般並非須要賦值的,而是用來讀取以獲取關於輸入的有用信息。第一個是 NF 變量,也叫作「字段數量」變量。awk 會自動將該變量設置成當前記錄中的字段數量。可使用 NF 變量來只顯示某些輸入行: NF == 3 { print "this particular record has three fields: " $0 } 固然,也能夠在條件語句中使用 NF 變量,以下: { if ( NF > 2 ) { print $1 " " $2 ":" $3 } } 九、處理記錄 記錄號 記錄號 (NR) 是另外一個方便的變量。它始終包含當前記錄的編號(awk 將第一個記錄算做記錄號 1)。迄今爲止,咱們已經處理了每一行包含一個記錄的輸入文件。對於這些狀況,NR 還會告訴您當前行號。然而,當咱們在本系列之後部分中開始處理多行記錄時,就不會再有這種狀況,因此要注意!能夠象使用 NF 變量同樣使用 NR 來只打印某些輸入行: (NR < 10 ) || (NR > 100) { print "We are on record number 1-9 or 101+" } 另外一個示例: { #skip header if (NR>10) { print "ok, now for the real information!" } } awk 提供了適合各類用途的附加變量。咱們將在之後的文章中討論這些變量。 多行記錄 awk 是一種用於讀取和處理結構化數據(如系統的 /etc/passwd 文件)的極佳工具。/etc/passwd 是 UNIX 用戶數據庫,而且是用冒號定界的文本文件,它包含許多重要信息,包括全部現有用戶賬戶和用戶標識,以及其它信息。在個人前一篇文章中,我演示了 awk 如何輕鬆地分析這個文件。咱們只須將 FS(字段分隔符)變量設置成 ":"。 正確設置了 FS 變量以後,就能夠將 awk 配置成分析幾乎任何類型的結構化數據,只要這些數據是每行一個記錄。然而,若是要分析佔據多行的記錄,僅僅依靠設置 FS 是不夠的。在這些狀況下,咱們還須要修改 RS 記錄分隔符變量。RS 變量告訴 awk 當前記錄何時結束,新記錄何時開始。 譬如,讓咱們討論一下如何完成處理「聯邦證人保護計劃」所涉及人員的地址列表的任務: Jimmy the Weasel 100 Pleasant Drive San Francisco, CA 12345 Big Tony 200 Incognito Ave. Suburbia, WA 67890 理論上,咱們但願 awk 將每 3 行看做是一個獨立的記錄,而不是三個獨立的記錄。若是 awk 將地址的第一行看做是第一個字段 ($1),街道地址看做是第二個字段 ($2),城市、州和郵政編碼看做是第三個字段 $3,那麼這個代碼就會變得很簡單。代碼以下: BEGIN { FS="n" RS="" } 在上面這段代碼中,將 FS 設置成 "n" 告訴 awk 每一個字段都佔據一行。經過將 RS 設置成 "",還會告訴 awk 每一個地址記錄都由空白行分隔。一旦 awk 知道是如何格式化輸入的,它就能夠爲咱們執行全部分析工做,腳本的其他部分很簡單。讓咱們研究一個完整的腳本,它將分析這個地址列表,並將每一個記錄打印在一行上,用逗號分隔每一個字段。 address.awk BEGIN { FS="n" RS="" } { print $1 ", " $2 ", " $3 } 將腳本保存爲 address.awk,地址數據存儲在文件 address.txt 中,能夠經過輸入 "awk -f address.awk address.txt" 執行此腳本。輸出以下: Jimmy the Weasel, 100 Pleasant Drive, San Francisco, CA 12345 Big Tony, 200 Incognito Ave., Suburbia, WA 67890 OFS 和 ORS 在 address.awk 的 print 語句中,能夠看到 awk 會鏈接(合併)一行中彼此相鄰的字符串。咱們使用此功能在同一行上的三個字段之間插入一個逗號和空格 (", ")。這個方法雖然有用,但比較難看。與其在字段間插入 ", " 字符串,倒不如讓經過設置一個特殊 awk 變量 OFS,讓 awk 完成這件事。 print "Hello", "there", "Jim!" 這行代碼中的逗號並非實際文字字符串的一部分。事實上,它們告訴 awk "Hello"、"there" 和 "Jim!" 是單獨的字段,而且應該在每一個字符串之間打印 OFS 變量。 缺省狀況下,awk 產生如下輸出: Hello there Jim! 這是缺省狀況下的輸出結果,OFS 被設置成 " ",單個空格。不過,咱們能夠方便地從新定義 OFS,這樣 awk 將插入咱們中意的字段分隔符。如下是原始 address.awk 程序的修訂版,它使用 OFS 來輸出那些中間的 ", " 字符串: address.awk 的修訂版 BEGIN { FS="n" RS="" OFS=", " } { print $1, $2, $3 } awk 還有一個特殊變量 ORS,全稱是「輸出記錄分隔符」。經過設置缺省爲換行 ("n") 的 OFS,咱們能夠控制在 print 語句結尾自動打印的字符。缺省 ORS 值會使 awk 在新行中輸出每一個新的 print 語句。若是想使輸出的間隔翻倍,能夠將 ORS 設置成 "nn"。或者,若是想要用單個空格分隔記錄(而不換行),將 ORS 設置成 " "。 將多行轉換成用 tab 分隔的格式 假設咱們編寫了一個腳本,它將地址列表轉換成每一個記錄一行,且用 tab 定界的格式,以便導入電子表格。使用稍加修改的 address.awk 以後,就能夠清楚地看到這個程序只適合於三行的地址。若是 awk 遇到如下地址,將丟掉第四行,而且不打印該行: Cousin Vinnie Vinnie's Auto Shop 300 City Alley Sosueme, OR 76543 要處理這種狀況,代碼最好考慮每一個字段的記錄數量,並依次打印每一個記錄。如今,代碼只打印地址的前三個字段。如下就是咱們想要的一些代碼: 適合具備任意多字段的地址的 address.awk 版本 BEGIN { FS="n" RS="" ORS="" } { x=1 while ( x<NF ) { print $x "t" x++ } print $NF "n" } 首先,將字段分隔符 FS 設置成 "n",將記錄分隔符 RS 設置成 "",這樣 awk 能夠象之前同樣正確分析多行地址。而後,將輸出記錄分隔符 ORS 設置成 "",它將使 print 語句在每一個調用結尾不輸出新行。這意味着若是但願任何文本重新的一行開始,那麼須要明確寫入 print "n"。 在主代碼塊中,建立了一個變量 x 來存儲正在處理的當前字段的編號。起初,它被設置成 1。而後,咱們使用 while 循環(一種 awk 循環結構,等同於 C 語言中的 while 循環),對於全部記錄(最後一個記錄除外)重複打印記錄和 tab 字符。最後,打印最後一個記錄和換行;此外,因爲將 ORS 設置成 "",print 將不輸出換行。程序輸出以下,這正是咱們所指望的(不算漂亮,但用 tab 定界,以便於導入電子表格): Jimmy the Weasel 100 Pleasant Drive San Francisco, CA 12345 Big Tony 200 Incognito Ave. Suburbia, WA 67890 Cousin Vinnie Vinnie's Auto Shop 300 City Alley Sosueme, OR 76543