AWK/GAWK 入門參考
【摘要】GNU AWK 的基礎參考資料。所舉的「範例」都很淺顯,能直接執行。對想以中文學習 AWK 的新手而言,應該有一定程度的幫助。
目錄
前言A. 基本用語
B. 執行方式
C. 選項
C1. 提醒
D. 常用內建變數
D1. 特論:RS
D2. 特論:FS
D3. 特論:NF
D4. 特論:FIELDWIDTHS
D5. 特論:FPAT
D6. 特論:ORS 和 OFS
E. 更改 field 的內容
F. 常數表述式
G. 變數
H. 陣列
I. 運算子
J. Pattern 和 Action
K. Pattern
L. Action
L1. 控制敘述
L2. 特殊檔案
L3. 輸入敘述
L4. 輸出敘述
L41. 特論:printf
M. 函數
M1. 數字運算函數
M2. 字串函數
M3. 時間函數
M4. Bit 運算函數
M5. 類型函數
M6. 翻譯函數
M7. 自訂函數
M8. 離開狀態
前言
▪過去為了編輯倉頡碼,收集了網路上許多版本的資料。因各版本的排版不同,就用多個程式將所需資料抽取出來,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 轉換儒略日期與公曆日期」。
▪參考資料:
man awk
2018 版。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
,則SOURCE1
和FILE1
都會執行。若有兩個-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=2
和c=3
則有。
▪ARGIND
是正在處理的檔案。
▪input3
是在END
段新增的,要用getline
敘述才會讀取,不然$0
仍是Input two
。
SYMTAB
程式中所有全域(global)變數、陣列的值所形成的陣列。
▪SYMTAB
的索引(index)即是這些變數、陣列的名稱。
▪所形成的陣列不含SYMTAB
和FUNCTAB
。
▪可以用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
。但若重新設定FS
或FPAT
,則忽略FIELDWIDTHS
。
▪類似地,若有先設定FPAT
,便以此為準。但若重新設定FS
或FIELDWIDTHS
,則忽略FPAT
。
▪內建變數IGNORECASE
的設定會影響到RS
和FS
。若設定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
在設定額外項的前後都列出$0
和NF
,顯示:
⬧C: 345 丙: cde 寅: HIJ ⬧6 ⬧C: 345 丙: cde 寅: HIJ xyz ⬧8
▪第一次顯示的$0
仍保留輸入檔的間距;但設定新值時,awk 重新計算$0
,便採用預設的輸出分隔符(OFS
),所以間距都只剩單一空格。
▪輸出的第三行在HIJ
和xyz
之間有兩個空格,因為$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
的前後都列出$0
和NF
,顯示:
⬧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
輸出時的RS
和FS
。
▪一個print
敘述所顯示的內容是一筆輸出 record。ORS
(Output Record Separator)即是輸出 records 之間的分隔符,預設為換行符(\n)。所以,一個print
敘述預設是輸出一行資料。而此一行資料各 fields 之間的分隔符即是OFS
(Output Field Separator),預設為單一空格(space「 」)。
▪要設定輸出的格式,就要先設定ORS
和OFS
。這通常是放在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
的前後都列出$0
和NF
:
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=12
,A
是數字。若設定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。十六進位用0xNN
或0XNN
表示。例如:十六進位的 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
的預設值。要改成其他符號就設定SUBSEP
。A[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
連接在一起,所以VAR
是a\034b\034c
,ARRAY
是A
。在split()
中,把VAR
用SUBSEP
分成個別索引所構成的陣列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") }'
▪如果沒加引號,會被當成變數。
|
|&
getline
、print
、printf
的輸出入 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
。由此可知,EXPR2
和EXPR3
只會執行其一,略過另一。
=
+=
-=
*=
/=
%=
^=
指定(也就是值的設定)。
▪多個變數可以一起設定,例如:a=b=c="abc"
。a
、b
、c
三個變數的值都是 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
段不能有next
和nextfile
敘述,因為還沒開始讀入。
▪若 awk 程式碼只有BEGIN
段,執行完就會離開,不會讀取輸入檔。這就是為什麼本文舉例常有它的原因。也因為如此,除非用getline
讀入資料,不然不會有$0
等項。
END
在處理完輸入檔後要執行的項目。
▪END
段沒有預設的 action。
▪END
段只執行一次。
▪可以有多個END
段,它們會依序被執行。
▪END
段不一定要放在最後的位置。但為了易於檢視,還是放在後面比較好。
▪END
段不能有next
和nextfile
敘述,因為都已讀完了。
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
範圍從pattern1
到pattern2
,含此二者。
▪方式是先找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
▪如果某一筆資料同時符合pattern1
和pattern2
,就只處理該筆資料。處理完,再繼續找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 的/...|.../
。
▪敘述
通常以break
、exit
、next
、nextfile
等敘述結束。
範例:
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
▪跳出for
、while
、do
的最內層,或結束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
▪立刻執行for
、while
、do
下一 loop。
▪若不在上述的狀況,continue
沒有意義。
next
▪略過目前這筆資料剩餘的指令,立刻處理下一筆資料(record)。
▪從頭開始執行指令。
▪不能用在BEGIN
、BEGINFILE
、END
、ENDFILE
段。
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;這同時設定了NF
、NR
、FNR
、RT
。
▪若成功,傳回 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;這同時設定了NF
、RT
。
getline VAR 讀入下一筆資料並設為變數 VAR 之值
▪這也設定了NR
、FNR
、RT
。
範例:將數字檔的偶數行加總
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
,且NF
、RT
也被設定,而NR
和FNR
不變。
範例:計算檔案尺寸
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 之值
▪這設定了VAR
和RT
,但其他內建變數沒變。
範例:
echo | awk '("hostname" | getline name) > 0 { print name } END { close("hostname") }'
COMMAND |& getline 讀入 COMMAND 輸出的下一筆資料
▪|&
和|
的差異在於用前者時,COMMAND
的輸出可以讀取。這種COMMAND
稱為 coprocess(協程序)。屬於 gawk 的擴充。
▪這重新設定了$0
,且NF
、RT
也被設定,而NR
和FNR
不變。
▪一般的使用方法是先將資料寫入協程序,經協程序處理後,讀取其結果。而 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 中的print
或printf
使用他們時,需用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,必需自行設定。
▪ORS
和OFS
對printf
無效。
▪格式說明符用 % 開頭,用一設定格式的字母結尾。列舉如下:
%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,前加0x
或0X
。
▪%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|
*
(星號)
值取自變數。
▪只適用於width
和prec
。
▪變數依序列於對應列舉項的前面。
範例:
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|
▪若width
和prec
有count$
,把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 開始的流水號。
▪目標陣列DESTINATION
和HOW
的用法同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 的擴充,能較細緻地尋找和取代。
▪若HOW
是g
或G
,取代所有符合者;若是數字 N,則僅取代第 N 個。如果不是g
或G
開頭的文字或是小於等於 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
的索引。
▪這也設定了RSTART
和RLENGTH
。RSTART
就是這個索引,而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] ])
將STRING
依REGEXP
分解為非分隔符陣列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] ])
將STRING
依REGEXP
分解為符合者陣列ARRAY
和間隔字串陣列SEPARATORS
。
▪傳回元素數。
▪這裡的REGEXP
和split()
的相反,符合的字串被分為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
,視為八進位數字;若是0x
或0X
,視為十六進位數字;其餘視為十進位數字。
▪這是 gawk 的擴充。
▪把字串和0
相加也可以變成數字,但只能用在十進位。
substr(STRING, START [, LENGTH])
子字串,傳回STRING
自START
位置開始共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 [, ...])
V1
和V2
的位元與
▪皆為 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 [, ...])
V1
和V2
的位元或
▪任一為 1 時為 1。
▪至少要有兩個引數。
範例:
awk 'BEGIN { print or(11, 22) }'
顯示:
⬧31
xor(V1, V2 [, ...])
V1
和V2
的位元互斥或
▪僅一為 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
的類型。
▪類型有:array
、number
、regexp
、string
、strnum
、undefined
(尚未出現)、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]])
傳回STRING
在DOMAIN
中CATEGORY
類的翻譯。
▪前面的dc
是指 domain 和 category。
▪DOMAIN
的預設值是內建變數TEXTDOMAIN
的值。
▪CATEGORY
的預設值是LC_MESSAGES
。可用的尙有:LC_COLLATE
、LC_CTYPE
、LC_MONETARY
、LC_NUMERIC
、LC_TIME
、LC_ALL
。
dcngettext(STRING1, STRING2, NUMBER [, DOMAIN [, CATEGORY]])
傳回STRING1
和STRING2
在DOMAIN
中CATEGORY
類翻譯NUMBER
的複數型。
▪前面的dcn
是指 domain、category、number。
▪STRING1
和STRING2
分別是英文的單數型和複數型。
▪DOMAIN
的預設值是內建變數TEXTDOMAIN
的值。
▪CATEGORY
的預設值是LC_MESSAGES
。可用的尙有:LC_COLLATE
、LC_CTYPE
、LC_MONETARY
、LC_NUMERIC
、LC_TIME
、LC_ALL
。
M7. 自訂函數
▪格式:function NAME(PARAMETERS) { STATEMENTS }
▪function
是內建關鍵字,某些版本的 awk 可以用func
,但為了相容性,最好用全名。
▪NAME
是函數名,可用的字元包括英文大小寫、數字、底線,但不能以數字開頭。
▪NAME
和小括號之間不能有空白。雖然內建函數名和小括號可以空開,但為免出錯,最好養成習慣,都不要分開。
▪PARAMETERS
是用逗號分開的引數和局部變數(該函數才用的變數)。局部變數要在引數之後,且用多個空格分開。譬如:
function f(p, q, a, b) { ... }
其中p
和q
是引數,而a
和b
是局部變數。局部變數若是文字,起啟值是空字串;若是數字,則是 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。