天天看點

AWK 簡明教程

有一些網友看了前兩天的《Linux下應該知道的技巧》希望我能教教他們用awk和sed,是以,出現了這篇文章。我估計這些80後的年輕朋友可能對awk/sed這類上古神器有點陌生了,是以需要我這個老家夥來炒炒冷飯。況且,AWK是貝爾實驗室1977年搞出來的文本出現神器,今年是蛇年,是AWK的本命年,而且年紀和我相仿,是以非常有必要為他寫篇文章。

之是以叫AWK是因為其取了三位創始人 Alfred Aho,Peter Weinberger, 和 Brian Kernighan 的Family Name的首字元。要學AWK,就得提一提AWK的一本相當經典的書《The AWK Programming Language》,它在豆瓣上的評分是9.4分!在亞馬遜上居然賣1022.30元。

我在這裡的教程并不想面面俱到,本文和我之前的Go語言簡介一樣,全是示例,基本無廢話。

我隻想達到兩個目的:

1)你可以在乘坐公交地鐵上下班,或是在坐馬桶拉大便時讀完(保證是一泡大便的工夫)。

2)我隻想讓這篇博文像一個火辣的脫衣舞女挑起你的興趣,然後還要你自己去下工夫去撸。

廢話少說,我們開始脫吧(注:這裡隻是topless)。

起步上台

我從netstat指令中提取了如下資訊作為用例:   

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

$ cat netstat.txt 

Proto Recv-Q Send-Q Local-Address          Foreign-Address             State 

tcp        0      0 0.0.0.0:3306           0.0.0.0:*                   LISTEN 

tcp        0      0 0.0.0.0:80             0.0.0.0:*                   LISTEN 

tcp        0      0 127.0.0.1:9000         0.0.0.0:*                   LISTEN 

tcp        0      0 coolshell.cn:80        124.205.5.146:18245         TIME_WAIT 

tcp        0      0 coolshell.cn:80        61.140.101.185:37538        FIN_WAIT2 

tcp        0      0 coolshell.cn:80        110.194.134.189:1032        ESTABLISHED 

tcp        0      0 coolshell.cn:80        123.169.124.111:49809       ESTABLISHED 

tcp        0      0 coolshell.cn:80        116.234.127.77:11502        FIN_WAIT2 

tcp        0      0 coolshell.cn:80        123.169.124.111:49829       ESTABLISHED 

tcp        0      0 coolshell.cn:80        183.60.215.36:36970         TIME_WAIT 

tcp        0   4166 coolshell.cn:80        61.148.242.38:30901         ESTABLISHED 

tcp        0      1 coolshell.cn:80        124.152.181.209:26825       FIN_WAIT1 

tcp        0      0 coolshell.cn:80        110.194.134.189:4796        ESTABLISHED 

tcp        0      0 coolshell.cn:80        183.60.212.163:51082        TIME_WAIT 

tcp        0      1 coolshell.cn:80        208.115.113.92:50601        LAST_ACK 

tcp        0      0 coolshell.cn:80        123.169.124.111:49840       ESTABLISHED 

tcp        0      0 coolshell.cn:80        117.136.20.85:50025         FIN_WAIT2 

tcp        0      0 :::22                  :::*                        LISTEN

下面是最簡單最常用的awk示例,其輸出第1列和第4例,

其中單引号中的被大括号括着的就是awk的語句,注意,其隻能被單引号包含。

其中的$1..$n表示第幾例。注:$0表示整個行。

$ awk '{print $1, $4}' netstat.txt 

Proto Local-Address 

tcp 0.0.0.0:3306 

tcp 0.0.0.0:80 

tcp 127.0.0.1:9000 

tcp coolshell.cn:80 

tcp :::22

我們再來看看awk的格式化輸出,和C語言的printf沒什麼兩樣:   

$ awk '{printf "%-8s %-8s %-8s %-18s %-22s %-15s\n",$1,$2,$3,$4,$5,$6}' netstat.txt 

Proto    Recv-Q   Send-Q   Local-Address      Foreign-Address        State 

tcp      0        0        0.0.0.0:3306       0.0.0.0:*              LISTEN 

tcp      0        0        0.0.0.0:80         0.0.0.0:*              LISTEN 

tcp      0        0        127.0.0.1:9000     0.0.0.0:*              LISTEN 

tcp      0        0        coolshell.cn:80    124.205.5.146:18245    TIME_WAIT 

tcp      0        0        coolshell.cn:80    61.140.101.185:37538   FIN_WAIT2 

tcp      0        0        coolshell.cn:80    110.194.134.189:1032   ESTABLISHED 

tcp      0        0        coolshell.cn:80    123.169.124.111:49809  ESTABLISHED 

