(點選上方公眾號,可快速關註一起學Python)
作者:Marshall Mason 連結:
https://learnxinyminutes.com/docs/zh-cn/awk-cn/
AWK是POSIX相容的UNIX系統中的標準工具. 它像簡化版的Perl, 非常適用於文字處理任務和其他指令碼類需求. 它有著C風格的語法, 但是沒有分號, 沒有手動記憶體管理, 沒有靜態型別. 他擅長於文字處理, 你可以透過shell指令碼呼叫AWK, 也可以用作獨立的指令碼語言.
為什麼使用AWK而不是Perl, 大概是因為AWK是UNIX的一部分, 你總能依靠它, 而Perl已經前途未卜了. AWK比Perl更易讀. 對於簡單的文字處理指令碼, 特別是按行讀取檔案, 按分隔符分隔處理, AWK極可能是正確的工具.
#!/usr/bin/awk -f
# 註釋使用井號
# AWK程式由一系列 樣式(patterns) 和 動作(actions) 組成.
# 最重要的樣式叫做 BEGIN. 動作由大括號包圍.
BEGIN {
# BEGIN在程式最開始執行. 在這裡放一些在真正處理檔案之前的準備和setup的程式碼.
# 如果沒有文字檔案要處理, 那就把BEGIN作為程式的主入口吧.# 變數是全域性的. 直接賦值使用即可, 無需宣告.
count = 0# 運運算元和C語言系一樣
a = count + 1
b = count – 1
c = count * 1
d = count / 1 # 整數除法
e = count % 1 # 取餘
f = count ^ 1 # 取冪a += 1
b -= 1
c *= 1
d /= 1
e %= 1
f ^= 1# 自增1, 自減1
a++
b–# 前置運算, 傳回增加之後的值
++a
–b# 註意, 不需要分號之類的標點來分隔陳述句
# 控制陳述句
if (count == 0)
print “Starting with count of 0”
else
print “Huh?”# 或者三目運運算元
print (count == 0) ? “Starting with count of 0” : “Huh?”# 多行的程式碼塊用大括號包圍
while (a < 10) {
print “String concatenation is done” ” with a series” ” of”
” space-separated strings”
print aa++
}for (i = 0; i < 10; i++)
print “Good ol’ for loop”# 標準的比較運運算元
a < b # 小於
a <= b # 小於或等於
a != b # 不等於
a == b # 等於
a > b # 大於
a >= b # 大於或等於# 也有邏輯運運算元
a && b # 且
a || b # 或# 並且有超實用的正則運算式匹配
if (“foo” ~ “^fo+$”)
print “Fooey!”
if (“boo” !~ “^fo+$”)
print “Boo!”# 陣列
arr[0] = “foo”
arr[1] = “bar”
# 不幸的是, 沒有其他方式初始化陣列. 必須像這樣一行一行的賦值.# 關聯陣列, 類似map或dict的用法.
assoc[“foo”] = “bar”
assoc[“bar”] = “baz”# 多維陣列. 但是有一些侷限性這裡不提了.
multidim[0,0] = “foo”
multidim[0,1] = “bar”
multidim[1,0] = “baz”
multidim[1,1] = “boo”# 可以檢測陣列包含關係
if (“foo” in assoc)
print “Fooey!”# 可以使用in遍歷陣列
for (key in assoc)
print assoc[key]# 命令列引數是一個叫ARGV的陣列
for (argnum in ARGV)
print ARGV[argnum]# 可以從陣列中移除元素
# 在 防止awk把檔案引數當做資料來處理 時delete功能很有用.
delete ARGV[1]# 命令列引數的個數是一個叫ARGC的變數
print ARGC# AWK有很多內建函式, 分為三類, 會在接下來定義的各個函式中介紹.
return_value = arithmetic_functions(a, b, c)
string_functions()
io_functions()
}# 定義函式
function arithmetic_functions(a, b, c, d) {# 或許AWK最讓人惱火的地方是沒有區域性變數, 所有東西都是全域性的,
# 對於短的指令碼還好, 對於長一些的就會成問題.# 這裡有一個技巧, 函式引數是對函式區域性可見的, 並且AWK允許定義多餘的引數,
# 因此可以像上面那樣把區域性變數插入到函式宣告中.
# 為了方便區分普通引數(a,b,c)和區域性變數(d), 可以多鍵入一些空格.# 現在介紹數學類函式
# 多數AWK實現中包含標準的三角函式
localvar = sin(a)
localvar = cos(a)
localvar = atan2(a, b) # arc tangent of b / a# 對數
localvar = exp(a)
localvar = log(a)# 平方根
localvar = sqrt(a)# 浮點型轉為整型
localvar = int(5.34) # localvar => 5# 隨機數
srand() # 接受隨機種子作為引數, 預設使用當天的時間
localvar = rand() # 0到1之間隨機
# 函式傳回
return localvar
}function string_functions( localvar, arr) {
# AWK, 作為字元處理語言, 有很多字串相關函式, 其中大多數都嚴重依賴正則運算式.
# 搜尋並替換, 第一個出現的 (sub) or 所有的 (gsub)
# 都是傳回替換的個數
localvar = “fooooobar”
sub(“fo+”, “Meet me at the “, localvar) # localvar => “Meet me at the bar”
gsub(“e+”, “.”, localvar) # localvar => “m..t m. at th. bar”# 搜尋匹配正則的字串
# index() 也是搜尋, 不支援正則
match(localvar, “t”) # => 4, ‘t’在4號位置.
# (譯者註: awk是1開始計數的,不是常見的0-base)
# 按分隔符分隔
split(“foo-bar-baz”, arr, “-“) # a => [“foo”, “bar”, “baz”]# 其他有用的函式
sprintf(“%s %d %d %d”, “Testing”, 1, 2, 3) # => “Testing 1 2 3”
substr(“foobar”, 2, 3) # => “oob”
substr(“foobar”, 4) # => “bar”
length(“foo”) # => 3
tolower(“FOO”) # => “foo”
toupper(“foo”) # => “FOO”
}function io_functions( localvar) {
# 你已經見過的print函式
print “Hello world”# 也有printf
printf(“%s %d %d %d\n”, “Testing”, 1, 2, 3)# AWK本身沒有檔案控制代碼, 當你使用需要檔案的東西時會自動開啟檔案,
# 做檔案I/O時, 字串就是開啟的檔案控制代碼. 這看起來像Shell
print “foobar” >“/tmp/foobar.txt”# 現在”/tmp/foobar.txt”字串是一個檔案控制代碼, 你可以關閉它
close(“/tmp/foobar.txt”)# 在shell裡執行一些東西
system(“echo foobar”) # => prints foobar# 從標準輸入中讀一行, 並儲存在localvar中
getline localvar# 從管道中讀一行, 並儲存在localvar中
“echo foobar” | getline localvar # localvar => “foobar”
close(“echo foobar”)# 從檔案中讀一行, 並儲存在localvar中
getline localvar <“/tmp/foobar.txt”
close(“/tmp/foobar.txt”)
}# 正如開頭所說, AWK程式由一系列樣式和動作組成. 你已經看見了重要的BEGIN pattern,
# 其他的pattern在你需要處理來自檔案或標準輸入的的資料行時才用到.
#
# 當你給AWK程式傳引數時, 他們會被視為要處理檔案的檔案名, 按順序全部會處理.
# 可以把這個過程看做一個隱式的迴圈, 遍歷這些檔案中的所有行.
# 然後這些樣式和動作就是這個迴圈裡的switch陳述句一樣/^fo+bar$/ {
# 這個動作會在匹配這個正則(/^fo+bar$/)的每一行上執行. 不匹配的則會跳過.
# 先讓我們列印它:# 哦, 沒有引數, 那是因為print有一個預設引數 $0.
# $0 是當前正在處理的行, 自動被建立好了.# 你可能猜到有其他的$變數了.
# 每一行在動作執行前會被分隔符分隔. 像shell中一樣, 每個欄位都可以用$符訪問# 這個會列印這行的第2和第4個欄位
print $2, $4# AWK自動定義了許多其他的變數幫助你處理行. 最常用的是NF變數
# 列印這一行的欄位數
print NF# 列印這一行的最後一個欄位
print $NF
}# 每一個樣式其實是一個true/false判斷, 上面那個正則其實也是一個true/false判斷, 只不過被部分省略了.
# 沒有指定時預設使用當前處理的整行($0)進行匹配. 因此, 完全版本是這樣:$0 ~ /^fo+bar$/ {
print “Equivalent to the last pattern”
}a > 0 {
# 只要a是整數, 這塊會在每一行上執行.
}# 就是這樣, 處理文字檔案, 一次讀一行, 對行做一些操作.
# 按分隔符分隔, 這在UNIX中很常見, awk都幫你做好了.
# 你所需要做的是基於自己的需求寫一些樣式和動作.# 這裡有一個快速的例子, 展示了AWK所擅長做的事.
# 它從標準輸入讀一個名字, 列印這個first name下所有人的平均年齡.
# 示例資料:
#
# Bob Jones 32
# Jane Doe 22
# Steve Stevens 83
# Bob Smith 29
# Bob Barker 72
#
# 示例指令碼:BEGIN {
# 首先, 問使用者要一個名字
print “What name would you like the average age for?”# 從標準輸入獲取名字
getline name <“/dev/stdin”
}# 然後, 用給定的名字匹配每一行的第一個欄位.
$1 == name {# 這裡我們要使用幾個有用的變數, 已經提前為我們載入好的:
# $0 是整行
# $3 是第三個欄位, 就是我們所感興趣的年齡
# NF 欄位數, 這裡是3
# NR 至此為止的行數
# FILENAME 在處理的檔案名
# FS 在使用的欄位分隔符, 這裡是空格” ”
# …等等, 還有很多, 在說明檔案中列出.# 跟蹤 總和以及行數
sum += $3
nlines++
}# 另一個特殊的樣式叫END. 它會在處理完所有行之後執行. 不像BEGIN, 它只會在有輸入的時候執行.
# 它在所有檔案依據給定的樣式和動作處理完後執行, 目的通常是輸出一些最終報告, 做一些資料聚合操作.
END {
if (nlines)
print "The average age for " name " is " sum / nlines
}