首頁 > 軟體

使用awk格式化輸出文字

2020-06-16 17:43:38

注意:本文並不是一篇awk入門文章,而是偏重範例講解

awk借鑒了c語法,因此awk在許多地方還保留有C語言的痕跡,比如printf語句;for,if的語法結構等

介紹

最簡單地說,AWK 是一種用於處理文字的程式語言工具,處理模式是只要在輸入資料中有模式匹配,就執行一系列指令。awk命令格式為:

awk {pattern + action} {filenames}
awk可以讀取後接的檔案,也可以讀取來自前一命令的標準輸入,它分別掃描輸入資料的每一行,查詢命令列中pattern是否匹配。如果匹配,則進行後續動作action。如果pattern不匹配或action部分處理完畢,則繼續處理下一行,直到結束

相比於sed常常作用於一整行的處理,awk則比較傾向於將一行分成數個欄位來處理。awk將輸入資料視為一個文字資料庫,像資料庫一樣,它也有記錄和欄位的概念。預設情況下,記錄的分隔符是回車,欄位的分隔符是空白符(空格,t),所以輸入資料的每一行表示一個記錄,而每一行中的內容被空白分隔成多個欄位。利用欄位和記錄,awk可以非常靈活地處理檔案

語法

1 語法

一個典型的awk語法如下:

awk '{ 

     BEGIN{stat1} 
     BEGIN{stat2} 
pattern1{action1} pattern2{action2} ... patternn{actionn} {預設動作,無條件,始終執行} END{stat1} END{stat2} }
'
其中BEGIN為處理文字前的操作,一般用於改變FS,OFS,RS,ORS等,BEGIN部分完成之後,awk讀取第一行輸入,並將第一行的資料填入0,  0, 1,$2,NR,NF等變數,然後進入正式處理階段,待所有行處理完畢之後,進入END部分,END一般用於總結,列印報表等。正式處理是一個內建的迴圈,每一次迴圈讀取一行資料,每一行的處理分為多模式,多動作,文字行符合條件pattern1就執行動作action1符合pattern2就執行動作action2…,還可以有預設的動作, 即沒有pattern判斷,始終執行此{}內的action。
 
BEGIN,END部分不是必須出現,可以沒有,也可以有任意多個
pattern部分的寫法有:
  • /reg/: 在整行範圍內匹配reg,匹配到就執行後續動作
  • ! /reg/: 整行沒匹配到reg,才執行後續動作
  • $1 ~ /reg/:只在第一欄位匹配reg
  • $1 !~ /reg/: 不匹配
  • NR>=2: 從第二行開始處理

pattern,部分和隨後的if,for部分,能用到的符號有:

2 內建變數

$0
當前記錄(這個變數中存放著整個行的內容) 
$1~$n
當前記錄的第n個欄位,欄位間由FS分隔 
FS
輸入欄位分隔符 預設是空格或t
NF
當前記錄中的欄位個數,就是有多少列 
NR
已經讀出的記錄數,就是行號,從1開始,如果有多個檔案話,這個值也是不斷累加中。 
FNR
當前記錄數,與NR不同的是,這個值會是各個檔案自己的行號 
RS
輸入的記錄分隔符, 預設為換行符 
OFS
輸出欄位分隔符, 預設也是空格 
ORS
輸出的記錄分隔符,預設為換行符 
FILENAME
當前輸入檔案的名字

3 if,for語句

#在任何時候{}內都可以跟多個並列動作(使用“;”分隔),下面的{action1} 和 {action1;action2;...} 都表示{}體內有多個動作,兩種表示沒有任何區別,寫第二種僅僅是為了直觀的表示可以有多個動作

#for迴圈寫法

for(i=1;i<=NF;i++){action1; action2; ..} #{}中用分號分隔多個動作
for(i=1;i<=NF;i++)if; else if;else #for後接一個if結構
for(i=1;i<=NF;i++)printf “for add” #簡單的迴圈列印

#if 判斷寫法

if($1 ~ /reg/){action1}; else if($1 ~ /reg2/){action2}; else{action3} #else if部分可以沒有
if($1 ~ /reg/ && $2 ~ /reg2/){action} #多個條件用”&&”,”||”表示
if($1 ~ /reg/ || NR >= 5){action

# if,for 混合寫法

{ for(i=1;i<=NF;i++)if(…) printf “test”; else if(…) printf “test2”; else printf “test3”; print "not_for" } 
#print “not_for”部分是並列與for迴圈結構的另一個action,在for迴圈之外,只會列印一次 {
for(i=1;i<=NF;i++){if(…) printf “test”; else if(…) printf “test2”; else printf “test3”;print “in_for“}; print "not_in_for" } { for(i=1;i<=NF;i++){if {s1;s2;} else if {s3;s4;} else {s5;s6;}; print "test"} } #else if前不加分號 { for(i=1;i<=NF;i++)printf "for_add"; if(…);else if(…); else } #if並不在for迴圈體內
for迴圈的作用範圍為:
  • 其後緊跟的if; else if; else語句
  • 其後緊跟的{}中的多個動作
  • 其後緊跟的一個第一個普通動作

