AWK/GAWK Getting Started Reference

AWK/GAWK 入門參考


【摘要】GNU AWK 的基礎參考資料。所舉的「範例」都很淺顯,能直接執行。對想以中文學習 AWK 的新手而言,應該有一定程度的幫助。



前言

▪過去為了編輯倉頡碼,收集了網路上許多版本的資料。因各版本的排版不同,就用多個程式將所需資料抽取出來,awk 便是其中之一。但是當時只是以解決問題為主,並未了解 awk 的全部功能。近日特別騰出時間將 awk 手冊從頭看到尾,用中文記錄下來,便產生了本篇網誌。

▪awk 是原版的程式,gawk 是 GNU 擴充的版本,本文以 gawk 為主。

▪本文只是入門的介紹,並不完整,但大部分的內容都包括了。

▪本文故意將參考資料中特定的英文字詞改為大寫是為了區別非特定字詞。

▪本文是入門的介紹,所舉的例子並沒有很深奧。此外,「範例」都是能執行的,各位可以複製到終端機模擬器中執行看看。

▪例子若有輸出入,會用⬧標出範圍。通常只加在左邊,顯示開頭的位置;若有必要標出結束的地方,也會加在最右邊。

▪參考資料列舉項目時,大多依字母排列;本文為了將類似的項目放在一起,有所更動。

▪某些項目有標出「這是 gawk 的擴充」,表示在 compatibility 模式(選項有-c--traditional-P--posix)下執行會失效。

▪若 shell 使用 BASH,且 awk 程式碼中有 !,要先執行 set +H。因為 BASH 對 ! 另眼看待。

▪本文測試的作業系統是自組的 Fluxbox Debian GNU/Linux 10

▪幾個月前本文曾在 Google 的 Blogger 發表過,今也在此分享。

▪如果想看 AWK 的實際應用可以看後面兩篇網誌,一篇是文字的處理「用 AWK 將 Markdown 轉為 HTML」,另一篇是數字的計算「以 AWK 轉換儒略日期與公曆日期」。

▪參考資料:

  1. man awk 2018 版。
  2. gawk-doc 中的 《GAWK: Effective AWK Programming - A User’s Guide for GNU Awk》 4.2 版。

A. 基本用語

▪awk 每次讀入的一筆資料稱為一筆 Record。將多筆 records 分開的符號稱為 Record Separator(RS)。如果輸入的資料來自檔案,檔名會存在內建變數FILENAME中。一個輸入檔的資料總筆數存在內建變數FNR中。而目前讀入的總筆數存在內建變數NR(Number of Records so far)中。

▪除空白行外,一筆 record 是由一或多項資料組成,一項資料稱為一項 Field。將多項 fields 分開的符號稱為 Field Separator(FS)。一筆 record 的 field 總項數存在內建變數NF(number of fields)中。

▪各 field 可依其位置呼叫,如:$1$2、…,而$0是整筆 record。

▪輸出時,field 分隔符設定在內建變數OFS(Output Field Separator)中。而 record 分隔符設在ORS(Output Record Separator)中。

▪awk 程式碼是由「pattern { action }」組成。「pattern」是比對模式,通常是 regular expression(以下用 regexp 表示),用來尋找要處理的對象。「action」即是處理的動作。

▪從英文名詞來看,awk 是將輸入資料視為表格。橫的一行是一筆 record,而其中的一個項目是一項 field。以下的內容會用一筆表示一 record,一項表示一 field。

▪提醒:一個 awk 程式中的名稱最好不要重複,以免出錯。這些包括:變數、引數、參數、陣列、函數、檔案…等。


B. 執行方式

執行 awk 程式的方式主要有兩種:

一、在指令行(command line)給 awk 程式碼

  awk '程式碼' INPUTS

  ▪這通常是程式碼較短時用。

  ▪INPUTS是待處理的輸入檔。若無輸入檔,通常由「標準輸入」提供。欲結束時,按Ctrl+D。若要指定INPUTS用標準輸入,可以用一短橫-/dev/stdin

  ▪若程式碼只存在於BEGIN片段中,也不需輸入檔。範例:

    awk 'BEGIN { print "您好" }'

  ▪也可以用前一指令的輸出做為輸入。範例:

    echo "您好" | awk '{ print }'

  ▪大括號外儘量用單引號 ‘{…}’;若用雙引號 “{…}",有時會行不通。

二、由檔案提供 awk 程式碼

  awk -f 程式檔 INPUTS

  ▪若程式碼較多時,此法較方便。

  ▪程式檔名若很單純,不需加引號。可用 .awk 做副檔名,以與其他檔案區別。

  ▪程式檔的第一行若如下且設為可執行檔,可直接執行。
    #! /bin/awk -f

  ▪程式檔內不需加單引號 ‘…’,但仍要用大括號 {…}。

  ▪程式檔的註解用 #,不一定要在行首,其後內容一律忽略。

  範例:尋找含有 4 的行,並列出該行的第三個 field 的內容。

  input 檔內容(不含⬧,下同):

  ⬧A: 123    甲: abc    子: XYZ
  ⬧B: 234    乙: bcd    丑: RST
  ⬧C: 345    丙: cde    寅: HIJ

  執行:

  awk '/4/ { print $3 }' input

  顯示(不含⬧,下同):

  ⬧乙:
  ⬧丙:

  說明:

  ▪/4/用斜線包起來的是 regexp 常數,屬於一種 pattern。

  ▪{ print $3 }是對符合比對模式的內容要做的動作。

  ▪print是顯示出來。

  ▪$3是指第三項,即第三個 field。各項間預設是用空白分開。


C. 選項

-f 程式檔
--file 程式檔
指定程式碼所在的檔案。

  ▪可多個。


-F 分隔符
--field-separator 分隔符
指定讀入時各項之間的分隔符(FS)。

  ▪預設為空白(空格或跳格)。

  ▪若要指定是跳格,-F後面用下面任一種皆可(當然,通常只會用最簡單的):
    ‘\t’、’\\t’、’\\\t’、
    “\t"、"\\t"、"\\\t"、"\\\\t"、"\\\\\t"、"\\\\\\t"、
    \\t、\\\t、\\\\t、\\\\\t、\\\\\\t、\\\\\\\t

    ▪若在BEGIN中設定,只有FS="\t"FS="\\t"FS="\\\t"有效。

    ▪測試系統:Debian GNU/Linux 10,無-c-P選項,即非 compatibility 模式。

  ▪若要指定是反斜線,-F後面用下面任一種皆如預期(當然,通常只會用最簡單的):
    ‘\’、’\\’、’\\\’、’\\\\’、
    “\\"、"\\\\"、"\\\\\\"、"\\\\\\\\"、
    \\、\\\\、\\\\\\、\\\\\\\\

    ▪若在BEGIN中設定,只有FS="\\"FS="\\\\"有效。

    ▪測試系統:Debian GNU/Linux 10,無-c-P選項,即非 compatibility 模式。


-v 變數=值
--assign 變數=值
在執行程式前設定變數值。

  ▪一個-v設定一個變數,可有多個-v

  ▪字串用雙引號包起。

  ▪變數在BEGIN段即有效。

  ▪不要用來設定內建變數。

  ▪呼叫變數時,前面不需加 $,如下例的name

  範例:

  awk -v name="AWK" 'BEGIN { print "Hello, " name "." }'

以下是部分常用的 gawk 擴充選項:

-c
--traditional
以 compatibility 模式執行。

  ▪也就是不用 GNU 的擴充功能。


-e 程式碼
--source 程式碼
在指令行提供程式碼

  ▪可多個。

  ▪通常用在附加額外的指令。例如:

    awk -e 'PATTERN { ACTION }' -f SOURCE.awk INPUTS

-i 程式檔
--include 程式檔
從環境變數AWKPATH所列的路徑載入程式檔

  ▪「-i和檔名之間有無空格」與「檔名有無引號」都可以。但檔名若有空格,要用引號。

  ▪參考資料中說AWKPATH的預設值是/usr/local/share/awk。但是執行:

    awk 'BEGIN { print ENVIRON["AWKPATH"] }'

    卻顯示:

    .:/usr/share/awk

    (其中的 . 是目前的目錄;而 : 是將不同路徑隔開的分隔符。)

    所以最好先用上述指令找出自己的預設路徑,不同的系統可能不一樣。本例作業系統是 Debian GNU/Linux 10。

  ▪若要在執行 awk 程式前更改程式檔的路徑,可在 awk 指令前設定。例如:

    AWKPATH="$HOME:$HOME/.local/bin:." awk -f 程式檔 INPUTS

  ▪提醒:這是找程式檔,不是找輸入檔。


▪用來加入其他 awk 程式碼的方法除了-f-i選項外,也可用@include。例如在程式檔頭用:

#!/bin/awk -f
@include "SOURCE1"
@include "/PATH/TO/SOURCE2"

  ▪若檔案在AWKPATH中,不需用全路徑;反之則需。


▪通常指令行中非選項的引數被視為檔名,除非具有var=value格式,這是設定變數值。

  ▪這可以用來設定針對某檔才用的變數,例如:

    awk -f SOURCE.awk INPUT1 flag=1 INPUT2

    ▪flag=1是針對INPUT2設定的,對INPUT1無效。


C1. 提醒

-e選項提供的程式碼是「或」,也就是這個會執行,其他的程式碼也會執行。譬如有一個-e SOURCE1和一個-f FILE1,則SOURCE1FILE1都會執行。若有兩個-e,也是兩個都會執行。

範例:

input 檔內容:

⬧A: 123    甲: abc    子: XYZ
⬧B: 234    乙: bcd    丑: RST
⬧C: 345    丙: cde    寅: HIJ

執行:

awk -e '/1/' -e '/5/' input

顯示:

⬧A: 123    甲: abc    子: XYZ
⬧C: 345    丙: cde    寅: HIJ

也就是有 1 或有 5 的行都會處理,不是同時有 1 和 5 才會。

這個指令是由下面的指令省略而來:

awk -e '/1/ { print }' -e '/5/ { print }' input

awk -e '/1/ { print $0 }' -e '/5/ { print $0 }' input

因為預設的 action 是print,而print的預設項目是$0(整筆資料)。

不過,不能用:

awk '/1/ || /5/' input

雖然顯示的內容一樣,但機制不同。為什麼機制不同而結果一樣呢?因為 1 和 5 沒有出現在同一行;也就是「巧合」。若是找/2//3/,就可以看出差異了。

執行:

awk -e '/2/' -e '/3/' input

顯示:

⬧A: 123    甲: abc    子: XYZ
⬧A: 123    甲: abc    子: XYZ
⬧B: 234    乙: bcd    丑: RST
⬧B: 234    乙: bcd    丑: RST
⬧C: 345    丙: cde    寅: HIJ

執行:

awk '/2/ || /3/' input

顯示:

⬧A: 123    甲: abc    子: XYZ
⬧B: 234    乙: bcd    丑: RST
⬧C: 345    丙: cde    寅: HIJ

從這兩個結果就可以知道:機制是讀入一行後,前指令是先找有沒有 2;有,就執行其預設的動作 —— 顯示該行;然後「同一行」繼續找有沒有 3,也有,就顯示該行。比對兩次,所以會「連續」輸出兩次。而後指令是讀入一行後,找有無 2 或 3,只比對一次,所以只輸出一次。

如果要同時有 2 也有 3,就用:

awk '/2/ && /3/' input

D. 常用內建變數

ARGC
指令行(command line)中引數的數目(argument count)。

  ▪不含選項。

  ▪直接設定新值即可增減。


ARGV
指令行中引數所構成的陣列(array)。

  ▪直接設定就可以新增;要去掉的話,將之設成空字串 “",或用delete敘述。


ARGIND
ARGV 的索引(index)。

  ▪從 0 到 ARGC-1。

  ▪這是 gawk 的擴充。

  範例:

  input1 檔內容:

  ⬧Input one

  input2 檔內容:

  ⬧Input two

  input3 檔內容:

  ⬧Input three

  執行:

  awk -v a=1 'BEGIN {
    print "剛開始的 ARGC:"ARGC
    for (i=0; i<ARGC; i++) print "索引:"i, "; 值:"ARGV[i]
  } {
    print "索引:"ARGIND, "; 內容:"$0
  } END {
    ARGC=7
    ARGV[6]="input3"
    getline
    print "最後的 ARGC:"ARGC, "; 新增的檔案:"ARGV[6], "; 內容:"$0
  }' b=2 input1 c=3 input2

  顯示:

  ⬧剛開始的 ARGC:5
  ⬧索引:0 ; 值:awk
  ⬧索引:1 ; 值:b=2
  ⬧索引:2 ; 值:input1
  ⬧索引:3 ; 值:c=3
  ⬧索引:4 ; 值:input2
  ⬧索引:2 ; 內容:Input one
  ⬧索引:4 ; 內容:Input two
  ⬧最後的 ARGC:7 ; 新增的檔案:input3 ; 內容:Input three

  說明:

  ▪並沒有包括a=1,因為是用-v選項設定的。而後面的b=2c=3則有。

  ▪ARGIND是正在處理的檔案。

  ▪input3是在END段新增的,要用getline敘述才會讀取,不然$0仍是Input two


SYMTAB
程式中所有全域(global)變數、陣列的值所形成的陣列。

  ▪SYMTAB的索引(index)即是這些變數、陣列的名稱。

  ▪所形成的陣列不含SYMTABFUNCTAB

  ▪可以用SYMTAB更改其內變數值、陣列值。

  ▪可以用SYMTAB新增變數、陣列,但不能刪除。

  ▪這是 gawk 的擴充。

  範例:input 檔只有一個空格

  執行:

  awk -v a=1 '{
    print SYMTAB["FILENAME"]
    print SYMTAB["a"]
    b=2
    print SYMTAB["b"]
    SYMTAB["a"]=3
    SYMTAB["c"]=a+b
    print SYMTAB["c"]
  }' input

  顯示:

  ⬧input
  ⬧1
  ⬧2
  ⬧5

ENVIRON
目前環境變數的陣列,用環境變數名做索引。

  範例:

  awk 'BEGIN { print ENVIRON["PATH"] }'

  顯示:

  ⬧/usr/local/bin:/usr/bin:/bin

FILENAME
目前輸入檔的名稱。若在指令行中未指定,則是-


FNR
目前輸入檔讀入之 record 筆數(Record Number)。

  ▪可以修改。

  範例:input 檔有 7 行資料

  執行:

  awk 'NR == 3 {
    FNR=10
    NR=20
  } {
    print FNR, NR
  }' input

  顯示:

  ⬧1 1
  ⬧2 2
  ⬧10 20
  ⬧11 21
  ⬧12 22
  ⬧13 23
  ⬧14 24

  說明:

  ▪到第三筆資料時,更改設定值,其後的值也都跟著變。


