yyying67
新手上路
积分 19
发帖 11
注册 2008-7-10
状态 离线
|
『楼 主』:
==awk教程==
awk教程
转自:http://linux.tnc.edu.tw/techdoc/awk_intro/index.html
________________________________________
目錄內容
1. 開始 awk !
1.1. 前言
1.2. 使用編輯器
2. awk 簡介
2.1. awk 是什麼?
2.2. awk 的特色
2.3. Hello awk !
2.4. awk 的執行方式
2.5. awk 的命令列選項
2.6. 第一支有用的 awk 程式
2.7. 習題
3. awk 的運作方式
3.1. awk 的程式結構
3.2. awk 的運作方式
3.3. BEGIN 區塊
3.4. 主程式區塊
3.5. END 區塊
3.6. 程式例: pr3-1.awk
3.7. 資料檔的結構和內建變數
3.8. 資料檔範例 data3-1.txt
3.9. 練習程式
3.10. 習題
4. awk 的資料型態
4.1. awk 的常數
4.2. awk 的算術運算
4.3. awk 的變數
4.4. awk 的變數設值
4.5. awk 的亂數
4.6. awk 的真假值
4.7. awk 的關係式
4.8. awk 的邏輯關係式
4.9. 練習程式
4.10. 習題
5. awk 的資料顯示和輸入輸出
5.1. awk 的資料顯示
5.2. print 用例
5.3. printf 簡介
5.4. 輸入和輸出
5.5. awk 的輸入
5.6. 習題
6. 樣式比對
6.1. awk 的樣式
6.2. 正規表示式(regexp)
6.3. 常用的正規表示式
6.4. 利用樣式和轉向輸入,找出網卡IP
6.5. 利用樣式分析Apache2記錄檔
6.6. 習題
7. 流程控制
7.1. 關於 awk 的流程控制。
7.2. 條件判斷
7.3. if-else 語法
7.4. 條件運算式 ?:
7.5. while 迴圈
7.6. do-while 迴圈
7.7. 幫你的程式碼加上列號
7.8. for 迴圈
7.9. switch-case 選擇(gawk 預設未必支援)
7.10. 特別控制指令
7.11. 利用 awk 大量建帳號
7.12. 習題
8. 陣列
8.1. awk 的陣列
8.2. 數值註標的陣列
8.3. 關聯式陣列
8.4. 判斷是否為陣列的一員
8.5. 刪除陣列的元素
8.6. 陣列的排序
8.7. 對陣列註標做排序
8.8. 習題
9. awk 的函式
9.1. 內建函式
9.2. 字串處理相關函式
9.3. 輸出輸入相關函式
9.4. 系統相關函式
9.5. 數值相關函式
9.6. 使用者自訂函式
9.7. '幫程式碼加上列號' 版本二
9.8. 習題
10. 綜合運用
10.1. 刪除 C 語言程式碼中的註解符號
10.2. 取得網卡 IP 的第二種版本
10.3. 取得網卡 IP 的第三種版本
10.4. ping 主機時,取得其回應時間
10.5. 主機監控
10.6. 主機監控,以網頁呈現結果
10.7. 習題
11. 參考文件
List of Tables
5-1. printf 格式字元
5-2. printf 的修飾字元
用圖列表
1-1. B2D 的註冊標誌
1-2. Kwrite
3-1. awk 的運作方式圖
5-1. 大寫檔名改成小寫
Chapter 1. 開始 awk !
1.1. 前言
網路管理的工作,許多時候,其實是在處理資料,產生報表,然後研究這些報表,提出解決方案。如果能有一些好用的工具,幫助我們把這些事情自動化,相信,不但可以減輕工作負擔,而且,最重要的是,工作會變得比較有趣。
傳統上,網路管理者,通常會選用一些小工具,不斷地進行組合使用,輕鬆輕鬆便可解決問題。awk 正是組合使用的藝術中,一個重要、不可或缺的小工具。awk 的威力,常在幾列就可以完成工作的程式碼中,展露無遺。:-)
本研習是 Bash 研習的第二部,著重在介紹處理資料和產出報表的利器 awk。希望經由這份文份,能讓大家認識這個好用的工具。至於於 Bash 的基本操作和語法,本講義不再贅述,研習前請先預讀 Shell 設計入門。
本講義完全在 B2D Jacana 平台上以 Emacs 編輯器產出,awk 範例程式也是在該平台上實做及測試。[1]
Figure 1-1. B2D 的註冊標誌
Notes
[1]
如果你並沒有多餘的機器可以安裝 B2D Jacana,可以考慮使用 B2D Jacana 的虛擬機器版本,比如在 Windows 平台上,下載 B2D-Jacana-2008-0223.rar 解壓後,使用 VMware player 等工具來開啟 B2D VM,如此,就可以在不必重新安裝作業系統的情況下使用 B2D。當然,使用虛擬機器,你的主機不能太慢,記憶體也要稍微多一點。
1.2. 使用編輯器
撰寫 awk 程式,需要備好文字編輯器。那麼,要用那一種呢? 其實,任何文字編輯器都可以,只要你喜歡。
若不知道自己喜歡那一種,就重新開始體驗起。選項很多: Emacs, Vim, Nano, Kwrite, Kate, Gedit ...,等等。
以下是在 B2D Jacana 安裝 kate/kwrite 的方法:
apt-get update
apt-get install kate
然後,執行 kate 或 kwrite 都可以。
kate/kwrite 支援 awk 的區塊折疊功能,如下圖所示:
Figure 1-2. Kwrite
Chapter 2. awk 簡介
本章將對 awk 做個簡單介紹。
2.1. awk 是什麼?
awk 這個名稱的由來是,取名自三位設計者的姓名縮寫: Alfred V. Aho, Peter J. Weinberger 以及 Brian W. Kernighan. 它是一種善於處理資料並產生格式化報表的程式語言。
早年 awk 版本眾多,相容性不一。這裡要介紹的是 GNU 的 awk 版本,稱為 gawk。gawk 不但自由免費,而且法語支援度高,是目前甚為流行的版本之一。也有 Windows 平台的版本喔!
本講義往後,若提到 awk,除非有特別聲明,否則,指的就是 gawk。
2.2. awk 的特色
awk 的特色(這裡指 gawk),除了是一種自由軟體之外,通常撰寫的 awk 程式碼的列數,往往非常短,但卻非常有威力。
如果你發現寫好的 awk 程式碼,有好幾百列,那麼,這程式所要處理的工作,最好是應該改用其它程式語言來完成。
awk 是懶惰程式人的最佳法寶。;-)
2.3. Hello awk !
先來寫一支 awk 的小程式
#! /usr/bin/awk -f
# 檔名: hello.awk
# 用途: 這是一支簡單的 awk 示範程式
#
# 第一列以 #! 開頭,表示告訴 Shell 要用 /usr/bin/awk 程式來執行這個 script 檔
# -f 表示要指定 awk script 檔案來執行,這裡是以本 script 內容為執行標的
#
# 其它以 # 開頭,則為註解,awk 不予處理。
#
BEGIN {
print "Hello awk !\n";
}
執行前,應設妥執行權(chmod +x 或 chmod 755)。執行結果如下:
ols3@mybk:~$ chmod +x hello.awk
ols3@mybk:~$ ./hello.awk
Hello awk !
2.4. awk 的執行方式
除了用前一節的方式執行 awk script 之外,還可以利用 awk 的命令列選項 -f 來執行:
ols3@mybk:~$ awk -f hello.awk
Hello awk !
或者把執行檔的程式內容,放在一對單引號之間:
ols3@mybk:~$ awk 'BEGIN { print "Hello awk !" }'
Hello awk !
把以下內容存成 hello.awk,然後以 ./hello.awk 來執行也行:
awk '
BEGIN {
print "Hello awk !"
}
'
2.5. awk 的命令列選項
以下這個命令列選項可以得知 awk 的版本代碼:
ols3@mybk:~$ awk --version
GNU Awk 3.1.4
Copyright (C) 1989, 1991-2003 Free Software Foundation.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
由上可知,所用的 awk 版本是 gawk 3.1.4
awk 還有其它命令列選項,欲知詳情,請下:
ols3@mybk:~$ awk --help
Usage: awk [POSIX or GNU style options] -f progfile [--] file ...
Usage: awk [POSIX or GNU style options] [--] 'program' file ...
POSIX options: GNU long options:
-f progfile --file=progfile
-F fs --field-separator=fs
-v var=val --assign=var=val
-m[fr] val
-W compat --compat
-W copyleft --copyleft
-W copyright --copyright
-W dump-variables[=file] --dump-variables[=file]
-W gen-po --gen-po
-W help --help
-W lint[=fatal] --lint[=fatal]
-W lint-old --lint-old
-W non-decimal-data --non-decimal-data
-W profile[=file] --profile[=file]
-W posix --posix
-W re-interval --re-interval
-W source=program-text --source=program-text
-W traditional --traditional
-W usage --usage
-W version --version
To report bugs, see node `Bugs' in `gawk.info', which is
section `Reporting Problems and Bugs' in the printed version.
gawk is a pattern scanning and processing language.
By default it reads standard input and writes standard output.
Examples:
gawk '{ sum += $1 }; END { print sum }' file
gawk -F: '{ print $1 }' /etc/passwd
用得比較多的選項: -f 用來指定 awk 的執行檔名; -v 可在命令列指定變數名稱和其值; -F 指定欄位分隔符號。
2.6. 第一支有用的 awk 程式
列出 /etc/passwd 檔中所有的帳號
#! /usr/bin/awk -f
#
# 檔名: pr2-2.awk
# 用途: 列出 /etc/passwd 檔中所有的帳號
#
BEGIN {
FS=":"
}
{ print $1 }
執行結果的片斷:
ols3@mybk:~$ chmod +x pr2-2.awk
ols3@mybk:~$ ./pr2-2.awk /etc/passwd
root
daemon
bin
sys
sync
games
man
lp
mail
news
uucp
proxy
上述程式也可以直接、很簡潔地在命令列中執行:
awk -F : '{ print $1 }' /etc/passwd
2.7. 習題
1. 請寫一支 awk script,印出您的大名及電子郵件帳號。
2. awk 的註解符號為何?
3. 直接在命令列執行 awk script 檔案,應使用那一個選項?
Chapter 3. awk 的運作方式
本章將對 awk 程式的寫法和運作方式,做個簡介。
3.1. awk 的程式結構
awk 的程式結構,可分成三個區塊:
• BEGIN
• 主程式區塊 ( 由 0 個或 0 個以上的 '樣式 { 動作 }' 等程式句所組成 )
• END
#! /usr/bin/awk -f
# BEGIN 區塊
BEGIN {
}
# 程式主體區塊
樣式1 {
動作
}
樣式2 {
動作
}
....
樣式N {
動作
}
# END 區塊
END {
}
大部份,awk 程式執行時,後面會接上要處理的資料檔名,比如:
#! /usr/bin/awk -f
#
# 檔名: pr2-2.awk
# 用途: 列出 /etc/passwd 檔中所有的帳號
#
BEGIN {
FS=":"
}
{ print $1 }
-----------------------------------------------------
執行方式:
awk 程式 資料檔名
==============================
./pr2-2.awk /etc/passwd
3.2. awk 的運作方式
Figure 3-1. awk 的運作方式圖
OLS3 (ols3@lxer.tw) 製圖。
3.3. BEGIN 區塊
BEGIN 區塊,主要用來做一些初始化設定等準備動作,比如: 設定不同的欄位分隔符號:
BEGIN {
FS=":"
}
BEGIN 以 { } 括住程式碼,這一區塊的程式碼,在資料檔讀入之前,只執行一次。BEGIN 區塊中的程式碼,不會對資料檔的內容進行處理。比如以下程式例,只顯示 "Hello awk !",對資料檔不做任何處理,因此執行時,程式檔名後面,可以不必接上資料檔名:
#! /usr/bin/awk -f
#
# hello.awk
#
BEGIN {
print "Hello awk !\n"
}
----------------------------------------
執行方式:
./hello.awk
./hello.awk datafile.txt
以上二種執行方式,結果都一樣,只是印出 "Hello awk !" 字串而已。
3.4. 主程式區塊
主程式區塊的程式句,會套用到資料檔的每一列(我們稱它為一筆記錄),比如:
# 顯示每一列的第 1 個欄位(用 $1 來表示)
{ print $1 }
主程式區塊的每一個程式句結構為: 樣式 {動作} [1]
它的意思是說: 若資料檔的一列資料(記錄),有符合指定的 '樣式',就執行 { } 中的 '動作'。這裡所謂的 '動作' 包含一個或多個變數設定、命令或函式,必須置於 {} 中,且各須以換列字元或 ; 號來分隔。
'動作' 的組成方式:
{
變數 = 設值
命令1 ; 命令2; 命令3
函式1
函式2
}
比如以下 {} 這四列,形成整個 '動作',這四列將套用到每一資料列上。
{
sum=10
if ($1 > 10) print "欄位1 大於 10"; sum += $1; div = sum / NF
asort(arr)
total = sum * div
}
若主程式區塊有 N 個 '程式句' ('程式句' 由 '樣式{動作}' 組成),每一列資料就要套用 N 次。程式句套用到資料列時,要不要執行指定的動作,則端視該列資料是否符合其指定的樣式。比如以下程式例:
#! /usr/bin/awk -f
#
# pr3-1.awk
#
# 若資料列含有 abc 字串,就把該列記錄(以 $0 表示)印出來
/abc/ { print $0 }
若程式句沒有指定樣式,只有 {動作},則無條件地,對每一列資料(記錄),執行 { } 中的動作。
若程式句沒有 {動作},只有 '樣式',對符合樣式的資料列,則以預設的動作來處理,即把該列全部印出來。
Notes
[1]
往後提到樣式時,也會以 pattern 表示;動作則以 action 表示。
3.5. END 區塊
END 區塊,主要用來做一些後續的處理動作,比如: 刪除暫存檔、顯示統計總和結果等:
END {
print "共有 " total " 列.\n"
}
END 以 { } 括住程式碼,這一區塊的程式碼,在資料檔讀取完後,只執行一次。END 區塊中的程式碼,不會對資料檔的內容進行處理。
3.6. 程式例: pr3-1.awk
#! /usr/bin/awk -f
#
# 用途: 統計 /etc/passwd 中,不能登入的帳號
#
BEGIN {
FS = ":"
total = 0
}
# 若第 7 個欄位(即帳號使用的 shell 程式),含有關鍵字 false,則代表無法登入主機。
$7 ~ /false/ {
total++
print $1
}
END {
print "不能登入的帳號共有 " total " 個\n";
}
執行結果:
ols3@mybk:~$ ./pr3-1.awk /etc/passwd
mysql
postfix
partimag
telnetd
distccd
bind
spop3d
ftp
sslwrap
captive
gdm
ntop
snort
messagebus
jabber
saned
freerad
siproxd
ser
dovecot
不能登入的帳號共有 20 個
也可以把樣式的判斷,拉到 { } 中來做,如以下程式例:
#! /usr/bin/awk -f
BEGIN {
FS = ":"
total = 0
}
# 把樣式的判斷,拿到 {動作} 區中來做:
{
if ($7 ~ /false/) {
total++
print $1
}
}
END {
print "不能登入的帳號共有 " total " 個\n";
}
3.7. 資料檔的結構和內建變數
awk 程式的處理對象是資料檔。資料檔中的每一列,我們稱它為一筆記錄(record)。每筆記錄可分成若干欄位,各欄位間彼此以相同的分隔符號隔開。
並不是任何資料檔,awk 都可以適用。awk 比較適合拿來處理具有固定結構的資料檔,比如: /etc/passwd 的每一列共有 7 個欄位,每個欄位用 ":" 隔開。
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/bin/sh
man:x:6:12:man:/var/cache/man:/bin/sh
lp:x:7:7:lp:/var/spool/lpd:/bin/sh
mail:x:8:8:mail:/var/mail:/bin/sh
news:x:9:9:news:/var/spool/news:/bin/sh
再比如 Apache2 的記錄檔 /var/log/apache2/access.log,每筆記錄中,各欄位彼此用空白字元隔開。
127.0.0.1 - - [04/Dec/2006:02:36:18 +0800] "POST /phpmyadmin/index.php HTTP/1.1" 302 - "http://localhost/phpmyadmin/" "Mozilla/5.0 (X11; U; Linux i686; zh-TW; rv:1.8.0.8) Gecko/20061025 Firefox/1.5.0.8"
127.0.0.1 - - [04/Dec/2006:02:36:19 +0800] "GET /phpmyadmin/index.php?lang=zhtw-big5&server=1 HTTP/1.1" 200 12346 "http://localhost/phpmyadmin/" "Mozilla/5.0 (X11; U; Linux i686; zh-TW; rv:1.8.0.8) Gecko/20061025 Firefox/1.5.0.8"
127.0.0.1 - - [04/Dec/2006:02:36:19 +0800] "GET /phpmyadmin/css/phpmyadmin.css.php?lang=zh-TW&js_frame=right&js_isDOM=1 HTTP/1.1" 200 11670 "http://localhost/phpmyadmin/index.php?lang=zhtw-big5&server=1" "Mozilla/5.0 (X11; U; Linux i686; zh-TW; rv:1.8.0.8) Gecko/20061025 Firefox/1.5.0.8"
像這種已有固定結構的資料檔,用 awk 程式來處理,特別有威力。通常只要短短的數列程式碼,就可以處理完成。
awk 預設的欄位分隔符號為空白字元,若資料檔不是以空白字元分隔欄位,也可以設定 FS 這個內建變數,改用其它分隔符號,以利取出資料列的各個欄位。
一旦資料檔讀入之後,awk 程式會設定許多內建變數。以下是常用的幾個內建變數:
• NF : 該筆記錄中,共有多少欄位。
• $0 代表該筆記錄本身(即整列資料); $1 為欄位1; $2 為欄位2; $3 為欄位3 ....,其它依此類推。
• NR : 目前已讀到第幾筆記錄。
• FS : 欄位分隔符號。預設為空白字元。即 FS = " "。當 awk 讀入一列資料時,會把該列前後的空白字元刪除!
• OFS : 輸出欄位分隔符號。預設為空白字元。即 OFS = " "。
• RS : 記錄分隔符號。預設為換列字元(\n)。
• ORS : 輸出記錄分隔符號。預設為換列字元(\n)。
• OFMT : 輸出格式轉換控制字元。預設為 "%.6g"。
• FILENAME : 資料檔名稱。
• ARGV : 命令列參數陣列。ARGV[0] 即 awk 程式本身的名稱, ARGV[1] 表第一個參數, ....; 用例: ./test.awk A B C , 此時 ARGV[0] 為 awk, ARGV[1] 為 A, ARGV[2] 為 B, ARGV[3] 為 C
#! /usr/bin/awk -f
BEGIN { print ARGC, ARGV[0], ARGV[1], ARGV[2], ARGV[3] }
===
./test.awk A B C
結果顯示
4 awk A B C
• ARGC : 命令列參數個數, 包含 awk 程式本身。
3.8. 資料檔範例 data3-1.txt
以下是練習用的資料檔 data3-1.txt,往後我們會用到它。
所在鄉鎮 學校名稱 學校網址 校長姓名 學校電話 VOIP前三碼 學校地址
新營市 南新國中 http://www.ns1jh.tnc.edu.tw ABC 06-6563130 101 新營市民治路65號
佳里鎮 佳里國中 http://www.jl1jh.tnc.edu.tw NOP 06-7222244 116 佳里鎮安南路523號
新營市 新營國小 http://www.sy3es.tnc.edu.tw DEF 06-6322136 142 新營市中正路4號
新營市 新東國中 http://www.sdjh.tnc.edu.tw GHI 06-6322954 102 新營市民治東路30號
七股鄉 竹橋國中 http://www.jcjh.tnc.edu.tw BXY 06-7891733 121 七股鄉義合村74之2號
新營市 新民國小 http://www.smes.tnc.edu.tw JKL 06-6562152 143 新營市公園路一段136號
佳里鎮 佳里國小 http://www.jl1es.tnc.edu.tw QRS 06-7222031 216 佳里鎮安西里公園路445號
新營市 新進國小 http://www.sj1es.tnc.edu.tw MNO 06-6322378 144 新營市中正路41號
佳里鎮 仁愛國小 http://www.raes.tnc.edu.tw WXY 06-7222227 217 佳里鎮仁愛路307號
新營市 新橋國小 http://www.sc1es.tnc.edu.tw BCD 06-6581343 149 新營市鐵線里1號
七股鄉 後港國中 http://www.hgjh.tnc.edu.tw STU 06-7941357 120 七股鄉大潭村93號
新營市 太子國中 http://www.ttjh.tnc.edu.tw HIJ 06-6524762 103 新營市太北里140之21號
新營市 新泰國小 http://www.htes.tnc.edu.tw KLM 06-6330496 323 新營市東學路77號
佳里鎮 佳興國中 http://www.jsjh.tnc.edu.tw TUV 06-7260291 117 佳里鎮民安里新宅34之1號
七股鄉 七股國小 http://www.cg2es.tnc.edu.tw AYZ 06-7872076 235 七股鄉大埕村395號
佳里鎮 佳興國小 http://www.js2es.tnc.edu.tw ZAB 06-7260311 218 佳里鎮佳化里214號
新營市 新生國小 http://www.ss7es.tnc.edu.tw EFG 06-6552524 150 新營市姑爺里52號
佳里鎮 通興國小 http://www.ts2es.tnc.edu.tw LMN 06-7873531 222 佳里鎮通興里5之1號
佳里鎮 信義國小 http://www.sy2es.tnc.edu.tw OPQ 06-7211918 223 佳里鎮忠仁里信義一街336號
下載: data3-1.txt
3.9. 練習程式
pr3-3.awk
#! /usr/bin/awk -f
#
# 將 data3-1.txt 中,在佳里鎮的學校網址印出來
#
/^佳里鎮/ {
print $3
}
pr3-4.awk
#! /usr/bin/awk -f
#
# 將 data3-1.txt 中,VOIP前三碼數字最大的學校找出來
#
{
# 資料檔的第一列不要(因為該列為標題)
if (NR > 1) {
if ($6 > max_no) {
max_no = $6
school = $2
}
}
}
END {
print "VOIP前三碼數字最大的學校是 " school, maxno
}
pr3-5.awk
#! /usr/bin/awk -f
#
# 將 data3-1.txt 中,位於新營市的國中找出來
#
/^新營市/ && /國中/ {
print $2
}
pr3-6.awk
#! /usr/bin/awk -f
#
# 刪去 data3-1.txt 中的空白列,把資料另存成其它檔名
#
! /^$/ {
print $0
}
[1]
執行方法:
./pr3-6.awk data3-1.txt > data3-1-nospl.txt
data3-2.txt 的內容如下:
ABCDEFG XYZ IJK 中文 CDEF
DEFG XYZ IJK 中文 CDEF
G XYZ I EFG CDEF
JK 中文 BCDEFG XYZ
中文 BCDEFG XYZ
KIL NMO QPR STUV JJJ KKK
下載: data3-2.txt
pr3-7.awk
#! /usr/bin/awk -f
#
# 把資料檔每一列前的空白字元刪除,各欄位間只用一空白字元隔開
#
{
$1 = $1
print $0
}
====
執行方法:
./pr3-7.awk data3-2.txt
結果:
ABCDEFG XYZ IJK 中文 CDEF
DEFG XYZ IJK 中文 CDEF
G XYZ I EFG CDEF
JK 中文 BCDEFG XYZ
中文 BCDEFG XYZ
KIL NMO QPR STUV JJJ KKK
Notes
[1]
^ 在樣式比對中代表一列的開頭; $ 則代表一列的結尾; ! 則代表 '非'。^ 和 $ 直接相連,中間沒有任何東西,表示它是一個空白列,因此,我們利用 /^$/ 這個樣式,就可以比對出空白列來,而 ! /^$/ 則是指非空白列的意思。
3.10. 習題
1. 請寫一支 awk 程式,計算 data3-3.txt 共有幾列?
2. 請寫一支 awk 程式,找出大內鄉的學校有哪些?
3. 請寫一支 awk 程式,列出分校。
4. 請寫一支 awk 程式,把所有 "鄉名-校名" 這一欄,存成另一個資料檔。
5. 請寫一支 awk 程式,計算 data3-3.txt 檔案列表中,所有一般檔案(非目錄)的檔案大小總和。
6. 請寫一支 awk 程式,找出主機中 apache2 行程所有的 pid 編號。
---------------------------------------------------------------------
資料檔 data3-3.txt 內容如下:
drwxr-xr-x 27 root root 4096 2008-03-24 08:31 .
drwxr-xr-x 23 root root 4096 2007-08-03 14:24 ..
drwxr-xr-x 2 root root 4096 2006-11-06 08:53 apache
drwxr-xr-x 4 root root 4096 2008-01-16 08:46 apache2
-rw-r--r-- 1 root root 0 2004-08-09 08:38 aptitude
-rw-r----- 1 root adm 286204 2008-03-24 08:50 auth.log
-rw-r----- 1 root adm 230677 2008-03-18 08:20 auth.log.0
-rw-r----- 1 root adm 14920 2008-03-11 08:25 auth.log.1.gz
-rw-r----- 1 root adm 11526 2008-03-04 11:50 auth.log.2.gz
-rw-r----- 1 root adm 12110 2008-02-26 08:30 auth.log.3.gz
-rw-rw-r-- 1 root utmp 0 2008-03-03 08:03 btmp
-rw-rw-r-- 1 root utmp 0 2008-02-05 14:49 btmp.1
drwxr-xr-x 2 clamav clamav 4096 2008-03-24 08:31 clamav
drwxr-xr-x 2 root root 4096 2008-03-24 08:31 cups
-rw-r----- 1 root adm 6081 2008-03-24 08:26 daemon.log
-rw-r----- 1 root adm 14292 2008-03-18 08:10 daemon.log.0
-rw-r----- 1 root adm 955 2008-03-11 08:19 daemon.log.1.gz
-rw-r----- 1 root adm 767 2008-03-04 11:45 daemon.log.2.gz
-rw-r----- 1 root adm 850 2008-02-26 08:21 daemon.log.3.gz
-rw-r----- 1 root adm 5175 2008-03-24 08:26 debug
-rw-r----- 1 root adm 11579 2008-03-18 08:10 debug.0
-rw-r----- 1 root adm 889 2008-03-11 08:20 debug.1.gz
-rw-r----- 1 root adm 639 2008-03-04 11:45 debug.2.gz
-rw-r----- 1 root adm 662 2008-02-26 08:21 debug.3.gz
drwxr-xr-x 3 root root 4096 2008-03-12 12:29 七股鄉-昭明國中
drwxr-xr-x 3 root root 4096 2008-03-12 12:29 七股鄉-篤加國小
drwxr-xr-x 3 root root 4096 2008-03-12 12:29 七股鄉-頂山分校
drwxr-xr-x 3 root root 4096 2008-03-12 12:29 仁德鄉-依仁國小
drwxr-xr-x 3 root root 4096 2008-03-12 12:29 六甲鄉-湖東分校
drwxr-xr-x 3 root root 4096 2008-03-12 12:29 北門鄉-玉湖分校
drwxr-xr-x 3 root root 4096 2008-03-12 12:29 南化鄉-玉山國小
drwxr-xr-x 3 root root 4096 2008-03-12 12:29 南化鄉-瑞峰國小
drwxr-xr-x 3 root root 4096 2008-03-12 12:29 善化鎮-善糖國小
drwxr-xr-x 3 root root 4096 2008-03-12 12:29 善化鎮-小新國小
drwxr-xr-x 3 root root 4096 2008-03-12 12:29 大內鄉-嗚頭分校
drwxr-xr-x 3 root root 4096 2008-03-12 12:29 大內鄉-大內國中
drwxr-xr-x 3 root root 4096 2008-03-12 12:29 大內鄉-頭社分校
drwxr-xr-x 3 root root 4096 2008-03-12 12:29 左鎮鄉-岡林分校
drwxr-xr-x 3 root root 4096 2008-03-12 12:29 左鎮鄉-澄山分校
drwxr-xr-x 3 root root 4096 2008-03-12 12:29 新化鎮-新化國中
drwxr-xr-x 3 root root 4096 2008-03-12 12:29 新化鎮-正新國小
drwxr-xr-x 3 root root 4096 2008-03-12 12:29 新化鎮-礁坑國小
drwxr-xr-x 3 root root 4096 2008-03-12 12:29 歸仁鄉-保西國小
drwxr-xr-x 3 root root 4096 2008-03-12 12:29 歸仁鄉-歸南國小
drwxr-xr-x 3 root root 4096 2008-03-12 12:29 歸仁鄉-沙崙國中
drwxr-xr-x 3 root root 4096 2008-03-12 12:29 歸仁鄉-紅瓦厝國小
drwxr-xr-x 3 root root 4096 2008-03-12 12:29 白河鎮-內角國小
drwxr-xr-x 3 root root 4096 2008-03-12 12:29 白河鎮-六溪分校
drwxr-xr-x 3 root root 4096 2008-03-12 12:29 白河鎮-關嶺分校
drwxr-xr-x 3 root root 4096 2008-03-12 12:29 鹽水鎮-大豐分校
drwxr-xr-x 3 root root 4096 2008-03-12 12:29 鹽水鎮-汫水分校
drwxr-xr-x 3 root root 4096 2008-03-12 12:29 龍崎鄉-龍船分校
下載: data3-3.txt
Chapter 4. awk 的資料型態
本章介紹 awk 使用的常數和變數。
4.1. awk 的常數
awk 的常數,可分成三種:
• 數字: 比如 十進位整數 288、小數 10.5、科學記號數 2.88e+2 ; 2880e-1、16進位數 0x18 (等於 1*16+8 = 24)、八進位數 072 (等於 7*8+2 = 58)
• 字串: 比如 ""(空字串)、"Hello world!"、" "(空白字元)、"\t" tab 字元、"\n" 換列字元,等等。 [1]
• 樣式常數: 比如 /大內鄉/ 、 /abc/ 、 /^Yes, I do.$/
另外,awk 還有一些跳脫字元:
\\
代表倒斜線, `\'.
\a
ASCII 碼 7 (鈴聲 BEL).
\b
Backspace, ASCII 碼 8 (BS).
\f
Formfeed, ASCII 碼 12 (FF).
\n
換列字元, ASCII 碼 10 (LF).
\r
回列字元, ASCII 碼 13 (CR).
\t
水平 TAB, ASCII 碼 9 (HT).
\v
垂直 tab, ASCII 碼 11 (VT).
\nnn
8 進位數.
\xhh
16 進位數.
\/
斜線 /
\"
雙引號 "
[2]
Notes
[1]
字串的結合用例: "共有", "5 列" 會變成 "共有 5 列", "," 逗號會在二個字串中放上一個空白字元; 但若是 "共有" "5列",則會變成 "共有5列",也就是說若沒有逗號,會把字串直接連接在一起,中間沒有空白字元。
[2]
在 awk 中比較特別的是,如果在字串或樣式中要使用 \ 來跳脫其它字元(未列在上面、awk 有正式定義者),要用 \\。比如 \\& ; 若只用 \& ,則 awk 會自動把 & 前面的 \ 移除,如此一來,& 仍然為其原本字元含義,並沒有真的跳脫(ESCAPE)掉其意義。
4.2. awk 的算術運算
這一節介紹 awk 的算術運算
加: awk 'BEGIN { print 15 + 6 }'
減: awk 'BEGIN { print 15 - 6 }'
乘: awk 'BEGIN { print 15 * 6 }'
除: awk 'BEGIN { print 15 / 6 }' (結果: 2.5)
同餘: awk 'BEGIN { print 15 % 6 }' (結果: 3, 即餘 3 之意)
乘方: awk 'BEGIN { print 2 ^ 3 }' (2 的 3 次方, 結果: 8)
用 awk 'BEGIN { print 2 ** 3 }' 也可以。
負數: awk 'BEGIN { print -2 }' (結果: -2)
轉成數值: +x 會把 x 轉成數值,例如: awk 'BEGIN { x="222"; print 1 +x }' (把字串 "222" 轉成數值 222, 結果: 223)
另外,像大家熟知的 '先乘除後加減; 有括號先算括號裡的數',在 awk 中一樣適用:
awk 'BEGIN { print (10-2)*9/6+8 }' (結果: 20)
4.3. awk 的變數
awk 的變數可用來設定 awk 的合法數值,以便往後的程式可以參考到變數。其命名規則很簡單,awk 的變數,可由英數字元及底線組成,但不能以數字開頭。
比如: total, sum, FFS, just0123, _may 都是正確的變數;而 3xy 則是不正確的。
awk 的變數是屬於無型態變數,使用前,不必事先宣告。變數是數值型態或字串型態,由變數設值時來決定。
比如: sum=10; total=100; pipe="ls -la"。其二者是數值型態,後者是字串型態。
另外,要特別注意的是: 變數名稱和大小寫有關,換言之,ABC 和 abc 是不同的變數。
4.4. awk 的變數設值
這裡舉一些 awk 的變數設值用例:
a = 1
a = b = c = 9 把 a、b、c 都設值為 9
sum = sum + 10 可以寫成 sum += 10
sum = sum - 5 可以寫成 sum -= 5
sum = sum * 5 可以寫成 sum *= 5
sum = sum / 5 可以寫成 sum /= 5
sum = sum % 5 可以寫成 sum %= 5
a++
++a 把 a 的值加一
a--
--a 把 a 的值減一
FS = ":" 把 FS 欄位分隔符號設為 :
str = "我是字串"
str2 = str " ,我也是!" 此把 str 和 後頭的字串相連接,等於是把 str2 設成 "我是字串 ,我也是!"
a++ 和 ++a 是不同的! ++ 放在變數之後,表示先做完運算後 a 的值再加上一; ++ 放在變數之前,則表示 a 的值先加上一之後,再進行運算。比如:
awk 'BEGIN { a=5; print "a 的值 = " a, ", 運算式 (1 * a++) 的值 = ", 1 * a++; print ", 之後 a 的值變成 ", a; }'
執行結果:
a 的值 = 5 , 運算式 (1 * a++) 的值 = 5
, 之後 a 的值變成 6
====
awk 'BEGIN { a=5; print "a 的值 = " a, ", 運算式 (1 * ++a) 的值 = ", 1 * ++a; print ", 之後 a 的值變成 ", a; }'
執行結果:
a 的值 = 5 , 運算式 (1 * ++a) 的值 = 6
, 之後 a 的值變成 6
4.5. awk 的亂數
在 awk 中,有提供產生亂數的相關函式 rand 和 srand。 rand 會產生 0~1 之間的小數。srand 稱為亂數種子,這讓亂數的機制看起來好像真是 '亂',不然,每次程式執行時產生的亂數都一樣,這就不好囉。
用例如下:
#! /usr/bin/awk -f
BEGIN {
# 使用現在系統時間當作亂數種子
srand( systime() )
# 產生 1~38 的一個整數。 int() 會截掉小數部份。
print int(rand() * 38) + 1
}
4.6. awk 的真假值
在 awk 中,若不是 0 或不是空字串,便是真(true),其值為 1; 否則為假(false),其值為 0。以下會印出三個 "真值"。
#! /usr/bin/awk -f
BEGIN {
if (1.4142759688)
print "真值"
if ("佳里鎮")
print "真值"
if (x = 66)
print "真值"
}
要特別注意的是: 0 為假值; 但 "0" 卻為真值,因為 "0" 為非空字串。
4.7. awk 的關係式
這一節說明 awk 的比較關係式
若 x 小於 y ,則 x < y 為真。
若 x 小於或等於 y ,則 x <= y 為真。
若 x 大於 y ,則 x > y 為真。
若 x 大於或等於 y ,則 x >= y 為真。
若 x 等於 y ,則 x == y 為真。
若 x 不等於 y ,則 x != y 為真。
若 x 合於樣式 y ,則 x ~ y 為真。 例如: x="I am a foo !"; x ~ /foo/
若 x 不合於樣式 y ,則 x !~ y 為真。 例如: x="I am a spider man"; x !~ /foo/
注意! 經常犯的錯誤是把 if (x == y) 寫成 if ( x = y )
另外,字串也可以比較大小,比如 BEGIN { x="abc"; if ( x < "hij" ) ...},這是依二個字串各對應字母比較其 ASCII 值的大小,來決定整個字串的大小。
4.8. awk 的邏輯關係式
這一節說明 awk 的邏輯關係式
awk 有三種邏輯運算子,而且這三個可任何組合成邏輯關係式。
&& 且(and)
/abc/ && /XYZ/ 若樣式含有字串 abc '且' 含有字串 XYZ,則整個樣式比對結果為真。
|| 或(or)
/abc/ || /XYZ/ 若樣式含有字串 abc '或' 含有字串 XYZ,二個比對中,只要有一個成立,則整個樣式比對結果為真。
! 非(not)
! /^$/ 若樣式 '不' 是空白列,則整個樣式比對結果為真。
另外,awk 也有條件運算式 ?: ,和 C/C++/PHP 用法相同:
str = (x > 5) ? "對的" : "錯的"
若 (x > 5) 為真,則 str 的值被設為 "對的",若不然,則設為 "錯的"。
4.9. 練習程式
pr4-1.awk
#! /usr/bin/awk -f
#
# 計算資料檔字元總數,含空白字元, 但不含換列字元(\n)
#
BEGIN {
FS = ""
}
{
total += NF
}
END {
print FILENAME " 共有 " total, "個字元"
}
data4-1.txt
ABCDEFG HIJK NOPQ
RSTUV WYZXY AB 1234
09871345 124
下載: data4-1.txt
[1]
pr4-2.awk
#! /usr/bin/awk -f
#
# 計算資料檔字元總數,含空白字元, 但不含換列字元(\n)
# 同時把每個字元顯示出來
#
BEGIN {
FS = ""
}
{
total += NF
for (i=1; i<=NF; i++) {
count++
print "第 " count " 個字元 : " $i "\n"
}
}
END {
print FILENAME " 共有 " total, "個字元"
}
[2]
Notes
[1]
請用 hexedit 觀看 data4-1.txt 這個檔案,以印證 pr4-1.awk 計算得到的字元總數的正確性。
[2]
請把 pr4-2.awk 套用在 data3-2.txt 這個檔案,並觀察是否有什麼奇特的地方?
4.10. 習題
1. a=5*4+8, 試問 if (a = 20) print "Just do it!" 中, 字串會不會顯示 ?
2. 寫一支 awk 程式, 找出以下test.dat資料檔中的最大值:
23 66
54 100
9 300
112
48 90
3. 寫一支 awk 程式,把以下資料列的分隔符號由 "," 換成 "|" :
abc,93,101,97,3,26
4. 已知 a = 2987.1234567 ,試求 length(a) = ?
5. 同上,試求 print a 為何?
6. 請將 http://www.tnc.edu.tw/school/index.php?name=%B7s%C0%E7%A5%AB 表格中的資料,轉成 csv 檔格式(即用 , 分隔資料欄位).
7. a = 121; b = "one two one"; c = "go go go" , 請把 a,b,c 組合成一個字串變數.
8. 寫一支 awk 程式, 隨機顯示 1~45 中 6 個號碼, 但這 6 個號碼不可重覆.
9. 請寫一支 awk 程式, 計算 /etc/passwd 的列數.
Chapter 5. awk 的資料顯示和輸入輸出
這一章介紹 awk 的顯示和輸入輸出。
5.1. awk 的資料顯示
awk 的資料顯示,主要利用 print 和 printf 這二個語法。前者,為最常用的輸出指令,沒有格式化的功能;後者,則提供強大格式化輸出的能力。
[1]
先來看 print。
print 用來產生簡單的輸出。通常是顯示記錄中的欄位,可能是數值或字串,彼此以 ',' 符號分隔。其語法如下:
print 項目1, 項目2, 項目3, ...
比如:
print "Hello awk !\n"
(語法正確)
print Hello awk
(語法錯誤: awk 會產生錯誤訊息,因為 awk 會把 Hello awk 視為正式的表達式,但 Hello awk 並非正確的語法。)
print
print $0
(以上二者作用相同)
print ""
print $1, $2, $NF
print "共有", total " 列"
print "A", "B"
(結果: A B)
print "A" "B"
(結果: AB)
print 在輸出前,若有數值在其中,則會把各項數值都轉成字串,連接起來後再顯示出來,並自動換列。[2]預設的輸出分隔符號變數 OFS 的內容是一個空白字元。也就是說: OFS = " "。若各項之間有用分隔符號 ',',則輸出時,各項彼此之間會用 OFS 的內容串接,即預設用一個空白字元連接各項。若沒有用分隔符號 ',',則項和項就直接連在一起。
在 print 的各項中,若有關係式語法,比如用到 '>' 符號的式子: 5 > 3,則應用小括號( )括住,否則會造成語意不清,因為 > 若沒有在 "" 或 () 中,對 awk 而言,是指轉向輸出。用例如下:
#! /usr/bin/awk -f
BEGIN {
print (5 > 3)
}
====
執行結果:
1
因為 (5 > 3) 是對的,為真值。
但若是以下例子:
#! /usr/bin/awk -f
BEGIN {
print 5 > 3
}
====
執行結果,會產生 '3' 這個檔案,其檔案內容為 5。
另外,若 print 後面沒有接任何項目,則預設就是印出 $0,也就是整列記錄。
若 print "",則會印出一個空白列。
Notes
[1]
當然還有其它語法,比如 sprintf。它把原本顯示到標準輸出的資料,改放到字串變數中。
[2]
這種數值輸出轉換成字串的動作,會使用 OFMT 這個內建變數,預設格式為 '%.6g'。g 代表輸出可以是科學記號型式或浮點數型式,二者中選較少字元數者來顯示。'.6' 代表精確度6位數。printf 其實是使用 sprintf 來轉換數值變成字串,OFMT 就是要丟給 sprintf 使用的格式化字元。
5.2. print 用例
pr5-1.awk
#! /usr/bin/awk -f
#
BEGIN {
print "第1列\n第2列\n第3列\n"
}
pr5-1.awk
pr5-2.awk
#! /usr/bin/awk -f
#
BEGIN {
print "姓名 手機"
print "====================";
}
{
print $1, " ", $2
}
====
mf.txt:
王大同 09xx-12x-001
李小同 091x-88x-99x
陳中同 093x-76x-9x3
./pr5-2.awk mf.txt
執行結果:
姓名 手機
====================
王大同 09xx-12x-001
李小同 091x-88x-99x
陳中同 093x-76x-9x3
pr5-2.awk
mf.txt
pr5-3.awk
#! /usr/bin/awk -f
#
BEGIN {
# 設定輸出欄位分隔符號
OFS = "---"
print "姓名 手機"
print "====================";
}
{
print $1, $2
}
./pr5-3.awk mf.txt
執行結果:
姓名 手機
====================
王大同---09xx-12x-001
李小同---091x-88x-99x
陳中同---093x-76x-9x3
pr5-3.awk
5.3. printf 簡介
這一節介紹格式化輸出指令 printf
printf 和 C 語言的 printf 運用方式相同,若您使用過 C like 的程式語法,比如 C++/PHP/Perl 等,對 printf 應該就不陌生。
printf 的語法結構如下:
printf "格式", 項目1, 項目2, 項目3, ...
其中,格式包含特殊的語法記號,例如: %s 代表字串; %d 代表整數。常用的格式字元如下表:
Table 5-1. printf 格式字元
格式 用途 用例
%s 以字串顯示 awk 'BEGIN {printf "歌名: %s !!!\n", "OSOLAMILO"}' 顯示 : OSOLAMILO !!! 並換到下一列。
%c 將數字以 ASCII 符號顯示 awk 'BEGIN {printf "%c", 88}' 顯示 X
%d 十進位整數顯示 awk 'BEGIN {printf "%d", 88}' 顯示 88
%e 或 %E 以科學記號顯示 awk 'BEGIN {printf "%9.2e", 32.175}' 顯示 _3.22e+01 (_ 這裡表示空白字元)[a]
%f 以浮點數顯示 awk 'BEGIN {printf "%8.2f", 32.175}' 顯示 ___32.17
%g 以科學記號型式或浮點數型式,二者中選較少字元數者來顯示。 awk 'BEGIN {printf "%.6g", 2008.13}' 顯示 2008.13[c]
%o 顯示八進位數。 awk 'BEGIN {printf "%o", 18}' 顯示 22[d]
%x 顯示十六進位數。 awk 'BEGIN {printf "%x", 60}' 顯示 3c[e]
Notes:
a. '9.2' 表示全部顯示寬度為 9 位數(包含小數點 . 和 + 這二個符號),小數部份用3位數。若使用 %E,則顯示大寫的記號 _3.22E+01
b. '8.2' 表示全部顯示寬度為8位數(包含小數點.這個符號),小數部份用2位數,不足部份補空白。
c. '.6' 表示精確度6位數
d. 八進位數 22 = 2*8 +2 = 十進位數 18
e. 十六進位數 3c = 3*16 + 12 = 十進位數 60。c 代表 10 進位數的 12。
Table 5-2. printf 的修飾字元
格式 用途 用例
- 向左對齊 awk 'BEGIN {printf "歌名:%-5s !\n", "LAB"}' 顯示 '歌名:LAB !',B 和 ! 之間還有3個空格。[a]
+ 正數在開頭顯示+號 awk 'BEGIN {printf "%+4.2f\n", 208.231}' 顯示: +208.23。
0 不足位數補0 awk 'BEGIN {printf "#%06s#\n", "abc"}' 顯示: #000abc#
' (單引號) 每三位數以 , 號分隔 BEGIN {printf "%'d\n", 3939889} 顯示: 3,939,889[c]
Notes:
a. printf 預設是向右對齊。
b. printf 不足位數預設是補空白字元,例如: awk 'BEGIN {printf "#%6s#\n", "abc"} 顯示: #___abc#。這裡的_代表空白字元。
c. 請用 pr5-4.awk 來執行。
5.4. 輸入和輸出
這一節介紹 awk 的轉向輸出和輸入
awk 的輸出,預設是顯示到作業系統的 '標準輸出',不過,我們可以把它改輸出到別的地方,這種機制稱為轉向輸出。比如以下做法,會把 "Hello awk !" 轉存到 test.txt 檔案中。要特別注意的是,若 test.txt 已存在,該檔原先的內容會被清空; 若不存在,則會建立一個新檔。
awk 'BEGIN {print "Hello awk !" > "test.txt"}'
以下做法,會把 "再一次" 附加到 test.xt 檔案的後面:
awk 'BEGIN {print "再一次" >> "test.txt" }'
test.txt 的內容如下:
Hello awk !
再一次
這種機制稱為 '轉向附加'。
awk 除了可以轉向輸出之外,也可以把欲顯示的內容,改輸出到作業系統的 '標準錯誤':
awk 'BEGIN {print "這是錯誤訊息!" > "/dev/stderr" }'
標準錯誤的用途,主要是用來顯示錯誤訊息,用以和程式預設的顯示訊息管道分開。
[1]
另外,awk 也可以輸出輸向到 pipe line。
所謂 pipe line 是指 awk 的輸出會變成其它程式的輸入來源,用例如下:
awk 'BEGIN {print "Hello awk " | "cut -c2-4" }'
cut 會截取第2到第4個字元,結果如下:
ell
awk 也可以輸出 pipe line 到 shell 去!
pr5-5.awk
#! /usr/bin/awk -f
#
# 把大寫的檔名轉成小寫檔名
#
{ printf("mv %s %s\n", $0, tolower($0)) | "sh" }
END { close("sh") }
====
資料檔 BIGFILENAME 用來存放那些大寫檔名的檔案列表,內容如下:
A
B
C
同時,在目錄下存在 A, B, C 這三個檔案。
執行方式:
./pr5-5.awk BIGFILENAME
如此一來,A, B, C 就會變成小寫檔名 a, b, c
pr5-5.awk 的工作原理如下圖所示:
Figure 5-1. 大寫檔名改成小寫
pr5-6.awk
#! /usr/bin/awk -f
#
# 把小寫的檔名轉成大寫檔名
#
{ printf("mv %s %s\n", $0, toupper($0)) | "sh" }
END { close("sh") }
====
資料檔 smallfilename 用來存放那些小寫檔名的檔案列表,內容如下:
a
b
c
同時,在目錄下存在 a, b, c 這三個檔案。
執行方式:
./pr5-6.awk smallfilename
如此一來,a, b, c 就會變成大寫檔名 A, B, C
pr5-5.awk 和 pr5-6.awk 這二支程式,對大量更改檔名的工作,很有幫助。由此,更可以看出 awk 的威力。:-)
Notes
[1]
awk 有三個特殊的 I/O 檔案: /dev/stdin 稱為標準輸入; /dev/stdout 稱為標準輸出; /dev/stderr 稱為標準錯誤。
5.5. awk 的輸入
這一節介紹 awk 的輸入功能。
awk 在主程式區塊,便會自動開啟資料檔,形成一個隱形的迴圈,對每一列資料套用主程式區塊中所有的程式句,程式設計者並不需要自行去做開檔的動作。
不過,有時候,為了特殊目的,我們仍然需要自行去開啟一些資料檔。這時便可以使用轉向輸入 < 的功能來達成。其用法如下:
pr5-7.awk
#! /usr/bin/awk -f
#
BEGIN {
# 開啟 tmp.txt 這個檔案,並將其內容輸入到 getline 中,
# 此時,getline 會重新計算每一欄位,我們可以用 $1, $2 等內建變數來取得這些欄位
getline < "tmp.txt"
# 養成良好習慣,開檔之後,記得要關閉檔案,以免檔案緩衝區未寫入檔案中,造成奇怪的現象。
close("tmp.txt")
# 印出第二個欄位
print $2
}
====
用例:
tmp.txt 的內容如下:
inet addr:192.168.1.53 Bcast:192.168.1.255 Mask:255.255.255.0
./pr5-7.awk
執行結果:
addr:192.168.1.53
pr5-7.awk
tmp.txt
以下這支程式, 會把記憶中關於 apache2 的行程編號顯示出來:
#! /usr/bin/awk -f
#
BEGIN {
RUN = "ps auxw"
while(RUN | getline) {
# 如果第 11 個欄位含關鍵字 apach2, 則印出其 PID 編號(即第2個欄位)
if ($11 ~ /apache2/) print $2
}
# 務必要關閉指令
close(RUN)
}
5.6. 習題
1. 請把以下資料,以 HTML 檔的 table 格式呈現:
姓名 生日 電話 住址
張大同,98/01/02,123-4567,台南縣不成里 1 號
王大同,98/02/03,223-5567,台南縣不成里 11 號
李大同,98/03/04,323-6567,台南縣不成里 21 號
陳大同,98/04/05,423-7567,台南縣不成里 31 號
Chapter 6. 樣式比對
這一章介紹 awk 的樣式比對。
6.1. awk 的樣式
在第 3 章,我們曾提到 awk 的運作方式,在主程式區塊中,是否會對資料列執行某一個 {動作},完全是由該程式句中的 '樣式' 來決定。如果,樣式比對符合,就執行;否則,就略過。因此,樣式在 awk 的運作中,扮演一個十分重要的角色。如何準確使用 awk 的樣式,變成操作 awk 的一個重要關鍵。這一章將簡單介紹 awk 的樣式語法。
6.2. 正規表示式(regexp)
正規表示式(regular expression) 是網路管理者和程式人應有的基本修練,無論如何,都要把它學好才行!
建議您不妨由 Perl 的正規表示式開始入門。
awk 的樣式,可由正規表示式、數字、字串以及某一段記錄的範圍所組成,語法格式如下:
1. /正規表示式/ 比如: /^$/ 、 /[ \t\n]+/ 、/\w+\\&/
2. /數字/ 比如: /123.05/
3. /字串/ 比如: /abc/ 、可以接上選擇符號 '|',比如: /AC|BD|EG/ 若包含有字串 AC 或 BD 或 EG,則該樣式為真。
4. 某一段記錄範圍 比如: /AB/,/XY/ 這表示從含有字串 AB 到含有字串 XY 的連續資料列。
各個樣式,可以用邏輯運算子 &&, ||, ! 做結合,另外,也可以用括號()來強制優先順序,比如:
/ABC/ && /XYZ/
/abc/ || /^中文/
(!/ooo/) && (/中文/ || /英文/)
注意!
相符: /abc/ 其實等於 $0 ~ /abc/
不相符: !/abc/ 其實等於 $0 !~ /abc/
pr6-1.awk
#! /usr/bin/awk -f
#
# 跳過某一連續資料列
#
/大內鄉/,/歸仁鄉/{ next }
{ print }
請把 pr5-7.awk 套用到 data3-3.txt,它會跳過大內鄉到第一個歸仁鄉的資料列。結果如下:
./pr5-7.awk data3-2.txt > d.txt
使用 diff 比較 data3-2.txt 和執行結果的檔案差異,可以發現跳過的資料段為:
35,43d34
< drwxr-xr-x 3 root root 4096 2008-03-12 12:29 大內鄉-嗚頭分校
< drwxr-xr-x 3 root root 4096 2008-03-12 12:29 大內鄉-大內國中
< drwxr-xr-x 3 root root 4096 2008-03-12 12:29 大內鄉-頭社分校
< drwxr-xr-x 3 root root 4096 2008-03-12 12:29 左鎮鄉-岡林分校
< drwxr-xr-x 3 root root 4096 2008-03-12 12:29 左鎮鄉-澄山分校
< drwxr-xr-x 3 root root 4096 2008-03-12 12:29 新化鎮-新化國中
< drwxr-xr-x 3 root root 4096 2008-03-12 12:29 新化鎮-正新國小
< drwxr-xr-x 3 root root 4096 2008-03-12 12:29 新化鎮-礁坑國小
< drwxr-xr-x 3 root root 4096 2008-03-12 12:29 歸仁鄉-保西國小
6.3. 常用的正規表示式
這一節舉一些常用的 awk 正規表示式語法,來做說明。
\ 抑制字元、回復其正常字元意義,比如: \$
^ 開頭,用例: /^abc/ { print }
$ 結尾,用例: /xyz$/ { print }
. 代表任意字元,也包括換列字元 \n。用例: /X.y/ { print },如果該列資料含有 Xay 也是算比對成功。
[....] 代表字元列表,比如 [axmn] 若含有 a 或 x 或 m 或 n,就算比對成功。/[1-9]/ 代表 1 到 9 的數字只要其中一個數字就可以比對符合, /[a-m]/ 代表 a 到 m 字元範圍可以比對符合。
[^....] 代表不在字元列表中的,則算符合。比如 /[^abc]/ 不含 a,b,c任一種字元,就算比對符合。[^1-9] 不含 1 到 9 者符合, [^a-m] 不含 a 到 m 者符合。
? 代表 '?' 前面的樣式字元,若有 0 個或 1 個,則比對符合。用例: /(abc)?/ , 不含 abc 或僅含有一個 abc 字串的資料列可以比對相符。
* 代表 '*' 前面的樣式字元,若有 0 個或 0 個以上,則比對符合。用例: /x*/
+ 代表 '+' 前面的樣式字元,若有 1 個或 1 個以上,則比對符合。用例: /x+/
...{n} 代表前面的樣式字元,剛好有 n 個。用例 /ABX{3}/ ==> /ABXXX/ 相符; /(ABX}{3}/ ==> /ABXABXABX/ 相符。注意!
這個樣式必須使用 POSIX 標準才作用,執行方法:
t1.awk 內容:
/(ABX){3}/ { print $1 }
awk --posix -f t1.awk data.txt
...{n,} 代表前面的樣式字元,有 n 個以上,則相符。 用例 /ABX{2,}/ 。必須使用 POSIX 標準才作用。
...{n,m} 代表前面的樣式字元,有 n~m 個,則相符。 用例 /ABX{2,5}/ 。必須使用 POSIX 標準才作用。
另外,POSIX 標準定義了幾種樣式:
[:alnum:] 英數字元集
[:alpha:] 字母。用例 /[[:alpha]]/ { print }
[:blank:] 空白字元和 TAB 字元
[:cntrl:] 控制字元
[:digit:] 數字
[:graph:] 可印出並可看見的字元(比如 'x' 即可印出也可看見,但空白字元雖可印出,但看不見。
[:lower:] 小寫字母
[:print:] 可印出字元(即非控制字元)
[:punct:] 標點符號
[:space:] 空白字元 (如空白, TAB, formfeed)
[:upper:] 大寫字母
[:xdigit:] 16進位數字字母(01-9A-Fa-f)
以下是 gawk 支援的樣式字元:
\w 英數字
\W 非英數字
\< 字頭, 例如 /\<big/ 符合 bigtree, 但不符合 treebig
\> 字尾, 例如 /big\>/ 符合 treebig, 但不符合 bigtree
\y 字的邊界(單獨成字), 例如 /\yYes\y/ 符合 'Sure, Yes I do.' 但不符合 itYesOk
\B 連字(不單獨成字), 例如 /\Booo\B/ 符合 YESoooYES, 但不符合 'YES ooo'
6.4. 利用樣式和轉向輸入,找出網卡IP
本節將示範,如何使用之前介紹過的 awk 樣式和轉向輸入,來找出網卡的 IP 位址。
get_ip.awk
#! /usr/bin/awk -f
#
# written by ols3@lxer.tw
#
# 執行方法: ifconfig | ./get_ip.awk
#
# 取得 eth? 中的 ip 資料
#
# 改變記錄的分隔方式, 以空白列當作記錄的分隔符號, 如此, 才能只取出 eth? 的資料
# 而丟掉 lo
#
BEGIN {
FS="\n"
RS=""
}
/^eth/ {
print $2 > "tmp.txt"
}
END {
close("tmp.txt")
FS = ":"
getline < "tmp.txt"
close("tmp.txt");
print $2 > "tmp.txt"
close("tmp.txt");
FS = " "
getline < "tmp.txt"
print $1
}
get_ip.awk
6.5. 利用樣式分析Apache2記錄檔
這一節將利用樣式比對,來分析 Apache2 的記錄檔,以找出試圖利用 File Include 漏洞來攻擊主機者。
Apache2 記錄檔樣本: access.log
get-badguy.awk
#! /usr/bin/awk -f
#
# written by OLS3 (ols3@lxer.tw)
#
BEGIN {
printf "%-26s %-20s %-20s\n", "攻擊時間", "攻擊者IP", "攻擊弱點"
print "==========================================================================="
}
/\.php\?\w+\=http:\/\/(\w+\.)+\w+\/.+\?/ {
printf "%-20s%s %-20s %-20s\n", $4, "]", $1, $7
}
===
執行方法:
./get-badguy.awk access.log > badguy.txt
結果片斷:
攻擊時間 攻擊者IP 攻擊弱點
===========================================================================
[17/Mar/2008:03:52:45] 89.108.67.135 //modules/xfsection/modify.php//modules/xfse
ction/modify.php?dir_module=http://tompakovcapital.com/.dat/idscan3.txt??
get-badguy.awk
6.6. 習題
1. 請找出 book.html 檔案中,所有的 href 的連結位址.
2. 請把 popt.c 以網頁格式呈現,並把關鍵字 main、if、struct、return 以紅色呈現。
book.html
popt.c
Chapter 7. 流程控制
這一章介紹 awk 的流程控制。
7.1. 關於 awk 的流程控制。
流程控制,是使程式語言看起來像有點智慧的基礎。它讓程式設計者,可以依條件判斷(true-false),進行決策(if-else)、重覆執行相同的工作(loop)、以及在多樣中,依不同的狀況,擇一執行(switch-case)。
7.2. 條件判斷
在 4.5 節曾提到,awk 真假值的判斷方法: 若非空(不是 0 且 不是空字串),則為真,反之(0 或 空字串),為假。
一個判斷式若為真,則其值為1;若為假,則其值為 0。
各種真假值判斷式,可以單純,也可以用邏輯運算子組合成更複雜的條件判斷式,因此,常會見到類似以下這些程式碼:
#! /usr/bin/awk -f
#
# 單純判斷式
{
if ($1 == "338") print "Bingo !"
}
# 運用 && 連接二個判斷式
{
if (($2 > 30) && ($2 < 100))
max = $2
}
# 複雜的判斷式
{
if ( (($0 ~ /abc/) || ($2 !~ /xyz$)) && ($3 ~ /^ipx/) ) {
print $5
}
}
7.3. if-else 語法
第 1 種: 單純 if 語法
# 若條件成立,則執行單一動作
if (條件判斷式)
單一動作
# 若條件成立,則執行多個動作。此時,要把這多個動作用 {} 含括。
if (條件判斷式) {
動作A
動作B
}
用例:
#! /usr/bin/awk -f
#
BEGIN {
"date" | getline
close("date")
w = $1; m = $2; d = $3
if ((m == "三月") && (d == "26"))
print "生日快樂!"
}
===
#! /usr/bin/awk -f
#
BEGIN {
"date" | getline
close("date")
w = $1; m = $2; d = $3
print "今天是 ", m, d, "日"
if ((m == "三月") && (d == "26")) {
str = "生日快樂!"
print str
}
}
第 2 種: if-else 語法
# 若條件成立,則執行單一動作,否則,執行另一個動作。
if (條件判斷式)
單一動作A
else
單一動作B
# 也可以把敘述放在同一列,但要用 ; 隔開:
if (條件判斷式) 單一動作A; else
單一動作B
# 若條件成立,則執行多個動作。此時,要把這多個動作用 {} 含括。
if (條件判斷式) {
動作A
動作B
} else {
動作C
動作D
}
用例:
#! /usr/bin/awk -f
#
BEGIN {
"date" | getline
close("date")
w = $1; m = $2; d = $3
print "今天是 ", m, d, "日"
if ((m == "三月") && (d == "26"))
print "生日快樂!"
else
print "普通日子"
}
7.4. 條件運算式 ?:
條件運算式在 C/C++/PHP/Perl 都可以見到,awk 也有這個語法。
變數C = (判斷式) ? A值 : B值
它的意思是: 如果 '判斷式' 為真,則傳回 'A值' 給 '變數C',否則傳回 'B值'。
用例:
#! /usr/bin/awk -f
#
{
# 傳回 $1 和 $2 的較大值
max = ($1 >= $2) ? $1 : $2
if (max > MAX) MAX = max
}
END {
print "最大的是", MAX
}
7.5. while 迴圈
while 迴圈的語法如下:
while (若判斷式為真) {
動作
}
它的意思是說: 如果判斷式為真,則持續做 {} 迴圈中的 '動作',直到判斷式變假才停止。
用例:
#! /usr/bin/awk -f
BEGIN {
while ( getline < "access.log" ) {
total_lines++
}
print "這個檔案共有", total_lines, "列"
}
====
執行結果:
./t1.awk
這個檔案共有 2345 列
無窮迴圈:
#! /usr/bin/awk -f
BEGIN {
while ( 1 ) {
print "I love u."
}
}
7.6. do-while 迴圈
do-while 迴圈的語法如下:
do {
動作
} while (若判斷式為真)
它的意思是說: 第一次,無條件就執行 {} 中的動作,接著會對 while 後面的判斷式,進行判斷,若為真,則持續做 {} 迴圈中的 '動作',直到判斷式變假才停止。
用例:
#! /usr/bin/awk -f
BEGIN {
x=1
do {
sum += x
x++
} while (x<=100)
}
7.7. 幫你的程式碼加上列號
以下這支程式,可以把你的程式碼加上列號,並轉成 HTML 格式。
add-line-no.awk
#! /usr/bin/awk -f
#
# written by OLS3 (ols3@lxer.tw)
#
# 幫程式碼加上列號, 並以 HTML 格式呈現
#
BEGIN {
# 先計算總列數
while ( getline < ARGV[1] ) {
total_lines++
}
T = length(total_lines)
print "<html><head><meta http-equiv=\"Content-type\" content=\"text/html; charset=big5\"></head><body><pre>\n";
}
{
gsub(/</, "\\<", $0)
gsub(/>/, "\\>", $0)
printf("%0"T"d %s\n", NR, $0)
}
END {
print "</pre></body></html>\n";
}
add-line-no.awk
popt.c
目標: 把 popt.c 加上列號,並用 HTML 檔呈現原始碼。執行方法:
./add-line-no.awk popt.c > popt.c.html
執行結果: popt.c.html
7.8. for 迴圈
for 迴圈的語法如下:
for (初始值; 終止條件; 變量) {
動作
}
它的意思是說: 首先會設定一個初始值給迴圈變數,並規定一個終止條件,每次都判斷迴圈變數是否符合終止條件,若尚未到達,就執行 {} 迴圈中的動作,接著對迴圈變數進行增量或減量,然後再判斷終止條件滿足了沒有,若尚未達終止條件,就重覆前面的步驟 ,直到終止條件成真為止。
用例:
for (i=1; i<=100; i++) {
sum += i
}
# 上面這個 for 迴圈,其終止條件就是: i > 100 。
另外,awk 有一種特殊的 for 迴圈,和陣列(array)有關,雖然尚未介紹陣列,不過,因為不是很困難,所以這裡先提用一下。
for (x in arr) {
動作
}
# 它的意思是說: 對每一個在 arr 陣列中的註標(index),都執行一次 {} 中的動作。
用例: pr7-1.awk
#! /usr/bin/awk -f
#
# 讀取 data3-1.txt,以 voip 前三碼做為陣列的註標(index),
# 以學校名稱當作陣列元素的值。
# $2 是學校名稱; $6 是 voip 前三碼
{
# 第一列是標題, 不要; 空白列不要.
if ((NR >1) && ($1 !=""))
arr[$6] = $2
}
END {
for(x in arr) {
print arr[x], "的網路電話前三碼是", x
}
}
====
執行方式:
./pr7-1.awk data3-1.txt
結果:
信義國小 的網路電話前三碼是 223
佳里國小 的網路電話前三碼是 216
七股國小 的網路電話前三碼是 235
新橋國小 的網路電話前三碼是 149
仁愛國小 的網路電話前三碼是 217
佳興國小 的網路電話前三碼是 218
新泰國小 的網路電話前三碼是 323
後港國中 的網路電話前三碼是 120
新東國中 的網路電話前三碼是 102
太子國中 的網路電話前三碼是 103
竹橋國中 的網路電話前三碼是 121
新生國小 的網路電話前三碼是 150
新營國小 的網路電話前三碼是 142
新民國小 的網路電話前三碼是 143
佳里國中 的網路電話前三碼是 116
佳興國中 的網路電話前三碼是 117
新進國小 的網路電話前三碼是 144
通興國小 的網路電話前三碼是 222
pr7-1.awk
7.9. switch-case 選擇(gawk 預設未必支援)
switch-case 用來在多種狀況中擇一執行,其語法如下:
switch (表達式/計算其值) {
case 值1相同/或符合樣式1:
動作
break
case 值2相同/或符合樣式2:
動作
break
case 值N相同/或符合樣式N:
動作
break
default:
預設動作
}
用例: pr7-2.awk
#! /usr/bin/awk -f
{
# 判斷目前讀取的列數
switch (NR) {
case 11:
print "已經讀到第 11 列了"
break
case 38:
print "已經讀到第 38 列了"
break
case 62:
print "已經讀到第 62 列了"
break
case 119:
print "已經讀到第 119 列了"
break
default:
printf "%s", "."
}
}
注意! gawk 在編譯時,預設可能沒有打開支援 switch-case 的選項,在這種情況下,這個例子就無法執行。
我有編譯一份支援 switch-case 語法的 gawk,在 B2D Server 環境下,可用 dpkg -i gawk_3.1.4-2-enable-switch_i386.deb 來安裝它。
下載 gawk-enable-switch-case。
pr7-2.awk
7.10. 特別控制指令
迴圈在某些情況下,需要提早終止,或者,在某一次執行過程中,剩餘的動作不再做,直接跳下一個迴圈,這些需要特別的控制指令。
提早終止迴圈: 使用 break
for (i=1; i<=20; i++) {
if (i==10) break;
sum += i
}
# 上式,當 i 等於 10 時,就離開迴圈。
跳下一迴圈: 使用 continue
for (i=1; i<=20; i++) {
if (i==10) continue;
sum += i
}
# 上式,當 i 等於 10 時,不做 sum 的加總。
使某一 awk 程式段不再執行: 使用 next
#! /usr/bin/awk -f
#
{
# 當第 10 筆記錄時,用 next 跳過 print NR 指令,不予執行。
if (NR == 10)
next
print NR
}
中止程式的執行: 使用 exit
#! /usr/bin/awk -f
#
{
# 當第 10 筆記錄時,結束程式,傳回 1
if (NR == 10) {
print "模擬錯誤中斷" > "/dev/stderr"
exit 1
}
print NR
}
7.11. 利用 awk 大量建帳號
#! /usr/bin/awk -f
#
# written by OLS3 (ols3@lxer.tw)
#
# 大量建帳號
#======================================
# 帳號檔格式,每一列帳號和密碼(明碼)以 ':' 分隔
#
# 帳號1:密碼1
# 帳號2:密碼2
# 帳號3:密碼3
# ....
#======================================
# 使用法:
# ./madduer.awk 帳號檔
#
BEGIN {
FS = ":"
}
{
if ($1) {
doit = "adduser --quiet --disabled-password --gecos '' " $1
print "新增帳號 " $1
system( doit )
}
}
END {
doit = "chpasswd < " FILENAME
system( doit )
}
madduser.awk
7.12. 習題
1. 請用 awk 撰寫一支大量刪除帳號的程式。
2. 請寫一支 awk 程式, 秀出九九乘法表.
3. 請寫一支 awk 程式, 計算 1+2+3+...+50 總和
4. 同上, 但其中 6 的倍數要扣掉, 總和 = ?
Chapter 8. 陣列
這一章介紹 awk 中,陣列如何運作。
8.1. awk 的陣列
陣列是一種資料表格,這些資料稱為陣列的元素。元素的取用,依靠陣列的註標(index)。awk 的陣列註標,可以用數字,也可以用字串。
前者即一般常見的陣列;後者稱為關聯式陣列(associative array),又可稱為雜湊(hash),是一種描述一對對的'鍵-值'關係的方法。
8.2. 數值註標的陣列
用法如下: pr8-1.awk
#! /usr/bin/awk -f
#
{
arr[NR]=$0
}
END {
for(i=1; i<=NR; i++)
print i, "==>", arr
}
# 套用在 mf.txt 的執行結果如下:
1 ==> 王大同 09xx-12x-001
2 ==> 李小同 091x-88x-99x
3 ==> 陳中同 093x-76x-9x3
pr8-1.awk
mf.txt
8.3. 關聯式陣列
用法如下: pr8-2.awk
#! /usr/bin/awk -f
#
# 讀取 data3-1.txt,以學校名稱做為陣列的註標(index),
# 以網址當作陣列元素的值。
# $2 是學校名稱; $3 是網址
{
# 第一列是標題, 不要; 空白列不要.
if ((NR >1) && ($1 !=""))
# 學校名稱當作鍵(key), 學校網址當作值(value)
arr[$2] = $3
}
END {
for(x in arr) {
print x, "的網址", arr[x]
}
}
====
執行結果片斷:
新生國小 的網址 http://www.ss7es.tnc.edu.tw
佳里國中 的網址 http://www.jl1jh.tnc.edu.tw
新橋國小 的網址 http://www.sc1es.tnc.edu.tw
佳興國小 的網址 http://www.js2es.tnc.edu.tw
後港國中 的網址 http://www.hgjh.tnc.edu.tw
太子國中 的網址 http://www.ttjh.tnc.edu.tw
佳興國中 的網址 http://www.jsjh.tnc.edu.tw
......
仁愛國小 的網址 http://www.raes.tnc.edu.tw
關聯式陣列的作法很簡單,就是把字串當註標,把值塞進對應註標的元素中,就完成了。比如上式中的 arr[$2] = $3;$2 是學校名稱(字串),對應註標的元素為 arr[$2],把值($3) 設定給它,就可以完成這個陣列。
pr8-2.awk
8.4. 判斷是否為陣列的一員
這個判斷方法,是輪巡陣列全部元素的方法基礎。
(x in arr) 是一個條件判斷,若 x 是 arr 這個陣列的註標,則為真,否則為假。因此,把它拿來做為 for 迴圈的判斷式,就可對陣列的所有元素輪巡。其作法如下:
for(x in arr) {
對 x 或 arr[x] 做處理
}
以下程式會把 awk 的環境變數陣列內容顯示出來:
#! /usr/bin/awk -f
BEGIN {
for(x in ENVIRON) {
print x, "===>", ENVIRON[x]
}
}
====
執行結果片斷:
AWKPATH ===> .:/usr/share/awk
XMODIFIERS ===> @im=xcin
COLORTERM ===> rxvt-xpm
COLORFGBG ===> 15;default;0
LANG ===> zh_TW.Big5
GDM_XSERVER_LOCATION ===> local
LC_ALL ===> zh_TW.Big5
LANGUAGE ===> zh_TW.Big5
USER ===> root
8.5. 刪除陣列的元素
刪除陣列元素的作法很簡單,使用 delete 這個語法:
delete 陣列[註標]
比如: delete arr["佳里國小"] 或 delete arr[4],這樣一來,這個鍵值關係或陣列元素,就被移除了。
8.6. 陣列的排序
陣列排序,awk 提供二個內建函式供選用: asort、asorti。前者針對陣列元素做排序;後者則對陣列註標做排序。
asort 的用法 1:
# 使用 asort 對陣列 arr 排序,它會傳回陣列的大小 n
n = asort(陣列arr)
# 利用 for 迴圈,可輪巡 arr 全部陣列元素
for (i = 1; i <= n; i++) {
處理 arr
}
這種做法的缺點是,asort 會破壞陣列 arr 的註標,其全部註標會消失,改為由 1 到 n 的數字註標;換言之,原陣列 arr 的結構,已被改變。有時候,我們想要保留陣列的註標做其它用途,此種叫用 asort 方法,就不適合。所幸 asort 還有第二種用法,請看:
asort 的用法 2:
# 使用 asort 對陣列 arr 排序,但把結果改存到另一個陣列 brr,原陣列 arr 不受影響!
n = asort(arr, brr)
# 利用 for 迴圈,可輪巡 brr 全部陣列元素
for (i = 1; i <= n; i++) {
處理 brr
}
8.7. 對陣列註標做排序
有時,我們希望對註標做排序,asorti 可以滿足這樣的需求。
asorti 用法:
# 使用 asorti 對陣列 arr 的註標做排序,但把結果改到另一個陣列 brr,原陣列 arr 不受影響!
n = asorti(arr, brr)
# 此時, brr 放的是已排序過的註標
用例如下: pr8-3.awk
#! /usr/bin/awk -f
BEGIN {
arr["xyz"]="英文"
arr["abc"]="中文"
arr["mno"]="法文"
n = asorti(arr, brr)
# brr 放的是排序過的註標
print "原陣列 arr 的註標, 經過排序後:"
for(i=1; i<=n; i++) {
print "原陣列 arr 的註標 ==>", brr
}
print "=========================="
print "原陣列 arr 的鍵值, 經過排序後:"
for(i=1; i<=n; i++) {
print "原陣列 arr 的鍵值對應:", brr, "==>", arr[brr]
}
}
====
執行結果:
原陣列 arr 的註標, 經過排序後:
原陣列 arr 的註標 ==> abc
原陣列 arr 的註標 ==> mno
原陣列 arr 的註標 ==> xyz
==========================
原陣列 arr 的鍵值, 經過排序後:
原陣列 arr 的鍵值對應: abc ==> 中文
原陣列 arr 的鍵值對應: mno ==> 法文
原陣列 arr 的鍵值對應: xyz ==> 英文
pr8-3.awk
8.8. 習題
1. 以下是甲乙丙丁四人修課的情況,請寫一支 awk 程式,統計各科目修課的人數,並以 HTML 的 table 格式呈現。
甲 awk程式設計 資料結構 微積分 國文 英文
乙 國文 統計學 資料結構 awk程式設計
丙 憲法 微積分 國文 統計學
丁 統計學 資料結構 英文
戊 awk程式設計 國文 統計學
Chapter 9. awk 的函式
這一章介紹 awk 的函式。
9.1. 內建函式
awk 內建許多函式,可加速程式的開發,增進效益。
9.2. 字串處理相關函式
gsub(reg, str) : 使用正規表示式 reg 來比對 $0 (即一列資料記錄),
若找到則全部用 str 字串取代它。gsub 會回傳取代的次數。
gsub(reg, str, dest) : 同上,但比對的對象改為 dest
用例: 以下將把全部的 < 符號用 < 來取代。
gsub(/</, "\\<", $0)
====
傳回取代結果 = gensub(reg, str, how) : 使用正規表示式 reg 來比對 $0 (即一列資料記錄),
若找到, 則使用 str 來取代. how 若為 g 或 G, 則全部取代, 若為數字n, 則表示取代第n個.
取代的結果會傳回來, $0 的內容不受改變.
傳回取代結果 = gensub(reg, str, how, dest)
同上, 但取代的對象改為 dest.
用例:
X = "xyz abc"
Y = gensub(/(.+) (.+)/, "\\2 \\1", "g", X)
print Y
其中 \\2 代表第二個比對符合的位置, \\1 代表第一個比對符合的位置
結果:
abc xyz
====
sub(reg, str) : 使用正規表示式 reg 來比對 $0 (即一列資料記錄),
第一個找到樣式,用 str 字串取代它。若有找到,sub 會傳回 1,若無傳回 0。
sub(reg, str, dest) : 同上,但比對的對象改為 dest
用例: 以下將把第一個找到的 : 替換為空白字元。
sub(/:/, " ")
====
index(str, substr) : 找尋子字串 substr 在字串 str 中第一次出現的位置(由位置 1 開始計數)
若沒有找到,則傳回 0。
用例: 以下將找尋記錄列 $0 中,是否含有 '/*' 這個字串,若有,則把其第一次出現的位置,回傳給變數 t。
t = index($0, "/*")
====
match(str, reg) : 使用正規表示式 reg 來比對字串 str,若符合,則傳回比對位置,若否,傳回 0。
此函式會設定二個內建變數: RSTART(比對到字串的第一個位置) 和 RLENGTH(比對到字串的長度)。
====
substr(str, m, n) : 由字串 str 中,切割指定範圍的子字串。m 是起始位置,n 是字元數,
若 n 不指定,表示取到字串尾部。
用例:
str = "abcdefg"
s = substr(str, 5)
s 為 efg
====
tolower(str) : 把字串轉成小寫
toupper(str) : 把字串轉成大寫
length(str) : 傳回變數的長度
====
split(str, arr) : 把字串以 FS 分隔符號做分割,把各分割結果存入陣列 arrr 中。
split(str, arr, sep) : 同上,但改用 sep 這個指定的分隔符號做分割。
9.3. 輸出輸入相關函式
輸出相關:
print 和 printf 這二個在之前已說明得很清楚了,請參考前面章節。
sprintf : 語法同 printf,不過,它並不顯示到標準輸出,而是存入一個字串中。
s = sprintf "格式", 項目1, 項目2, .....
用例:
s = sprintf("%08.2f", 32.175)
得到 s 為 "00032.17"
====
輸入相關:
getline 有三種用法:
1. getline var : 把讀取結果存入變數 var 中
2. getline < 檔案 : 由檔案讀取資料,放入 $0 內建變數中
用例:
awk 'BEGIN { while (getline < "mf.txt") print $0 }'
3. 命令列程式 | getline 或 命令列程式 | getline var
取得命令列程式的輸出,放入 $0 或 var 中。
9.4. 系統相關函式
system(程式) : 跳到 shell 去執行 '程式'
用例:
awk 'BEGIN { system("date") }'
結果:
四 3月 27 11:19:25 CST 2008
====
systime() : 顯示自 1970 年 1 月 1 日 00:00:00 UTC 到現在為止的秒數,此秒數稱為 timestamp。
用例:
awk 'BEGIN { print systime() }'
結果:
1207420291
====
mktime("時間格式") : 依時間格式產生 timestamp。
用例:
awk 'BEGIN { print mktime("2008 04 01 08 20 30") }'
結果:
1207009230
====
strftime("日期格式", [timestamp]) : 依現在或指定的 timestamp 產生對應的日期時間資訊。
用例:
awk 'BEGIN { print strftime("%a %b %d %H:%M:%S %Z %Y") }'
結果:
二 4月 08 10:09:40 CST 2008
日期格式說明:
%a : 星期幾(當地時間顯示)
%b : 月(當地月份顯示)
%d : 日(01-31)
%H : 時(00-23)
%M : 分(00-59)
%S : 秒(00-59)
%Z : 時區
%Y : 年(西元)
9.5. 數值相關函式
int(arg) : 傳回 arg 的整數值
用例:
awk 'BEGIN { print int(3.14) }
結果: 3
====
rand() : 傳回介於 0 和 1 之間的亂數
srand(arg) : 使用 arg 當作亂數種子
====
sqrt(arg) : 傳回 arg 的平方根
====
log(arg) : 傳回 arg 的自然對數值
exp(arg) : 傳回 arg 的自然指數值
====
sin(arg), cos(arg), atan2(x,y) 傳回三角函數值
9.6. 使用者自訂函式
使用者可以自己定義函式,位置放在任何地方都可以。作法如下:
function 函式名稱(參數) {
動作
return 傳回值
}
用例: pr9-1.awk
#! /usr/bin/awk -f
BEGIN {
print getsum(100)
}
function getsum(i) {
for(j=1; j<=i; j++)
sum += j
return sum
}
====
執行結果:
5050
pr9-1.awk
9.7. '幫程式碼加上列號' 版本二
以下是 7.7 節 '幫程式碼加上列號' 的第二種版本,使用自訂函式來處理顯示 HTML 語法。
#! /usr/bin/awk -f
#
# written by OLS3 (ols3@lxer.tw)
#
# 幫程式碼加上列號, 並以 HTML 格式呈現
#
BEGIN {
cl="wc -l " ARGV[1]
cl | getline
T=length($1)
pr_html_head()
}
{
gsub(/</, "\\<", $0)
gsub(/>/, "\\>", $0)
sub(/^[ ]*int /, "<font color=green>&</font>", $0)
printf("%0"T"d %s\n", NR, $0)
}
END {
pr_html_end()
}
function pr_html_head() {
print "<html><head><meta http-equiv=\"Content-type\" content=\"text/html; charset=big5\"></head><body><pre>\n";
}
function pr_html_end() {
print "</pre></body></html>\n";
}
====
執行方法:
./add-line-no2.awk popt.c > popt.c2.html
add-line-no2.awk
執行結果檔案: popt.c2.html
9.8. 習題
1. 請寫一個自訂函式,找出以下test2.dat資料檔中,介於 9~15 之間的數。
1.2 9.2 3.4 5.6 7.8 13.3
9.10 0.3 11.12 6.6 13.14 15.16
17.18 19.20 10.8 21.22 23.24 14.2
Chapter 10. 綜合運用
這一章介紹一些 awk 程式的寫法。
10.1. 刪除 C 語言程式碼中的註解符號
C 語言程式的註解符號為 /* 開頭,一直到遇到下一個 */ 為止,這段範圍內的字串,都被視為註解。C 語言的註解可以單列,也可以跨越多列,因此,欲使用程式把它刪除,在作法上,需要多一點的考量。
del-c-comment.awk
#! /usr/bin/awk -f
#
# written by OLS3 (ols3@lxer.tw)
#
{
# 若含有左註解符號
if ( (t = index($0, "/*")) != 0) {
# 找出左註解符號在該列的起點位置
tmp = substr($0, 1, t-1)
# 保留該列左註解之前的字串
tmp2 = substr($0,t+2)
# 剩下的部份是否含有右註解
if (u = index(tmp2, "*/")) {
# 若有, 則去除右註解之前的部份
k = substr(tmp2,u+2)
# 印出去掉 /* .... */ 的部份
print tmp k
# 以下的動作就不必再做了
next
} else {
# 當找不到右註解時, 重覆本迴圈
while (u==0) {
# 若沒有下一列了, 表示註解的結構不完整
if (getline<=0) {
print "ERROR"
exit 1
}
# 若有找到右註解
if (u = index($0, "*/")) {
# 刪去右註解左方的部份
k = substr($0,u+2)
# 印出右方的部份即可
print k
# 以下的動作不必再做了
next
}
}
}
}
# 將不含 C 註解符號的列顯示出來
print $0
}
====
執行方法:
./del-c-comment.awk sample.c
del-c-comment.awk
執行結果: d2.txt
使用 diff sample.c d2.txt 可以比較處理前後的差異,如下所示,的確可以正確的刪去註解!
14,18c14
< /*
< *
< * 這是跨越多列的註解
< *
< */
---
>
27c23
< /* 去掉 newline */
---
>
33c29
< printf("%s : %d\n", p, strlen(p)); /* 註解 */
---
> printf("%s : %d\n", p, strlen(p));
10.2. 取得網卡 IP 的第二種版本
以下是 6.4 取得網卡 IP 的第二種版本作法:
#! /usr/bin/awk -f
#
# written by OLS3 (ols3@lxer.tw)
#
# 執行方法: ifconfig | ./get_ip.awk
# 取得 eth? 中的 ip 資料
# 改變記錄的分隔方式, 以空白列當作記錄的分隔符號, 如此, 才能只取出 eth? 的資料
# 而丟掉 lo
BEGIN {
FS="\n"
RS=""
}
/^eth/ {
split($2, ar, " ") # 此時, 只剩 inet addr:192.168.1.144
split(ar[2], ip, ":")
print ip[2]
}
get-ip2.awk
10.3. 取得網卡 IP 的第三種版本
以下是 6.4 取得網卡 IP 的第三種版本作法:
#! /usr/bin/awk -f
#
# written by OLS3 (ols3@lxer.tw)
#
# 執行方法: ifconfig | ./get-ip2.awk
# 取得 eth? 中的 ip 資料
# 改變記錄的分隔方式, 以空白列當作記錄的分隔符號, 如此, 才能只取出 eth? 的資料
# 而丟掉 lo
BEGIN {
FS="\n"
RS=""
}
/^eth/ {
match($2, /inet addr:(\w+\.\w+\.\w+\.\w+) /, ar)
print ar[1]
}
get-ip3.awk
10.4. ping 主機時,取得其回應時間
這支程式,用來取得 ping 主機時的 '最小/平均/最大' 回應時間,以量測網路連線品質。
#! /usr/bin/awk -f
#
# written by OLS3 (ols3@lxer.tw)
#
# 取得 ping 主機時,最小/平均/最大 回應時間
#
BEGIN \
{
ip = "192.168.1.2"
t = 4
pit = "ping -c " t " " ip
timeis = strftime("%Y-%m-%d %H:%M:%S", systime())
while ( pit | getline ) {
if ($0 ~/round-trip min/) {
match($0, /([0-9]+.[0-9]+)\/([0-9]+.[0-9]+)\/([0-9]+.[0-9]+) ms/, ar)
OFS = ","
print ip, timeis, ar[1], ar[2], ar[3]
}
}
}
get-ping-time.awk
10.5. 主機監控
這支程式,用來對一群主機進行 ping 測試,以監控其是否仍在運作。這是網路管理最常要做的基本功之一。
det-ips.awk
#! /usr/bin/awk -f
#
# written by OLS3 (ols3@lxer.tw)
#
# 使用 ping,監控主機群
#
{
# 讀取第二列的控制設定
if (NR == 2) {
t = $1 ? $1 : 4
OFS = $2
}
if (NR > 2) {
if ($1) ip = $1; else next
pit = "ping -c " t " " ip
timeis = strftime("%Y-%m-%d %H:%M:%S", systime())
while ( pit | getline ) {
if ($0 ~/round-trip min/) {
match($0, /([0-9]+.[0-9]+)\/([0-9]+.[0-9]+)\/([0-9]+.[0-9]+) ms/, ar)
print ip, timeis, ar[1], ar[2], ar[3]
}
}
close( pit )
}
}
主機資料檔 ip-list.txt,格式如下:
指定 ping 的次數 指定輸出分隔符號
4 ,
192.168.1.2
192.168.1.4
192.168.1.9
192.168.1.15
192.168.1.26
192.168.1.38
192.168.1.45
192.168.1.77
192.168.1.88
執行方法: ./det-ips.awk ip-list.txt
det-ips.awk
ip-list.txt
執行結果片斷:
192.168.1.2,2008-03-28 06:29:56,0.3,0.8,1.4
192.168.1.4,2008-03-28 06:29:59,0.7,0.8,0.9
192.168.1.9,2008-03-28 06:30:02,1.6,10.5,32.4
192.168.1.15,2008-03-28 06:30:05,0.4,1.8,4.8
192.168.1.26,2008-03-28 06:30:08,0.0,0.0,0.1
10.6. 主機監控,以網頁呈現結果
這支程式,作用同前一節,但改以網頁呈現結果。
det-ips2.awk
#! /usr/bin/awk -f
#
# written by OLS3 (ols3@lxer.tw)
#
# 使用 ping,監控主機群,以網頁格式呈現
#
BEGIN {
pr_html_head()
}
{
# 讀取第二列的控制設定
if (NR == 2) {
t = $1 ? $1 : 4
OFS = $2
}
if (NR > 2) {
if ($1) ip = $1; else next
pit = "ping -c " t " " ip
timeis = strftime("%Y-%m-%d %H:%M:%S", systime())
while ( pit | getline ) {
if ($0 ~/round-trip min/) {
match($0, /([0-9]+.[0-9]+)\/([0-9]+.[0-9]+)\/([0-9]+.[0-9]+) ms/, ar)
print "<tr>",ip, timeis, ar[1], ar[2], ar[3] "</tr>"
}
}
close( pit )
}
}
END {
pr_html_end()
}
function pr_html_head() {
print "<html><head><meta http-equiv=\"Content-type\" content=\"text/html; charset=big5\"></head><body>\n";
print "<table border=1><tr colspan=5><td>主機IP<td>偵測時間<td>最小回應時間<td>平均回應時間<td>最大回應時間</tr>\n";
}
function pr_html_end() {
print "</table></body></html>\n";
}
主機資料檔 ip-list2.txt,格式如下:
指定 ping 的次數 指定輸出分隔符號
4 <td>
192.168.1.2
192.168.1.4
192.168.1.9
192.168.1.15
192.168.1.26
192.168.1.38
192.168.1.45
192.168.1.77
192.168.1.88
執行方法: ./det-ips2.awk ip-list2.txt > ip-list2.html
det-ips2.awk
ip-list2.txt
執行結果:
ip-list2.html
10.7. 習題
1. 請寫一支大量建帳號的 awk 程式,並自動幫他把自家目錄建起來,權限設妥
(若帳號為 abc, 則自家目錄的擁有者也要是 abc),同時幫他建立一個 index.html 的首頁檔。
2. 請寫一支 awk 程式,統計 /var/mail 中各信包檔的大小,若超過您規定的上限,就自動發信通知管理者。
3. 請寫一支 awk 程式,統計 tcpdump 執行時抓到的封包中,所有連線 IP 和協定,並存成 csv 檔格式。
4. 請寫一支 awk 程式,將 test.cpp 中,C++ 的註解列(以// 開頭者) 以及 C 的註解 (/*...*/) 刪除。
test.cpp
Chapter 11. 參考文件
gawk 手冊
Shell 設計入門
Shell 文件收集
Shell 文件
• Bash
o bash 參考手冊
o Bash FAQ
• Sed
o GNU sed 手冊
o sed tricks
o sed 手冊 (中研院提供)
•
o gawk
o The GNU Awk User's Guide
o gawk 和 TCP/IP 網路
o awk 入門指引 (中研院提供)
o gawk for win32
|
|