有一些網友看了前兩天的《Linux下應該知道的技巧》希望我能教教他們用awk和sed,所以,出現了這篇文章。我估計這些年輕朋友可能對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表示整個行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
$
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 coolshell.cn:80 tcp coolshell.cn:80 tcp coolshell.cn:80 tcp coolshell.cn:80 tcp coolshell.cn:80 tcp coolshell.cn:80 tcp coolshell.cn:80 tcp coolshell.cn:80 tcp coolshell.cn:80 tcp coolshell.cn:80 tcp coolshell.cn:80 tcp coolshell.cn:80 tcp coolshell.cn:80 tcp :::22 |
我們再來看看awk的格式化輸出,和C語言的printf沒什麼兩樣:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
$
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)
1
2
3
4
5
|
$
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 |
其中的“==”爲比較運算符。其他比較運算符:!=, >, <, >=, <=
我們來看看各種過濾記錄的方式:
1
2
3
4
5
|
$
awk
' $3>0 {print $0}' netstat .txt Proto Recv-Q Send-Q Local-Address Foreign-Address State 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 1 coolshell.cn:80 208.115.113.92:50601 LAST_ACK |
如果我們需要表頭的話,我們可以引入內建變量NR:
1
2
3
4
5
6
|
$
awk
'$3==0 && $6=="LISTEN" || NR==1 ' 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 :::22 :::* LISTEN |
再加上格式化輸出:
1
2
3
4
5
6
|
$
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 | 當前輸入文件的名字 |
怎麼使用呢,比如:我們如果要輸出行號:
1
2
3
4
5
6
7
|
$
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 |
指定分隔符
1
2
3
4
5
6
7
8
9
|
$
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的意思就是指定分隔符)
1
|
$
awk
-F: '{print $1,$3,$6}'
/etc/passwd |
注:如果你要指定多個分隔符,你可以這樣來:
1
|
awk
-F '[;:]' |
再來看一個以\t作爲分隔符輸出的例子(下面使用了/etc/passwd文件,這個文件是以:分隔的):
1
2
3
4
5
6
7
|
$
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 |
脫掉襯衫
字符串匹配
我們再來看幾個字符串匹配的示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
$
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 1 Local-Address Foreign-Address State 5 coolshell.cn:80 124.205.5.146:18245 TIME_WAIT 6 coolshell.cn:80 61.140.101.185:37538 FIN_WAIT2 9 coolshell.cn:80 116.234.127.77:11502 FIN_WAIT2 11 coolshell.cn:80 183.60.215.36:36970 TIME_WAIT 13 coolshell.cn:80 124.152.181.209:26825 FIN_WAIT1 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一樣的去匹配第一行,就像這樣:
1
2
3
4
5
|
$
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 :
1
2
3
4
5
6
7
8
9
|
$
awk
'$6 ~ /FIN|TIME/ || NR==1 {print NR,$4,$5,$6}'
OFS= "\t"
netstat .txt 1 Local-Address Foreign-Address State 5 coolshell.cn:80 124.205.5.146:18245 TIME_WAIT 6 coolshell.cn:80 61.140.101.185:37538 FIN_WAIT2 9 coolshell.cn:80 116.234.127.77:11502 FIN_WAIT2 11 coolshell.cn:80 183.60.215.36:36970 TIME_WAIT 13 coolshell.cn:80 124.152.181.209:26825 FIN_WAIT1 15 coolshell.cn:80 183.60.212.163:51082 TIME_WAIT 18 coolshell.cn:80 117.136.20.85:50025 FIN_WAIT2 |
再來看看模式取反的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
$
awk
'$6 !~ /WAIT/ || NR==1 {print NR,$4,$5,$6}'
OFS= "\t"
netstat .txt 1 Local-Address Foreign-Address State 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 |
或是:
1
|
awk
'!/WAIT/'
netstat .txt |
折分文件
awk拆分文件很簡單,使用重定向就好了。下面這個例子,是按第6例分隔文件,相當的簡單(其中的NR!=1表示不處理表頭)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 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 123.169.124.111:49829 ESTABLISHED tcp 0 4166 coolshell.cn:80 61.148.242.38:30901 ESTABLISHED tcp 0 0 coolshell.cn:80 110.194.134.189:4796 ESTABLISHED tcp 0 0 coolshell.cn:80 123.169.124.111:49840 ESTABLISHED $
cat
FIN_WAIT1 tcp 0 1 coolshell.cn:80 124.152.181.209:26825 FIN_WAIT1 $
cat
FIN_WAIT2 tcp 0 0 coolshell.cn:80 61.140.101.185:37538 FIN_WAIT2 tcp 0 0 coolshell.cn:80 116.234.127.77:11502 FIN_WAIT2 tcp 0 0 coolshell.cn:80 117.136.20.85:50025 FIN_WAIT2 $
cat
LAST_ACK tcp 0 1 coolshell.cn:80 208.115.113.92:50601 LAST_ACK $
cat
LISTEN 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 $
cat
TIME_WAIT tcp 0 0 coolshell.cn:80 124.205.5.146:18245 TIME_WAIT tcp 0 0 coolshell.cn:80 183.60.215.36:36970 TIME_WAIT tcp 0 0 coolshell.cn:80 183.60.212.163:51082 TIME_WAIT |
你也可以把指定的列輸出到文件:
1
|
awk
'NR!=1{print $4,$5 > $6}'
netstat .txt |
再複雜一點:(注意其中的if-else-if語句,可見awk其實是個腳本解釋器)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
$
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 tcp 0 0 coolshell.cn:80 124.205.5.146:18245 TIME_WAIT 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 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 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 0 coolshell.cn:80 123.169.124.111:49840 ESTABLISHED $
cat
2.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 $
cat
3.txt tcp 0 0 coolshell.cn:80 61.140.101.185:37538 FIN_WAIT2 tcp 0 0 coolshell.cn:80 116.234.127.77:11502 FIN_WAIT2 tcp 0 1 coolshell.cn:80 124.152.181.209:26825 FIN_WAIT1 tcp 0 1 coolshell.cn:80 208.115.113.92:50601 LAST_ACK tcp 0 0 coolshell.cn:80 117.136.20.85:50025 FIN_WAIT2 |
統計
下面的命令計算所有的C文件,CPP文件和H文件的文件大小總和。
1
2
|
$
ls
-l *.cpp *.c *.h | awk
'{sum+=$5} END {print sum}' 2511401 |
我們再來看一個統計各個connection狀態的用法:(我們可以看到一些編程的影子了,大家都是程序員我就不解釋了。注意其中的數組的用法)
1
2
3
4
5
6
7
|
$
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那一列)
1
2
3
4
5
6
|
$
ps
aux | awk
'NR!=1{a[$1]+=$6;} END { for(i in a) print i ", " a[i]"KB";}' dbus, 540KB mysql, 99928KB www, 3264924KB root, 63644KB hchen, 6020KB |
脫掉內衣
awk腳本
在上面我們可以看到一個END關鍵字。END的意思是“處理完所有的行的標識”,即然說到了END就有必要介紹一下BEGIN,這兩個關鍵字意味着執行前和執行後的意思,語法如下:
- BEGIN{ 這裏面放的是執行前的語句 }
- END {這裏面放的是處理完所有的行後要執行的語句 }
- {這裏面放的是處理每一行時要執行的語句}
爲了說清楚這個事,我們來看看下面的示例:
假設有這麼一個文件(學生成績表):
1
2
3
4
5
6
|
$
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腳本如下(我沒有寫有命令行上是因爲命令行上不易讀,另外也在介紹另一種用法):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
$
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
"---------------------------------------------\n" 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)
1
2
3
4
5
6
7
8
9
10
11
|
$
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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
$ 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 |
幾個花活
最後,我們再來看幾個小例子:
1
2
3
4
5
6
7
8
|
#從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
(全文完)