NR
目前讀入的 record 總筆數(Number of Records so far)。

  ▪多檔也一直累計。

  ▪可以修改。

  範例:

  input 檔內容:

  ⬧Line one
  ⬧Line two

  執行:

  awk '{ print FILENAME, FNR, $0, NR }' input input

  顯示:

  ⬧input 1 Line one 1
  ⬧input 2 Line two 2
  ⬧input 1 Line one 3
  ⬧input 2 Line two 4

RS
讀入時,record 的分隔符(Record Separator)。

  ▪預設為換行符(\n)。


ORS
輸出時,record 的分隔符(Output Record Separator)。

  ▪預設為換行符(\n)。


RT
一筆 record 的結束符(Record Terminator)。

  ▪預設為RS值。

  ▪這是 gawk 的擴充。


FPAT
描述 field 內容的 regexp(Field PATtern)。

  ▪若有設定,以此模式讀入。

  ▪這是 gawk 的擴充。


NF
目前 record 的 field 總項數(Number of Fields)。


FS
讀入時,field 的分隔符(Field Separator)。

  ▪預設是空白(space「 」或 tab「\t」)。


OFS
輸出時,field 的分隔符(Output Field Separator)。

  ▪預設為單一空格(space「 」)。


SUBSEP
多維陣列的引數分隔符(SUBscript SEParator)。

  ▪預設為\034,一個無法顯示又很少用的字元。


CONVFMT
將數字轉換為文字的格式(CONversion ForMaT)。

  ▪預設為%.6g小數下六位,用%e%f中比較短的。


OFMT
print輸出時,將數字轉換為文字的格式(Output ForMaT)。

  ▪預設為%.6g。小數下六位,用%e%f中比較短的。

  ▪要控制數字的輸出格式,用printf較精確。若用print,則設定此變數。

  範例:

  awk 'BEGIN {
    OFMT="%08.3f"
    print 12.34
    print 56
    print .78
    print -9.
  }'

  顯示:

  ⬧0012.340
  ⬧56
  ⬧0000.780
  ⬧-9

FIELDWIDTHS
field 寬度的列表,以空格分隔。

  ▪若有設定,以此固定寬度讀入。

  ▪這是 gawk 的擴充。


IGNORECASE
控制 regexp 和字串的處理是否要忽略大小寫(IGNORE CASE)。

  ▪預設為 0,要分別大小寫;若非 0,則不分。

  ▪這是 gawk 的擴充。


PREC
浮點數的精確度(PRECision)。

  ▪預設為 53。

  ▪這是 gawk 的擴充。


ROUNDMODE
捨入法(ROUNDing MODE)。

  ▪預設為 N 或 n(roundTiesToEven);另有 U 或 u(roundTowardPositive)、D 或 d(roundTowardNegative)、Z 或 z(roundTowardZero)、A 或 a(roundTiesToAway)。

  ▪這是 gawk 的擴充。


RSTART
match()找到的字串中第一個字元的索引(index)。

  ▪若無符合者,則為 0。


RLENGTH
match()找到的字串長。

  ▪若無符合者,則為 -1。


ERRNO
錯誤訊息。

  ▪這是 gawk 的擴充。


PROCINFO
設定執行方式。

  ▪這是 gawk 的擴充。以下列舉部分較常用的項目。

  ▪PROCINFO["strftime"]strftime()的預設時間格式。

  ▪PROCINFO["NONFATAL"]:若有此項,所有輸出入的重導錯誤(redirection error)皆非致命性。

  ▪PROCINFO["輸入", "READ_TIMEOUT"]:從「輸入」讀取時,所限定的時間。單位:毫秒(千分之一秒)。

  ▪PROCINFO["輸入", "RETRY"]:從「輸入」讀取若有錯,是否要重試。

  ▪PROCINFO["sorted_in"]:排序方法;可用值有:

    @ind_num_asc:將索引視為數字升階排序;若非數字,視為 0。
    @ind_num_desc:將索引視為數字降階排序;若非數字,視為 0。
    @ind_str_asc:將索引視為字串升階排序。
    @ind_str_desc:將索引視為字串降階排序。
    @unsorted:不排序;此為預設值。
    @val_num_asc:將值視為數字升階排序;若有子陣列,排在最後;若數字值相等,用字串值排序。
    @val_num_desc:將值視為數字降階排序;若有子陣列,排在最前。
    @val_str_asc:將值視為文字升階排序;若有子陣列,排在最後。
    @val_str_desc:將值視為文字降階排序;若有子陣列,排在最前。
    @val_type_asc:依值之類型升階排序(數字、字串、子陣列)。
    @val_type_desc:依值之類型降階排序(子陣列、字串、數字)。
    自訂的比較函數名。

    ▪這要在BEGIN段設定。

    ▪這個設定是全域的(global),也就是說對任何陣列的排序都有效。若要更改,先將原設定用一個變數儲存起來,待要復原時再改回來。如果是要改回預設值,可設成空字串。


D1. 內建變數特論:RS(Record Separator)

讀入時,record 的分隔符。

▪若未設定,awk 將輸入檔每一行視為一筆 record。

▪若將RS設為某一字元,就用它做分隔符。

  範例:

  input 檔內容:

  ⬧A: 123    甲: abc    子: XYZ
  ⬧B: 234    乙: bcd    丑: RST
  ⬧C: 345    丙: cde    寅: HIJ

  執行:

  awk 'BEGIN { RS=":" } { print }' input

  顯示:

  ⬧A
  ⬧ 123    甲
  ⬧ abc    子
  ⬧ XYZ
  ⬧B
  ⬧ 234    乙
  ⬧ bcd    丑
  ⬧ RST
  ⬧C
  ⬧ 345    丙
  ⬧ cde    寅
  ⬧ HIJ
  ⬧

    ▪如果 input 檔最後一行末有換行符(\n),輸出的最後一行看起來會多一個空白行,這是因為原本的 \n 都保留著,雖然它現在不是RS,但它的換行功能仍在。如果用預設的RS(也就是 \n),就不會多,因為被最後一行末的 \n 所分開的後半是空字串。

    ▪此例設RS為冒號,RT(Record Terminator)也變成冒號。

▪若RS非單一字元,則視為 regexp。凡符合它的,就被當做分隔符。但在 compatibility 模式執行時,只有第一個字元會被當做分隔符。

▪如果將RS設為空字串(""),則用空白行做各筆的分隔符;而 \n 會被當做每項的分隔符,原先的FS也是每項的分隔符。此時不論連續空幾行都當做「一個」分隔符,如果它們不含空格或跳格的話。可以利用這個現象來刪除空白行。

  範例:(刪除 input 的空白行)

  awk 'BEGIN { RS="" } { print }' input > output

  範例:(一筆資料含有多行的內容)

  input 檔內容:

  ⬧
  ⬧
  ⬧A: 123
  ⬧甲: abc    子: XYZ
  ⬧
  ⬧B: 234    乙: bcd
  ⬧丑: RST
  ⬧
  ⬧
  ⬧C: 345
  ⬧丙: cde
  ⬧寅: HIJ

  執行:

  awk 'BEGIN { RS=""; OFS="-" } {
    print $1, $2, $3, $4, $5, $6, NF, NR
  }' input

  顯示:

  ⬧A:-123-甲:-abc-子:-XYZ-6-1
  ⬧B:-234-乙:-bcd-丑:-RST-6-2
  ⬧C:-345-丙:-cde-寅:-HIJ-6-3

    ▪通常這種一筆資料跨有多行時,會用空白行分開各筆資料,以利閱讀。而將RS設成空字串時,會用空白行做 record 分隔符。所以剛好可以利用這種特性來處理這種狀況。

    ▪這個指令最後有顯示筆數(NR),可見的確是將輸入資料分成三筆。

    ▪輸入檔在第一筆之前有多個空白行,都會被忽略。

    ▪第二筆和第三筆之間空了兩行,也是被當成一個分隔符。

▪將RS設成空字串和設成"\n\n+"幾乎有相同的效果,都是用一或多個空白行做 record 分隔符。但二者之間有一明顯的差異:後者不會忽略第一筆之前的空白行。

  範例:輸入檔同上

  執行:

  awk 'BEGIN { RS="\n\n+"; OFS="-" } {
    print $1, $2, $3, $4, $5, $6, NF, NR
  }' input

  顯示:

  ⬧------0-1
  ⬧A:-123-甲:-abc-子:-XYZ-6-2
  ⬧B:-234-乙:-bcd-丑:-RST-6-3
  ⬧C:-345-丙:-cde-寅:-HIJ-6-4

D2. 內建變數特論:FS(Field Separator)

讀入時,field 的分隔符。

▪一筆 record 是由一或多項 fields 組成。將 fields 分開的稱為 Field Separator(FS),預設是空白,這可以是空格(space「 」)或跳格(tab「\t」)。

▪如果FS設成單一字元,就用它做各項的分隔符。但有一特殊狀況:若FS設成單一空格,即FS=" ",則空格、跳格(\t)、換行(\n)都是分隔符。

▪若FS設成空字串(""),則每一字元(包括空白)都會被視為一項。這是 gawk 的擴充。

▪若FS設成多字元,則視為 regexp。

▪若有先設定FIELDWIDTHS,可以用固定的寬度讀入資料。

▪field 可依其位置呼叫,如:$1$2、…,而$0是整筆 record。

▪$ 後可以是敘述式,例如$(2+3),awk 會計算其結果,呼叫$5

▪如果有先設定FIELDWIDTHS,則忽略FS。但若重新設定FSFPAT,則忽略FIELDWIDTHS

▪類似地,若有先設定FPAT,便以此為準。但若重新設定FSFIELDWIDTHS,則忽略FPAT

▪內建變數IGNORECASE的設定會影響到RSFS。若設定IGNORECASE=1忽略大小寫,但FS設成單一字母,如FS="x",仍然只有小寫 x 有效。若單一字母要忽略大小寫,要用 regexp 的形式,如FS="[x]"。若是多字母則沒有關係,因為多字元自動被當做是 regexp。

FS=" "FS="[ \t\n]+"的差異:

  上面曾說:若設FS=" ",則空格、\t、\n 都是分隔符。但這和設定FS="[ \t\n]+"有些不同。雖然這兩種狀況這三個的確都是分隔符,但 awk 處理的方式不同,結果也不一樣。以下兩個範例所列舉的輸出入項目一樣,輸出分隔符(OFS)都設成一短橫,只有一開始讀入分隔符的設定方式不同,試看兩者結果的差異。

  執行:

  echo " 甲 乙 丙 丁 " |
  awk 'BEGIN { FS=" "; OFS="-" } {
    print $1, $2, $3, $4, $5, $6, NF
  }'

  顯示:

  ⬧甲-乙-丙-丁---4

  執行:

  echo " 甲 乙 丙 丁 " |
  awk 'BEGIN { FS="[ \t\n]+"; OFS="-" } {
    print $1, $2, $3, $4, $5, $6, NF
  }'

  顯示:

  ⬧-甲-乙-丙-丁--6

    ▪為什麼會有這種差異?連總項數(NF)都不一樣。
      因為當FS用預設值或設定FS=" ",awk 會先去除頭尾的空白,才繼續處理。而設定FS="[ \t\n]+",則不會。

    ▪前例只有「甲、乙、丙、丁」四項,而後者有「空字串、甲、乙、丙、丁、空字串」六項。


D3. 內建變數特論:NF(Number of Fields)

一筆 record 的 field 總項數。

▪一筆 record 的最後一項可以用$NF呼叫。

▪如果呼叫超過總項數(假設NF是 5)的 field(假設是$7)會得到空字串,但不會更改NF

  範例:

  input 檔內容:

  ⬧A: 123    甲: abc    子: XYZ
  ⬧B: 234    乙: bcd    丑: RST
  ⬧C: 345    丙: cde    寅: HIJ

  用冒號做分隔符,輸出時 field 用短橫分開,執行:

  awk 'BEGIN { FS=":" } {
    print $1 "-" $2 "-" $3 "-" $4 "-" $5 "-" NF
  }' input

  顯示:

  ⬧A- 123    甲- abc    子- XYZ--4
  ⬧B- 234    乙- bcd    丑- RST--4
  ⬧C- 345    丙- cde    寅- HIJ--4

    ▪雖然呼叫五項,NF仍是 4,多呼叫的$5介於兩個連續短橫之間,是空字串。

▪如果呼叫負數的 field(例如$-2)是致命錯誤。

▪如果更改某一 field 為空字串,並不會改變NF

▪可以設定超過NF的 field,NF會增加到其數。此外,$0也會變。

  範例:

  input 檔內容:

  ⬧A: 123    甲: abc    子: XYZ
  ⬧B: 234    乙: bcd    丑: RST
  ⬧C: 345    丙: cde    寅: HIJ

  執行:(為了縮短篇幅,只輸出末行的結果,所以加END

  awk 'END {
    print $0
    print NF
    $(NF+2)="xyz"
    print $0
    print NF
  }' input

  在設定額外項的前後都列出$0NF,顯示:

  ⬧C: 345    丙: cde    寅: HIJ
  ⬧6
  ⬧C: 345 丙: cde 寅: HIJ  xyz
  ⬧8

    ▪第一次顯示的$0仍保留輸入檔的間距;但設定新值時,awk 重新計算$0,便採用預設的輸出分隔符(OFS),所以間距都只剩單一空格。

    ▪輸出的第三行在HIJxyz之間有兩個空格,因為$7是空字串。

▪降低NF,會失去超過其值的項目,$0也會變。

  範例:

  input 檔內容:

  ⬧A: 123    甲: abc    子: XYZ
  ⬧B: 234    乙: bcd    丑: RST
  ⬧C: 345    丙: cde    寅: HIJ

  執行:(為了縮短篇幅,只輸出末行的結果,所以加END

  awk 'END {
    print $0
    print NF
    NF=4
    print $0
    print NF
  }' input

  在降低NF的前後都列出$0NF,顯示:

  ⬧C: 345    丙: cde    寅: HIJ
  ⬧6
  ⬧C: 345 丙: cde
  ⬧4

D4. 內建變數特論:FIELDWIDTHS

以固定寬度讀入資料。

