博客 / 詳情

返回

Y 分鐘速成 AWK

源代碼下載: 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 協議進行許可。

user avatar mapvthree 頭像
1 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.