天天看點

GNU awk 的使用及相關練習

gawk

gawk - pattern scanning and processing language 來自手冊頁的介紹:模式掃描和處理語言。awk 掃描檔案中的每一行,查找在指令行中有無相比對的模式;若有則進行程式設計步驟,若無則進行下一行的處理。

在 CentOS 7 中執行指令

ls -l `which awk
# 即可知 awk 為 gawk 的連結檔案,因為 gawk 為 awk 的 GNU 版本
           

基本用法

AWK是一種處理文本檔案的語言。它将檔案作為記錄序列處理。在一般情況下,檔案内容的每行都是一個記錄。每行内容都會被分割成一系列的域,是以,我們可以認為一行的第一個詞為第一個域,第二個詞為第二個,以此類推。AWK程式是由一些處理特定模式的語句塊構成的。AWK一次可以讀取一個輸入行。對每個輸入行,AWK解釋器會判斷它是否符合程式中出現的各個模式,并執行符合的模式所對應的動作。

——阿爾佛雷德·艾侯,The A-Z of Programming Languages: AWK

gawk [option] 'program' FILE ...
# 其中 program: PATTERN{ACTION STATEMENTS}
           

上面的 program 即為由模式與指令語句所編寫的程式,action 意為對比對到的模式執行的一系列指令。awk 将輸入分為多個 record,預設以換行符分割。而後對每條 recoed 進行模式掃描,對比對的 record 進行 action 操作。

常用選項:

  • -F:指明輸入時用到的分隔符
  • -v var=value:自定義變量

awk 變量:

内建變量:

  • FS:field separator,域分隔符;将輸入的 record 分為多個 fields,其預設為空白字元,也可以進行自定義。
  • OFS:output field separator,輸出域分隔符;預設為空格。
awk -v FS=':' -v OFS=':' '{print $1,$3,$7}' /etc/passwd
           
  • RS:record separator,輸入記錄分隔符;預設輸入的每一行為一個 record,是以預設的 RS 為換行符。
  • ORS:output record separator,輸出記錄分隔符;執行 print 指令時的分隔符。
  • NF:the number of fields,目前 record 中域的個數。
awk '{print NF}' /etc/fstab # 做變量時不需要加 $ 進行引用
awk '{print $Nf}' /etc/fstab # 加上 $ 後意為列印最後一個域的内容
           
  • NR:the number of input records,輸入記錄的數量。

兩個輸出指令

print 指令:

print item1, item2, ...
           

以逗号分隔各 item,其可以為數值、字元串、變量或是域;若省略 item 則表示列印 $0 即整行輸出。

awk 'BEGIN{print "Hello World"}'
           

printf 指令:

格式化輸出,使用類似于 C 語言中的用法。

printf FORMAT,item1,item2, ...
           

其中 FORMAT 必須給出,若要換行須使用 \n,常用有 %d,%f;以及修飾符 - 使其左對齊。

PATTERN

到這你已經看見了,每個 awk 語句都包含有 PATTERN,同時模式在控制着 awk 的執行。

  • empty:空模式,比對每一行。
  • /regular expression/:僅處理能夠被此處的模式所比對到的行。
  • BEGIN:僅在開始處理檔案中的文本之前執行一次。
  • END:僅在文本處理完成之後執行一次,有在添加表頭表格尾部時使用。

常用 action 中的控制語句

  • if-else:使用場景為對 awk 取得的整行或某個字段做條件判斷。
awk -F: '{if($3>=1000) print $1,$3}' /etc/passwd
# 以#為分隔符分割/etc/passwd 檔案,找出 UID 大于 1000 的使用者并列印其使用者名與 UID

awk -F: '{if($NF=="/bin/bash") print $1}' /etc/passwd
# 以#為分隔符分割/etc/passwd 檔案,找出其中最後一字段為"/bin/bash"的行,并列印其第一字段

awk '{if(NF>5) print $0}' /etc/fstab
# 找出/etc/fstab 檔案中,域大于 5 個的記錄

df -h |awk -F% '/^\/dev/{print $1}' |awk '{if($NF>20) print $1}'
# 以易讀的方式列出磁盤的使用情況,并 awk 出以/dev 開頭的記錄,後再 awk 出最後一字段即使用量大于 20 的裝置
           
  • while 循環:對一行内的多個字段逐一做類似處理時使用;對數組中的各元素統一處理時使用
awk '/^[[:space:]]*linux16/{i=1;while(i<=NF) {print $i,length($i);i++}}' /etc/grub2.cfg
# 先在檔案中比對出以多個空白字元後跟linux16開頭的記錄,在列印出每個字段的内容及其長度

awk '/^[[:space:]]*linux16/{i=1;while(i<=NF) {if(length($i)>=7) {print $i,length($i)};i++}}' /etc/grub2.cfg
# 先在檔案中比對出以多個空白字元後跟linux16開頭的記錄,在列印出每個長度大于 7 的字段的内容及其長度
           
  • for 循環:文法如下
for(variable assigment;condition;iteration process) {for-body}

awk '/^[[:space:]]*linux16/{for(i=1;i<=NF;i++) {print $i,length($i)}}' /etc/grub2.cfg
# 先在檔案中比對出以多個空白字元後跟linux16開頭的記錄,在列印出每個字段的内容及其長度

#同時 for 也能夠周遊數組中的元素:
for(var in array_name) {for-body}
           
  • next:前結束對本行的處理而直接進入下一行
awk -F: '{if($3%2!=0) next; print $1,$3}' /etc/passwd
# 在/etc/passwd 檔案中 awk 時,遇見奇數行則跳過,即列印偶數行
           

數組

awk 中使用更多的為關聯數組,即 array[index-expression];同時我們需要注意,索引可使用任意字元串,但字元串要使用雙引号。如果某數組元素事先不存在,則在引用時 awk 會自動建立此元素,并将其初始化為“空串”。

awk 'BEGIN{weekdays["mon"]="Monday";weekdays["tue"]="Tuesday";for(i in weekdays) {print weekdays[i]}}'
# 使用 for 循環周遊數組中的每個元素

netstat -tan | awk '/^tcp\>/{state[$NF]++}END{for(i in state) {print i,state[i]}}'
# 比對以 tcp 開頭并錨定在詞尾的記錄,同時 state 數組的索引為目前 record 最後一個字段的字元串,并以之完成自增即計數操作

awk '/^UUID/{fs[$3]++}END{for(i in fs) {print i,fs[i]}}' /etc/fstab
# 比對以 UUID 開頭的記錄,同時 fs 數組的索引為目前 record 的第三字段的字元串,并以之完成自增即計數操作

awk '{for(i=1;i<=NF;i++) {count[$i]++}}END{for(i in count) {print i,count[i]}}' /etc/fstab
# 統計指定檔案中每個單詞出現的次數
           

函數的使用

rand()

length()

sub()

split()

繼續閱讀