一、FIELDWIDTHS的內容是一系列用空格分開的數字,每一數字是讀入一項的寬度。

  範例:

  input 檔內容:

  ⬧日期  摘要  收入  支出  餘額
  ⬧0301  購物     0  2050  2345
  ⬧0302  電費     0   234  2111
  ⬧0302  購物     0   543  1568
  ⬧0303  保費     0  1230   338
  ⬧0304  提款  2000     0  2338
  ⬧0305  購物     0   456  1882

  執行:

  awk 'BEGIN {
    FIELDWIDTHS="4 4 6 6 6"
    OFS="-"
  } NR >= 2 {
    print $1, $2, $3, $4, $5, NF
  }' input

  顯示:

  ⬧0301-  購物-     0-  2050-  2345-5
  ⬧0302-  電費-     0-   234-  2111-5
  ⬧0302-  購物-     0-   543-  1568-5
  ⬧0303-  保費-     0-  1230-   338-5
  ⬧0304-  提款-  2000-     0-  2338-5
  ⬧0305-  購物-     0-   456-  1882-5

    ▪設定OFS是為了容易區分各項。

    ▪NR >= 2是一個 pattern,符合此條件的才處理。也就是從第二筆資料開始才採取後述的動作。

    ▪指令最後顯示總項數NF,除了可以知道項數外,還能知道第五項是在何處結束。但有一個問題,如果FIELDWIDTHS最後一項設定太小或太大會如何?上面設定最後項的寬度是 6,若設 4 或 8 呢?

    ▪第二項寬度設成 4,結果顯示兩個空格和兩個中文;由此可知,中文一個字是被當成一個字元。

  ▪上面顯示的結果全如預期。但是如果用來處理第一筆資料會如何?執行:

    awk 'BEGIN {
      FIELDWIDTHS="4 4 6 6 6"
      OFS="-"
    } NR == 1 {
      print $1, $2, $3, $4, $5, NF
    }' input

    結果如下:

    ⬧日期  -摘要  -收入  支出-  餘額--4

      ▪先提醒:NR == 1是兩個等號,因為這裡要的是 pattern,不是設定變數。

      ▪這個結果明顯不是想要的,原因也是因為一個中文字被當成一個字元。若要如一般預期地顯示,要如何設定FIELDWIDTHS?請自己想想看。

  ▪有的時候會看到一些表格,左邊的各欄寬度都很整齊,但最右邊一欄是備註,而其內容或多或少,不太固定。這時要如何設定最後一項的寬度呢?很簡單,末項用一個 * 就可以了。如:FIELDWIDTHS="4 4 6 6 *"

二、FIELDWIDTHS 的設定還有一種方法。

  範例:

  input 檔內容:

  ⬧日期  摘要  收入  支出  餘額
  ⬧0301  購物     0  2050  2345
  ⬧0302  電費     0   234  2111
  ⬧0302  購物     0   543  1568
  ⬧0303  保費     0  1230   338
  ⬧0304  提款  2000     0  2338
  ⬧0305  購物     0   456  1882

  執行:

  awk 'BEGIN {
    FIELDWIDTHS="4 2:2 2:4 2:4 2:4"
    OFS="-"
  } NR >= 2 {
    print $1, $2, $3, $4, $5, NF
  }' input

  顯示:

  ⬧0301-購物-   0-2050-2345-5
  ⬧0302-電費-   0- 234-2111-5
  ⬧0302-購物-   0- 543-1568-5
  ⬧0303-保費-   0-1230- 338-5
  ⬧0304-提款-2000-   0-2338-5
  ⬧0305-購物-   0- 456-1882-5

    ▪有沒有發現:每一項前面的空格都被去掉了。

  ▪這種方法在冒號前的數字是「要被去掉的寬度」,後面的數字是「要取用的寬度」,兩者之和即是第一種方法所設的寬度。

三、特殊狀況。

  ▪如果輸入檔的項數有五項,但FIELDWIDTHS只有四項,或有六項,awk 會如何處理?
    答案是:前者只取四項,也就是NF=4,捨棄多的資料。後者只取五項,NF=5,捨棄多的設定。

  例如輸入:1234567890

  若FIELDWIDTHS="2 4"
  結果:NF=2$1為 12,$2為 3456。

  若FIELDWIDTHS="4 4 4 4 4"
  結果:NF=3$1為 1234,$2為 5678,$3為 90。

  若FIELDWIDTHS="2 4 *"
  結果:NF=3$1為 12,$2為 3456,$3為 7890。


D5. 內建變數特論:FPAT(Field Pattern)

描述一筆資料中各項內容的 regexp。

▪這是 gawk 的擴充。

▪因內容不是固定的,所以這個內建變數通常用來找「非分隔符的內容」。

  例如想要列出一個檔案路徑各個名稱,因名稱不固定,而路徑用 / 做分隔符,所以找「非 / 的部分」。

  範例:

  執行:

  echo "/usr/share/awk/gettime.awk" |
  awk 'BEGIN {
    FPAT="[^/]+"
    OFS="\n"
  } {
    print $1, $2, $3, $4, $5, NF
  }'

  顯示:

  ⬧usr
  ⬧share
  ⬧awk
  ⬧gettime.awk
  ⬧
  ⬧4

    ▪如果 regexp 中有方括號,其內第一字元若是 ^,表示尋找非後面所列的項目。所以[^/]是找「非/」 的內容。加號是指至少一個字元。

    ▪如所預期的,總項數是 4;因為沒有第五項,所以有一空白行。

▪這種方式和直接設定FS="/"有何不同?

  執行:

  echo "/usr/share/awk/gettime.awk" |
  awk 'BEGIN {
    FS="/"
    OFS="\n"
  } {
    print $1, $2, $3, $4, $5, NF
  }'

  顯示:

  ⬧
  ⬧usr
  ⬧share
  ⬧awk
  ⬧gettime.awk
  ⬧5

    ▪被第一個 / 分開的左邊是一空字串,所以輸出的第一行是空白行,而總項數是 5。


D6. 內建變數特論:ORS 和 OFS

print輸出時的RSFS

▪一個print敘述所顯示的內容是一筆輸出 record。ORS(Output Record Separator)即是輸出 records 之間的分隔符,預設為換行符(\n)。所以,一個print敘述預設是輸出一行資料。而此一行資料各 fields 之間的分隔符即是OFS(Output Field Separator),預設為單一空格(space「 」)。

▪要設定輸出的格式,就要先設定ORSOFS。這通常是放在BEGIN段,也就是在輸出任何資料之前就要先設定好。另外也可以在指令行中設定,放在輸入檔之前或用-v選項。

  範例:

  input 檔內容:

  ⬧日期  摘要  收入  支出  餘額
  ⬧0301  購物     0  2050  2345
  ⬧0302  電費     0   234  2111
  ⬧0302  購物     0   543  1568
  ⬧0303  保費     0  1230   338
  ⬧0304  提款  2000     0  2338
  ⬧0305  購物     0   456  1882

  改為 csv(comma-separated values)檔以便使用試算表程式,執行:

  awk 'BEGIN { OFS="," } {
    $1=$1
    print $0
  }' input > output.csv

  output.csv 內容為:

  ⬧日期,摘要,收入,支出,餘額
  ⬧0301,購物,0,2050,2345
  ⬧0302,電費,0,234,2111
  ⬧0302,購物,0,543,1568
  ⬧0303,保費,0,1230,338
  ⬧0304,提款,2000,0,2338
  ⬧0305,購物,0,456,1882

  ▪上例輸出的內容是所預期的,沒什麼問題,但為什麼要有$1=$1這個設定?

    ▪先讓我們回顧前面的範例(在「內建變數特論:NF」一節):在降低NF的前後都列出$0NF

      awk 'END {
        print $0
        print NF
        NF=4
        print $0
        print NF
      }' input

      結果顯示:

      ⬧C: 345    丙: cde    寅: HIJ
      ⬧6
      ⬧C: 345 丙: cde
      ⬧4

    ▪awk 是依序執行程式碼的。在執行第一個print $0時,內容沒有改變,所以輸出原樣。中間改了NF,awk 便重新建立$0,也就是將$1$NF重新用OFS連接。因為OFS的預設值是單一空格,所以第二個print $0所顯示的間隔都只剩單一空格。

    ▪這就是為什麼要有$1=$1的原因 —— 讓 awk 重新計算$0,這樣才會輸出新的內容。當然,用其他項也可以,例如$2=$2

    ▪前面BEGIN段設定OFS只改變了內建變數,並沒有動到資料內容。


▪更改ORS,一個可能的用處是將分散的多個段落連接成一段,例如:

  awk -v ORS=" " '{ $1=$1; print }' input > output

▪另一個用處是將數個沒有間隔的段落用空白行分開,例如:

  awk '{ $1=$1; print }' ORS="\n\n" input > output

E. 更改 field 的內容

範例:

input 檔內容:

⬧A: 123    甲: abc    子: XYZ
⬧B: 234    乙: bcd    丑: RST
⬧C: 345    丙: cde    寅: HIJ

執行:

awk '{ $2=$2-100; print }' input

顯示:

⬧A: 23 甲: abc 子: XYZ
⬧B: 134 乙: bcd 丑: RST
⬧C: 245 丙: cde 寅: HIJ

執行:

awk '{ $1=$3 $4; print }' input

顯示:

⬧甲:abc 123 甲: abc 子: XYZ
⬧乙:bcd 234 乙: bcd 丑: RST
⬧丙:cde 345 丙: cde 寅: HIJ

  ▪前指令是數字的運算,後者是字串的銜接,結果皆如預期。如果數字和字串之間做運算或銜接會如何?

執行:

awk '{ $2=$3-100; print }' input

顯示:

⬧A: -100 甲: abc 子: XYZ
⬧B: -100 乙: bcd 丑: RST
⬧C: -100 丙: cde 寅: HIJ

執行:

awk '{ $2=100-$3; print }' input

顯示:

⬧A: 100 甲: abc 子: XYZ
⬧B: 100 乙: bcd 丑: RST
⬧C: 100 丙: cde 寅: HIJ

  ▪由此可知,文字數字若做數學運算,文字會被當成 0。

執行:

awk '{ $1=$2 $4; print }' input

顯示:

⬧123abc 123 甲: abc 子: XYZ
⬧234bcd 234 乙: bcd 丑: RST
⬧345cde 345 丙: cde 寅: HIJ

執行:

awk '{ $1=$4 $2; print }' input

顯示:

⬧abc123 123 甲: abc 子: XYZ
⬧bcd234 234 乙: bcd 丑: RST
⬧cde345 345 丙: cde 寅: HIJ

  ▪由此可知,文字數字若做銜接,數字會被當成字串。

▪從上面這些範例可知:更改某項的內容,$0會跟著變。類推:更改$0,fields 也會變。

▪另外也可以知道:不論原先是文字還是數字,當進行數學運算時,會轉成數字;當進行文字連接時,會被當成文字。

▪所以,若要讓一個變數被當做數字,就把它加上 0。
  例如A="12"A是文字。若設定B=A+0,則B是數字。

▪如果要讓一個變數被當做文字,就把它連上空字串「""」。
  例如A=12A是數字。若設定B=A"",則B是文字。

  範例:

  awk -v a=100hundred100 'BEGIN { b=2; c=3; print a-(b c) }'

  顯示:

  ⬧77

  說明:

  ▪在小括號中的b c是字串連接的型式,就把它們當成文字連起來變成 23。

  ▪小括號外的減號是數字運算的型式,就把 100hundred100 和文字的 23 轉成數字再做數學運算。

  ▪100hundred100 轉成數字時,只取開頭可以轉換的部分,其餘的捨棄;若開頭是文字,如 hundred100,則視為 0。

▪地區設定(locale)會影響轉換,因為有些地方小數點不是用句號。

▪比較下面八種狀況的輸出:

  echo ' +3.14' | awk '{ print ($0 == " +3.14") }'
  echo ' +3.14' | awk '{ print ($0 == "+3.14") }'
  echo ' +3.14' | awk '{ print ($0 == "3.14") }'
  echo ' +3.14' | awk '{ print ($0 == 3.14) }'
  echo ' +3.14' | awk '{ print ($1 == " +3.14") }'
  echo ' +3.14' | awk '{ print ($1 == "+3.14") }'
  echo ' +3.14' | awk '{ print ($1 == "3.14") }'
  echo ' +3.14' | awk '{ print ($1 == 3.14) }'

  分別顯示:

  ⬧1 0 0 1 0 1 0 1

  說明:

  ▪1 為 true, 0 為 false。

  ▪$0含前面的空格,$1沒有。

  ▪第一和第六種狀況是相同的文字;第四和第八是相同的數字;其餘是不相同的文字,差在有無空格和正號。

  ▪凡是和文字做比較,皆視為文字。


F. 常數表述式(Constant Expression)

最簡單的表述式是常數,包括文字、數字、regexp。

一、在 awk 中,文字用雙引號 “…" 包起來,數字則不用。但有一些是如下的跳脫符。

  ▪\a、\b、\f、\n、\r、\t、\v 如常。

  ▪\c 表字母 c。

  ▪\\ 表一個反斜線。

  ▪\xhex 表十六進位數字所對應的 ASCII 字元(0-7F)。

  ▪\N \NN \NNN 表八進位數字所對應的 ASCII 字元(0-177)。

  範例:

  awk 'BEGIN { print "\e", "\\7e", "~", "\x7e", "\176" }'

  顯示:

  ⬧e \7e ~ ~ ~

二、數字包括整數、浮點數、科學記號,這些是十進位數字。awk 將所有的數字都當成浮點數。

  ▪gawk 另有八進位和十六進位數字。八進位用0NN表示。例如:八進位的 011 = 十進位的 9。十六進位用0xNN0XNN表示。例如:十六進位的 0x11 = 十進位的 17。

  ▪要注意的是八進位沒有 8 和 9,所以如果數字以 0 開頭又有 8 或 9,會有問題。

  ▪參考資料 2 說018會被當成十進位 18,但執行:

    awk 'BEGIN { print 017, 018, 019, 020 }'

    顯示:

    ⬧15 0 0 16

    ▪本例作業系統是 Debian GNU/Linux 10。

三、regexp 常數要用斜線包起來/.../

  範例:尋找含有 2 的那筆資料,並顯示其第三項

  echo -e "1 a A\n2 b B\n3 c C" |
  awk '$0 ~ /2/ { print $3 }'

  顯示:

  ⬧B

  ▪regexp 常數不能直接設定為變數,譬如:reg=/2/是錯的。此設定的實際意義是將「用/2/比對的結果」設為變數 reg,也就是說 reg 不是 0 就是 1,而不是預期的/2/。gawk 修正了這個問題,在 regexp 常數前加 @ 即可,如:reg=@/2/。gawk 稱這種常數為 strongly typed regexp constant。


G. 變數(Variable)

▪變數的目的是儲存數據,以便後續的使用,尤其是多次的使用。

▪可以做為變數名稱的字元包括英文大小寫、底線、數字,但第一個字元不能是數字。有區分大小寫。

