有一些網友看了前兩天的《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