tcp      0        0        coolshell.cn:80    116.234.127.77:11502   FIN_WAIT2 

tcp      0        0        coolshell.cn:80    123.169.124.111:49829  ESTABLISHED 

tcp      0        0        coolshell.cn:80    183.60.215.36:36970    TIME_WAIT 

tcp      0        4166     coolshell.cn:80    61.148.242.38:30901    ESTABLISHED 

tcp      0        1        coolshell.cn:80    124.152.181.209:26825  FIN_WAIT1 

tcp      0        0        coolshell.cn:80    110.194.134.189:4796   ESTABLISHED 

tcp      0        0        coolshell.cn:80    183.60.212.163:51082   TIME_WAIT 

tcp      0        1        coolshell.cn:80    208.115.113.92:50601   LAST_ACK 

tcp      0        0        coolshell.cn:80    123.169.124.111:49840  ESTABLISHED 

tcp      0        0        coolshell.cn:80    117.136.20.85:50025    FIN_WAIT2 

tcp      0        0        :::22              :::*                   LISTEN

脫掉外套

過濾記錄

我們再來看看如何過濾記錄(下面過濾條件為:第三列的值為0 && 第6列的值為LISTEN)   

$ awk '$3==0 && $6=="LISTEN" ' netstat.txt 

tcp        0      0 0.0.0.0:3306               0.0.0.0:*              LISTEN 

tcp        0      0 0.0.0.0:80                 0.0.0.0:*              LISTEN 

tcp        0      0 127.0.0.1:9000             0.0.0.0:*              LISTEN 

tcp        0      0 :::22                      :::*                   LISTEN

其中的“==”為比較運算符。其他比較運算符:!=, >, <, >=, <=

我們來看看各種過濾記錄的方式:   

$ awk ' $3>0 {print $0}' netstat.txt 

tcp        0      1 coolshell.cn:80        208.115.113.92:50601        LAST_ACK

如果我們需要表頭的話,我們可以引入内建變量NR:   

$ awk '$3==0 && $6=="LISTEN" || NR==1 ' netstat.txt 

再加上格式化輸出:   

$ awk '$3==0 && $6=="LISTEN" || NR==1 {printf "%-20s %-20s %s\n",$4,$5,$6}' netstat.txt 

Local-Address        Foreign-Address      State 

0.0.0.0:3306         0.0.0.0:*            LISTEN 

0.0.0.0:80           0.0.0.0:*            LISTEN 

127.0.0.1:9000       0.0.0.0:*            LISTEN 

:::22                :::*                 LISTEN

内建變量

說到了内建變量,我們可以來看看awk的一些内建變量:   

$0

目前記錄(這個變量中存放着整個行的内容)

$1~$n

目前記錄的第n個字段,字段間由FS分隔

FS

輸入字段分隔符 預設是空格或Tab

NF

目前記錄中的字段個數,就是有多少列

NR

已經讀出的記錄數,就是行号,從1開始,如果有多個檔案話,這個值也是不斷累加中。

FNR

目前記錄數,與NR不同的是,這個值會是各個檔案自己的行号

RS

輸入的記錄分隔符, 預設為換行符

OFS

輸出字段分隔符, 預設也是空格

ORS

輸出的記錄分隔符,預設為換行符

FILENAME

目前輸入檔案的名字

怎麼使用呢,比如:我們如果要輸出行号:   

$ awk '$3==0 && $6=="ESTABLISHED" || NR==1 {printf "%02s %s %-20s %-20s %s\n",NR, FNR, $4,$5,$6}' netstat.txt 

01 1 Local-Address        Foreign-Address      State 

07 7 coolshell.cn:80      110.194.134.189:1032 ESTABLISHED 

08 8 coolshell.cn:80      123.169.124.111:49809 ESTABLISHED 

10 10 coolshell.cn:80      123.169.124.111:49829 ESTABLISHED 

14 14 coolshell.cn:80      110.194.134.189:4796 ESTABLISHED 

17 17 coolshell.cn:80      123.169.124.111:49840 ESTABLISHED

指定分隔符

$  awk 'BEGIN{FS=":"} {print $1,$3,$6}' /etc/passwd

root 0 /root

bin 1 /bin

daemon 2 /sbin

adm 3 /var/adm

lp 4 /var/spool/lpd

sync 5 /sbin

shutdown 6 /sbin

halt 7 /sbin

上面的指令也等價于:(-F的意思就是指定分隔符)   

$ awk -F: '{print $1,$3,$6}' /etc/passwd

注:如果你要指定多個分隔符,你可以這樣來:   

awk -F '[;:]'

再來看一個以\t作為分隔符輸出的例子(下面使用了/etc/passwd檔案,這個檔案是以:分隔的):   