▪在指令行設定變數時,可以放在 awk 後,輸入檔前。其生效的時機依其位置而定。若用-v選項設定變數,則是一開始就有效。例如:

awk -v var=1 '{ print $var }' FILE1 var=2 FILE2 var=3 FILE3

  ▪對 FILE1 而言,$var$1,這來自於-v的設定。等所有FILE1中的$1都處理完後,才讀入FILE2。而在FILE2前,var的值被改成 2,就顯示FILE2每一筆的第二項$2。等全部的$2顯示完後,var的值變成 3,才開始顯示FILE3中所有的第三項。

▪awk 也可以使用 shell 的變數,較好的方法是將之設為 awk 的變數。

  範例:

  shell_var="some value"
  awk -v awk_var="$shell_var" 'BEGIN { print awk_var }'

    ▪awk_var="$shell_var"的引號不要省略,因為其值可能有空格如此例。


H. 陣列(Array)

  ▪用途是將相關資料儲存在一起。

  ▪陣列要有獨特的名稱,不能和變數等重複,命名原則同變數等。

  ▪awk 陣列的索引(index)可以是文字或數字或表述式。awk 會先將表述式計算成值,轉成文字,才記住陣列,捨棄原先的表述式。

  ▪awk 的陣列不需事先宣告。

  ▪內建變數IGNORECASE對陣列的索引無效。也就是一開始是如何定義的,呼叫時就必需完全一致。

  ▪呼叫不存在的元素會新增該元素,其值為空字串。

一、一維陣列

  ▪設定元素的語法:ARRAY[INDEX]=VALUE

  ▪要檢查陣列是否有此元素,要用INDEX in ARRAY

  ▪要列出或處理所有元素,用for (VAR in ARRAY)敘述。

  ▪要刪除某元素,用delete ARRAY[INDEX];要刪除整個陣列,用delete ARRAY

二、多維陣列

  ▪索引不是單一項目,而是用逗號分開的多個項目。

  ▪設定語法:ARRAY[INDEX1,INDEX2,...]=VALUE

  ▪awk 將多個索引用\034連在一起,然後當成一維陣列處理。例如:A[a,b,c]變成A[a\034b\034c]\034是內建變數SUBSEP的預設值。要改成其他符號就設定SUBSEPA[a,b,c]等於A[a SUBSEP b SUBSEP c]

  ▪要檢查多維陣列是否含某元素,也是用INDEX in ARRAY,只不過INDEX要用小括號包起來,如:if ((a,b,c) in A)

  ▪要處理所有元素,用for敘述和split()函數如下:

    for (VAR in ARRAY) {
      split(VAR, ARR, SUBSEP)
      ......
    }

    ▪用上面A[a,b,c]的例子來說明,因為 awk 將多個索引用\034連接在一起,所以VARa\034b\034cARRAYA。在split()中,把VARSUBSEP分成個別索引所構成的陣列ARR,其內容即a,b,c。這就恢復了原先的A[a,b,c]

三、真多維陣列

  ▪gawk 擴充了真正的多維陣列,其設定元素的語法為:ARRAY[INDEX1][INDEX2]...=VALUE

  ▪要測試一個元素是否為陣列,用isarray()內建函數。

  ▪要檢查多維陣列是否含某元素,也是用INDEX in ARRAY

    範例:

    awk 'BEGIN {
      A["a"]["a"]=1
      A["a"]["b"]=2
      A["a"]["c"]=3
      A["b"]["a"]=4
      A["b"]["b"]=5
      A["b"]["c"]=6
      print ("a" in A) ? "yes" : "no"
    }'

    顯示:

    ⬧yes

    補充:

    若是問(a in A),則為 no;因為a是變數,索引定義的是文字。

    若是問("c" in A),也是 no;因為層級不同。若問("c" in A["b"]),則是 yes。

    若是print isarray(A), isarray("A"), isarray(A[a]), isarray(A["a"]), isarray(A["c"]),則顯示:1 0 0 1 0

  ▪要列出或處理所有元素,也是用for (VAR in ARRAY)

    範例:

    awk 'BEGIN {
      a=1
      b=2
      c=3
      A[a][a]=4
      A[a][b]=5
      A[a][c]=6
      A[b][a]=7
      A[b][b]=8
      A[b][c]=9
      for (x in A) for (y in A[x]) {
        printf "A[%s][%s]=", x, y
        print A[x][y]
      }
    }'

    顯示:

    ⬧A[1][1]=4
    ⬧A[1][2]=5
    ⬧A[1][3]=6
    ⬧A[2][1]=7
    ⬧A[2][2]=8
    ⬧A[2][3]=9

I. 運算子(operator)

依優先順序遞減排列:

(...)
分組。


$
field 的位置參照符,如:$0$1$2…。


++ --
增量、減量;可在數字前或後。

  範例:

  awk 'BEGIN { a=b=3; print ++a, b++, a, b }'

  顯示:

  ⬧4 3 4 4

^**
指數(**=是在設定時用)。

  ▪這兩個儘量用前者,因為不是所有版本的 awk 都會認得後者。


+ - !
正、負、非。


* / %
乘、除、餘。


+ -
加、減。


space(空格)
連接字串用。

  ▪連接時,若可能有不同的結果,用小括號分組,比較不會有錯。

  ▪若被連接項都有小括號,其間可以不使用空格。譬如下面三種寫法都顯示 abc:

    awk 'BEGIN { print "a" "b" "c" }'
    awk 'BEGIN { print ("a") ("b") ("c") }'
    awk 'BEGIN { print ("a")("b")("c") }'

    ▪如果沒加引號,會被當成變數。


| |&
getlineprintprintf的輸出入 pipe。


< > <= >= == !=
一般的關係運算子。

  ▪字串在做比較時,是逐字元比較。

  範例:

  echo 12 3 |
  awk '{
    print $1, $2
    print ($1 < $2) ? "true" : "false"
  }'

  顯示:

  ⬧12 3
  ⬧false

  echo '"12" "3"' |
  awk '{
    print $1, $2
    print ($1 < $2) ? "true" : "false"
  }'

  顯示:

  ⬧"12" "3"
  ⬧true

~ !~
符合與不符合 regexp。

  ▪語法:'被比項 ~ REGEXP''被比項 !~ REGEXP'

    例如列出 input 中第一 field 含 Z 的行:

    awk '$1 ~ /Z/' input

    這相當於:

    awk '{ if ($1 ~ /Z/) print }' input

    ▪若用!~,則列出第一 field 不含 Z 的行。

  ▪不要將常數型 regexp 放在左邊。

    /test/ ~ REGEXP

    相當於

    (($0 ~ /test/) ~ REGEXP)

    這通常不是想要的。


in
陣列是否有此元素?

  ▪若要檢查陣列是否含有某元素,要用INDEX in ARRAY

    範例:

    echo "x y z" |
    awk 'BEGIN {
      A["a"]=$1
      A["b"]=$2
      A["c"]=$3
    } {
      if ("b" in A) print "true"; else print "false"
    }'

    顯示:

    ⬧true

  ▪有些人會認為陣列若無某元素,其值應該是空字串,檢查時就看它是否為空字串,如:if (A["d"] == "")。這是錯的。因為這會新增A["d"]項,其值為空字串。結果就是有存在。


&&
與。

  ▪當前者為 true 時,才會評估後者;若前者為 false,即略過後者。


||
或。

  ▪當前者為 false 時,才會評估後者;若前者為 true,即略過後者。


?:
條件式。

  ▪格式:EXPR1 ? EXPR2 : EXPR3

  ▪若EXPR1真,值是EXPR2,否則是EXPR3。由此可知,EXPR2EXPR3只會執行其一,略過另一。


= += -= *= /= %= ^=
指定(也就是值的設定)。

  ▪多個變數可以一起設定,例如:a=b=c="abc"abc三個變數的值都是 abc 字串。


J. Pattern 和 Action

▪awk 程式碼由「PATTERN { ACTION }」組成。「PATTERN」是用來尋找被處理的對象,「ACTION」是處理的動作。

▪pattern 和 action 可無其一,但不能皆無。若無 pattern,則 action 是處理每一筆 record。若無 action,則用預設的 action —— print

  範例:列出 input 中超過80字元的行

  awk 'length($0) > 80' input

  範例:刪除空白行(也就是只取 field 數大於 0 的行)

  awk 'NF > 0' input

    ▪此二例是省略 action。

▪若無 action,要去掉 {},不然會沒有任何動作。

▪多個短敘述要放在同一行,可用分號 ; 分開如:

  pattern1 { action1 }; pattern2 { action2 }

▪較長的敘述可以分行,方法是在行末加反斜線 \。而以下各項不需反斜線即可自動連接:

  ,
  {
  ?
  :
  ||
  &&
  do
  else

▪pattern 和 action 最好列在同一行。


K. Pattern

  BEGIN
  END
  BEGINFILE
  ENDFILE
  expression
  /regular expression/
  relational expression
  pattern && pattern
  pattern || pattern
  pattern ? pattern : pattern
  (pattern)
  ! pattern
  pattern1, pattern2
  無 pattern


BEGIN
在讀入輸入檔前要執行的項目。

  ▪BEGIN段沒有預設的 action。

  ▪BEGIN段只執行一次。

  ▪可以有多個BEGIN段,它們會依序被執行。

  ▪BEGIN段不一定要放在開頭的位置。但為了閱讀方便,還是放在前面比較好。

  ▪BEGIN段不能有nextnextfile敘述,因為還沒開始讀入。

  ▪若 awk 程式碼只有BEGIN段,執行完就會離開,不會讀取輸入檔。這就是為什麼本文舉例常有它的原因。也因為如此,除非用getline讀入資料,不然不會有$0等項。


END
在處理完輸入檔後要執行的項目。

  ▪END段沒有預設的 action。

  ▪END段只執行一次。

  ▪可以有多個END段,它們會依序被執行。

  ▪END段不一定要放在最後的位置。但為了易於檢視,還是放在後面比較好。

  ▪END段不能有nextnextfile敘述,因為都已讀完了。


BEGINFILE
在讀入某輸入檔前要執行的項目。

  ▪主要目的在檢查可否開啟輸入檔;若可,ERRNO為空字串,程式順利執行。若無法開啟輸入檔又未設定BEGINFILE,則是致命錯誤。若無法開啟輸入檔而有設定BEGINFILE,則只是將ERRNO設為非空字串;此時程式碼應設定執行nextfile以略過此檔。這是 gawk 的擴充。

  ▪會將內建變數FILENAME設定為要讀入的檔案。FNR設為 0。

  範例:

  假設沒有 input0 檔,而 input1 的內容如下:

  ⬧A: 123    甲: abc    子: XYZ
  ⬧B: 234    乙: bcd    丑: RST
  ⬧C: 345    丙: cde    寅: HIJ

  執行:

  awk 'BEGINFILE { if (ERRNO != "") nextfile } {
    print FILENAME, "Line", FNR, ">", $0
  }' input0 input1

  顯示:

  ⬧input1 Line 1 > A: 123    甲: abc    子: XYZ
  ⬧input1 Line 2 > B: 234    乙: bcd    丑: RST
  ⬧input1 Line 3 > C: 345    丙: cde    寅: HIJ

ENDFILE
在處理完某輸入檔後(執行END段程式碼前)要執行的項目,功能類似BEGINFILE


expression
單純的表述式。

  ▪若結果非空字串或 0,即執行 action。

  ▪awk 將空字串和數字 0 視為 false,其餘的皆是 true,包括數字 0 形成的字串 “0″ 也是 true。


/regular expression/
這等於$0 ~ /regexp/


relational expression: <  >  <=  >=  !=  ==
通常用來測試 field 和 regexp 的關係。


pattern1 && pattern2
邏輯與。


pattern1 || pattern2
邏輯或。


pattern1 ? pattern2 : pattern3
pattern1為真,用pattern2進行測試;反之,用pattern3


(pattern)
加小括號改變測試順序。


! pattern
邏輯非。


pattern1, pattern2
範圍從pattern1pattern2,含此二者。

  ▪方式是先找pattern1,符合的那筆開始,每筆都算符合,直到符合pattern2的那筆資料為止(也算在內)。然後再繼續找pattern1,如此重複,直到檔末。

  範例:

  input 檔內容:

  ⬧日期  摘要  收入  支出  餘額
  ⬧0301  購物     0  2050  2345
  ⬧0302  電費     0   234  2111
  ⬧0302  購物     0   543  1568
  ⬧0303  保費     0  1230   338
  ⬧0304  提款  2000     0  2338
  ⬧0305  購物     0   456  1882

  執行:

  awk '/0302/, /0304/' input

  顯示:

  ⬧0302  電費     0   234  2111
  ⬧0302  購物     0   543  1568
  ⬧0303  保費     0  1230   338
  ⬧0304  提款  2000     0  2338

  ▪如果某一筆資料同時符合pattern1pattern2,就只處理該筆資料。處理完,再繼續找pattern1

  ▪此 pattern 不能和其他 pattern 合用,因為其間的逗號的優先順序是最低的。例如:/a/,/b/ && /c/並不是被當成(/a/,/b/) && (/c/),而是(/a/),(/b/ && /c/)
    如果用(/a/,/b/) && (/c/)會有錯誤訊息。


無 pattern
每一筆資料都處理。


L. Actions

就是對符合 pattern 的內容要做的動作。這些動作的敘述要用大括號 {} 包起來。內容不外乎設定、條件、控制、輸出、輸入等。


L1. 控制敘述

if (條件)
  敘述1
[else
  敘述2]

if (條件) 敘述1[; else 敘述2]

  ▪else段是選擇性的。

  ▪若條件為 true,執行敘述1;若為 false,執行敘述2

  ▪若條件的值是空字串或 0 視為 false,反之為 true。

  ▪else內也可以有if敘述。


while (條件) 敘述

  ▪若條件為 true,執行敘述;若為 false,略過while loop,執行後面的指令。

  ▪若一開始就是 false,不會執行敘述

  範例:

  echo 1234567890 |
  awk 'BEGIN { FS=""; N=0 } {
    while (N < 4) {
      printf "$%d=%d\n", N, $N; N++
    }
  }'

  顯示:

  ⬧$0=1234567890
  ⬧$1=1
  ⬧$2=2
  ⬧$3=3

do 敘述 while (條件)

  ▪若條件為 true,執行敘述;若為 false,略過while loop,執行後面的指令。

  ▪若一開始就是 false,會執行敘述一次。

  範例:

  echo 1234567890 |
  awk 'BEGIN { FS=""; N=0 } {
    do {
      printf "$%d=%d\n", N, $N
      N++
    } while (N > 4)
  }'

  顯示:

  ⬧$0=1234567890

