源代碼下載: learnawk-cn.awk
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 a
a++
}
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
# 哦, 沒有參數, 那是因為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
}
更多:
- Awk 教程
- Awk 手冊
- The GNU Awk 用户指南 GNU Awk 在大多數 Linux 中預裝
有建議?或者發現什麼錯誤?在Github上開一個 issue ,或者發起 pull request !
原著 Marshall Mason,並由 0 個好心人修改。
© 2022 Marshall Mason
Translated by: Tian Zhipeng
本作品採用 CC BY-SA 3.0 協議進行許可。