歡迎光臨
每天分享高質量文章

經典 | 10分鐘速成 awk

(點選上方公眾號,可快速關註一起學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 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
}

贊(0)

分享創造快樂