for (設定起始值; 條件; 增量) 敘述

  ▪若條件為 true,執行敘述;然後計算增量,測試條件,如此重複直到條件為 false。

  ▪起始變數若有多個,只能用合併設定的方式,如:i=j=1。若值不同,只留一個,其他的在for敘述之前設定;而其增量for loop 的末尾設定。


for (VAR in ARRAY) 敘述

  ▪對陣列ARRAY的每一元素進行敘述的動作。

  範例:

  awk 'BEGIN {
    A[1]="x"
    A[2]="y"
    A[3]="z"
    for (i in A) printf "%s  %s\n", i, A[i]
  }'

  顯示:

  ⬧1  x
  ⬧2  y
  ⬧3  z

switch (表述式) {
  case 值或REGEXP : 敘述1
  ...
  [ default: 敘述2 ]
}

  ▪表述式的結果符合REGEXP時,執行敘述1。皆不符合時,執行敘述2;若無default,沒有任何動作。

  ▪若有多種狀況的動作相同,就把case並排,或用 regexp 的/...|.../

  ▪敘述通常以breakexitnextnextfile等敘述結束。

  範例:

  awk 'BEGIN { x=10; y=20; z=30 } { switch ($1) {
    case "x":
    case "X":
      print x
      break
    case "y":
      print y
      break
    case /z|Z/:
      print z
      break
    default:
      print $1
      exit
    }
  }' -

break

  ▪跳出forwhiledo的最內層,或結束switch的一個case

  ▪若不在上述的狀況,break沒有意義。

  範例:找出非質數的正整數及其最小因數(這不是最好的方法,只為了示範 awk 用法)

  echo "7 107 207 307 407 507 607 707 807 907" | awk '{
    for (field=1; field<=NF; field++)
      if ($field > 3)
        for (i=2; i<=sqrt($field); i++) {
          if ($field%i == 0) {
            printf "%d 的最小因數是 %d。\n", $field, i
            break
          } else
            continue
        }
  }'

  顯示:

  ⬧207 的最小因數是 3。
  ⬧407 的最小因數是 11。
  ⬧507 的最小因數是 3。
  ⬧707 的最小因數是 7。
  ⬧807 的最小因數是 3。

  說明:

  ▪2 和 3 是質數,所以從大於 3 的數字開始。

  ▪只要測試到平方根的數字即可,再上去的沒意義,因為再上去的值若可以整除,必有較小的值可以整除。

  ▪凡能整除就已找到,不需再測試更大的值,所以用break結束for loop。

  ▪第一次不能整除,不表示就是質數,例如 9 不能被 2 整除,所以用continue繼續測試更大的值。


continue

  ▪立刻執行forwhiledo下一 loop。

  ▪若不在上述的狀況,continue沒有意義。


next

  ▪略過目前這筆資料剩餘的指令,立刻處理下一筆資料(record)。

  ▪從頭開始執行指令。

  ▪不能用在BEGINBEGINFILEENDENDFILE段。


nextfile

  ▪略過目前檔案剩餘的指令,立刻處理下一檔。

  ▪更新內建變數FILENAME為新檔名。FNR重設為 1。從頭開始執行指令。


delete ARRAY[INDEX]

  ▪刪除陣列的某元素。

  ▪INDEX可以是表述式。

  ▪把元素值設成空字串,並不能刪除它。


delete ARRAY

  ▪刪除陣列。

  ▪split("", ARRAY)也能刪除陣列。


exit [ EXPRESSION ]

  ▪停止執行讀入的資料,跳到END段(如果有的話)。

  ▪如果exit是在END段,立刻終止程式。

  ▪傳回EXPRESSION的值。若無EXPRESSION,傳回success


L2. 特殊檔案

▪通常用在輸出入重導。

  -          標準輸入(檔案描述符為 0)
  /dev/stdin  標準輸入(檔案描述符為 0)
  /dev/stdout 標準輸出(檔案描述符為 1)
  /dev/stderr 標準錯誤輸出(檔案描述符為 2)
  /dev/fd/N   檔案描述符為 N 的檔案

▪以|&新增 TCP/IP 網路連線,格式:

  /NET-TYPE/PROTOCOL/LOCAL-PORT/REMOTE-HOST/REMOTE-PORT

  /inet/tcp/lport/rhost/rport
  /inet4/tcp/lport/rhost/rport
  /inet6/tcp/lport/rhost/rport
  /inet/udp/lport/rhost/rport
  /inet4/udp/lport/rhost/rport
  /inet6/udp/lport/rhost/rport


L3. 輸入敘述

close(FILE [, HOW])
關閉輸出入檔案 FILE 或 coprocess 或 pipe

  ▪可選的HOW只用在關閉雙向 coprocess 的一端,其值不是to就是from(大小寫皆可)。這是 gawk 的擴充。

  ▪用getline讀入檔案時,剛開啟檔案會讀入第一筆資料,再用getline便讀入下一筆。檔案只開啟一次。若想再從第一筆開始,必需先關閉檔案。從指令的輸出來讀入資料也是一樣。輸出亦同。

  ▪FILE的內容必需和開啟時完全一樣,所以最好是先設為變數。例如:

    cmd="COMMAND"
    cmd | getline
    ......
    close(cmd)

  ▪如果名稱給錯了,當然就不會關閉;這時會傳回負值,ERRNO也會有相關訊息。


getline
讀入下一筆資料

  ▪也就是設定$0為輸入資料的下一 record;這同時設定了NFNRFNRRT

  ▪若成功,傳回 1;讀到檔末,傳回 0;有錯,傳回 -1。若有錯,ERRNO會有相關訊息。如果ERRNO顯示可以重試,會傳回 -2。

  ▪執行getline後,就用新的$0進行後續的動作。這和next不同,next讀入下一筆資料後,是從頭開始所有的動作。

  範例:

  input 檔內容:

  ⬧日期  摘要  收入  支出  餘額
  ⬧0301  購物     0  2050  2345
  ⬧0302  電費     0   234  2111
  ⬧0302  購物     0   543  1568
  ⬧0303  保費     0  1230   338
  ⬧0304  提款  2000     0  2338
  ⬧0305  購物     0   456  1882

  只列出第一行:

  awk 'BEGIN { getline; print $0 }' input

  列出奇數行:

  awk 'BEGIN { getline; print $0 } getline > 0' input
  或
  awk 'BEGIN { getline; print $0 } { getline; print }' input

  顯示:

  ⬧日期  摘要  收入  支出  餘額
  ⬧0302  電費     0   234  2111
  ⬧0303  保費     0  1230   338
  ⬧0305  購物     0   456  1882

  列出偶數行:

  awk 'getline > 0' input

  顯示:

  ⬧0301  購物     0  2050  2345
  ⬧0302  購物     0   543  1568
  ⬧0304  提款  2000     0  2338

  如果偶數行也想有標題:

  awk 'BEGIN { getline; print $0 } { print; getline }' input

getline < FILE
自輸入檔 FILE 讀入下一筆資料

  ▪也就是設定$0為輸入檔的下一 record;這同時設定了NFRT


getline VAR
讀入下一筆資料並設為變數 VAR 之值

  ▪這也設定了NRFNRRT

  範例:將數字檔的偶數行加總

  awk 'BEGIN { sum=0 }
  getline num > 0 { sum+=num }
  END { print sum }' 數字檔

  說明:

  ▪在BEGIN段和getline之間讀入第一行,遇到getline就讀入第二行,所以都是偶數行。

  ▪getline num > 0是一個 pattern,成功讀入及設定,傳回 1;讀到檔末,傳回 0。

  ▪若要全部加總,請見下法。


getline VAR < FILE
讀入 FILE 的下一筆資料並設為變數 VAR 之值

  ▪這也設定了RT

  範例:將數字檔 input 加總

  awk 'BEGIN { sum=0 }
  (getline num < "input") > 0 { sum+=num }
  END { print sum; close("input") }' input

  說明:

  ▪指令最後的輸入檔並沒有實際參與運作,用其他檔或-也可以。之所以要和getline用同一檔,是因為這麼做的話,行數才會一樣。

  ▪輸入檔應是一行一個數字,若有多個,雖然getline會讀入全行,但加總時只會取用第一個數字。