$ awk -F: '{print $1,$3,$6}' OFS="\t" /etc/passwd

root    0       /root

bin     1       /bin

daemon  2       /sbin

adm     3       /var/adm

lp      4       /var/spool/lpd

sync 5       /sbin

脫掉襯衫

字元串比對

我們再來看幾個字元串比對的示例:   

$ awk '$6 ~ /FIN/ || NR==1 {print NR,$4,$5,$6}' OFS="\t" netstat.txt 

1       Local-Address   Foreign-Address State 

6       coolshell.cn:80 61.140.101.185:37538    FIN_WAIT2 

9       coolshell.cn:80 116.234.127.77:11502    FIN_WAIT2 

13      coolshell.cn:80 124.152.181.209:26825   FIN_WAIT1 

18      coolshell.cn:80 117.136.20.85:50025     FIN_WAIT2 

$ $ awk '$6 ~ /WAIT/ || NR==1 {print NR,$4,$5,$6}' OFS="\t" netstat.txt 

5       coolshell.cn:80 124.205.5.146:18245     TIME_WAIT 

11      coolshell.cn:80 183.60.215.36:36970     TIME_WAIT 

15      coolshell.cn:80 183.60.212.163:51082    TIME_WAIT 

18      coolshell.cn:80 117.136.20.85:50025     FIN_WAIT2

上面的第一個示例比對FIN狀态, 第二個示例比對WAIT字樣的狀态。其實 ~ 表示模式開始。/ /中是模式。這就是一個正規表達式的比對。

其實awk可以像grep一樣的去比對第一行,就像這樣:   

$ awk '/LISTEN/' netstat.txt 

tcp        0      0 0.0.0.0:3306            0.0.0.0:*               LISTEN 

tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN 

tcp        0      0 127.0.0.1:9000          0.0.0.0:*               LISTEN 

tcp        0      0 :::22                   :::*                    LISTEN

我們可以使用 “/FIN|TIME/” 來比對 FIN 或者 TIME :   

$ awk '$6 ~ /FIN|TIME/ || NR==1 {print NR,$4,$5,$6}' OFS="\t" netstat.txt 

再來看看模式取反的例子:   

$ awk '$6 !~ /WAIT/ || NR==1 {print NR,$4,$5,$6}' OFS="\t" netstat.txt 

2       0.0.0.0:3306    0.0.0.0:*       LISTEN 

3       0.0.0.0:80      0.0.0.0:*       LISTEN 

4       127.0.0.1:9000  0.0.0.0:*       LISTEN 

7       coolshell.cn:80 110.194.134.189:1032    ESTABLISHED 

8       coolshell.cn:80 123.169.124.111:49809   ESTABLISHED 

10      coolshell.cn:80 123.169.124.111:49829   ESTABLISHED 

12      coolshell.cn:80 61.148.242.38:30901     ESTABLISHED 

14      coolshell.cn:80 110.194.134.189:4796    ESTABLISHED 

16      coolshell.cn:80 208.115.113.92:50601    LAST_ACK 

17      coolshell.cn:80 123.169.124.111:49840   ESTABLISHED 

19      :::22   :::*    LISTEN

或是:   

awk '!/WAIT/' netstat.txt

折分檔案

awk拆分檔案很簡單,使用重定向就好了。下面這個例子,是按第6例分隔檔案,相當的簡單(其中的NR!=1表示不處理表頭)。   

21

22

23

24

25

26

27

28

29

30

31

32

33

34

$ awk 'NR!=1{print > $6}' netstat.txt 

$ ls

ESTABLISHED  FIN_WAIT1  FIN_WAIT2  LAST_ACK  LISTEN  netstat.txt  TIME_WAIT 

$ cat ESTABLISHED 

$ cat FIN_WAIT1 

$ cat FIN_WAIT2 

$ cat LAST_ACK 

$ cat LISTEN 

tcp        0      0 :::22                  :::*                        LISTEN 

$ cat TIME_WAIT 

tcp        0      0 coolshell.cn:80        183.60.212.163:51082        TIME_WAIT

你也可以把指定的列輸出到檔案:   

awk 'NR!=1{print $4,$5 > $6}' netstat.txt

再複雜一點:(注意其中的if-else-if語句,可見awk其實是個腳本解釋器)   

$ awk 'NR!=1{if($6 ~ /TIME|ESTABLISHED/) print > "1.txt"; 

else if($6 ~ /LISTEN/) print > "2.txt"; 

else print > "3.txt" }' netstat.txt 

$ ls ?.txt 

1.txt  2.txt  3.txt 

$ cat 1.txt 

$ cat 2.txt 

$ cat 3.txt 

tcp        0      0 coolshell.cn:80        117.136.20.85:50025         FIN_WAIT2