if語句的作用範圍:

  • if後緊跟的第一個動作
  • if後緊跟的{}中的多個動作

4 awk技巧

1: AWK使用的RE為ERE

2: 如果在BEGIN中設定了OFS, 只有$0有改動OFS才能生效

3: printf 與 print 的區別: printf 不自動列印換行符, print 則自動列印

4: gsub的返回值並不是替換後的字串,而是返回替換的次數

5: 字串常數一定在用" "包圍起來,否則當作變數使用, 如 $1=="ipaddress"

6: AWK 的 for 迴圈為 C-Style,即為 for(), 區別於shell中的for i in ...

7: AWK中可以使用多個分隔符,要封裝在方括號裡,用' '包圍,以防 shell 對它們進行解釋,如 awk -F '[ :/t]' ,使用空格,冒號,tab作為分隔符

8: next語句:從輸入檔案中取得下一個輸入行,在AWK命令表頂部重新執行命令,一般用於跳過一些特殊的行

9: awk 匹配多個條件: awk '/kobe/ && /james/' #匹配同時有kobe和james的行

10: FS的預設值是[ /t/n]+, OFS的預設值為空格,RS,ORS的預設值都是換行

11: 定位行有兩種方法: 1: NR==行號 2: 用RE /Love$/

12: exit語句:終止AWK程式,但不跳過END語句

13:1.. 1.. n表示第幾列(欄位),$0表示整個行.

14:awk可用比較運算子:!=, >, <, >=, <=

15: 字串匹配:~: 匹配 !~: 不匹配

16: &&:多個條件且, || 多個條件或

17: {s1;s2;s3;...}中多個語句用分號隔開;if; else if; else

18: print 後不帶任何引數時,相當於print $0 ,將會列印整行記錄

awk字元函數

函數 說明
gsub( Ere, Repl, [ In ] ) 除了正規表示式所有具體值被替代這點,它和 sub 函數完全一樣地執行,。
sub( Ere, Repl, [ In ] ) 用 Repl 引數指定的字串替換 In 引數指定的字串中的由 Ere 引數指定的擴充套件正規表示式的第一個具體值。sub 函數返回替換的數量。出現在 Repl 引數指定的字串中的 &(和符號)由 In 引數指定的與 Ere 引數的指定的擴充套件正規表示式匹配的字串替換。如果未指定 In 引數,預設值是整個記錄($0 記錄變數)。
index( String1, String2 ) 在由 String1 引數指定的字串(其中有出現 String2 指定的引數)中,返回位置,從 1 開始編號。如果 String2 引數不在 String1 引數中出現,則返回 0(零)。
length [(String)] 返回 String 引數指定的字串的長度(字元形式)。如果未給出 String 引數,則返回整個記錄的長度($0 記錄變數)。
blength [(String)] 返回 String 引數指定的字串的長度(以位元組為單位)。如果未給出 String 引數,則返回整個記錄的長度($0 記錄變數)。
substr( String, M, [ N ] ) 返回具有 N 引數指定的字元數量子串。子串從 String 引數指定的字串取得,其字元以 M 引數指定的位置開始。M 引數指定為將 String 引數中的第一個字元作為編號 1。如果未指定 N 引數,則子串的長度將是 M 引數指定的位置到 String 引數的末尾 的長度。
match( String, Ere ) 在 String 引數指定的字串(Ere 引數指定的擴充套件正規表示式出現在其中)中返回位置(字元形式),從 1 開始編號,或如果 Ere 引數不出現,則返回 0(零)。RSTART 特殊變數設定為返回值。RLENGTH 特殊變數設定為匹配的字串的長度,或如果未找到任何匹配,則設定為 -1(負一)。
split( String, A, [Ere] ) 將 String 引數指定的引數分割為陣列元素 A[1], A[2], . . ., A[n],並返回 n 變數的值。此分隔可以通過 Ere 引數指定的擴充套件正規表示式進行,或用當前欄位分隔符(FS 特殊變數)來進行(如果沒有給出 Ere 引數)。除非上下文指明特定的元素還應具有一個數位值,否則 A 陣列中的元素用字串值來建立。
tolower( String ) 返回 String 引數指定的字串,字串中每個大寫字元將更改為小寫。大寫和小寫的對映由當前語言環境的 LC_CTYPE 範疇定義。
toupper( String ) 返回 String 引數指定的字串,字串中每個小寫字元將更改為大寫。大寫和小寫的對映由當前語言環境的 LC_CTYPE 範疇定義。
sprintf(Format, Expr, Expr, . . . ) 根據 Format 引數指定的 printf 子例程格式字串來格式化 Expr 引數指定的表示式並返回最後生成的字串。
以上函數轉自:Linux awk 內建函數詳細介紹(範例)

awk 範例

1.awk作用域
awk '/AL/ {printf $1; print $2}' emp.txt
awk '/AL/{print $1} {print $2}' emp.txt