COMMAND | getline
讀入 COMMAND 輸出的下一筆資料

  ▪重新設定$0,且NFRT也被設定,而NRFNR不變。

  範例:計算檔案尺寸

  ls -l /usr/share/awk/*.awk |
  awk -v cmd="ls -l /usr/share/awk/*.awk" 'BEGIN { size=0 }
  (cmd | getline) > 0 { size+=$5 }
  END { printf "The total size is %4.1f KB.\n", size/1024; close(cmd) }'

  說明:

  ▪awk 的輸入是它自己的cmd,指令最前面的輸入並沒有實際參與運作,只是為了提供相同的行數(NR)。


COMMAND | getline VAR
讀入 COMMAND 輸出的下一筆資料並設為變數 VAR 之值

  ▪這設定了VARRT,但其他內建變數沒變。

  範例:

  echo | awk '("hostname" | getline name) > 0 { print name }
  END { close("hostname") }'

COMMAND |& getline
讀入 COMMAND 輸出的下一筆資料

  ▪|&|的差異在於用前者時,COMMAND的輸出可以讀取。這種COMMAND稱為 coprocess(協程序)。屬於 gawk 的擴充。

  ▪這重新設定了$0,且NFRT也被設定,而NRFNR不變。

  ▪一般的使用方法是先將資料寫入協程序,經協程序處理後,讀取其結果。而 awk 指令中能寫入協程序的只有print,所以通常是:

    print DATA |& COMMAND
    COMMAND |& getline
    close(COMMAND)

  範例:用wc計算檔案字數、字元數

  input 檔內容:(不含⬧)

  ⬧A: 123    甲: abc    子: XYZ⬧
  ⬧B: 234    乙: bcd    丑: RST⬧
  ⬧C: 345    丙: cde    寅: HIJ⬧

  執行:

  awk 'BEGIN { cmd="wc --words --chars" }
  { print |& cmd }
  END {
    close(cmd, "to")
    cmd |& getline
    printf "word counts = %d\ncharacter counts = %d\n", $1, $2
    close(cmd)
  }' input

  顯示:

  ⬧word counts = 18
  ⬧character counts = 81

  說明:

  ▪在BEGIN段定義外部指令。

  ▪在主段將輸入檔內容重導給外部指令。

  ▪在END段先關閉輸出端,讓外部指令知道資料已結束,可以開始計算。然後取得並輸出結果。最後關閉檔案。

  ▪一個 word 是用空白分開的連續字元。一個空格或一個中文字或一個換行符都算一個字元。


COMMAND |& getline VAR
讀入 COMMAND 輸出的下一筆資料並設為變數 VAR 之值

  ▪此COMMAND是一種 coprocess,屬於 gawk 的擴充。

  ▪這也設定了RT

  範例:input 檔同上例,計算其 md5

  執行:

  awk 'BEGIN { cmd="md5sum" }
  { print |& cmd }
  ENDFILE {
    close(cmd, "to")
    cmd |& getline output
    close(cmd)
  }
  END { $0=output; print $1 }' input

  顯示:

  2e1d84fc37294009c98d8dde84b5658c

  說明:

  ▪在ENDFILE段取得外部程式的輸出也可以。用變數output儲存。

  ▪因其輸出有兩項:md5 和檔名,所以將之設為$0,只顯示$1


限時輸入(Timeout)

  ▪有時讀入資料需要等待一段時間,譬如等使用者回應。這是 gawk 的擴充。

  ▪設定方法如下:

    PROCINFO["輸入", "READ_TIMEOUT"] = TIME

    ▪READ_TIMEOUT是內建關鍵字;TIME是等待時間,以毫秒(千分之一秒)為單位,若是負數或 0,表不等待。

  範例:重複輸入的內容,限時 3 秒

  awk 'BEGIN { PROCINFO["-", "READ_TIMEOUT"] = 3000 } { print }'

    ▪若逾時,會有Connection timed out的致命錯誤訊息。


重試輸入

  ▪讀入資料若ERRNO為 -2,表示可以重試;如果有如下之設定,便會再次讀取。

    PROCINFO["輸入", "RETRY"] = 1

    ▪RETRY是內建關鍵字。這是 gawk 的擴充。


L4. 輸出敘述

print
顯示目前的這筆資料,也就是 $0;其終止符為 ORS 之值

print EXPR-LIST
顯示表述式 EXPR-LIST 的結果

  ▪欲顯示的項目可以是常數型的文字或數字、目前這筆資料的項目(如$1等)、變數、任何 awk 的表述式。

  ▪EXPR-LIST若有多項時,項間用逗號分開,如:print 第一項, 第二項, ...

  ▪所有的引數可以用一對小括號包起,如:print (第一項, 第二項, ...)

  ▪如果項目中有大於符號 >,一定要用小括號,以免和重新導向混淆。

  ▪若項目間沒有逗號,表示連接在一起,中間沒有空格。

  ▪若要顯示字串,記得加雙引號。

  ▪若要換行,用 \n。

  ▪若要顯示空白行,用空字串 “"。

  ▪print的輸出是用單一空格(OFS的預設值)分開各項,最後加 \n(ORS的預設值)。

  ▪如果輸出的資料是表格,而第一行想要有標題,可將print敘述放在BEGIN段。這樣會只顯示一次,不然會一直出現。


print EXPR-LIST > FILE
輸出表述式 EXPR-LIST 的結果至檔案 FILE

  ▪大致同print EXPR-LIST,只差在將結果輸出到檔案。

  ▪FILE可以是表述式。

  ▪FILE若已存在,內容會被取代;若不存在,會新增之。

  範例:

  echo "abc def 123 456" |
  awk '{
    print $1, $3 > $1".txt"
    close($1".txt")
    print $2, $4 > $2".txt"
    close($2".txt")
  }'

printf FMT, EXPR-LIST
顯示 FMT,但其中格式化之處用 EXPR-LIST 之值取代

  ▪後有專節詳述。


printf FMT, EXPR-LIST > FILE
輸出 FMT 到檔案 FILE,但其中格式化之處用 EXPR-LIST 之值取代

  範例:

  echo "abc def 123 456" |
  awk '{
    printf "%s: %d", $1, $3 > $1".txt"
    close($1".txt")
    printf "%s: %d", $2, $4 > $2".txt"
    close($2".txt")
  }'

fflush([FILE])
將緩衝區的內容輸出

  ▪FILE可以是輸出檔名稱,或是將輸出重導到 pipe 或 coprocess 的指令。

  ▪若無FILE或給空字串,則包含全部。

  ▪若成功,傳回 0;反之,傳回 -1。


system(CMD)
執行作業系統的指令 CMD,並傳回離開狀態(exit status)

  ▪system("")也有fflush()的效果。

  範例:

  awk 'BEGIN { system("date") }'

    ▪這個動作也可以用:

    awk 'BEGIN { print "date" | "/bin/sh"; close("/bin/sh")} '

print 和 printf 還有以下的輸出重導:

print ... >> FILE
將 print 的內容附加到檔案 FILE 後

  ▪若FILE不存在,會新增之。

  ▪如果輸出至同一檔,不要混用>>>

  範例:

  echo "abc def 123 456" |
  awk '{
    printf "%s: %d\n", $1, $3 >> "output.txt"
    printf "%s: %d\n", $2, $4 >> "output.txt"
    close("output.txt")
  }'

  如果 output.txt 原先不存在,其內容為:

  ⬧abc: 123
  ⬧def: 456

print ... | COMMAND
將 print 的輸出做為 COMMAND 的輸入

  範例:

  input 檔內容:

  ⬧日期  摘要  收入  支出  餘額
  ⬧0301  購物     0  2050  2345
  ⬧0302  電費     0   234  2111
  ⬧0302  購物     0   543  1568
  ⬧0303  保費     0  1230   338
  ⬧0304  提款  2000     0  2338
  ⬧0305  購物     0   456  1882

  執行:

  awk '{ print | "grep 購物" }
  END { close("grep 購物") }' input

  顯示:

  ⬧0301  購物     0  2050  2345
  ⬧0302  購物     0   543  1568
  ⬧0305  購物     0   456  1882

    ▪如果「購物」要加引號,要用\";如:

      awk '{ print | "grep \"購物\"" }
      END { close("grep \"購物\"") }' input

  ▪如果COMMAND內容較複雜,可先設定為變數;如:

    awk '{ cmd="grep \"購物\""; print | cmd }
    END { close(cmd) }' input

print ... |& COMMAND
將 print 的輸出做為 COMMAND 的輸入

  ▪|&|的差異在於用前者時,COMMAND的輸出可以用getline讀取。這種COMMAND稱為 coprocess。這是 gawk 的擴充。


▪無法開啟雙向 socket,會傳回非致命錯誤訊息。

▪當 pipe 或 coprocess 或 socket 傳回EOF時,awk 不會自動關閉他們。所以用他們getline時,或從 loop 中的printprintf使用他們時,需用close()


L41. 輸出敘述特論 printf

  語法:printf "含格式字串", 第一項, 第二項, ...

  ▪所有的引數可以用小括號包起,如:printf ("含格式字串", 第一項, 第二項, ...)

  ▪如果項目中有大於符號 >,一定要用小括號,以免和重新導向混淆。

    範例:

    awk 'BEGIN { printf ("%f\n", 12 > 8) }'

    顯示:

    ⬧1.000000

    ▪這是在測試 12 是否大於 8,若非,顯示 0;若是,顯示 1;但因要求用浮點數格式,所以有小數。

  ▪含格式字串(format string)是欲輸出的內容,但其中要格式化的地方用格式說明符(format specifier)替代,整個要加雙引號。

  ▪格式說明符的數目和後面列舉項的數目必需一致。舉例說明:若有五個說明符,後面就需有五項。若說明符較多,列舉項不足,是致命錯誤。如果說明符較少,列舉項較多,可以正常顯示結果,但有--lint選項時,可以看到有警告訊息。

  ▪printf不會在最後自動加上 \n,必需自行設定。

  ▪ORSOFSprintf無效。

  ▪格式說明符用 % 開頭,用一設定格式的字母結尾。列舉如下:

    %c
    單一字元。

      ▪引數若是數字,輸出對應的一個字元。

        範例:

        awk 'BEGIN { printf "十進位碼 97 的字元是:%c", 97 }'

        顯示:

        ⬧十進位碼 97 的字元是:a

      ▪如果系統設定的地區(locale)屬於多位元組文字,如中文,gawk 會儘量顯示對應的文字。

        範例:

        awk 'BEGIN { printf "十進位碼 12068 的字元是:%c", 12068 }'

        顯示:

        ⬧十進位碼 12068 的字元是:⼤

          ▪康熙部首「大」的統一碼十六進位是 2F24,十進位是 12068。

      ▪引數若是文字,需加雙引號。

        範例:

        awk 'BEGIN { printf "%c", "2F24" }'

        顯示:

        ⬧2

          ▪僅顯示第一字元。


    %s
    字串。

      範例:

      echo 9876.54321 | awk '{ printf "%s\n", $1 }'
      echo "9876.54321" | awk '{ printf "%s\n", $1 }'
      awk 'BEGIN { printf "%s\n", 9876.54321 }'
      awk 'BEGIN { printf "%s\n", "9876.54321" }'

      分別顯示:

      ⬧9876.54321
      ⬧9876.54321
      ⬧9876.54
      ⬧9876.54321

      說明:

      ▪由echo讀入的資料都當成文字。

      ▪awk 中,數字加引號即是文字。

      ▪數字轉為文字用OFMT所設定的格式,預設為%.6g


    %d, %i
    皆是十進位整數。

      ▪無條件捨去小數部分。

      範例:

      awk 'BEGIN { printf "%d\n", 1.2-3*4 }'

      顯示:

      ⬧-10

        ▪由 -10.8 得,只取整數部分。

      範例:

      awk 'BEGIN { printf "%d\n", 1234a5678 }'

      顯示:

      ⬧1234

        ▪像這種狀況,awk 是把 a5678 當成一個變數。


    %u
    無正負號的十進位整數。

      範例:

      echo 9876.543 | awk '{ printf "%u\n", $1 }'

      顯示:

      ⬧9876

      ▪如果給負值 -N,會得到 2^64-N 的十進位正整數。


    %f
    具 [-]ddd.dddddd 格式的浮點數。

      範例:

      awk 'BEGIN { printf "%f\n", 1.2-3*4 }'

      顯示:

      ⬧-10.800000


    %e, %E
    具 [-]d.dddddde[+-]dd 格式的浮點數。%E用大寫 E。

      範例:

      awk 'BEGIN { printf "%e\n", -0.00012068 }'

      顯示:

      ⬧-1.206800e-04

    %g, %G
    用%e%f中比較短的。

      ▪若採指數型式,%G%E

      範例:

      awk 'BEGIN { printf "%e\n", 1234*5678 }'
      awk 'BEGIN { printf "%f\n", 1234*5678 }'
      awk 'BEGIN { printf "%G\n", 1234*5678 }'

      分別顯示:

      ⬧7.006652e+06
      ⬧7006652.000000
      ⬧7.00665E+06

      ▪請問:為什麼最後一個小數下只有五位?

        因為內建變數OFMT(output format)的預設值是%.6g。對%g%G而言,指有效數字「最多」六位。


    %o
    無正負號的八進位整數。

      範例:

      echo 9876.543 | awk '{ printf "%o\n", $1 }'

      顯示:

      ⬧23224

    %x, %X
    無正負號的十六進位整數。

      ▪%X用大寫字母。

      範例:

      echo 9876543 | awk '{ printf "%X\n", $1 }'

      顯示:

      ⬧96B43F

    %%
    % 字元。


    ▪以下的修飾符(modifier)可加在 % 和上述控制字母之間,進一步設定格式。(依出現的位置由左向右排序)

    N$(正整數緊接著 $ 符號)
    指定該說明符用在後面列舉項中的第 N 項。

      ▪這稱為位置說明符(positional specifier)。

      ▪主要用在格式化後的字串而非之前的。這是 gawk 的擴充。

      ▪原本說明符的順序和列舉項的順序必需一致,用此修飾符即可改變順序。

      ▪要用位置說明符,必需每一個說明符都用。譬如:%3$d %f %2$e三個說明符中有一個不是位置說明符屬於致命錯誤。

      範例:

      awk 'BEGIN { printf "%d 加 %f 是 %e\n", 1, 2, 1+2 }'
      awk 'BEGIN { printf "%3$d 加 %1$f 是 %2$e\n", 2, 1+2, 1 }'

      都顯示:

      ⬧1 加 2.000000 是 3.000000e+00

    -(減號)
    置左。

      ▪預設是靠右對齊,若在寬度數字前加-,則改成靠左對齊。

      範例:

      input 檔內容:

      ⬧A: 123    甲: abc    子: XYZ⬧
      ⬧B: 234    乙: bcd    丑: RST⬧
      ⬧C: 345    丙: cde    寅: HIJ⬧

      執行:

      awk '{ printf "%2s%8s%2s%-8s%2s%8s\n", $1, $2, $3, $4, $5, $6 }' input

      顯示:

      ⬧A:     123甲:abc     子:     XYZ⬧
      ⬧B:     234乙:bcd     丑:     RST⬧
      ⬧C:     345丙:cde     寅:     HIJ⬧

    space(單一空格)
    用在數字。正數時,留一個空格;負數時,放負號。

      範例:

      echo "12  -12" | awk '{ printf "|% d|% d|\n", $1, $2 }'

      顯示:

      ⬧| 12|-12|⬧

    +(加號)
    即使是正數也顯示正號。

      ▪有此加號,上面單一空格說明符即失效。

      範例:

      echo "12 12 12 -12" |
      awk '{ printf "|%+d|%+ d|% +d|%+d|\n", $1, $2, $3, $4 }'

      顯示:

      ⬧|+12|+12|+12|-12|⬧

    #(井字號)
    使用替換格式。

      ▪%o前加0

      ▪%x%X若結果非 0,前加0x0X

      ▪%e%E%f%F必有小數點。

      ▪%g%G不去除末尾的 0。

      範例:

      awk 'BEGIN { printf "%o  %x  %E  %f  %G\n",
        123.4, 123.4, 123.4, 123.4, 123.4 }'
      awk 'BEGIN { printf "%#o  %#x  %#E  %#f  %#G\n",
        123.4, 123.4, 123.4, 123.4, 123.4 }'

      分別顯示:

      ⬧173  7b  1.234000E+02  123.400000  123.4
      ⬧0173  0x7b  1.234000E+02  123.400000  123.400

    0(數字零)
    當所設的 field 寬度比實值寬時,前面填以0而非空格。

      ▪只對數字有效。

      範例:

      echo "123.4 0.1234567" | awk '{ printf "%5d, %10f\n", $1, $2 }'
      echo "123.4 0.1234567" | awk '{ printf "%05d, %010f\n", $1, $2 }'
      echo "123.4 123.4" | awk '{ printf "%8s, %08s\n", $1, $2 }'

      分別顯示:

      ⬧  123,   0.123457
      ⬧00123, 000.123457
      ⬧   123.4,    123.4

    '(一個單引號)
    依系統設定之地區(locale),十進位的整數部分插入千位分隔符,浮點數也用當地的小數點符號。

      ▪在指令行用單引號有 escape 的問題,所以最好將 awk 程式碼放在檔案中,然後用-f選項呼叫。

      範例:

      awk 程式檔 source.awk 的內容:

      ⬧{ printf "%'f  %'d\n", $1, $2 }

      資料檔 input 的內容:

      ⬧1234567.89  1234567.89

      執行:

      LC_ALL=C  awk -f source.awk input
      LC_ALL=zh_TW.UTF-8  awk -f source.awk input

      分別顯示:

      ⬧1234567.890000  1234567
      ⬧1,234,567.890000  1,234,567

    width(正整數)
    field 的最小寬度。

      ▪若前有0,即用0填在前面(左邊);若無,用空格。這只對數字有效。

      ▪若前有-也有0,則忽略0。不論文字或數字,用空格填在後面(右邊),不填0

      範例:

      echo "12.34 567 89 89" |
      awk '{ printf "|%10f|%06d|%-06s|%06s|", $1, $2, $3, $4 }'

      顯示:

      ⬧| 12.340000|000567|89    |    89|

    .prec(句點和正整數)
    精確度。

      ▪對%e%E%f%F指小數點右方的位數。

        範例:

        echo "12.34  567" | awk '{ printf "|%8.3f|%8.3E|", $1 , $2 }'

        顯示:

        ⬧|  12.340|5.670E+02|

      ▪對%g%G指有效位數的最大值。

        範例:

        echo "123.456789  123456.789" |
        awk '{ printf "|%08.5g|%12.4G|", $1 , $2 }'

        顯示:

        ⬧|00123.46|   1.235E+05|

      ▪對%d%i%u%o%x%X指欲顯示的最小位數。

        範例:

        echo "-456.7  456.7  456.7" |
        awk '{ printf "|%#6.4d|%#6.4o|%#6.4X|", $1, $2, $3 }'
        echo "-456.7  456.7  456.7" |
        awk '{ printf "|%6.4d|%6.4o|%6.4X|", $1, $2, $3 }'

        分別顯示:

        ⬧| -0456| 00710|0X01C8|
        ⬧| -0456|  0710|  01C8|

      ▪對%s指欲顯示的最大字元數。

        範例:

        input 檔內容:

        ⬧A: 123    甲: abc    子: XYZ
        ⬧B: 234    乙: bcd    丑: RST
        ⬧C: 345    丙: cde    寅: HIJ

        執行:

        awk '{ printf "|%2s|%8s|%2s|%-8s|%2s|%8s|",
          $1, $2, $3, $4, $5, $6 }' input
        awk '{ printf "|%2s|%8.2s|%2s|%-8.2s|%2s|%8.2s|",
          $1, $2, $3, $4, $5, $6 }' input

        分別顯示:

        ⬧|A:|     123|甲:|abc     |子:|     XYZ|
        ⬧|B:|     234|乙:|bcd     |丑:|     RST|
        ⬧|C:|     345|丙:|cde     |寅:|     HIJ|
        ⬧|A:|      12|甲:|ab      |子:|      XY|
        ⬧|B:|      23|乙:|bc      |丑:|      RS|
        ⬧|C:|      34|丙:|cd      |寅:|      HI|

    *(星號)
    值取自變數。

      ▪只適用於widthprec

      ▪變數依序列於對應列舉項的前面。

      範例:

      input 檔內容:

      ⬧A: 123    甲: abc    子: XYZ
      ⬧B: 234    乙: bcd    丑: RST
      ⬧C: 345    丙: cde    寅: HIJ

      執行:

      awk 'BEGIN { width=6; prec=1 }
      { printf "|%-*s|%.*s|\n", width, $1, prec, $2 }' input
      awk -v width=6 -v prec=1 '{
        printf "|%-*.*s|%*.*s|\n", width, prec, $1, width, prec, $2
      }' input

      分別顯示:

      ⬧|A     |     1|
      ⬧|B     |     2|
      ⬧|C     |     3|
      ⬧|A:    |1|
      ⬧|B:    |2|
      ⬧|C:    |3|

      ▪若widthpreccount$,把count$放在*後。

      範例:

      echo "abcde fghij" |
      awk -v w=6 -v p1=2 -v p2=3 '{
        printf "%2$-*3$.*5$s|%1$*3$.*4$s", $1, $2, w, p1, p2
      }'

      顯示:

      ⬧fgh   |    ab

      說明:

      ▪列舉項是$1, $2, w, p1, p2

      ▪含格式字串指定先列出第二個 field($2)是在第二項,所以用2$,這要列在%後;然後才是第一個 field($1),因在第一項,所以用1$

      ▪二者共用的寬度w在第三項,所以用3$;各自的精密度分別是第五項的p2和第四項的p1

      ▪第二個 field 指定靠左對齊,所以加-;參考資料 2 說:置左的負號要在位置說明符N$後,所以上面指令如此排序;但經測試,放在最前面,也就是%N$前,也可以。

      ▪再次提醒:要用位置說明符N$,必需每一個格式說明符都用。


練習:前面曾有例子將以下資料改成 CSV 格式:

日期  摘要  收入  支出  餘額
0301  購物     0  2050  2345
0302  電費     0   234  2111
0302  購物     0   543  1568
0303  保費     0  1230   338
0304  提款  2000     0  2338
0305  購物     0   456  1882

得到:

日期,摘要,收入,支出,餘額
0301,購物,0,2050,2345
0302,電費,0,234,2111
0302,購物,0,543,1568
0303,保費,0,1230,338
0304,提款,2000,0,2338
0305,購物,0,456,1882

請問:如何反過來,將 CSV 資料改成上面格式化的內容?

參考答案:
假設 CSV 檔稱為 input.csv

awk -F "," 'NR == 1 { printf "%2s%4s%4s%4s%4s\n", $1, $2, $3, $4, $5 }
NR >= 2 { printf "%s%4s%6s%6s%6s\n", $1, $2, $3, $4, $5 }' input.csv

M. 函數(Function)

▪呼叫法:函數名(引數列表)

▪函數名和小括號之間,內建函數可以有空格,但自訂函數不可。最好是都不要空。

▪引數列表用逗號分隔各引數。

▪有些函數若省略引數會用預設值。

▪內建函數若給過多的引數,gawk 視為致命錯誤。


M1. 數字運算函數

int(EXPR)
整數函數。

  ▪直接去掉小數,只取整數部分。


sin(EXPR)
EXPR的 sine。

  ▪單位:弧度/弳度/radian。


cos(EXPR)
EXPR的 cosine。

  ▪單位:弧度/弳度/radian。


atan2(Y, X)
Y/X的 arctangent。

  ▪單位:弧度/弳度/radian。

  ▪π = atan2(0, -1)

  範例:輸入一角度值(0 到 360 度),顯示其三角函數值

  awk 'BEGIN { printf "輸入一角度值(0~360): " } {
    rad=atan2(0, -1)*($1)/180
    s=sin(rad)
    c=cos(rad)
    printf "sin(x)=%.3f\ncos(x)=%.3f\ntan(x)=%.3f\n", s, c, s/c
    printf "cot(x)=%.3f\nsec(x)=%.3f\ncsc(x)=%.3f\n", c/s, 1/c, 1/s
    exit
  }' -

exp(EXPR)
指數函數。

  ▪即e^EXPR


log(EXPR)
自然對數函數。

  ▪若EXPR == 0,顯示-inf。若EXPR < 0,顯示nan

  範例:計算以 2、e、10 為底的對數值

  awk 'BEGIN { printf "輸入一值: x=" } {
    printf " 2 為底 lb(x)=%.3f\n", log($1)/log(2)
    printf " e 為底 ln(x)=%.3f\n", log($1)
    printf "10 為底 lg(x)=%.3f\n", log($1)/log(10)
    exit
  }' -

sqrt(EXPR)
EXPR的平方根。

  ▪若EXPR < 0,顯示-nan

  範例:輸入直角三角形的兩邊長,計算斜邊長

  awk 'BEGIN {
    printf "\n直角三角形的兩邊長分別是(用空格分開兩個數字):"
  } {
    if (int($1) > 0 && int($2) > 0)
      printf "斜邊長 %.3f\n", sqrt(($1)^2+($2)^2)
    else {
      print "輸入值有問題"
      exit
    }
  }' -

rand()
亂數函數。

  ▪範圍:0 ≤ 傳回值 < 1。

  ▪若要正整數,可用int(n*rand())+1


srand([EXPR])
設定rand()的種子為EXPR

  ▪傳回前一種子。

  ▪若無EXPR,用當時的時間。

  範例:模擬彩劵電腦選號,列出 1 到 49 中的 6 個亂數

  awk 'BEGIN {
    n=1
    srand()
    while (n <= 6) {
      r=int(49*rand())+1
      if (r in A)
        continue
      else {
        A[r]=r
        n++
      }
    }
    for (i in A) print i
    exit
  }'

M2. 字串函數

asort(SOURCE [, DESTINATION [, HOW] ])
將來源陣列SOURCE排序,並將其索引改為從 1 開始的流水號。

  ▪若有指定目標陣列DESTINATION,會先把SOURCE複製到DESTINATION,將DESTINATION排序,而SOURCE的索引保持不變。

  ▪HOW控制排序的方向與方法,可用值見內建變數PROCINFO["sorted_in"]

  ▪IGNORECASE的設定對排序有影響。

  ▪傳回其元素數。

  ▪這是 gawk 的擴充。

  範例:

  input 檔內容:

  ⬧A: 123    甲: abc    子: XYZ
  ⬧B: 234    乙: bcd    丑: RST
  ⬧C: 345    丙: cde    寅: HIJ

  執行:

  awk 'BEGIN { RS="    |\n"; FS=": " }
  { A[$1]=$2; printf "A[%s] = %s\n", $1, $2 }
  ENDFILE {
    print ""
    N=asort(A)
    for (i=1; i<=N; i++) printf "A[%d] = %s\n", i, A[i]
  }' input

  顯示:

  ⬧A[A] = 123
  ⬧A[甲] = abc
  ⬧A[子] = XYZ
  ⬧A[B] = 234
  ⬧A[乙] = bcd
  ⬧A[丑] = RST
  ⬧A[C] = 345
  ⬧A[丙] = cde
  ⬧A[寅] = HIJ
  ⬧
  ⬧A[1] = 123
  ⬧A[2] = 234
  ⬧A[3] = 345
  ⬧A[4] = HIJ
  ⬧A[5] = RST
  ⬧A[6] = XYZ
  ⬧A[7] = abc
  ⬧A[8] = bcd
  ⬧A[9] = cde

  說明:

  ▪在BEGIN段定義每筆和每項的分隔符。

  ▪在主段設定A陣列並顯示之。

  ▪在ENDFILE段用asort()排序,直接更改A陣列的內容。

  範例:

  input 檔內容同上:

  ⬧A: 123    甲: abc    子: XYZ
  ⬧B: 234    乙: bcd    丑: RST
  ⬧C: 345    丙: cde    寅: HIJ

  執行:

  awk 'BEGIN { RS="    |\n"; FS=": " }
  { A[$1]=$2 }
  END {
    N=asort(A, B, "@val_str_desc")
    for (i=1; i<=N; i++) printf "B[%d] = %s\n", i, B[i]
    for (j in A) printf "A[%s] = %s\n", j, A[j]
  }' input

  顯示:

  ⬧B[1] = cde
  ⬧B[2] = bcd
  ⬧B[3] = abc
  ⬧B[4] = XYZ
  ⬧B[5] = RST
  ⬧B[6] = HIJ
  ⬧B[7] = 345
  ⬧B[8] = 234
  ⬧B[9] = 123
  ⬧A[A] = 123
  ⬧A[B] = 234
  ⬧A[C] = 345
  ⬧A[子] = XYZ
  ⬧A[丙] = cde
  ⬧A[乙] = bcd
  ⬧A[寅] = HIJ
  ⬧A[甲] = abc
  ⬧A[丑] = RST

  說明:

  ▪在END段用asort()排序:先複製為B再依@val_str_desc排序,這是將值依文字降階排序。

  ▪這裡用END,前面用ENDFILE,只是示範都可以,因為只有一個輸入檔。

  ▪A的內容雖然沒變,但順序有。這表示 awk 並不是依輸入檔的順序儲存資料。


asorti(SOURCE [, DESTINATION [, HOW] ])
將來源陣列SOURCE依其索引排序,並用其索引取代其對應值(故原值消失),而新的索引用從 1 開始的流水號。

  ▪目標陣列DESTINATIONHOW的用法同asort()

  ▪傳回其元素數。

  ▪這是 gawk 的擴充。

  範例:

  input 檔內容同上:

  ⬧A: 123    甲: abc    子: XYZ
  ⬧B: 234    乙: bcd    丑: RST
  ⬧C: 345    丙: cde    寅: HIJ

  執行:

  awk 'BEGIN { RS="    |\n"; FS=": " }
  { A[$1]=$2 }
  END {
    N=asorti(A, B)
    for (i=1; i<=N; i++) printf "B[%d] = %s\n", i, B[i]
  }' input

  顯示:

  ⬧B[1] = A
  ⬧B[2] = B
  ⬧B[3] = C
  ⬧B[4] = 丑
  ⬧B[5] = 丙
  ⬧B[6] = 乙
  ⬧B[7] = 子
  ⬧B[8] = 寅
  ⬧B[9] = 甲

  說明:

  ▪在END段中用asorti()排序:先複製為B才依原索引排序並設為值。


sub(REGEXP, REPLACEMENT [, TARGET])
REGEXP比對目標字串TARGET,並用REPLACEMENT取代第一個符合者。

  ▪傳回取代的次數,不是 1 就是 0。

  ▪TARGET不能是單純的字串,必需是能儲存、能被呼叫的物件,例如:變數、field、陣列元素等。因為傳回的是取代次數,而不是取代的結果。

  ▪若未提供TARGET,則用$0

  ▪TARGET即改為取代後的結果。

    範例:

    awk -v str="abcabc" 'BEGIN { print sub("b", "B", str); print str }'

    顯示:

    ⬧1
    ⬧aBcabc

    範例:

    awk -v str="abcabc" 'BEGIN { print sub("b", "B", "str"); print str }'

    顯示:

    ⬧0
    ⬧abcabc

  ▪在REPLACEMENT中,可以用&代表符合的字串。若要使用字元 &,必需用\\&

    範例:

    awk -v str="abcabc" 'BEGIN { print sub("b", "B&\&\\&", str); print str }'

    顯示警告訊息和:

    ⬧1
    ⬧aBbb&cabc

  ▪若REGEXP想用空字串,可以用""//

    範例:

    awk -v str="abcabc" 'BEGIN { print sub("", "B", str); print str }'

    顯示:

    ⬧1
    ⬧Babcabc

    說明:

    ▪每個字元間有一個空字串,在最前面和最後面也各有一個空字串。

    ▪因為sub()只取代第一個符合者,所以B在最前面。


gsub(REGEXP, REPLACEMENT [, TARGET])
REGEXP比對目標字串TARGET,並用REPLACEMENT取代所有符合者。

  ▪傳回取代的次數。

  ▪TARGET不能是單純的字串,必需是能儲存、能被呼叫的物件,例如:變數、field、陣列元素等。因為傳回的是取代次數,而不是取代的結果。

  ▪若未提供TARGET,則用$0

  ▪TARGET即改為取代後的結果。

  ▪在REPLACEMENT中,可以用&代表符合的字串。若要用字元 &,必需用\\&

  ▪若REGEXP想用空字串,可以用""//

    範例:

    awk -v str="abcabc" 'BEGIN { print gsub("", ":", str); print str }'

    顯示:

    ⬧7
    ⬧:a:b:c:a:b:c:

  ▪若REGEXP設成找空字串,不能用在有中文的場合。因為一個中文字會被當成三個未知字元。

    範例:

    awk 'BEGIN { str="日月"; print gsub("", ":", str); print str }'

    顯示:

    ⬧7
    ⬧:�:�:�:�:�:�:

  ▪若要將每一個中文字分開,REGEXP/\B/"\\B"

    範例:

    awk 'BEGIN { str="日月"; print gsub(/\B/, "\n", str); print str }'

    顯示:

    ⬧1
    ⬧日
    ⬧月

  ▪參考資料 2 提到用/m*/也可以當成是找空字串,這有點問題。它舉的例子:

    echo abc | awk '{ gsub(/m*/, "X"); print }'

    顯示:

    ⬧XaXbXcX

    ▪這樣看起來好像對,但事實上並不是。只不過它用的 m 剛好不在被處理的字串 abc 中而已。這只能算是巧合。m 可以換成任何單一字元,包括中文字,只要不在TARGET中即可。那如果是在TARGET中呢?看看下面這些例子:

    echo abc | awk '{ gsub(/a*/, "X"); print }'
    ⬧XbXcX
    echo abc | awk '{ gsub(/b*/, "X"); print }'
    ⬧XaXcX
    echo abc | awk '{ gsub(/c*/, "X"); print }'
    ⬧XaXbX
    echo abc | awk '{ gsub(/aa*/, "X"); print }'
    ⬧Xbc
    echo abc | awk '{ gsub(/bb*/, "X"); print }'
    ⬧aXc
    echo abc | awk '{ gsub(/cc*/, "X"); print }'
    ⬧abX
    echo abc | awk '{ gsub(/aaa*/, "X"); print }'
    ⬧abc
    echo abc | awk '{ gsub(/bbb*/, "X"); print }'
    ⬧abc
    echo abc | awk '{ gsub(/ccc*/, "X"); print }'
    ⬧abc

