Awk 是一種優(yōu)秀的文本樣式掃描和處理工具。本文側(cè)重介紹了 awk 在數(shù)值計(jì)算方面的運(yùn)用,并通過(guò)幾個(gè)實(shí)際工作中的例子,闡述了如何利用 awk 的計(jì)算功能來(lái)提高我們的工作效率。 Awk 是一種優(yōu)秀的文本樣式掃描和處理工具。 Awk 與 sed 和 grep 有些相似, 但功能比后者強(qiáng)不少。 awk 提供的功能包括樣式載入, 流控制,數(shù)學(xué)運(yùn)算符,進(jìn)程控制以及許多內(nèi)置的變量和函數(shù)等。 借助于這些功能, 我們可以很方便地利用 awk 對(duì)各種文件 (如試驗(yàn)產(chǎn)生的數(shù)據(jù)文件,數(shù)據(jù)庫(kù)文件等) 進(jìn)行處理。 本文介紹了 awk 在數(shù)值計(jì)算方面的運(yùn)用, 并通過(guò)幾個(gè)實(shí)際的例子, 闡述了如何利用 awk 的計(jì)算功能來(lái)提高我們的工作效率。 Awk 基本的運(yùn)算符,數(shù)學(xué)函數(shù)以及簡(jiǎn)單的運(yùn)算實(shí)例 Awk 支持不少常見(jiàn)的運(yùn)算符, 如 + (加),- (減), * (乘), / (除), ^ 或 ** (乘方), % (取模) 等等。 此外, awk 也提供了一些常用的數(shù)學(xué)函數(shù), 比如 sin(x), cos(x), exp(x), log(x), sqrt(x), rand()。 使用這些運(yùn)算符和函數(shù)可以直接進(jìn)行一些簡(jiǎn)單的運(yùn)算: 清單 1. 用 awk 做簡(jiǎn)單的數(shù)值計(jì)算 上面的計(jì)算也可以用一個(gè)腳本文件 calc.awk 來(lái)完成: 清單 2. 腳本文件 calc.awk 執(zhí)行 awk -f calc.awk 19 7 可以得到和清單 1 中一樣的計(jì)算結(jié)果。 這里選項(xiàng) -f 允許 awk 調(diào)用并執(zhí)行程序文件 calc.awk; 最后的 19 和 7 是輸入, 分別對(duì)應(yīng)于文件中的 $1 和 $2。
復(fù)雜一些的數(shù)值計(jì)算 現(xiàn)在我們利用 awk 來(lái)完成一些稍微復(fù)雜的計(jì)算。 我們首先用 awk 來(lái)計(jì)算 Fibonacci 數(shù)列,相應(yīng)的 awk 程序 Fib.awk 見(jiàn)清單3: 清單 3. 計(jì)算 Fibonacci 數(shù)列的程序文件 function fibo(n) { if(n<=1) return 1; return (fibo(n-2) + fibo(n-1)); } BEGIN { n = (ARGV[1] < 1) ? 1 : ARGV[1]; printf("%d\n", fibo(n)); exit; } | 計(jì)算時(shí)使用命令 awk -f Fib.awk n。 這里的輸入 n 是整數(shù)。 另外只要把上面程序中的函數(shù)fibo(n) 稍微改一下, 就可以用來(lái)進(jìn)行階乘的運(yùn)算, 修改后的代碼如下: 清單 4. 計(jì)算價(jià)乘的 awk 腳本 function factorial(n) { if(n<=1) return 1; return (n*factorial(n-1)); } BEGIN { n = (ARGV[1] < 1) ? 1 : ARGV[1]; printf("%d\n", factorial(n)); exit; } | 我們?cè)賮?lái)看一個(gè)求平方根的例子。 盡管 awk 提供了計(jì)算平方根的函數(shù), 但我們也可以通過(guò)自己寫(xiě)程序來(lái)實(shí)現(xiàn), 相應(yīng)的算法如清單 5 所示; 清單 6 則給出了一個(gè)具體的例子: 求數(shù)字 3.7 的平方根: 清單 5. 求平方根的算法 清單 6. 計(jì)算平方根的例子 BEGIN { a = 3.7; x = a; while((x**2-a)**2 > 1e-12) { x = (x + a/x)/2; } print x } |
實(shí)例1: 快速計(jì)算兩個(gè)文件之間的時(shí)間差 如果僅僅從事單純的數(shù)值計(jì)算, 恐怕 awk 不是我們最好的選擇, 畢竟 awk 是為了方便文本處理而設(shè)計(jì)的。 不過(guò)如果數(shù)值計(jì)算和文本有密切關(guān)系的話(huà), 比方說(shuō)計(jì)算之前要先處理文本中的數(shù)據(jù) (如查找,提取數(shù)據(jù)), 這時(shí) awk 的優(yōu)勢(shì)就會(huì)充分顯示出來(lái)。 而這樣的情況在工作中往往是經(jīng)常碰到的。 我們來(lái)看一個(gè)實(shí)際的例子。 假定我們要比較某些運(yùn)行在 Linux 集群上的并行程序的效率, 一個(gè)可行的方法是估算這些程序運(yùn)行所需的時(shí)間。 這些程序運(yùn)行的時(shí)間通常比較長(zhǎng), 可以從 10 幾個(gè)小時(shí)到一個(gè)多星期。 注意到程序在運(yùn)行中會(huì)不斷地生成數(shù)據(jù)文件, 而 Linux 系統(tǒng)會(huì)紀(jì)錄下每個(gè)數(shù)據(jù)文件被創(chuàng)建 (如果以前不存在) 或修改(如果以前存在) 的時(shí)間, 這樣就可以通過(guò)計(jì)算兩個(gè)文件的時(shí)間差來(lái)估計(jì)并行程序的效率。 我們知道 Linux 提供的 stat 命令可以用來(lái)獲取某個(gè)文件的各種屬性, 比如對(duì)數(shù)據(jù)文件 simu_space_1.dat 使用命令 stat simu_space_1.dat 會(huì)有如下的輸出: 清單 7. 命令 stat simu_space_1.dat 的輸出 File: "simu_space_1.dat" Size: 237928 Blocks: 480 IO Block: 4096 regular Datei Device: 801h/2049d Inode: 2768915 Links: 1 Access: (0644/-rw-r--r--) Uid: ( 1000/ nst) Gid: ( 1000/ nst) Access: 2008-11-14 10:56:05.000000000 +0100 Modify: 2008-11-13 23:26:44.000000000 +0100 Change: 2008-11-13 23:26:44.000000000 +0100 | 以上輸出包含了關(guān)鍵字 ’Modify’ 的一行中紀(jì)錄下了文件被修改的時(shí)間。 所以原則上說(shuō)只要對(duì)兩個(gè)文件分別使用 stat 命令, 得到它們的修改時(shí)間, 就可以計(jì)算出它們之間的時(shí)間差。 如果計(jì)算的次數(shù)很少的話(huà), 這個(gè)工作當(dāng)然可以手工完成。 不過(guò)要頻繁計(jì)算的話(huà)就很費(fèi)時(shí)間了, 而且出錯(cuò)的幾率也會(huì)變大。 這種情況下我們可以求助 awk, 讓它來(lái)自動(dòng)完成這個(gè)計(jì)算工作, 為此我們創(chuàng)建了下面的腳本 time_df.awk: 清單 8. 計(jì)算時(shí)間差的 awk 程序 BEGIN { n = 0; d1 = 0; s1 = 0; FS = ":|-| *"; } { for(i=1; i<=NF; i++) { if($i~/Modify/) { n = n + 1; d = $(i+4); h = $(i+5); m = $(i+6); s = $(i+7); d1 = d1 + ((-1)**n)*d*24*3600; s1 = s1 + ((-1)**n)*(3600*h + 60*m + s); } } } END { s1 = s1 + d1; D = int(s1/(24*3600)); H = int((s1 - D*24*3600)/3600); M = int((s1-D*24*3600-H*3600)/60); S = s1 % 60; printf("The total time required %d days, %d hours, %d minutes and %d seconds\n", D, H, M, S) ; } | 上面的代碼是基于如下的考慮: 首先使用 awk 找到包含 ’Modify’ 關(guān)鍵字的那一行, 然后把其中有關(guān)日期和時(shí)間的數(shù)據(jù)提取出來(lái)。 由于直接對(duì)日期和時(shí)間做減法不是很方便, 所以先把日期和時(shí)間轉(zhuǎn)化為一個(gè)以秒為單位的數(shù)字 (從每個(gè)月的第一天0時(shí)0分0秒算起)。 容易理解, 由兩個(gè)數(shù)字相減得到的時(shí)間差也是以秒為單位的。 為了能直觀(guān)顯示, 輸出時(shí)再把這個(gè)時(shí)間差表達(dá)為天, 小時(shí), 分鐘和秒。 要計(jì)算兩個(gè)文件 simu_space_1.dat 和 simu_space_100.dat 之間的時(shí)間差,可以用下面的命令: 清單 9. 計(jì)算文件時(shí)間差的命令 stat simu_space_1.dat simu_space_100.dat | awk -f time_df.awk | 先生成的文件 simu_space_1.dat (也就是時(shí)間較早一些的) 放在前面,后生成的文件simu_space_100.dat 放在后面。 如果要算另外兩個(gè)文件之間的時(shí)間差, 只要換一下文件名就可以了。 借助于上面的 awk 代碼我們可以快速且精確地得到任意兩個(gè)數(shù)據(jù)文件的時(shí)間間隔。 需要指出的是, 上面的程序沒(méi)有考慮跨月度這種情況。 也就是說(shuō), 如果第一個(gè)數(shù)據(jù)文件是在某個(gè)月的月末生成, 而第二個(gè)文件是在下個(gè)月的月初生成, 這時(shí)就不能用它來(lái)計(jì)算, 因?yàn)榈玫降臅r(shí)間是沒(méi)有意義的負(fù)數(shù)。
實(shí)例2: 驗(yàn)證通量:從多個(gè)文件中提取數(shù)據(jù)并計(jì)算 這個(gè)例子是通過(guò)計(jì)算不同位置處的流體的通量來(lái)驗(yàn)證它們是否相同。 這里的通量可視為某個(gè)截面上通過(guò)的顆粒濃度, 流體的速度和截面面積的乘積。 現(xiàn)在的問(wèn)題是濃度, 速度等參數(shù)分布在不同的數(shù)據(jù)文件中, 而這些文件是字符和數(shù)據(jù)共存的, 比如包含濃度的文件 simu_space_1.dat 有如下的格式: 清單 10. 數(shù)據(jù)文件 simu_space_1.dat 的格式 {0.436737, 0.429223, 3.000000, 1.000000, 43300806482080792.000000, 243231808.137785}, {1.296425, 0.429223, 3.000000, 1.000000, 107468809895964656.000000, 584622938.047805}, {2.128973, 0.429223, 3.000000, 1.000000, 102324821165926400.000000, 539067822.351442}, ...... {19.358569, 4.875000, 3.000000, 1.000000, 257544788738191712.000000, 1460324590.999991}, {19.620925, 4.875000, 3.000000, 1.000000, 266676357086157504.000000, 1464352706.940682}, {19.875000, 4.875000, 3.000000, 1.000000, 260249342336872224.000000, 1383971975.659338}, 第一步當(dāng)然是從上面的文件中把某個(gè)位置上的濃度數(shù)據(jù) (每一行左起第五個(gè)數(shù)字) 提取出來(lái)。 下面的 awk 代碼是把位置 x = 0.429223 處的濃度提取出來(lái), 并保存到一個(gè)臨時(shí)文件 number.txt 中: 清單 11. 提取指定位置處的數(shù)據(jù)并保存 awk -F'{|,\t|},' '{for(i=1; i<NF; i++) {if($i~/0.429223/) print $(i+3)}}' simu_space_1.dat > number.txt | 現(xiàn)在文件 number.txt 中有了一列濃度數(shù)據(jù)。 接著我們從其他文件中提取在同一位置處的速度和面積的數(shù)據(jù), 然后把它們分別保存到臨時(shí)文件 velocity.txt 和 area.txt 中。 然后把三個(gè)臨時(shí)文件中的數(shù)據(jù)合并到另一個(gè)文件 flux.txt 中以方便 awk 的計(jì)算。 這個(gè)合并操作可以用工具 paste 來(lái)輕松完成, 代碼如清單 12: 清單 12. 合并不同文件中的數(shù)據(jù)到一個(gè)文件 paste number.txt velocity.txt area.txt > flux.txt | 現(xiàn)在 flux.txt 中包含了三列數(shù)據(jù), 分別是濃度,速度和面積。 按照前面介紹的通量計(jì)算方法, 文件 flux.txt 中每一行的三個(gè)數(shù)據(jù)要首先相乘, 然后再把所有的乘積加起來(lái)就可以得到通過(guò)那個(gè)截面的通量了, 具體的代碼見(jiàn)清單13: 清單 13. 計(jì)算通量的 awk 代碼 awk '{x=x+($1*$2*$3)} END {print x}' flux.txt | 上面的代碼使用了一個(gè)變量 x, 第一次執(zhí)行時(shí), x 被賦予文件 flux.txt 中第一行三個(gè)數(shù)據(jù)的乘積。 第二次執(zhí)行時(shí), 它保留了第一次計(jì)算的值并加上第二行三個(gè)數(shù)據(jù)的乘積, 以此類(lèi)推, 直到達(dá)到累計(jì)的總合。 END 的作用是只顯示最后的結(jié)果, 而不顯示中間的累加結(jié)果。 我們可以做一個(gè)比較, 以前我們是利用其他的軟件 (比如 Excel 或者 OpenOffice Calc) 來(lái)計(jì)算通量的。 這必然要涉及到導(dǎo)入數(shù)據(jù), 選擇相應(yīng)的計(jì)算函數(shù)等一系列的操作, 而用 awk 只要一行代碼! 如果再考慮到計(jì)算之前從不同文件中提取數(shù)據(jù)的工作也是由 awk 完成的 (其實(shí)也就是幾行代碼), 所以對(duì)本例而言使用 awk 節(jié)約了可觀(guān)的時(shí)間。
總結(jié) 不應(yīng)忽視 awk 的數(shù)值計(jì)算功能, 它能完成從簡(jiǎn)單到比較復(fù)雜的數(shù)值運(yùn)算。 尤其當(dāng)計(jì)算過(guò)程中涉及到數(shù)據(jù)文件的處理, 這時(shí)使用 awk 往往會(huì)很方便。 因?yàn)?awk 本身有很強(qiáng)的文本處理功能, 它可以輕松地把數(shù)據(jù)從文本中分離出來(lái), 然后再進(jìn)行相應(yīng)的計(jì)算。 本文的實(shí)例說(shuō)明了如果能靈活地使用 awk 的這些功能, 有可能會(huì)顯著地提升我們的工作效率。
參考資料
|