1.第一種只處理匹配到AL的行; 然後列印這些行的第一欄位和第二欄位

2.第二種只有在匹配到AL的行才列印欄位一,但是欄位二是無條件的,始終列印

 2.awk內建函數用法

如下文字test.log,需要把文字中單位是"M"的欄位轉換為"G"

16G    16G    1.9G    40G none
4G     4G     952M    60G
16G    16G    1.6G    40G none
5G     780M   5G      80G

 若我們單純地想替換字串,則可以使用一下命令,注意:print沒帶預設引數時,預設列印整行記錄

cat 1.txt | awk '{
  sub(/M/,"G",$i)
  print
}
'

完整替換如下,替換的同時進行數值計算

cat 1.log | 
awk '{
    for(i=1;i<=NF;i++){
        if($i ~ /M/) 
            printf int(substr($i,1,match($i,/M/)-1)/1024*100)/100"Gt"
        else 
            printf $i"t"  
    }
    print "" 
}'

1.使用for遍歷每一行的每一個欄位,使用if處理匹配到"M"的欄位,然後呼叫awk內建函數int, substr, match處理, 關於awk內建函數,可以參考Linux awk 內建函數詳細介紹(範例) http://www.linuxidc.com/Linux/2012-11/74816.htm

2.先乘100,再除以100,是為了轉換為"G"單位後保留兩位小數

3.注意 printf,print 兩個函數的區別

3.awk條件語句

如第二個例子中的文字,現在需要分別統計每行中帶有"G", "M", "none" 欄位的個數,並輸出

cat test.log | 
awk '
     BEGIN{OFS="t"}
     {    
        a=b=c=0
        for(i=1;i<=NF;i++){
            if($i ~ /G/)
                a+=1
            else if($i ~ /M/)
                b+=1
            else
                c+=1
        }
        print $0,"G:"a,"M:"b,"none:"c
     }
     END{print "end!"}
'

結果如下:

16G    16G    1.9G    40G    G:4    M:0    none:0
4G     4G     952M    60G    G:3    M:1    none:0
16G    16G    1.6G    40G    G:4    M:0    none:0
5G     780M   5G      80G    G:3    M:1    none:0
end!
4.awk中break用法

給出一個如下文字test2.log,在每一行中,只輸出字母及其之前的字元

1  2  a  5 6
11 b  55 66
21 22 23 c 25  26

寫法如下:

cat test2.log | 
awk '{
    for(i=1;i<=NF;i++){
        if($i ~ /[a-z]/) {
            printf $i"t"
            break }
        else 
            printf $i"t"
    }
    print ""
}'

1.break 用法跟c語言用法一樣,跳出for迴圈

5.awk中陣列

如下文字test3.log,給定一個id值,輸出其在所有id中是第幾個

test1
id
=615187629 test2
test3
id=615183434 test4
id=615123789 test5
id=615975882

給定id值615123789,其在所有id中是第三個,計算如下:

cat test3.log | 
awk -v var1=615123789 -F [=] '
    /id/ {
        b+=1
        a[b]=$2
    }
    END {
        for(i in a) if(a[i] == var1) print "number:",i
    }
'

 6.awk中的整數計算

如下文字,這是一個kvm虛擬機器進程(省略了部分文字),我們要獲取其對映到宿主機上的vnc埠號,即由"-vnc 0.0.0.0:1"字串計算出其vnc埠號為5901(5900 + 1),若是"-vnc 0.0.0.0:2",則埠號為5902

cat kvm.txt
qemu 144148 4.7 4.2 ... /usr/local/qemu/bin/qemu-kvm -name lnmptest-107 ... -device isa-serial,chardev=charserial0,id=serial0 -vnc 0.0.0.0:1 -vga cirrus timestamp=on

實現有多種,sed,shell,awk都可以

#awk
cat kvm.txt
| awk '{ for(i=1;i<=NF;i++){if($i == "-vnc"){sub(/0.0.0.0:/,"",$(i+1));print $(i+1)+5900}} }'
#shell
cat kvm.txt | egrep -o "-vnc [^ ]*" | awk -F: '{print $2+5900}'
#sed
cat kvm.txt | sed "s/^.* -vnc [0-9.]*:([0-9]*).*/1+5900/g" | bc

Linux系統之文字格式化工具awk http://www.linuxidc.com/Linux/2016-02/128150.htm

AWK簡介及使用範例 http://www.linuxidc.com/Linux/2013-12/93519.htm

Linux awk文字分析工具 http://www.linuxidc.com/Linux/2015-12/126217.htm

Linux文字處理工具之awk  http://www.linuxidc.com/Linux/2015-01/111437.htm

如何在Linux中使用awk命令 http://www.linuxidc.com/Linux/2014-10/107542.htm

文字分析工具-awk  http://www.linuxidc.com/Linux/2014-12/110939.htm

本文永久更新連結地址http://www.linuxidc.com/Linux/2016-04/130193.htm


IT145.com E-mail:sddin#qq.com