gensub(REGEXP, REPLACEMENT, HOW [, TARGET])
REGEXP比對目標字串TARGET,並用REPLACEMENT取代符合者。

  ▪這是 gawk 的擴充,能較細緻地尋找和取代。

  ▪若HOWgG,取代所有符合者;若是數字 N,則僅取代第 N 個。如果不是gG開頭的文字或是小於等於 0 的數字,則僅取代一個。

  ▪若未提供TARGET,則用$0

  ▪在REPLACEMENT中,可以用位置參照變數\1\9代表符合REGEXP中小括號內容的字串,而\0&代表整個符合的字串。注意:要用連續兩個反斜線\\

  ▪傳回修改後的字串,而原先的TARGET不變。

  ▪若無符合者,傳回原TARGET

  ▪若REGEXP想用空字串,可以用""//

  範例:

  awk 'BEGIN {
    str="123456"
    print gensub("(..)(..)(..)", "(\\3)[\\2]{\\1}", "1", str)
  }'

  顯示:

  ⬧(56)[34]{12}

    ▪如果把REGEXP改成(..){3},則顯示:

    ⬧()[]{56}

index(SOURCE, TARGET)
傳回SOURCE字串中第一個TARGET字串的索引。

  ▪若無符合者,傳回 0。

  ▪TARGET必需是字串,若是 regexp,屬於致命錯誤。


