awk命令詳解(二)
第一篇的連結:Linux awk命令總結(一)
1. 處理陣列
為了在單個變數中儲存多個值,許多程式語言都提供了陣列,在awk中使用關聯陣列提供陣列的功能。
關聯陣列類似於散列表和字典,索引值可以是任意的文字字串,對索引的唯一要求是每個索引字串都能夠唯一的對應賦值給它的資料元素。
(1)定義陣列變數
可以使用標準的賦值語句來定義陣列變數,格式如下:
array[index] = element
其中array是變數名,index是索引值,element是資料元素的值。
$ awk 'BEGIN{ > nums["a"] = "abc" > nums["b"] = "def" > print nums["a"], nums["b"] > }' abc def
(2)遍歷陣列變數
如果要在awk中遍歷一個關聯陣列,可以使用for語句的一種特殊形式(for each):
for (var in array) {
statements
}
舉例如下:
$ awk 'BEGIN{ > var["a"] = 1 > var["g"] = 2 > var["m"] = 3 > var["u"] = 4 > for (element in var) { > print "Index:", element, " - Value:", var[element] > } > }' Index: g - Value: 2 Index: m - Value: 3 Index: u - Value: 4 Index: a - Value: 1
需要注意的是,索引值不會按照任何特定的順序返回,但它們都能夠指向對應的資料元素值。
(3)刪除陣列變數
從關聯陣列中刪除陣列索引需要一個特殊的命令:
delete array[index]
刪除命令會從陣列中刪除索引值和與其對應的資料元素值,一旦從關聯陣列中刪除了索引值,就無法再用它來提取資料元素值:
$ awk 'BEGIN { > var["a"] = 1 > var["g"] = 2 > for (element in var) > { > print "Index:", element, " - Value:", var[element] > } > delete var["g"] > print "-----" > for (element in var) > { > print "Index:", element, " - Value:", var[element] > } > }' Index: g - Value: 2 Index: a - Value: 1 ----- Index: a - Value: 1
2. 使用模式
awk程式支援多種型別的匹配模式類過濾資料,這一點與sed編輯器類似。BEGIN和END關鍵字是用來在讀取資料流之前和之後執行命令的特殊模式,類似地,你可以建立其他模式,在資料流中出現匹配資料時執行一些命令。
(1)正則表示式
在使用正則表示式時,正則表示式必須出現在它要控制的程式指令碼的左花括號之前:
$ cat test2.txt
data11,data12,data13
data21,data22,data23
data31,data32,data33
$ awk 'BEGIN {FS=","} /11/{print $1}' test2.txt
data11
$ awk 'BEGIN {FS=","} /11/{print $2}' test2.txt
data12
第一個例子很好理解,但是在第二個例子中匹配的是11為什麼打印出來的是data12呢?這是因為正則表示式匹配是對輸入記錄進行匹配的(變數RS規定的輸入記錄),在預設是按行匹配的情況下(RS的預設值為換行符\n),資料中的第一行含有11,因此第一行的第二個資料欄位data12列印了出來。顯然這不是我們想要的結果,如果我們需要用正則表示式匹配某個特定的資料欄位,應該使用匹配操作符。
(2)匹配操作符
匹配操作符(matching operator)允許將正則表示式限定在資料中的特定資料欄位。匹配操作符是波浪線(~)。可以指定匹配操作符、資料欄位變數以及要匹配的正則表示式。
對上面的例子稍作修改,我們想在test2.txt中匹配含有11的第二個資料欄位,可以寫做:
$ awk 'BEGIN {FS=","} $2 ~ /11/{print $2}' test2.txt
$
沒有任何輸出,說明沒有匹配到含有11的第二個資料欄位,這正式我們想要的結果。
也可以用!符號來排除正則表示式的匹配:
$ awk 'BEGIN {FS=","} $3 !~ /13/{print $3}' test2.txt
data23
data33
3. 數學表示式
除了正則表示式,你也可以在匹配模式中使用數學表示式。這個功能在匹配資料欄位中的數值時非常方便。舉個例子,如果你想顯示所有屬於root使用者組(組ID為0)的系統使用者,可以用這個指令碼:
$ awk 'BEGIN {FS=":"} $4 == 0 {print $1}' /etc/passwd
root
這段指令碼會檢視第四個資料欄位含有值0的記錄。
可以使用任何常見的數學比較表示式:
- x == y
- x <= y
- x < y
- x >= y
- x > y
也可以對文字資料使用表示式,但必須要小心,跟正則表示式不同,表示式必須完全匹配。
$ awk -F, '$1 == "data"{print $1}' test2.txt
$
$ awk -F, '$1 == "data11"{print $1}' test2.txt
data11
4. 結構化命令
(1)if語句
awk支援標準的if-else格式的if語句。你必須為if語句定義一個求值的條件,並將其用圓括號括起來。可以寫在不同行,也可以寫在同一行。下面是一個例子:
$ cat test5.txt
10
5
13
50
34
$ awk '{
> if ($1 > 20)
> {
> x = $1 * 2
> print x
> } else
> {
> x = $1 / 2
> print x
> }}' test5.txt
5
2.5
6.5
100
68
也可以在單行上使用else子句,但必須在if語句部分之後使用分號:
$ awk '{if ($1 > 20) print $1 * 2; else print $1 / 2}' test5.txt
5
2.5
6.5
100
68
(2)while語句
awk支援標準的while語句:
while (condition) {
statements
}
此外還支援在while迴圈中使用break語句和continue語句從迴圈中跳出或是跳過一個迴圈:
$ cat test6.txt
130 120 135
160 113 140
145 170 215
$ awk '{
> total = 0
> i = 1
> while (i < 4)
> {
> total += $i
> if (i == 2)
> break
> i++
> }
> avg = total / 2
> print "The average of the first two data elements is:", avg
> }' test6.txt
The average of the first two data elements is: 125
The average of the first two data elements is: 136.5
The average of the first two data elements is: 157.5
break語句用來在i的值為2時從while迴圈中跳出。
除此之外,awk還支援do-while語句,它類似於while語句,但會在檢查條件語句之前執行命令,下面時do-while的格式:
do {
statements
} while (condition)
(3)for語句
awk支援標準的C風格for迴圈:
$ awk '{
> total = 0
> for (i = 1; i < 4; i++)
> {
> total += $i
> }
> avg = total / 3
> print "Average:", avg
> }' test6.txt
Average: 128.333
Average: 137.667
Average: 176.667
5. 格式化列印
print語句在顯示資料上並沒有提供多少控制,你能做的知識控制輸出欄位的分隔符(OFS)。如果要建立詳盡的報告,通常都要為資料選擇特定的格式和位置。使用printf命令能夠實現這一效果。awk中的printf和C語言中的printf是一樣的。下面是printf命令的格式:
printf "format string", var1, var2, ......
format string是格式化輸出的關鍵,它會用文字元素和格式化指定符來具體制定如何呈現格式化輸出。格式化指定符是一種特殊的程式碼,會指明顯示什麼型別的變數以及如何顯示。awk程式會將每個格式化指定符作為佔位符,供命令中的變數使用,第一個格式化指定符對應第一個變數,第二個對應第二個變數,以此類推。
格式化指定符采用如下形式:
%[modifier]control-letter
其中control-letter是一個單字元程式碼,用於指明顯示什麼型別的資料,而modifier則定義了可選的格式化特性,下面列出了可用在格式化指定符中的控制字母:
控制字母 | 描述 |
c | 將一個數作為ASCII字元顯示 |
d | 顯示一個整數值 |
i | 顯示一個整數值(和d一樣) |
e | 用科學計數法顯示一個數 |
f | 顯示一個浮點值 |
g | 用科學計數法或浮點數顯示(選擇一個較短的格式) |
o | 顯示一個八進位制值 |
s | 顯示一個文字字串 |
x | 顯示一個十六進位制值 |
X | 顯示一個十六進位制值,但用大寫字母A~F |
因此,如果你需要顯示一個字串變數,可以使用%s,如果你需要顯示一個整數變數,可以用%d或%i。如果你想要用科學計數法顯示很大的值,就用%e。
$ awk 'BEGIN {x = 10 * 100; printf "The answer is: %e\n", x}'
The answer is: 1.000000e+03
除了控制字母外,還有三種修飾符可以用來進一步控制輸出:
- width:指定了輸出欄位最小寬度的數字值。如果輸出短於這個值,printf會將文字右對齊,並用空格進行填充。如果輸出比指定的寬度還要長,則按照實際的長度輸出。
- prec:這是一個數字值,制定了浮點數中小數點後面位數,或者文字字串中顯示的最大字元數。
- -(減號):指明在向格式化空間中放入資料時採用左對齊而不是右對齊。
舉一個之前的例子:
$ cat test4.txt
oldmanw
No.10 XiTuCheng Road
Beijing
(010)6228-1234
handsomeBoy
No.10 XiTuCheng Road
Beijing
12345678901
$ awk 'BEGIN {FS="\n"; RS=""} {print $1, $4}' test4.txt
oldmanw (010)6228-1234
handsomeBoy 12345678901
可以用printf命令來幫助格式化輸出,使得輸出資訊看起來更加美觀。首先將print命令替換成printf命令:
$ awk 'BEGIN {FS="\n"; RS=""} {printf "%s %s\n", $1, $4}' test4.txt
oldmanw (010)6228-1234
handsomeBoy 12345678901
需要注意的是,你需要在printf的末尾手動新增換行符\n來生成新行,如果沒有新增,printf命令會在同一行列印後續輸出:
$ awk 'BEGIN {FS="\n"; RS=""} {printf "%s %s", $1, $4}' test4.txt
oldmanw (010)6228-1234handsomeBoy 12345678901
如果需要用幾個單獨的printf命令在同一行上列印多個輸出,可以這麼寫:
$ cat test2.txt
data11,data12,data13
data21,data22,data23
data31,data32,data33
$ awk -F, '{printf "%s ", $1} END {printf "\n"}' test2.txt
data11 data21 data31
每個printf的輸出都會出現在同一行,為了終止該行,END部分列印了換行符。
下一步,用修飾符來格式化第一個字串值:
$ awk 'BEGIN {FS=","; RS=""} {printf "%16s %s\n", $1, $4}' test4.txt
oldmanw (010)6228-1234
handsomeBoy 12345678901
通過一個值為16的修飾符,我們強制將第一個字元的輸出寬度為16個字元。如果要顯示的字串長度超過限制的寬度,會將字串的前n個字元佔滿限制寬度(n為設定的寬度限制),剩下的字元也會顯示,並將剩餘部分依次右移:
$ awk 'BEGIN {FS=","; RS=""} {printf "%8s %s\n", $1, $4}' test4.txt
oldmanw (010)6228-1234
handsomeBoy 12345678901
在預設情況下,printf命令使用右對齊,如果要改成左對齊,只需要給修飾符加一個減號即可:
$ awk 'BEGIN {FS=","; RS=""} {printf "%-16s %s\n", $1, $4}' test4.txt
oldmanw (010)6228-1234
handsomeBoy 12345678901
printf在處理浮點值時也非常方便:
$ awk '{
> total = 0
> for (i = 1; i < 4; i++)
> {
> total += $i
> }
> avg = total / 3
> printf "Average: %5.1f\n", avg
> }' test6.txt
Average: 128.3
Average: 137.7
Average: 176.7
其中的5.1表示輸出共佔5列,其中有1位小數。關於printf格式控制符更詳細的介紹可以參考:關於printf()函式和浮點數
6. 內建函式
awk提供了不少內建函式,可以進行一些常見的數字、字串以及時間函式的運算。可以利用這些內建函式來減少指令碼中的編碼工作。
(1)數學函式
下表列出了awk中內建的數學函式:
函式 | 描述 |
atan2(x, y) | x/y的反正切,以弧度為單位 |
cos(x) | x的餘弦,以弧度為單位 |
exp(x) | x的指數函式(e^x) |
int(x) | x的整數部分,取靠近0的一側(int(5.6)=5, int(-5.6)=-5) |
log(x) | x的自然對數 |
rand() | 比0大比1小的隨機浮點值 |
sin(x) | x的正弦,以弧度為單位 |
sqrt(x) | x的平方根 |
srand(x) | 為隨機數指定一個種子值 |
除了標準數學函式外,awk還支援一些按位操作資料的函式:
函式 | 描述 |
and(v1, v2) | v1和v2按位與運算 |
coml(val) | val的補運算 |
lshift(val, count) | 將val左移count位 |
or(v1, v2) | v1和v2按位或運算 |
rshift(val, count) | 將val右移count位 |
xor(v1, v2) | v1和v2按位異或運算 |
(2)字串函式
在這裡不再詳細介紹,具體可以參考:
(3)時間函式
awk中包含一些函式來幫助處理時間值,如下所示:
函式 | 描述 |
mktime(datespec) | 將一個按YYYY MM DD HH MM SS[DST]格式指定的日期轉換成時間戳 |
strftime(format [, timestamp]) | 格式化時間輸出,將時間戳轉為時間字串,如果沒有timestamp預設對當前時間進行轉換。format的具體格式見下表 |
systime() | 得到時間戳,返回從1970年1月1日開始到當前時間的整秒數 |
strftime的格式說明符:
格式 | 描述 |
%a | 星期幾的縮寫(Sun) |
%A | 星期幾的完整寫法(Sunday) |
%b | 月名的縮寫(Oct) |
%B | 月名的完整寫法(October) |
%c | 本地日期和時間 |
%d | 十進位制日期 |
%D | 日期 08/20/99 |
%e | 日期,如果只有一位會補上一個空格 |
%H | 用十進位制表示24小時格式的小時 |
%I | 用十進位制表示12小時格式的小時 |
%j | 從1月1日起一年中的第幾天 |
%m | 十進位制表示的月份 |
%M | 十進位制表示的分鐘 |
%p | 12小時表示法(AM/PM) |
%S | 十進位制表示的秒 |
%U | 十進位制表示的一年中的第幾個星期(星期天作為一個星期的開始) |
%w | 十進位制表示的星期幾(星期天是0) |
%W | 十進位制表示的一年中的第幾個星期(星期一作為一個星期的開始) |
%x | 重新設定本地日期(08/20/99) |
%X | 重新設定本地時間(12:00:00) |
%y | 兩位數字表示的年(99) |
%Y | 當前月份 |
%Z | 時區(PDT) |
%% | 百分號(%) |
時間函式常用來處理日誌檔案,而日誌檔案則常含有需要進行比較的日期。通過將日期的文字表示形式轉換成時間戳的形式,能夠輕鬆的比較日期。下面是在awk程式中使用時間函式的例子:
$ awk 'BEGIN {date=systime(); print strftime("%Y, %B %d, %A", date)}'
2018, November 20, Tuesday
7. 自定義函式
(1)定義和使用自定義函式
要定義自己的函式,必須使用function關鍵字。
function name([variables]) {
statements
}
函式名必須能夠唯一標識函式,可以在呼叫的awk程式中傳給這個函式一個或多個變數。
在定義函式時,它必須出現在所有程式碼塊之前(包括BEGIN程式碼塊)。
$ cat test4.txt
oldmanw
No.10 XiTuCheng Road
Beijing
(010)6228-1234
handsomeBoy
No.10 XiTuCheng Road
Beijing
12345678901
$ awk '
> function myprint()
> {
> printf "%-16s - %s\n", $1, $4
> }
> BEGIN {FS="\n"; RS=""}
> {
> myprint()
> }' test4.txt
oldmanw - (010)6228-1234
handsomeBoy - 12345678901
一旦定義了函式,你就能在程式的程式碼中隨意使用。在涉及很大的程式碼量時,這會省去許多工作。
(2)建立庫函式
顯然,每次使用函式的時候都要重寫一遍並不美妙,不過awk提供了一種途徑來將多個函式放到一個庫檔案中,這樣就能在所有的awk程式中使用。
首先需要建立一個儲存所有awk函式的檔案:
$ cat funclib
function myprint()
{
printf "%-16s - %s\n", $1, $4
}
function myrand(limit)
{
return int(limit * rand())
}
function printthird()
{
print $3
}
funclib檔案中含有三個函式的定義。需要使用-f命令引數來使用它們,但是不能將-f和庫函式檔案單獨使用,還需要一個包含函式的程式檔案:
$ cat awkScript4
BEGIN {FS="\n"; RS=""}
{
myprint()
}
然後在命令列上同時指定庫檔案和程式檔案就可以了:
$ awk -f funclib -f awkScript4 test4.txt
oldmanw - (010)6228-1234
handsomeBoy - 12345678901
參考文獻:Linux命令列與shell指令碼程式設計大全(第3版)