統計

下面的指令計算所有的C檔案,CPP檔案和H檔案的檔案大小總和。   

$ ls -l  *.cpp *.c *.h | awk '{sum+=$5} END {print sum}'

2511401

我們再來看一個統計各個connection狀态的用法:(我們可以看到一些程式設計的影子了,大家都是程式員我就不解釋了。注意其中的數組的用法)   

$ awk 'NR!=1{a[$6]++;} END {for (i in a) print i ", " a[i];}' netstat.txt 

TIME_WAIT, 3 

FIN_WAIT1, 1 

ESTABLISHED, 6 

FIN_WAIT2, 3 

LAST_ACK, 1 

LISTEN, 4

再來看看統計每個使用者的程序的占了多少記憶體(注:sum的RSS那一列)   

$ ps aux | awk 'NR!=1{a[$1]+=$6;} END { for(i in a) print i ", " a[i]"KB";}'

dbus, 540KB 

mysql, ×××8KB 

www, 3264924KB 

root, 63644KB 

hchen, 6020KB

脫掉内衣

awk腳本

在上面我們可以看到一個END關鍵字。END的意思是“處理完所有的行的辨別”,即然說到了END就有必要介紹一下BEGIN,這兩個關鍵字意味着執行前和執行後的意思,文法如下:

BEGIN{ 這裡面放的是執行前的語句 } 

END {這裡面放的是處理完所有的行後要執行的語句 } 

{這裡面放的是處理每一行時要執行的語句} 

為了說清楚這個事,我們來看看下面的示例:

假設有這麼一個檔案(學生成績表):   

$ cat score.txt 

Marry   2143 78 84 77 

Jack    2321 66 78 45 

Tom     2122 48 77 71 

Mike    2537 87 97 95 

Bob     2415 40 57 62

我們的awk腳本如下(我沒有寫有指令行上是因為指令行上不易讀,另外也在介紹另一種用法):   

$ cat cal.awk

#!/bin/awk -f 

#運作前 

BEGIN { 

    math = 0 

    english = 0 

    computer = 0 

    printf "NAME    NO.   MATH  ENGLISH  COMPUTER   TOTAL\n"

    printf "---------------------------------------------\n"

#運作中 

    math+=$3 

    english+=$4 

    computer+=$5 

    printf "%-6s %-6s %4d %8d %8d %8d\n", $1, $2, $3,$4,$5, $3+$4+$5 

#運作後 

END { 

    printf "  TOTAL:%10d %8d %8d \n", math, english, computer 

    printf "AVERAGE:%10.2f %8.2f %8.2f\n", math/NR, english/NR, computer/NR

}

我們來看一下執行結果:(也可以這樣運作 ./cal.awk score.txt)   

$ awk -f cal.awk score.txt 

NAME    NO.   MATH  ENGLISH  COMPUTER   TOTAL 

--------------------------------------------- 

Marry  2143     78       84       77      239 

Jack   2321     66       78       45      189 

Tom    2122     48       77       71      196 

Mike   2537     87       97       95      279 

Bob    2415     40       57       62      159 

  TOTAL:       319      393      350 

AVERAGE:     63.80    78.60    70.00

環境變量

即然說到了腳本,我們來看看怎麼和環境變量互動:(使用-v參數和ENVIRON,使用ENVIRON的環境變量需要export)   

$ x=5 

$ y=10 

$ export y 

$ echo $x $y 

5 10 

$ awk -v val=$x '{print $1, $2, $3, $4+val, $5+ENVIRON["y"]}' OFS="\t" score.txt 

Marry   2143    78      89      87 

Jack    2321    66      83      55 

Tom     2122    48      82      81 

Mike    2537    87      102     105 

Bob     2415    40      62      72

幾個花活

最後,我們再來看幾個小例子:   

#從file檔案中找出長度大于80的行 

awk 'length>80' file

#按連接配接數檢視用戶端IP 

netstat -ntu | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -nr 

#列印99乘法表 

seq 9 | sed 'H;g' | awk -v RS='' '{for(i=1;i<=NF;i++)printf("%dx%d=%d%s", i, NR, i*NR, i==NR?"\n":"\t")}'

自己撸吧

關于其中的一些知識點可以參看gawk的手冊:

内建變量,參看:http://www.gnu.org/software/gawk/manual/gawk.html#Built_002din-Variables

流控方面,參看:http://www.gnu.org/software/gawk/manual/gawk.html#Statements

内建函數,參看:http://www.gnu.org/software/gawk/manual/gawk.html#Built_002din

正規表達式,參看:http://www.gnu.org/software/gawk/manual/gawk.html#Regexp

http://coolshell.cn/articles/9070.html

繼續閱讀