length([STRING])
傳回STRING的長度。

  ▪若未提供引數,傳回$0的長度。

  ▪若引數是數字,傳回其位數。

  ▪若引數是陣列,傳回其元素的個數。

  範例:

  awk -v str="abcabc" 'BEGIN { print index(str, "c"); print length(str) }'

  顯示:

  ⬧3
  ⬧6

match(STRING, REGEXP [, ARRAY])
傳回STRING中第一個符合REGEXP的索引。

  ▪這也設定了RSTARTRLENGTHRSTART就是這個索引,而RLENGTH是符合者的字串長。

  ▪若無符合者,傳回 0;即RSTART為 0,而RLENGTH為 -1。

  ▪若有ARRAY,其內容會被清除,而第 0 個元素(即ARRAY[0])設為符合整個REGEXP的字串。若REGEXP內有小括號,自元素 1 開始會依序設為STRING中符合小括號內容的字串。

  ▪ARRAY[N, "start"]是第 N 個符合者的索引;而ARRAY[N, "length"]是其長度。

  ▪ARRAY是 gawk 的擴充。

  範例:

  awk -v str="abcabc" 'BEGIN {
    print match(str, "(b).*(b)", arr)
    printf "-%s-%s-%s-%s-\n", arr[0], arr[1], arr[2], arr[3]
    print RSTART, RLENGTH
  }'

  顯示:

  ⬧2
  ⬧-bcab-b-b--
  ⬧2 4

split(STRING, ARRAY [, REGEXP [, SEPARATORS] ])
STRINGREGEXP分解為非分隔符陣列ARRAY和分隔符陣列SEPARATORS

  ▪傳回元素數 N。

  ▪此REGEXP是用來找分隔符的。

  ▪若無REGEXP,則採用內建變數FS(預設是單一空格)。

  ▪SEPARATORS[i]是介於ARRAY[i]ARRAY[i+1]之間且符合REGEXP的分隔符。這是 gawk 的擴充。

  ▪若REGEXP是單一空格,則SEPARATORS[0]是開頭的空白;而SEPARATORS[N]是末尾的空白。

  ▪若REGEXP是空字串,則分開每一字元。

  ▪若無符合者,ARRAY只有一個元素,就是原字串。

  範例:

  awk -v str="abcabc" 'BEGIN {
    N=split(str, arr, /b/, sep)
    for (i=0; i<=N; i++) {
      printf "arr[%d]=%s\n", i, arr[i]
      printf "sep[%d]=%s\n", i, sep[i]
    }
  }'

  顯示:

  ⬧arr[0]=
  ⬧sep[0]=
  ⬧arr[1]=a
  ⬧sep[1]=b
  ⬧arr[2]=ca
  ⬧sep[2]=b
  ⬧arr[3]=c
  ⬧sep[3]=

  範例:

  awk -v str="天地人" 'BEGIN {
    N=split(str, arr, "", sep)
    for (i=0; i<=N; i++) printf "arr[%d]=%s;\n", i, arr[i]
  }'

  顯示:

  ⬧arr[0]=;
  ⬧arr[1]=天;
  ⬧arr[2]=地;
  ⬧arr[3]=人;

patsplit(STRING, ARRAY [, REGEXP [, SEPARATORS] ])
STRINGREGEXP分解為符合者陣列ARRAY和間隔字串陣列SEPARATORS

  ▪傳回元素數。

  ▪這裡的REGEXPsplit()的相反,符合的字串被分為ARRAY,不符合的字串分為SEPARATORS。而split()是符合的字串被分為SEPARATORS,不符合的字串分為ARRAY

  ▪若無REGEXP,則採用內建變數FPAT(預設為[^[:space:]]+)。

  ▪第一個符合者是ARRAY[1],餘類推。

  ▪SEPARATORS[i]是在ARRAY[i]後的間隔字串。而SEPARATORS[0]是開頭的間隔字串。所以間隔字串會比符合者多一個。間隔字串可能是空字串。

  ▪這是 gawk 的擴充。

  範例:

  awk -v str="abcabc" 'BEGIN {
    N=patsplit(str, arr, /b/, sep)
    for (i=0; i<=N; i++) {
      printf "arr[%d]=%s\n", i, arr[i]
      printf "sep[%d]=%s\n", i, sep[i]
    }
  }'

  顯示:

  ⬧arr[0]=
  ⬧sep[0]=a
  ⬧arr[1]=b
  ⬧sep[1]=ca
  ⬧arr[2]=b
  ⬧sep[2]=c

sprintf(FMT, EXPR-LIST)
傳回FMT,但其中格式化之處用EXPR-LIST之值取代。

  ▪只傳回結果,並沒有顯示出來。

  範例:

  awk -v str="天地人" 'BEGIN {
    N=split(str, arr, "")
    for (i=1; i<=N; i++) print sprintf("arr[%d]=%s;", i, arr[i])
  }'

  顯示:

  ⬧arr[1]=天;
  ⬧arr[2]=地;
  ⬧arr[3]=人;

strtonum(STR)
將文字STR改為數字。

  ▪若STR開頭是0,視為八進位數字;若是0x0X,視為十六進位數字;其餘視為十進位數字。

  ▪這是 gawk 的擴充。

  ▪把字串和0相加也可以變成數字,但只能用在十進位。


substr(STRING, START [, LENGTH])
子字串,傳回STRINGSTART位置開始共LENGTH個字元。

  ▪STRING第一個字元的位置是 1。

  ▪若「無LENGTH」或「LENGTH超過到最後的字元數」,則傳回直至最後的內容。

  ▪如果LENGTH <= 0,傳回空字串。

  ▪如果START < 1,則視為 1。若超過STRING字元數,傳回空字串。

  範例:

  awk -v str="abcdefgh" 'BEGIN {
    print substr(str, 3)
    print substr(str, 3, 4)
    print substr(str, 3, -2)
    print substr(str, -2, 3)
  }'

  顯示:

  ⬧cdefgh
  ⬧cdef
  ⬧
  ⬧abc

tolower(STRING)
傳回全小寫的結果。

  ▪非文字者不變。


toupper(STRING)
傳回全大寫的結果。

  ▪非文字者不變。


M3. 時間函數

▪POSIX 系統的 Epoch 是指1970-01-01 00:00:00 UTC

mktime(DATETIME [, UTC-FLAG])
傳回DATETIME到 Epoch 的秒數。

  ▪DATETIME的格式是年 月 日 時 分 秒,各項之間用空格分開YYYY MM DD HH MM SS[ DST]。其中的用 4 碼,用 24 小時制,而DST是日光節約時間(Daylight Saving Time)。若不符此格式,傳回 -1。

  ▪DST若是正值,表示是日光節約時間;0 表標準時間;預設是負值,由mktime()判斷。

  ▪若UTC-FLAG非空字串亦非 0,表示是 UTC 時區;不然,是地方時區。

  範例:

  awk -v time="2020 1 1 0 0 0" 'BEGIN { print mktime(time) }'

  顯示:

  ⬧1577808000

strftime([FORMAT [, TIMESTAMP[, UTC-FLAG]]])
FORMAT顯示TIMESTAMP

  ▪若UTC-FLAG非空字串亦非 0,表示是 UTC 時區;不然,當做是地方時區。

  ▪若無TIMESTAMP,用目前時間。

  ▪FORMAT若省略,用內建變數PROCINFO["strftime"]的設定值(預設格式是%a %b %e %H:%M:%S %Z %Y)。

  ▪FORMAT若是空字串,也顯示空字串。

  ▪可用的格式請見man strftime

  範例:

  awk 'BEGIN { print strftime("%Y %m %d %a %H:%M:%S %Z", "1577808000") }'

  顯示:

  ⬧2020 01 01 Wed 00:00:00 CST

systime()
傳回目前時間到 Epoch 的秒數。


M4. Bit 運算函數

▪給這些函數負值屬於致命錯誤。

and(V1, V2 [, ...])
V1V2的位元與

  ▪皆為 1 時為 1。

  ▪至少要有兩個引數。

  範例:

  awk 'BEGIN { print and(11, 22) }'

  顯示:

  ⬧2

  說明:

  十進位 11 的二進位是 01011
  十進位 22 的二進位是 10110
  AND                  00010  十進位是 2
  OR                   11111  十進位是 31
  XOR                  11101  十進位是 29

or(V1, V2 [, ...])
V1V2的位元或

  ▪任一為 1 時為 1。

  ▪至少要有兩個引數。

  範例:

  awk 'BEGIN { print or(11, 22) }'

  顯示:

  ⬧31

xor(V1, V2 [, ...])
V1V2的位元互斥或

  ▪僅一為 1 時為 1。

  ▪至少要有兩個引數。

  範例:

  awk 'BEGIN { print xor(11, 22) }'

  顯示:

  ⬧29

lshift(VAL, COUNT)
VAL左移COUNT

  ▪原值向左移,新增者為 0。

  範例:

  awk 'BEGIN { print lshift(11, 3) }'

  顯示:

  ⬧88

  說明:

  十進位 11 的二進位是 0001011
  左移 3 位            1011000  十進位是 88
  右移 3 位            0000001  十進位是 1

rshift(VAL, COUNT)
VAL右移COUNT

  ▪原值向右移,新增者為 0。

  範例:

  awk 'BEGIN { print rshift(11, 3) }'

  顯示:

  ⬧1

compl(VAL)
VAL的位元補數

  ▪0、1 互換。

  範例:

  awk 'BEGIN { print compl(0) }'
  awk 'BEGIN { print compl(1) }'

  分別顯示:

  ⬧9007199254740991
  ⬧18014398509481982

  註:9007199254740992 = 2^53


M5. 類型函數

isarray (X)
X是否是陣列?

  ▪若是,傳回 1 (true);反之,傳回 0 (false)。


typeof(X)
傳回X的類型。

  ▪類型有:arraynumberregexpstringstrnumundefined(尚未出現)、unassigned(已出現,尙未設定)。

  範例:

  awk 'BEGIN { print typeof(x); x; print typeof(x) }'

  顯示:

  ⬧untyped
  ⬧unassigned

M6. 翻譯函數

bindtextdomain(DIR [, DOMAIN])
設定 .gmo 訊息翻譯檔所在的目錄。

  ▪預設是/usr/share/locale/

  ▪若無誤,傳回該目錄。

  ▪若DIR是空字串,傳回目前所設的目錄。

  ▪DOMAIN預設是TEXTDOMAIN之值,是指翻譯的範圍,通常是程式名,也就是此程式的字串才用此翻譯。譬如 awk 中的某些英文要譯成其他語言,DOMAIN就可以設為 awk;而其 .gmo 檔通常就是 awk.gmo。


dcgettext(STRING [, DOMAIN [, CATEGORY]])
傳回STRINGDOMAINCATEGORY類的翻譯。

  ▪前面的dc是指 domain 和 category。

  ▪DOMAIN的預設值是內建變數TEXTDOMAIN的值。

  ▪CATEGORY的預設值是LC_MESSAGES。可用的尙有:LC_COLLATELC_CTYPELC_MONETARYLC_NUMERICLC_TIMELC_ALL


dcngettext(STRING1, STRING2, NUMBER [, DOMAIN [, CATEGORY]])
傳回STRING1STRING2DOMAINCATEGORY類翻譯NUMBER的複數型。

  ▪前面的dcn是指 domain、category、number。

  ▪STRING1STRING2分別是英文的單數型和複數型。

  ▪DOMAIN的預設值是內建變數TEXTDOMAIN的值。

  ▪CATEGORY的預設值是LC_MESSAGES。可用的尙有:LC_COLLATELC_CTYPELC_MONETARYLC_NUMERICLC_TIMELC_ALL


M7. 自訂函數

▪格式:function NAME(PARAMETERS) { STATEMENTS }

function是內建關鍵字,某些版本的 awk 可以用func,但為了相容性,最好用全名。

NAME是函數名,可用的字元包括英文大小寫、數字、底線,但不能以數字開頭。

NAME和小括號之間不能有空白。雖然內建函數名和小括號可以空開,但為免出錯,最好養成習慣,都不要分開。

PARAMETERS是用逗號分開的引數和局部變數(該函數才用的變數)。局部變數要在引數之後,且用多個空格分開。譬如:
  function f(p, q,    a, b) { ... }
  其中pq是引數,而ab是局部變數。局部變數若是文字,起啟值是空字串;若是數字,則是 0。

▪自訂函數要傳回資料用return [EXPRESSION]敘述。

▪自訂函數可以放在程式碼的任何地方,不一定要放在前面。

▪函數可以自己呼叫自己,這種 recursive 函數要有停止的機制。

▪函數可以用間接方式呼叫,也就是先將一變數定義為函數名,然後呼叫此變數,但其前要加 @。內建、擴充、自訂函數皆可如此呼叫。例如:

  function FUNCTION() { ... }
  var="FUNCTION"
  @var()

範例:土法煉鋼的序列和(不超過末尾數字,間距調為正值)

sum.awk 檔的內容:

function sum(start, end, step,   i, j) {
  j=0
  if (step < 0) step=-step
  if (start > end)
    for (i=start; i>=end; i-=step) { j+=i }
  else
    for (i=start; i<=end; i+=step) { j+=i }
  return j
}

執行任一:

echo "2.9 25.3 1.7" | awk -e '{ printf "總和是 %.2f。\n", sum($1, $2, $3) }' -i sum.awk
echo "2.9 25.3 1.7" | awk -e '{ printf "總和是 %.2f。\n", sum($1, $2, $3) }' -f sum.awk
echo "2.9 25.3 1.7" | awk -i sum.awk -e '{ printf "總和是 %.2f。\n", sum($1, $2, $3) }'
echo "2.9 25.3 1.7" | awk -f sum.awk -e '{ printf "總和是 %.2f。\n", sum($1, $2, $3) }'
echo "2.9 25.3 1.7" | awk -i sum.awk '{ printf "總和是 %.2f。\n", sum($1, $2, $3) }'

顯示:

⬧總和是 195.30。

M8. 離開狀態(exit status)

▪若有設定exit VALUE,即傳回VALUE

▪若無設定,當執行沒有問題時,傳回 0;若有問題,傳回 1。

▪若是致命錯誤,傳回 2。


發表留言