国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

一篇文章玩透awk

這篇具有很好參考價(jià)值的文章主要介紹了一篇文章玩透awk。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

安裝新版本gawk

awk有很多種版本,例如nawk、gawk。gawk是GNU awk,它的功能很豐富。

本教程采用的是gawk 4.2.0版本,4.2.0版本的gawk是一個(gè)比較大的改版,新支持的一些特性非常好用,而在低于4.2.0版本時(shí)這些語法可能會(huì)報(bào)錯(cuò)。所以,請(qǐng)先安裝4.2.0版本或更高版本的gawk。

查看awk版本

awk --version

這里以安裝gawk 4.2.0為例。

# 1.下載
wget --no-check-certificate https://mirrors.tuna.tsinghua.edu.cn/gnu/gawk/gawk-4.2.0.tar.gz

# 2.解壓、進(jìn)入解壓后目錄
tar xf gawk-4.2.0.tar.gz
cd gawk-4.2.0/

# 3.編譯,并執(zhí)行安裝目錄為/usr/local/gawk4.2
./configure --prefix=/usr/local/gawk4.2 && make && make install

# 4.創(chuàng)建一個(gè)軟鏈接:讓awk指向剛新裝的gawk版本
ln -fs /usr/local/gawk4.2/bin/gawk /usr/bin/awk

# 此時(shí),調(diào)用awk將調(diào)用新版本的gawk,調(diào)用gawk將調(diào)用舊版本的gawk
awk --version
gawk --version

本系列的awk教程中,將大量使用到如下示例文件a.txt。

ID  name    gender  age  email          phone
1   Bob     male    28   abc@qq.com     18023394012
2   Alice   female  24   def@gmail.com  18084925203
3   Tony    male    21   aaa@163.com    17048792503
4   Kevin   male    21   bbb@189.com    17023929033
5   Alex    male    18   ccc@xyz.com    18185904230
6   Andy    female  22   ddd@139.com    18923902352
7   Jerry   female  25   exdsa@189.com  18785234906
8   Peter   male    20   bax@qq.com     17729348758
9   Steven  female  23   bc@sohu.com    15947893212
10  Bruce   female  27   bcbd@139.com   13942943905

讀取文件的幾種方式

讀取文件有如下幾種常見的方式:

  1. 按字符數(shù)量讀取:每一次可以讀取一個(gè)字符,或者多個(gè)字符,直到把整個(gè)文件讀取完
  2. 按照分隔符進(jìn)行讀取:一直讀取直到遇到了分隔符才停止,下次繼續(xù)從分隔的位置處向后讀取,直到讀完整個(gè)文件
  3. 按行讀取:每次讀取一行,直到把整個(gè)文件讀完
    • 它是按照分隔符讀取的一種特殊情況:將分隔符指定為了換行符\n
  4. 一次性讀取整個(gè)文件
    • 是按字符數(shù)量讀取的特殊情況
    • 也是按分隔符讀取的特殊情況
  5. 按字節(jié)數(shù)量讀取:一次讀取指定數(shù)量的字節(jié)數(shù)據(jù),直到把文件讀完

下面使用Shell的read命令來演示前4種讀取文件的方式(第五種按字節(jié)數(shù)讀取的方式read不支持)。

按字符數(shù)量讀取

read的-n選項(xiàng)和-N選項(xiàng)可以指定一次性讀取多少個(gè)字符。

# 只讀一個(gè)字符
read -n 1 data <a.txt

# 讀100個(gè)字符,但如果不足100字符時(shí)遇到換行符則停止讀取
read -n 100 data < a.txt

# 強(qiáng)制讀取100字符,遇到換行符也不停止
read -N 100 data < a.txt

如果按照字符數(shù)量讀取,直到把文件讀完,則使用while循環(huán),且將文件放在while結(jié)構(gòu)的后面,而不能放在while循環(huán)的條件位置:

# 正確
while read -N 3 data;do
  echo "$data"
done <a.txt


# 錯(cuò)誤
while read -N 3 data < a.txt;do
  echo "$data"
done

按分隔符讀取

read命令的-d選項(xiàng)可以指定讀取文件時(shí)的分隔符。

# 一直讀取,直到遇到字符m才停止,并將讀取的數(shù)據(jù)保存到data變量中
read -d "m" data <a.txt

如果要按分隔符讀取并讀完整個(gè)文件,則使用while循環(huán):

while read -d "m" data ;do
  echo "$data"
done <a.txt

按行讀取

read默認(rèn)情況下就是按行讀取的,一次讀取一行。

# 從a.txt中讀取第一行保存到變量data中
read line <a.txt

如果要求按行讀取完整個(gè)文件,則使用while循環(huán):

while read line;do
  echo "$line"
done <a.txt

一次性讀整個(gè)文件

要一次性讀取完整個(gè)文件,有兩種方式:

  • 按照字符數(shù)量讀取,且指定的字符數(shù)要大于文件的總大小
  • 按分隔符讀取,且指定的分隔符是文件中不存在的字符,這樣的話會(huì)一直讀取,因?yàn)檎也坏椒指舴x完整個(gè)文件
# 指定超出文件大小的字符數(shù)量
read -N 1000000 data <a.txt
echo "$data"

# 指定文件中不存在的字符作為分隔符
read -d "_" data <a.txt
echo "$data"

awk用法入門

awk 'awk_program' a.txt
  • a.txt是awk要讀取的文件,可以是0個(gè)文件或一個(gè)文件,也可以多個(gè)文件
    • 如果不給定任何文件,但又需要讀取文件,則表示從標(biāo)準(zhǔn)輸入中讀取
  • 單引號(hào)包圍的是awk代碼,也稱為awk程序
    • 盡量使用單引號(hào),因?yàn)樵赼wk中經(jīng)常使用$符號(hào),而$符號(hào)在Shell是變量符號(hào),如果使用雙引號(hào)包圍awk代碼,則$符號(hào)會(huì)被Shell解析成Shell變量,然后進(jìn)行Shell變量替換。使用單引號(hào)包圍awk代碼,則$會(huì)脫離Shell的魔掌,使得$符號(hào)留給了awk去解析
  • awk程序中,大量使用大括號(hào),大括號(hào)表示代碼塊,代碼塊中間可以之間連用,代碼塊內(nèi)部的多個(gè)語句需使用分號(hào)";"分隔

awk示例:

# 輸出a.txt中的每一行
awk '{print $0}' a.txt

# 多個(gè)代碼塊,代碼塊中多個(gè)語句
# 輸出每行之后還輸出兩行:hello行和world行
awk '{print $0}{print "hello";print "world"}' a.txt

對(duì)于awk '{print $0}' a.txt,它類似于shell的while循環(huán)while read line;do echo "$line";done <a.txt。awk隱藏了讀取每一行的while循環(huán),它會(huì)自動(dòng)讀取每一行,其中的{print $0}對(duì)應(yīng)于Shell的while循環(huán)體echo "$line"部分。

下面再分析該awk命令的執(zhí)行過程:

一篇文章玩透awk

BEGIN和END語句塊

awk的所有代碼(目前這么認(rèn)為)都是寫在語句塊中的。

例如:

awk '{print $0}' a.txt
awk '{print $0}{print $0;print $0}' a.txt

每個(gè)語句塊前面可以有pattern,所以格式為:

pattern1{statement1}pattern2{statement3;statement4;...}

語句塊可分為3類:BEGIN語句塊、END語句塊和main語句塊。其中BEGIN語句塊和END語句塊都是的格式分別為BEGIN{...}END{...},而main語句塊是一種統(tǒng)稱,它的pattern部分沒有固定格式,也可以省略,main代碼塊是在讀取文件的每一行的時(shí)候都執(zhí)行的代碼塊。

分析下面三個(gè)awk命令的執(zhí)行結(jié)果:

awk 'BEGIN{print "我在前面"}{print $0}' a.txt
awk 'END{print "我在后面"}{print $0}' a.txt
awk 'BEGIN{print "我在前面"}{print $0}END{print "我在后面"}' a.txt

根據(jù)上面3行命令的執(zhí)行結(jié)果,可總結(jié)出如下有關(guān)于BEGIN、END和main代碼塊的特性:

一篇文章玩透awk

awk命令行結(jié)構(gòu)和語法結(jié)構(gòu)

awk命令行結(jié)構(gòu)

awk [ -- ] program-text file ...        (1)
awk -f program-file [ -- ] file ...     (2)
awk -e program-text [ -- ] file ...     (3)

其中:

一篇文章玩透awk

awk語法結(jié)構(gòu)

awk語法結(jié)構(gòu)即awk代碼部分的結(jié)構(gòu)。

awk的語法充斥著pattern{action}的模式,它們稱為awk rule。

例如:

awk '
	BEGIN{n=3} 
	/^[0-9]/{$1>5{$1=333;print $1}
	/Alice/{print "Alice"}
	END{print "hello"}
' a.txt

# 等價(jià)的單行式:
awk 'BEGIN{n=3} /^[0-9]/{$1>5{$1=333;print $1} /Alice/{print "Alice"} END{print "hello"}' a.txt

上面示例中,有BEGIN語句塊,有END語句塊,還有2個(gè)main代碼塊,兩個(gè)main代碼塊都使用了正則表達(dá)式作為pattern。

關(guān)于awk的語法:

  • 多個(gè)pattern{action}可以直接連接連用
  • action中多個(gè)語句如果寫在同一行,則需使用分號(hào)分隔
  • pattern部分用于篩選行,action表示在篩選通過后執(zhí)行的操作
  • pattern和action都可以省略,其中:

一篇文章玩透awk

pattern和action

對(duì)于pattern{action}語句結(jié)構(gòu)(都稱之為語句塊),其中的pattern部分可以使用下面列出的模式:

# 特殊pattern
BEGIN
END

# 布爾代碼塊
/regular expression/    # 正則匹配成功與否 /a.*ef/{action}
relational expression   # 即等值比較、大小比較 3>2{action}
pattern && pattern      # 邏輯與 3>2 && 3>1 {action}
pattern || pattern      # 邏輯或 3>2 || 3<1 {action}
! pattern               # 邏輯取反 !/a.*ef/{action}
(pattern)               # 改變優(yōu)先級(jí)
pattern ? pattern : pattern  # 三目運(yùn)算符決定的布爾值

# 范圍pattern,非布爾代碼塊
pattern1, pattern2      # 范圍,pat1打開、pat2關(guān)閉,即flip,flop模式

action部分,可以是任何語句,例如print。

詳細(xì)分析awk如何讀取文件

awk讀取輸入文件時(shí),每次讀取一條記錄(record)(默認(rèn)情況下按行讀取,所以此時(shí)記錄就是行)。每讀取一條記錄,將其保存到$0中,然后執(zhí)行一次main代碼段。

awk '{print $0}' a.txt

如果是空文件,則因?yàn)闊o法讀取到任何一條記錄,將導(dǎo)致直接關(guān)閉文件,而不會(huì)進(jìn)入main代碼段。

touch x.log  # 創(chuàng)建一個(gè)空文件
awk '{print "hello world"}' x.log

可設(shè)置表示輸入記錄分隔符的預(yù)定義變量RS(Record Separator)來改變每次讀取的記錄模式。

# RS="\n" 、 RS="m"
awk 'BEGIN{RS="\n"}{print $0}' a.txt
awk 'BEGIN{RS="m"}{print $0}' a.txt

RS通常設(shè)置在BEGIN代碼塊中,因?yàn)橐扔谧x取文件就確定好RS分隔符。

RS指定輸入記錄分隔符時(shí),所讀取的記錄中是不包含分隔符字符的。例如RS="a",則$0中一定不可能出現(xiàn)字符a。

RS兩種可能情況:

  • RS為單個(gè)字符:直接使用該字符來分割記錄
  • RS為多個(gè)字符:將其當(dāng)做正則表達(dá)式,只要匹配正則表達(dá)式的符號(hào),都用來分割記錄
    • 設(shè)置預(yù)定義變量IGNORECASE為非零值,正則匹配時(shí)表示忽略大小寫
    • 兼容模式下,只有首字符才生效,不會(huì)使用正則模式去分割記錄

特殊的RS值用來解決特殊讀取需求:

一篇文章玩透awk

示例:

# 按段落讀?。篟S=''
$ awk 'BEGIN{RS=""}{print $0"------"}' a.txt     

# 一次性讀取所有數(shù)據(jù):RS='\0' RS="^$"
$ awk 'BEGIN{RS="\0"}{print $0"------"}' a.txt     
$ awk 'BEGIN{RS="^$"}{print $0"------"}' a.txt  

# 忽略空行:RS='\n+'
$ awk 'BEGIN{RS="\n+"}{print $0"------"}' a.txt 

# 忽略大小寫:預(yù)定義變量IGNORECASE設(shè)置為非0值
$ awk 'BEGIN{IGNORECASE=1}{print $0"------"}' RS='[ab]' a.txt  

預(yù)定義變量RT:

在awk每次讀完一條記錄時(shí),會(huì)設(shè)置一個(gè)稱為RT的預(yù)定義變量,表示Record Termination。

當(dāng)RS為單個(gè)字符時(shí),RT的值和RS的值是相同的。

當(dāng)RS為多個(gè)字符(正則表達(dá)式)時(shí),則RT設(shè)置為正則匹配到記錄分隔符之后,真正用于劃分記錄時(shí)的字符。

當(dāng)無法匹配到記錄分隔符時(shí),RT設(shè)置為控制空字符串(即默認(rèn)的初始值)。

awk 'BEGIN{RS="(fe)?male"}{print RT}' a.txt

兩種行號(hào):NR和FNR

在讀取每條記錄之后,將其賦值給$0,同時(shí)還會(huì)設(shè)置NR、FNR、RT。

  • NR:所有文件的行號(hào)計(jì)數(shù)器
  • FNR:是各個(gè)文件的行號(hào)計(jì)數(shù)器
awk '{print NR}' a.txt a.txt
awk '{print FNR}' a.txt a.txt

詳細(xì)分析awk字段分割

awk讀取每一條記錄之后,會(huì)將其賦值給$0,同時(shí)還會(huì)對(duì)這條記錄按照預(yù)定義變量FS劃分字段,將劃分好的各個(gè)字段分別賦值給$1 $2 $3 $4...$N,同時(shí)將劃分的字段數(shù)量賦值給預(yù)定義變量NF。

引用字段的方式

$N引用字段:

  • N=0:即$0,引用記錄本身
  • 0<N<=NF:引用對(duì)應(yīng)字段
  • N>NF:表示引用不存在的字段,返回空字符串
  • N<0:報(bào)錯(cuò)

可使用變量或計(jì)算的方式指定要獲取的字段序號(hào)。

awk '{n = 5;print $n}' a.txt
awk '{print $(2+2)}' a.txt   # 括號(hào)必不可少,用于改變優(yōu)先級(jí)
awk '{print $(NF-3)}' a.txt

分割字段的方式

讀取record之后,將使用預(yù)定義變量FS、FIELDWIDTHS或FPAT中的一種來分割字段。分割完成之后,再進(jìn)入main代碼段(所以,在main中設(shè)置FS對(duì)本次已經(jīng)讀取的record是沒有影響的,但會(huì)影響下次讀取)。

劃分字段方式(一):FS或-F

FS或者-F:字段分隔符

一篇文章玩透awk

# 字段分隔符指定為單個(gè)字符
awk -F":" '{print $1}' /etc/passwd
awk 'BEGIN{FS=":"}{print $1}' /etc/passwd

# 字段分隔符指定為正則表達(dá)式
awk 'BEGIN{FS=" +|@"}{print $1,$2,$3,$4,$5,$6}' a.txt

劃分字段方式(二):FIELDWIDTHS

指定預(yù)定義變量FIELDWIDTHS按字符寬度分割字段,這是gawk提供的高級(jí)功能。在處理某字段缺失時(shí)非常好用。

用法:

  • FIELDWIDTHS="3 5 6 9"表示第一個(gè)字段3字符,第二字段5字符...
  • FIELDWIDTHS = "8 1:5 6 2:33"表示:
    • 第一個(gè)字段讀8個(gè)字符
    • 然后跳過1個(gè)字符再讀5個(gè)字符作為第二個(gè)字段
    • 然后讀6個(gè)字符作為第三個(gè)字段
    • 然后跳過2個(gè)字符在讀33個(gè)字符作為第四個(gè)字段(如果不足33個(gè)字符,則讀到結(jié)尾)
  • FIELDWIDTHS="2 3 *"
    • 第一個(gè)字段2個(gè)字符
    • 第二個(gè)字段3個(gè)字符
    • 第三個(gè)字段剩余所有字符
    • 星號(hào)只能放在最后,且只能單獨(dú)使用,表示剩余所有

示例1:

# 沒取完的字符串DDD被丟棄,且NF=3
$ awk 'BEGIN{FIELDWIDTHS="2 3 2"}{print $1,$2,$3,$4}' <<<"AABBBCCDDDD"
AA BBB CC 

# 字符串不夠長(zhǎng)度時(shí)無視
$ awk 'BEGIN{FIELDWIDTHS="2 3 2 100"}{print $1,$2,$3,$4"-"}' <<<"AABBBCCDDDD"
AA BBB CC DDDD-

# *號(hào)取剩余所有,NF=3
$ awk 'BEGIN{FIELDWIDTHS="2 3 *"}{print $1,$2,$3}' <<<"AABBBCCDDDD"      
AA BBB CCDDDD

# 字段數(shù)多了,則取完字符串即可,NF=2
$ awk 'BEGIN{FIELDWIDTHS="2 30 *"}{print $1,$2,NF}' <<<"AABBBCCDDDD"  
AA BBBCCDDDD 2

示例2:處理某些字段缺失的數(shù)據(jù)。

如果按照常規(guī)的FS進(jìn)行字段分割,則對(duì)于缺失字段的行和沒有缺失字段的行很難統(tǒng)一處理,但使用FIELDWIDTHS則非常方便。

假設(shè)a.txt文本內(nèi)容如下:

ID  name    gender  age  email          phone
1   Bob     male    28   abc@qq.com     18023394012
2   Alice   female  24   def@gmail.com  18084925203
3   Tony    male    21   aaa@163.com    17048792503
4   Kevin   male    21   bbb@189.com    17023929033
5   Alex    male    18                  18185904230
6   Andy    female  22   ddd@139.com    18923902352
7   Jerry   female  25   exdsa@189.com  18785234906
8   Peter   male    20   bax@qq.com     17729348758
9   Steven  female  23   bc@sohu.com    15947893212
10  Bruce   female  27   bcbd@139.com   13942943905

因?yàn)閑mail字段有的是空字段,所以直接用FS劃分字段不便處理??墒褂肍IELDWIDTHS。

# 字段1:4字符
# 字段2:8字符
# 字段3:8字符
# 字段4:2字符
# 字段5:先跳過3字符,再讀13字符,該字段13字符
# 字段6:先跳過2字符,再讀11字符,該字段11字符
awk '
BEGIN{FIELDWIDTHS="4 8 8 2 3:13 2:11"}
NR>1{
    print "<"$1">","<"$2">","<"$3">","<"$4">","<"$5">","<"$6">"
}' a.txt

# 如果email為空,則輸出它
awk '
BEGIN{FIELDWIDTHS="4 8 8 2 3:13 2:11"}
NR>1{
    if($5 ~ /^ +$/){print $0}
}' a.txt

劃分字段方式(三):FPAT

FS是指定字段分隔符,來取得除分隔符外的部分作為字段。

FPAT是取得匹配的字符部分作為字段。它是gawk提供的一個(gè)高級(jí)功能。

FPAT根據(jù)指定的正則來全局匹配record,然后將所有匹配成功的部分組成$1、$2...,不會(huì)修改$0。

  • awk 'BEGIN{FPAT="[0-9]+"}{print $3"-"}' a.txt
  • 之后再設(shè)置FS或FPAT,該變量將失效

FPAT常用于字段中包含了字段分隔符的場(chǎng)景。例如,CSV文件中的一行數(shù)據(jù)如下:

Robbins,Arnold,"1234 A Pretty Street, NE",MyTown,MyState,12345-6789,USA

其中逗號(hào)分隔每個(gè)字段,但雙引號(hào)包圍的是一個(gè)字段整體,即使其中有逗號(hào)。

這時(shí)使用FPAT來劃分各字段比使用FS要方便的多。

echo 'Robbins,Arnold,"1234 A Pretty Street, NE",MyTown,MyState,12345-6789,USA' |\
awk '
	BEGIN{FPAT="[^,]*|(\"[^\"]*\")"}
	{
        for (i=1;i<NF;i++){
            print "<"$i">"
        }
	}
'

最后,patsplit()函數(shù)和FPAT的功能一樣。

檢查字段劃分的方式

有FS、FIELDWIDTHS、FPAT三種獲取字段的方式,可使用PROCINFO數(shù)組來確定本次使用何種方式獲得字段。

PROCINFO是一個(gè)數(shù)組,記錄了awk進(jìn)程工作時(shí)的狀態(tài)信息。

如果:

  • PROCINFO["FS"]=="FS",表示使用FS分割獲取字段
  • PROCINFO["FPAT"]=="FPAT",表示使用FPAT匹配獲取字段
  • PROCINFO["FIELDWIDTHS"]=="FIELDWIDTHS",表示使用FIELDWIDTHS分割獲取字段

例如:

if(PROCINFO["FS"]=="FS"){
    ...FS spliting...
} else if(PROCINFO["FPAT"]=="FPAT"){
    ...FPAT spliting...
} else if(PROCINFO["FIELDWIDTHS"]=="FIELDWIDTHS"){
    ...FIELDWIDTHS spliting...
}

修改字段或NF值的聯(lián)動(dòng)效應(yīng)

注意下面的分割和計(jì)算兩詞:分割表示使用FS(field Separator),計(jì)算表示使用預(yù)定義變量OFS(Output Field Separator)。

一篇文章玩透awk

關(guān)于$0

當(dāng)讀取一條record之后,將原原本本地被保存到$0當(dāng)中。

awk '{print $0}' a.txt

但是,只要出現(xiàn)了上面所說的任何一種導(dǎo)致$0重新計(jì)算的操作,都會(huì)立即使用OFS去重建$0。

換句話說,沒有導(dǎo)致$0重建,$0就一直是原原本本的數(shù)據(jù),所以指定OFS也無效。

awk 'BEGIN{OFS="-"}{print $0}' a.txt  # OFS此處無效

當(dāng)$0重建后,將自動(dòng)使用OFS重建,所以即使沒有指定OFS,它也會(huì)采用默認(rèn)值(空格)進(jìn)行重建。

awk '{$1=$1;print $0}'  a.txt  # 輸出時(shí)將以空格分隔各字段
awk '{print $0;$1=$1;print $0}' OFS="-" a.txt

如果重建$0之后,再去修改OFS,將對(duì)當(dāng)前行無效,但對(duì)之后的行有效。所以如果也要對(duì)當(dāng)前行生效,需要再次重建。

# OFS對(duì)第一行無效
awk '{$4+=10;OFS="-";print $0}' a.txt

# 對(duì)所有行有效
awk '{$4+=10;OFS="-";$1=$1;print $0}' a.txt

關(guān)注$0重建是一個(gè)非常有用的技巧。

例如,下面通過重建$0的技巧來實(shí)現(xiàn)去除行首行尾空格并壓縮中間空格:

$ echo "   a  b  c   d   " | awk '{$1=$1;print}'
a b c d
$ echo "     a   b  c   d   " | awk '{$1=$1;print}' OFS="-"            
a-b-c-d

awk數(shù)據(jù)篩選示例

篩選行

# 1.根據(jù)行號(hào)篩選
awk 'NR==2' a.txt   # 篩選出第二行
awk 'NR>=2' a.txt   # 輸出第2行和之后的行

# 2.根據(jù)正則表達(dá)式篩選整行
awk '/qq.com/' a.txt       # 輸出帶有qq.com的行
awk '$0 ~ /qq.com/' a.txt  # 等價(jià)于上面命令
awk '/^[^@]+$/' a.txt      # 輸出不包含@符號(hào)的行
awk '!/@/' a.txt           # 輸出不包含@符號(hào)的行

# 3.根據(jù)字段來篩選行
awk '($4+0) > 24{print $0}' a.txt  # 輸出第4字段大于24的行
awk '$5 ~ /qq.com/' a.txt   # 輸出第5字段包含qq.com的行

# 4.將多個(gè)篩選條件結(jié)合起來進(jìn)行篩選
awk 'NR>=2 && NR<=7' a.txt 
awk '$3=="male" && $6 ~ /^170/' a.txt       
awk '$3=="male" || $6 ~ /^170/' a.txt  

# 5.按照范圍進(jìn)行篩選 flip flop
# pattern1,pattern2{action}
awk 'NR==2,NR==7' a.txt        # 輸出第2到第7行
awk 'NR==2,$6 ~ /^170/' a.txt

處理字段

修改字段時(shí),一定要注意,可能帶來的聯(lián)動(dòng)效應(yīng):即使用OFS重建$0。

awk 'NR>1{$4=$4+5;print $0}' a.txt
awk 'BEGIN{OFS="-"}NR>1{$4=$4+5;print $0}' a.txt
awk 'NR>1{$6=$6"*";print $0}' a.txt

awk運(yùn)維面試試題

從ifconfig命令的結(jié)果中篩選出除了lo網(wǎng)卡外的所有IPv4地址。

# 1.法一:多條件篩選
ifconfig | awk '/inet / && !($2 ~ /^127/){print $2}'

# 2.法二:按段落讀取,然后取IPv4字段
ifconfig | awk 'BEGIN{RS=""}!/lo/{print $6}'

# 3.法三:按段落讀取,每行1字段,然后取IPv4字段
ifconfig | awk 'BEGIN{RS="";FS="\n"}!/lo/{$0=$2;FS=" ";$0=$0;print $2}'

awk工作流程

參考自:man awk的"AWK PROGRAM EXECUTION"段。

man --pager='less -p ^"AWK PROGRAM EXECUTION"' awk

執(zhí)行步驟

一篇文章玩透awk

getline用法詳解

除了可以從標(biāo)準(zhǔn)輸入或非選項(xiàng)型參數(shù)所指定的文件中讀取數(shù)據(jù),還可以使用getline從其它各種渠道獲取需要處理的數(shù)據(jù),它的用法有很多種。

getline的返回值:

  • 如果可以讀取到數(shù)據(jù),返回1
  • 如果遇到了EOF,返回0
  • 如果遇到了錯(cuò)誤,返回負(fù)數(shù)。如-1表示文件無法打開,-2表示IO操作需要重試(retry)。在遇到錯(cuò)誤的同時(shí),還會(huì)設(shè)置ERRNO變量來描述錯(cuò)誤

為了健壯性,getline時(shí)強(qiáng)烈建議進(jìn)行判斷。例如:

if( (getline) <= 0 ){...}
if((getline) < 0){...}
if((getline) > 0){...}

上面的getline的括號(hào)盡量加上,因?yàn)?code>getline < 0表示的是輸入重定向,而不是和數(shù)值0進(jìn)行小于號(hào)的比較。

無參數(shù)的getline

getline無參數(shù)時(shí),表示從當(dāng)前正在處理的文件中立即讀取下一條記錄保存到$0中,并進(jìn)行字段分割,然后繼續(xù)執(zhí)行后續(xù)代碼邏輯。

此時(shí)的getline會(huì)設(shè)置NF、RT、NR、FNR、$0和$N。

next也可以讀取下一行。

  • getline:讀取下一行之后,繼續(xù)執(zhí)行g(shù)etline后面的代碼

  • next:讀取下一行,立即回頭awk循環(huán)的頭部,不會(huì)再執(zhí)行next后面的代碼

它們之間的區(qū)別用偽代碼描述,類似于:

# next
exec 9<> filename
while read -u 9 line;do
  ...code...
  continue  # next
  ...code...  # 這部分代碼在本輪循環(huán)當(dāng)中不再執(zhí)行
done

# getline
while read -u 9 line;do
  ...code...
  read -u 9 line  # getline
  ...code...
done

例如,匹配到某行之后,再讀一行就退出:

awk '/^1/{print;getline;print;exit}' a.txt

為了更健壯,應(yīng)當(dāng)對(duì)getline的返回值進(jìn)行判斷。

awk '/^1/{print;if((getline)<=0){exit};print}' a.txt

一個(gè)參數(shù)的getline

沒有參數(shù)的getline是讀取下一條記錄之后將記錄保存到$0中,并對(duì)該記錄進(jìn)行字段的分割。

一個(gè)參數(shù)的getline是將讀取的記錄保存到指定的變量當(dāng)中,并且不會(huì)對(duì)其進(jìn)行分割。

getline var

此時(shí)的getline只會(huì)設(shè)置RT、NR、FNR變量和指定的變量var。因此$0和$N以及NF保持不變。

awk '
/^1/{
  if((getline var)<=0){exit}
  print var
  print $0"--"$2
}' a.txt

awk從指定文件中讀取數(shù)據(jù)

  • getline < filename:從指定文件filename中讀取一條記錄并保存到$0
    • 會(huì)進(jìn)行字段的劃分,會(huì)設(shè)置變量$0 $N NF,不會(huì)設(shè)置變量NR FNR
  • getline var < filename:從指定文件filename中讀取一條記錄并保存到指定變量var中
    • 不會(huì)劃分字段,不會(huì)設(shè)置變量NR FNR NF $0 $N

filename需使用雙引號(hào)包圍表示文件名字符串,否則會(huì)當(dāng)作變量解析getline < "c.txt"。此外,如果路徑是使用變量構(gòu)建的,則應(yīng)該使用括號(hào)包圍路徑部分。例如getline < dir "/" filename中使用了兩個(gè)變量構(gòu)建路徑,這會(huì)產(chǎn)生歧義,應(yīng)當(dāng)寫成getline <(dir "/" filename)。

注意,每次從filename讀取之后都會(huì)做好位置偏移標(biāo)記,下次再從該文件讀取時(shí)將根據(jù)這個(gè)位置標(biāo)記繼續(xù)向后讀取。

例如,每次行首以1開頭時(shí)就讀取c.txt文件的所有行。

awk '
  /^1/{
    print;
    while((getline < "c.txt")>0){print};
    close("c.txt")
}' a.txt

上面的close("c.txt")表示在while(getline)讀取完文件之后關(guān)掉,以便后面再次讀取,如果不關(guān)掉,則文件偏移指針將一直在文件結(jié)尾處,使得下次讀取時(shí)直接遇到EOF。

awk從Shell命令輸出結(jié)果中讀取數(shù)據(jù)

  • cmd | getline:從Shell命令cmd的輸出結(jié)果中讀取一條記錄保存到$0
    • 會(huì)進(jìn)行字段劃分,設(shè)置變量$0 NF $N RT,不會(huì)修改變量NR FNR
  • cmd | getline var:從Shell命令cmd的輸出結(jié)果中讀取數(shù)據(jù)保存到var中
    • 除了var和RT,其它變量都不會(huì)設(shè)置

如果要再次執(zhí)行cmd并讀取其輸出數(shù)據(jù),則需要close關(guān)閉該命令。例如close("seq 1 5"),參見下面的示例。

例如:每次遇到以1開頭的行都輸出seq命令產(chǎn)生的1 2 3 4 5。

awk '/^1/{print;while(("seq 1 5"|getline)>0){print};close("seq 1 5")}' a.txt

再例如,調(diào)用Shell的date命令生成時(shí)間,然后保存到awk變量cur_date中:

awk '
  /^1/{
    print
    "date +\"%F %T\""|getline cur_date
    print cur_date
    close("date +\"%F %T\"")
}' a.txt

可以將cmd保存成一個(gè)字符串變量。

awk '
  BEGIN{get_date="date +\"%F %T\""}
  /^1/{
    print
    get_date | getline cur_date
    print cur_date
    close(get_date)
}' a.txt

更為復(fù)雜一點(diǎn)的,cmd中可以包含Shell的其它特殊字符,例如管道、重定向符號(hào)等:

awk '
  /^1/{
    print
    if(("seq 1 5 | xargs -i echo x{}y 2>/dev/null"|getline) > 0){
      print
    }
    close("seq 1 5 | xargs -i echo x{}y 2>/dev/null")
}' a.txt

awk中的coprocess

awk雖然強(qiáng)大,但是有些數(shù)據(jù)仍然不方便處理,這時(shí)可將數(shù)據(jù)交給Shell命令去幫助處理,然后再從Shell命令的執(zhí)行結(jié)果中取回處理后的數(shù)據(jù)繼續(xù)awk處理。

awk通過|&符號(hào)來支持coproc。

awk_print[f] "something" |& Shell_Cmd
Shell_Cmd |& getline [var]

這表示awk通過print輸出的數(shù)據(jù)將傳遞給Shell的命令Shell_Cmd去執(zhí)行,然后awk再從Shell_Cmd的執(zhí)行結(jié)果中取回Shell_Cmd產(chǎn)生的數(shù)據(jù)。

例如,不想使用awk的substr()來取子串,而是使用sed命令來替換。

awk '
    BEGIN{
      CMD="sed -nr \"s/.*@(.*)$/\\1/p\"";
    }

    NR>1{
        print $5;
        print $5 |& CMD;
        close(CMD,"to");
        CMD |& getline email_domain;
        close(CMD);
        print email_domain;
}' a.txt

對(duì)于awk_print |& cmd; cmd |& getline的使用,須注意的是:

一篇文章玩透awk

對(duì)于那些要求讀完所有數(shù)據(jù)再執(zhí)行的命令,例如sort命令,它們有可能需要等待數(shù)據(jù)已經(jīng)完成后(遇到EOF標(biāo)記)才開始執(zhí)行任務(wù),對(duì)于這些命令,可以多次向coprocess中寫入數(shù)據(jù),最后close(CMD,"to")讓coprocess運(yùn)行起來。

例如,對(duì)age字段(即$4)使用sort命令按數(shù)值大小進(jìn)行排序:

awk '
    BEGIN{
      CMD="sort -k4n";
    }

    # 將所有行都寫進(jìn)管道
    NR>1{
      print $0 |& CMD;
    }

    END{
      close(CMD,"to");  # 關(guān)閉管道通知sort開始排序
      while((CMD |& getline)>0){
        print;
      }
      close(CMD);
} ' a.txt

close()

close(filename)
close(cmd,[from | to])  # to參數(shù)只用于coprocess的第一個(gè)階段

如果close()關(guān)閉的對(duì)象不存在,awk不會(huì)報(bào)錯(cuò),僅僅只是讓其返回一個(gè)負(fù)數(shù)返回值。

close()有兩個(gè)基本作用:

  • 關(guān)閉文件,丟棄已有的文件偏移指針
    • 下次再讀取文件,將只能重新打開文件,重新打開文件會(huì)從文件的最開頭處開始讀取
  • 發(fā)送EOF標(biāo)記

awk中任何文件都只會(huì)在第一次使用時(shí)打開,之后都不會(huì)再重新打開。只有關(guān)閉之后,再使用才會(huì)重新打開。

例如一個(gè)需求是只要在a.txt中匹配到1開頭的行就輸出另一個(gè)文件x.log的所有內(nèi)容,那么在第一次輸出x.log文件內(nèi)容之后,文件偏移指針將在x.log文件的結(jié)尾處,如果不關(guān)閉該文件,則后續(xù)所有讀取x.log的文件操作都從結(jié)尾處繼續(xù)讀取,但是顯然總是得到EOF異常,所以getline返回值為0,而且也讀取不到任何數(shù)據(jù)。所以,必須關(guān)閉它才能在下次匹配成功時(shí)再次從頭讀取該文件。

awk '
  /^1/{
    print;
    while((getline var <"x.log")>0){
      print var
    }
    close("x.log")
}' a.txt

在處理Coprocess的時(shí)候,close()可以指定第二個(gè)參數(shù)"from"或"to",它們都針對(duì)于coproc而言,from時(shí)表示關(guān)閉coproc |& getline的管道,使用to時(shí),表示關(guān)閉print something |& coproc的管道。

awk '
BEGIN{
  CMD="sed -nr \"s/.*@(.*)$/\\1/p\"";
}
NR>1{
    print $5;
    print $5 |& CMD;
    close(CMD,"to");   # 本次close()是必須的
    CMD |& getline email_domain;
    close(CMD);
    print email_domain;
}' a.txt

上面的第一個(gè)close是必須的,否則sed會(huì)一直阻塞。因?yàn)閟ed一直認(rèn)為還有數(shù)據(jù)可讀,只有關(guān)閉管道發(fā)送一個(gè)EOF,sed才會(huì)開始處理。

執(zhí)行Shell命令system()

多數(shù)時(shí)候,使用awk的print cmd | "sh"即可實(shí)現(xiàn)調(diào)用shell命令的功能。

$ awk 'BEGIN{print "date +\"%s.%N\" | "sh"}'

但也可以使用system()函數(shù)來直接執(zhí)行一個(gè)Shell命令,system()的返回值是命令的退出狀態(tài)碼。

$ awk 'BEGIN{system("date +\"%s.%N\"")}'
1572328598.653524342

$ awk 'BEGIN{system("date +\"%s.%N\" >/dev/null")}'

$ awk 'BEGIN{system("date +\"%s.%N\" | cat")}'
1572328631.308807331

system()在開始運(yùn)行之前會(huì)flush gawk的緩沖。特別的,空字符串參數(shù)的system(""),它會(huì)被gawk特殊對(duì)待,它不會(huì)去啟動(dòng)一個(gè)shell來執(zhí)行空命令,而是僅執(zhí)行flush操作。

關(guān)于flush的行為,參考下文。

fflush()

gawk會(huì)按塊緩沖模式來緩沖輸出結(jié)果,使用fflush()會(huì)將緩沖數(shù)據(jù)刷出。

fflush([filename])

從gawk 4.0.2之后的版本(不包括4.0.2),無參數(shù)fflush()將刷出所有緩沖數(shù)據(jù)。

此外,終端設(shè)備是行緩沖模式,此時(shí)不需要fflush,而重定向到文件、到管道都是塊緩沖模式,此時(shí)可能需要fflush()。

此外,system()在運(yùn)行時(shí)也會(huì)flush gawk的緩沖。特別的,如果system的參數(shù)為空字符串system(""),則它不會(huì)去啟動(dòng)一個(gè)shell子進(jìn)程而是僅僅執(zhí)行flush操作。

沒有flush時(shí):

# 在終端輸入幾行數(shù)據(jù),將不會(huì)顯示,直到按下Ctrl + D
awk '{print "first";print "second"}' | cat

使用fflush():

# 在終端輸入幾行數(shù)據(jù),觀察
awk '{print "first";fflush();print "second"}' | cat

使用system()來flush:

# 在終端輸入幾行數(shù)據(jù),觀察
awk '{print "first";system("echo system");print "second"}' | cat
awk '{print "first";system("");print "second"}' | cat

也可以使用stdbuf -oL命令來強(qiáng)制gawk按行緩沖而非默認(rèn)的按塊緩沖。

# 在終端輸入幾行數(shù)據(jù),觀察
stdbuf -oL awk '{print "first";print "second"}' | cat

fflush()也可以指定文件名或命令,表示只刷出到該文件或該命令的緩沖數(shù)據(jù)。

# 刷出所有流向到標(biāo)準(zhǔn)輸出的緩沖數(shù)據(jù)
awk '{print "first";fflush("/dev/stdout");print "second"}' | cat

最后注意,fflush()刷出緩沖數(shù)據(jù)不代表發(fā)送EOF標(biāo)記。

輸出操作

awk可以通過print、printf將數(shù)據(jù)輸出到標(biāo)準(zhǔn)輸出或重定向到文件。

print

print elem1,elem2,elem3...
print(elem1,elem2,elem3...)

逗號(hào)分隔要打印的字段列表,各字段都會(huì)自動(dòng)轉(zhuǎn)換成字符串格式,然后通過預(yù)定義變量OFS(output field separator)的值(其默認(rèn)值為空格)連接各字段進(jìn)行輸出。

$ awk 'BEGIN{print "hello","world"}'
hello world
$ awk 'BEGIN{OFS="-";print "hello","world"}'
hello-world

print要輸出的數(shù)據(jù)稱為輸出記錄,在print輸出時(shí)會(huì)自動(dòng)在尾部加上輸出記錄分隔符,輸出記錄分隔符的預(yù)定義變量為ORS,其默認(rèn)值為\n。

$ awk 'BEGIN{OFS="-";ORS="_\n";print "hello","world"}'
hello-world_

括號(hào)可省略,但如果要打印的元素中包含了特殊符號(hào)>,則必須使用括號(hào)包圍(如print("a" > "A")),因?yàn)樗禽敵鲋囟ㄏ蚍?hào)。

如果省略參數(shù),即print;等價(jià)于print $0;

print輸出數(shù)值

print在輸出數(shù)據(jù)時(shí),總是會(huì)先轉(zhuǎn)換成字符串再輸出。

對(duì)于數(shù)值而言,可以自定義轉(zhuǎn)換成字符串的格式,例如使用sprintf()進(jìn)行格式化。

print在自動(dòng)轉(zhuǎn)換數(shù)值(專指小數(shù))為字符串的時(shí)候,采用預(yù)定義變量OFMT(Output format)定義的格式按照sprintf()相同的方式進(jìn)行格式化。OFMT默認(rèn)值為%.6g,表示有效位(整數(shù)部分加小數(shù)部分)最多為6。

$ awk 'BEGIN{print 3.12432623}'
3.12433

可以修改OFMT,來自定義數(shù)值轉(zhuǎn)換為字符串時(shí)的格式:

$ awk 'BEGIN{OFMT="%.2f";print 3.99989}'
4.00

# 格式化為整數(shù)
$ awk 'BEGIN{OFMT="%d";print 3.99989}' 
3
$ awk 'BEGIN{OFMT="%.0f";print 3.99989}' 
4

printf

printf format, item1, item2, ...

格式化字符:

一篇文章玩透awk

修飾符:均放在格式化字符的前面

N$      N是正整數(shù)。默認(rèn)情況下,printf的字段列表順序和格式化字符
        串中的%號(hào)順序是一一對(duì)應(yīng)的,使用N$可以自行指定順序。
        printf "%2$s %1$s","world","hello"輸出hello world
        N$可以重復(fù)指定,例如"%1$s %1$s"將取兩次第一個(gè)字段

寬度     指定該字段占用的字符數(shù)量,不足寬度默認(rèn)使用空格填充,超出寬度將無視。
         printf "%5s","ni"輸出"___ni",下劃線表示空格

-       表示左對(duì)齊。默認(rèn)是右對(duì)齊的。
        printf "%5s","ni"輸出"___ni"
        printf "%-5s","ni"輸出"ni___"

空格     針對(duì)于數(shù)值。對(duì)于正數(shù),在其前添加一個(gè)空格,對(duì)于負(fù)數(shù),無視
        printf "% d,% d",3,-2輸出"_3,-2",下劃線表示空格

+       針對(duì)于數(shù)值。對(duì)于正數(shù),在其前添加一個(gè)+號(hào),對(duì)于負(fù)數(shù),無視
        printf "%+d,%+d",3,-2輸出"+3,-2",下劃線表示空格

#       可變的數(shù)值前綴。對(duì)于%o,將添加前綴0,對(duì)于%x或%X,將添加前綴0x或0X

0       只對(duì)數(shù)值有效。使用0而非默認(rèn)的空格填充在左邊,對(duì)于左對(duì)齊的數(shù)值無效
        printf "%05d","3"輸出00003
        printf "%-05d","3"輸出3
        printf "%05s",3輸出____3

'       單引號(hào),表示對(duì)數(shù)值加上千分位逗號(hào),只對(duì)支持千分位表示的locale有效
        $ awk "BEGIN{printf \"%'d\n\",123457890}"
        123,457,890
        $ LC_ALL=C awk "BEGIN{printf \"%'d\n\",123457890}"
        123457890

.prec   指定精度。在不同格式化字符下,精度含義不同
        %d,%i,%o,%u,%x,%X 的精度表示最大數(shù)字字符數(shù)量
        %e,%E,%f,%F 的精度表示小數(shù)點(diǎn)后幾位數(shù)
        %s 的精度表示最長(zhǎng)字符數(shù)量,printf "%.3s","foob"輸出foo
        %g,%G 的精度表示表示最大有效位數(shù),即整數(shù)加小數(shù)位的總數(shù)量

sprintf()

sprintf()采用和printf相同的方式格式化字符串,但是它不會(huì)輸出格式化后的字符串,而是返回格式化后的字符串。所以,可以將格式化后的字符串賦值給某個(gè)變量。

awk '
    BEGIN{
        a = sprintf("%03d", 12.34)
        print a  # 012
    }
'

重定向輸出

一篇文章玩透awk

print[f] something | Shell_Cmd時(shí),awk將創(chuàng)建一個(gè)管道,然后啟動(dòng)Shell命令,print[f]產(chǎn)生的數(shù)據(jù)放入管道,而命令將從管道中讀取數(shù)據(jù)。

# 例1:
awk '
    NR>1{
      print $2 >"name.unsort"
      cmd = "sort >name.sort"
      print $2 | cmd
      #print $2 | "sort >name.sort"
    }
    END{close(cmd)}
' a.txt

# 例2:awk中構(gòu)建Shell命令,通過管道交給shell執(zhí)行
awk 'BEGIN{printf "seq 1 5" | "bash"}'

print[f] something |& Shell_Cmd時(shí),print[f]產(chǎn)生的數(shù)據(jù)交給Coprocess。之后,awk再從Coprocess中取回?cái)?shù)據(jù)。這里的|&有點(diǎn)類似于能夠讓Shell_Cmd后臺(tái)異步運(yùn)行的管道。

stdin、stdout、stderr

awk重定向時(shí)可以直接使用/dev/stdin、/dev/stdout/dev/stderr。還可以直接使用某個(gè)已打開的文件描述符/dev/fd/N。

例如:

awk 'BEGIN{print "something OK" > "/dev/stdout"}'
awk 'BEGIN{print "something wrong" > "/dev/stderr"}'
awk 'BEGIN{print "something wrong" | "cat >&2"}'

awk 'BEGIN{getline < "/dev/stdin";print $0}'

$ exec 4<> a.txt
$ awk 'BEGIN{while((getline < "/dev/fd/4")>0){print $0}}'

awk變量

awk的變量是動(dòng)態(tài)變量,在使用時(shí)聲明。

所以awk變量有3種狀態(tài):

  • 未聲明狀態(tài):稱為untyped類型
  • 引用過但未賦值狀態(tài):unassigned類型
  • 已賦值狀態(tài)

引用未賦值的變量,其默認(rèn)初始值為空字符串或數(shù)值0。

在awk中未聲明的變量稱為untyped,聲明了但未賦值(只要引用了就聲明了)的變量其類型為unassigned。

gawk 4.2版提供了typeof()函數(shù),可以測(cè)試變量的數(shù)據(jù)類型,包括測(cè)試變量是否聲明。

awk 'BEGIN{
  print(typeof(a))            # untyped
  if(b==0){print(typeof(b))}  # unassigned
}'

除了typeof(),還可以使用下面的技巧進(jìn)行檢測(cè):

awk 'BEGIN{
  if(a=="" && a==0){    # 未賦值時(shí),兩個(gè)都true
    print "untyped or unassigned"
  } else {
      print "assigned"
  }
}'

變量賦值

awk中的變量賦值語句也可以看作是一個(gè)有返回值的表達(dá)式。

例如,a=3賦值完成后返回3,同時(shí)變量a也被設(shè)置為3。

基于這個(gè)特點(diǎn),有兩點(diǎn)用法:

  • 可以x=y=z=5,等價(jià)于z=5 y=5 x=5
  • 可以將賦值語句放在任意允許使用表達(dá)式的地方
    • x != (y = 1)
    • awk 'BEGIN{print (a=4);print a}'

問題:a=1;arr[a+=2] = (a=a+6)是怎么賦值的,對(duì)應(yīng)元素結(jié)果等于?arr[3]=7。但不要這么做,因?yàn)椴煌琣wk的賦值語句左右兩邊的評(píng)估順序有可能不同。

awk中聲明變量的位置

一篇文章玩透awk

awk中使用Shell變量

要在awk中使用Shell變量,有三種方式:

1.在-v選項(xiàng)中將Shell變量賦值給awk變量

num=$(cat a.txt | wc -l)
awk -v n=$num 'BEGIN{print n}'

-v選項(xiàng)是在awk工作流程的第一階段解析的,所以-v選項(xiàng)聲明的變量在BEGIN{}、END{}和main代碼段中都能直接使用。

2.在非選項(xiàng)型參數(shù)位置處使用var=value格式將Shell變量賦值給awk變量

num=$(cat a.txt | wc -l)
awk  '{print n}' n=$num a.txt

非選項(xiàng)型參數(shù)設(shè)置的變量不能在BEGIN代碼段中使用。

3.直接在awk代碼部分暴露Shell變量,交給Shell解析進(jìn)行Shell的變量替換

num=$(cat a.txt | wc -l)
awk 'BEGIN{print '"$num"'}'

這種方式最靈活,但可讀性最差,可能會(huì)出現(xiàn)大量的引號(hào)。

數(shù)據(jù)類型

gawk有兩種基本的數(shù)據(jù)類型:數(shù)值和字符串。在gawk 4.2.0版本中,還支持第三種基本的數(shù)據(jù)類型:正則表達(dá)式類型。

數(shù)據(jù)是什么類型在使用它的上下文中決定:在字符串操作環(huán)境下將轉(zhuǎn)換為字符串,在數(shù)值操作環(huán)境下將轉(zhuǎn)換為數(shù)值。這和自然語言中的一個(gè)詞語、一個(gè)單詞在不同句子內(nèi)的不同語義是一樣的。

隱式轉(zhuǎn)換:

  • 算術(shù)加0操作可轉(zhuǎn)換為數(shù)值類型
    • "123" + 0返回?cái)?shù)值123
    • " 123abc" + 0轉(zhuǎn)換為數(shù)值時(shí)為123
    • 無效字符串將轉(zhuǎn)換成0,例如"abc"+3返回3
  • 連接空字符串可轉(zhuǎn)換為字符串類型
    • 123""轉(zhuǎn)換為字符串"123"
awk 'BEGIN{a="123";print typeof(a+0)}' # number
awk 'BEGIN{a=123;print typeof(a"")}'   # string

awk 'BEGIN{a=2;b=3;print(a b)+4}' # 27

顯式轉(zhuǎn)換:

  • 數(shù)值->字符串:
    • CONVFMT或sprintf():功能等價(jià)。都是指定數(shù)值轉(zhuǎn)換為字符串時(shí)的格式
awk 'BEGIN{a=123.4567;CONVFMT="%.2f";print a""}' #123.46
awk 'BEGIN{a=123.4567;print sprintf("%.2f", a)}' #123.46
awk 'BEGIN{a=123.4567;printf("%.2f",a)}' 
  • 字符串->數(shù)值:strtonum()
gawk 'BEGIN{a="123.4567";print strtonum(a)}' # 123.457

awk字面量

awk中有3種字面量:字符串字面量、數(shù)值字面量和正則表達(dá)式字面量。

數(shù)值字面量

  • 整數(shù)、浮點(diǎn)數(shù)、科學(xué)計(jì)數(shù)
    • 105、105.0、1.05e+2、1050e-1
  • awk內(nèi)部總是使用浮點(diǎn)數(shù)方式保存所有數(shù)值,但用戶在使用可以轉(zhuǎn)換成整數(shù)的數(shù)值時(shí)總會(huì)去掉小數(shù)點(diǎn)
    • 數(shù)值12.0面向用戶的值為12,12面向awk內(nèi)部的值是12.0000000...0
# 結(jié)果是123而非123.0
awk 'BEGIN{a=123.0;print a}'

算術(shù)運(yùn)算

++ --    自增、自減,支持i++和++i或--i或i--  
^        冪運(yùn)算(**也用于冪運(yùn)算)
+ -      一元運(yùn)算符(正負(fù)數(shù)符號(hào))
* / %    乘除取模運(yùn)算
+ -      加減法運(yùn)算

# 注:
# 1.++和--既可以當(dāng)作獨(dú)立語句,也可以作為表達(dá)式,如:
#     awk 'BEGIN{a=3;a++;a=++a;print a}'
# 2.**或^冪運(yùn)算是從右向左計(jì)算的:print 2**1**3得到2而不是8

賦值操作(優(yōu)先級(jí)最低):

= += -= *= /= %= ^= **=

疑惑:b = 6;print b += b++輸出結(jié)果?可能是12或13。不同的awk的實(shí)現(xiàn)在評(píng)估順序上不同,所以不要用這種可能產(chǎn)生歧義的語句。

字符串字面量

awk中的字符串都以雙引號(hào)包圍,不能以單引號(hào)包圍。

  • "abc"
  • ""
  • "\0""\n"

字符串連接(串聯(lián)):awk沒有為字符串的串聯(lián)操作提供運(yùn)算符,可以直接連接或使用空格連接。

awk 'BEGIN{print ("one" "two")}'  # "onetwo"
awk 'BEGIN{print ("one""two")}'
awk 'BEGIN{a="one";b="two";print (a b)}'

注意:字符串串聯(lián)雖然方便,但是要考慮串聯(lián)的優(yōu)先級(jí)。例如下面的:

# 下面第一個(gè)串聯(lián)成功,第二個(gè)串聯(lián)失敗,
# 因?yàn)榇?lián)優(yōu)先級(jí)低于加減運(yùn)算,等價(jià)于`12 (" " -23)`
# 即:先轉(zhuǎn)為數(shù)值0-23,再轉(zhuǎn)為字符串12-23
$ awk 'BEGIN{a="one";b="two";print (12 " " 23)}'
12 23
$ awk 'BEGIN{a="one";b="two";print (12 " " -23)}'
12-23

正則表達(dá)式字面量

普通正則:

  • /[0-9]+/
  • 匹配方式:"str" ~ /pattern/"str" !~ /pattern/
  • 匹配結(jié)果返回值為0(匹配失敗)或1(匹配成功)
  • 任何單獨(dú)出現(xiàn)的/pattern/都等價(jià)于$0 ~ /pattern/
    • if(/pattern/)等價(jià)于if($0 ~ /pattern/)
    • 坑1:a=/pattern/等價(jià)于將$0 ~ /pattern/的匹配返回值(0或1)賦值給a
    • 坑2:/pattern/ ~ $1等價(jià)于$0 ~ /pattern/ ~ $1,表示用$1去匹配0或1
    • 坑3:/pattern/作為參數(shù)傳給函數(shù)時(shí),傳遞的是$0~/pat/的結(jié)果0或1
    • 坑4.坑5.坑6...

強(qiáng)類型的正則字面量(gawk 4.2.0才支持):

一篇文章玩透awk

gawk支持的正則

.       # 匹配任意字符,包括換行符
^
$
[...]
[^...]
|
+
*
?
()
{m}
{m,}
{m,n}
{,n}

[:lower:]
[:upper:]
[:alpha:]
[:digit:]
[:alnum:]
[:xdigit:]
[:blank:]
[:space:]
[:punct:]
[:graph:]
[:print:]
[:cntrl:]

以下是gawk支持的:
\y    匹配單詞左右邊界部分的空字符位置 "hello world"
\B    和\y相反,匹配單詞內(nèi)部的空字符位置,例如"crate" ~ `/c\Brat\Be/`成功
\<    匹配單詞左邊界
\>    匹配單詞右邊界
\s    匹配空白字符
\S    匹配非空白字符
\w    匹配單詞組成字符(大小寫字母、數(shù)字、下劃線)
\W    匹配非單詞組成字符
\`    匹配字符串的絕對(duì)行首  "abc\ndef"
\'    匹配字符串的絕對(duì)行尾

gawk不支持正則修飾符,所以無法直接指定忽略大小寫的匹配。

如果想要實(shí)現(xiàn)忽略大小寫匹配,則可以將字符串先轉(zhuǎn)換為大寫、小寫再進(jìn)行匹配?;蛘咴O(shè)置預(yù)定義變量IGNORECASE為非0值。

# 轉(zhuǎn)換為小寫
awk 'tolower($0) ~ /bob/{print $0}' a.txt

# 設(shè)置IGNORECASE
awk '/BOB/{print $0}' IGNORECASE=1 a.txt

awk布爾值

在awk中,沒有像其它語言一樣專門提供true、false這樣的關(guān)鍵字。

但它的布爾值邏輯非常簡(jiǎn)單:

一篇文章玩透awk

awk '
BEGIN{
    if(1){print "haha"}
    if("0"){print "hehe"}
    if(a=3){print "hoho"}  # if(3){print "hoho"}
    if(a==3){print "aoao"}
    if(/root/){print "heihei"}  # $0 ~ /root/
}'

awk中比較操作

strnum類型

awk最基本的數(shù)據(jù)類型只有string和number(gawk 4.2.0版本之后支持正則表達(dá)式類型)。但是,對(duì)于用戶輸入數(shù)據(jù)(例如從文件中讀取的各個(gè)字段值),它們理應(yīng)屬于string類型,但有時(shí)候它們看上去可能像是數(shù)值(例如$2=37),而有時(shí)候有需要這些值是數(shù)值類型。

一篇文章玩透awk

注意,strnum類型只針對(duì)于awk中除數(shù)值常量、字符串常量、表達(dá)式計(jì)算結(jié)果外的數(shù)據(jù)。例如從文件中讀取的字段$1、$2、ARGV數(shù)組中的元素等等。

$ echo "30" | awk '{print typeof($0) " " typeof($1)}'
strnum strnum
$ echo "+30" | awk '{print typeof($1)}'
strnum
$ echo "30a" | awk '{print typeof($1)}'
string
$ echo "30 a" | awk '{print typeof($0) " " typeof($1)}'
string strnum
$ echo " +30 " | awk '{print typeof($0) " " typeof($1)}'
strnum strnum

大小比較操作

比較操作符:

< > <= >= != ==  大小、等值比較
in     數(shù)組成員測(cè)試

比較規(guī)則:

       |STRING NUMERIC STRNUM
-------|-----------------------
STRING |string string  string
NUMERIC|string numeric numeric
STRNUM |string numeric numeric

簡(jiǎn)單來說,string優(yōu)先級(jí)最高,只要string類型參與比較,就都按照string的比較方式,所以可能會(huì)進(jìn)行隱式的類型轉(zhuǎn)換。

其它時(shí)候都采用num類型比較。

$ echo ' +3.14' | awk '{print typeof($0) " " typeof($1)}'  #strnum strnum
$ echo ' +3.14' | awk '{print($0 == " +3.14")}'    #1
$ echo ' +3.14' | awk '{print($0 == "+3.14")}'     #0
$ echo ' +3.14' | awk '{print($0 == "3.14")}'      #0
$ echo ' +3.14' | awk '{print($0 == 3.14)}'        #1
$ echo ' +3.14' | awk '{print($1 == 3.14)}'        #1
$ echo ' +3.14' | awk '{print($1 == " +3.14")}'    #0
$ echo ' +3.14' | awk '{print($1 == "+3.14")}'     #1
$ echo ' +3.14' | awk '{print($1 == "3.14")}'      #0 
$ echo 1e2 3|awk ’{print ($1<$2)?"true":"false"}’  #false

采用字符串比較時(shí)需注意,它是逐字符逐字符比較的。

"11" < "9"  # true
"ab" < 99   # false

邏輯運(yùn)算

&&          邏輯與
||          邏輯或
!           邏輯取反

expr1 && expr2  # 如果expr1為假,則不用計(jì)算expr2
expr1 || expr2  # 如果expr1為真,則不用計(jì)算expr2

# 注:
# 1. && ||會(huì)短路運(yùn)算
# 2. !優(yōu)先級(jí)高于&&和||
#    所以`! expr1 && expr2`等價(jià)于`(! expr1) && expr2`

!可以將數(shù)據(jù)轉(zhuǎn)換成數(shù)值的1或0,取決于數(shù)據(jù)是布爾真還是布爾假。!!可將數(shù)據(jù)轉(zhuǎn)換成等價(jià)布爾值的1或0。

$ awk 'BEGIN{print(!99)}'   # 0
$ awk 'BEGIN{print(!"ab")}' # 0
$ awk 'BEGIN{print(!0)}'    # 1
$ awk 'BEGIN{print(!ab)}'   # 1,因?yàn)閍b變量不存在

$ awk 'BEGIN{print(!!99)}'   # 1
$ awk 'BEGIN{print(!!"ab")}' # 1
$ awk 'BEGIN{print(!!0)}'    # 0
$ awk 'BEGIN{print(!!ab)}'   # 0

由于awk中的變量未賦值時(shí)默認(rèn)初始化為空字符串或數(shù)值0,也就是布爾假。那么可以直接對(duì)一個(gè)未賦值的變量執(zhí)行!操作。

下面是一個(gè)非常有意思的awk技巧,它通過多次!對(duì)一個(gè)flag取反來實(shí)現(xiàn)只輸出指定范圍內(nèi)的行。

# a.txt
$1==1{flag=!flag;print;next}    # 在匹配ID=1的行時(shí),flag=1
flag{print}               # 將輸出ID=2,3,4,5的行
$1==5{flag=!flag;next}    # ID=5時(shí),flag=0

借此,就可以讓awk實(shí)現(xiàn)一個(gè)多行處理模式。例如,將指定范圍內(nèi)的數(shù)據(jù)保存到一個(gè)變量當(dāng)中去。

$1==1{flag=!flag;next}
flag{multi_line=multi_line$0"\n"}
$1==5{flag=!flag;next}
END{printf multi_line}

運(yùn)算符優(yōu)先級(jí)

優(yōu)先級(jí)從高到低:man awk

()
$      # $(2+2)
++ --
^ **
+ - !   # 一元運(yùn)算符
* / %
+ -
space  # 這是字符連接操作 `12 " " 23`  `12 " " -23`
| |&
< > <= >= != ==   # 注意>即是大于號(hào),也是print/printf的重定向符號(hào)
~ !~
in
&&
||
?:
= += -= *= /= %= ^=

對(duì)于相同優(yōu)先級(jí)的運(yùn)算符,通常都是從左開始運(yùn)算,但下面2種例外,它們都從右向左運(yùn)算:

  • 賦值運(yùn)算:如= += -= *=
  • 冪運(yùn)算
a - b + c  =>  (a - b) + c
a = b = c  =>  a =(b = c)
2**2**3    =>  2**(2**3)

再者,注意print和printf中出現(xiàn)的>符號(hào),這時(shí)候它表示的是重定向符號(hào),不能再出現(xiàn)優(yōu)先級(jí)比它低的運(yùn)算符,這時(shí)可以使用括號(hào)改變優(yōu)先級(jí)。例如:

awk 'BEGIN{print "foo" > a < 3 ? 2 : 1)'   # 語法錯(cuò)誤
awk 'BEGIN{print "foo" > (a < 3 ? 2 : 1)}' # 正確

流程控制語句

注:awk中語句塊沒有作用域,都是全局變量。

if (condition) statement [ else statement ]
expr1?expr2:expr3
while (condition) statement
do statement while (condition)
for (expr1; expr2; expr3) statement
for (var in array) statement
break
continue
next
nextfile
exit [ expression ]
{ statements }
switch (expression) {
    case value|regex : statement
    ...
    [ default: statement ]
}

代碼塊

{statement}

if...else

# 單獨(dú)的if
if(cond){
    statements
}

# if...else
if(cond1){
    statements1
} else {
    statements2
}

# if...else if...else
if(cond1){
    statements1
} else if(cond2){
    statements2
} else if(cond3){
    statements3
} else{
    statements4
}

搞笑題:妻子告訴程序員老公,去買一斤包子,如果看見賣西瓜的,就買兩個(gè)。結(jié)果是買了兩個(gè)包子回來。

# 自然語言的語義
買一斤包子
if(有西瓜){
    買兩個(gè)西瓜
}

# 程序員理解的語義
if(沒有西瓜){
    買一斤包子
}else{
    買兩個(gè)包子
}
awk '
  BEGIN{
    mark = 999
    if (mark >=0 && mark < 60) {
      print "學(xué)渣"
    } else if (mark >= 60 && mark < 90) {
      print "還不錯(cuò)"
    } else if (mark >= 90 && mark <= 100) {
      print "學(xué)霸"
    } else {
      print "錯(cuò)誤分?jǐn)?shù)"
    }
  }
'

三目運(yùn)算符?:

expr1 ? expr2 : expr3

if(expr1){
    expr2
} else {
    expr3
}
awk 'BEGIN{a=50;b=(a>60) ? "及格" : "不及格";print(b)}'
awk 'BEGIN{a=50; a>60 ? b="及格" : b="不及格";print(b)}' 

switch...case

switch (expression) {
    case value1|regex1 : statements1
    case value2|regex2 : statements2
    case value3|regex3 : statements3
    ...
    [ default: statement ]
}

awk 中的switch分支語句功能較弱,只能進(jìn)行等值比較或正則匹配。

各分支結(jié)尾需使用break來終止。

{
    switch($1){
        case 1:
            print("Monday")
            break
        case 2:
            print("Tuesday")
            break
        case 3:
            print("Wednesday")
            break
        case 4:
            print("Thursday")
            break
        case 5:
            print("Friday")
            break
        case 6:
            print("Saturday")
            break
        case 7:
            print("Sunday")
            break
        default:
            print("What day?")
            break
    }
}

分支穿透:

{
    switch($1){
        case 1:
        case 2:
        case 3:
        case 4:
        case 5:
            print("Weekday")
            break
        case 6:
        case 7:
            print("Weekend")
            break
        default:
            print("What day?")
            break
    }
}

while和do...while

while(condition){
    statements
}

do {
    statements
} while(condition)

while先判斷條件再?zèng)Q定是否執(zhí)行statements,do...while先執(zhí)行statements再判斷條件決定下次是否再執(zhí)行statements。

awk 'BEGIN{i=0;while(i<5){print i;i++}}'
awk 'BEGIN{i=0;do {print i;i++} while(i<5)}'

多數(shù)時(shí)候,while和do...while是等價(jià)的,但如果第一次條件判斷失敗,則do...while和while不同。

awk 'BEGIN{i=0;while(i == 2){print i;i++}}'
awk 'BEGIN{i=0;do {print i;i++} while(i ==2 )}'

所以,while可能一次也不會(huì)執(zhí)行,do...while至少會(huì)執(zhí)行一次。

一般用while,do...while相比while來說,用的頻率非常低。

for循環(huán)

for (expr1; expr2; expr3) {
    statement
}

for (idx in array) {
    statement
}

break和continue

break可退出for、while、do...while、switch語句。

continue可讓for、while、do...while進(jìn)入下一輪循環(huán)。

awk '
BEGIN{
  for(i=0;i<10;i++){
    if(i==5){
      break
    }
    print(i)
  }

  # continue
  for(i=0;i<10;i++){
    if(i==5)continue
    print(i)
  }
}'

next和nextfile

next會(huì)在當(dāng)前語句處立即停止后續(xù)操作,并讀取下一行,進(jìn)入循環(huán)頂部。

例如,輸出除第3行外的所有行。

awk 'NR==3{next}{print}' a.txt
awk 'NR==3{getline}{print}' a.txt

nextfile會(huì)在當(dāng)前語句處立即停止后續(xù)操作,并直接讀取下一個(gè)文件,并進(jìn)入循環(huán)頂部。

例如,每個(gè)文件只輸出前2行:

awk 'FNR==3{nextfile}{print}' a.txt a.txt

exit

exit [exit_code]

直接退出awk程序。

注意,END語句塊也是exit操作的一部分,所以在BEGIN或main段中執(zhí)行exit操作,也會(huì)執(zhí)行END語句塊。

如果exit在END語句塊中執(zhí)行,則立即退出。

所以,如果真的想直接退出整個(gè)awk,則可以先設(shè)置一個(gè)flag變量,然后在END語句塊的開頭檢查這個(gè)變量再exit。

BEGIN{
    ...code...
    if(cond){
        flag=1
        exit
    }
}
{}
END{
    if(flag){
        exit
    }
    ...code...
}

awk '
    BEGIN{print "begin";flag=1;exit}
    {}
    END{if(flag){exit};print "end2"}
' 

exit可以指定退出狀態(tài)碼,如果觸發(fā)了兩次exit操作,即BEGIN或main中的exit觸發(fā)了END中的exit,且END中的exit沒有指定退出狀態(tài)碼時(shí),則采取前一個(gè)退出狀態(tài)碼。

$ awk 'BEGIN{flag=1;exit 2}{}END{if(flag){exit 1}}' 
$ echo $?
1

$ awk 'BEGIN{flag=1;exit 2}{}END{if(flag){exit}}'   
$ echo $?
2

數(shù)組

awk數(shù)組特性:

  • awk的數(shù)組是關(guān)聯(lián)數(shù)組(即key/value方式的hash數(shù)據(jù)結(jié)構(gòu)),索引下標(biāo)可為數(shù)值(甚至是負(fù)數(shù)、小數(shù)等),也可為字符串
    • 在內(nèi)部,awk數(shù)組的索引全都是字符串,即使是數(shù)值索引在使用時(shí)內(nèi)部也會(huì)轉(zhuǎn)換成字符串
    • awk的數(shù)組元素的順序和元素插入時(shí)的順序很可能是不相同的
  • awk數(shù)組支持?jǐn)?shù)組的數(shù)組

awk訪問、賦值數(shù)組元素

arr[idx]
arr[idx] = value

索引可以是整數(shù)、負(fù)數(shù)、0、小數(shù)、字符串。如果是數(shù)值索引,會(huì)按照CONVFMT變量指定的格式先轉(zhuǎn)換成字符串。

例如:

awk '
  BEGIN{
    arr[1]   = 11
    arr["1"] = 111
    arr["a"] = "aa"
    arr[-1]  = -11
    arr[4.3] = 4.33
# 本文來自駿馬金龍:www.junmajinlong.com
    print arr[1]     # 111
    print arr["1"]   # 111
    print arr["a"]   # aa
    print arr[-1]    # -11
    print arr[4.3]   # 4.33
  }
'

通過索引的方式訪問數(shù)組中不存在的元素時(shí),會(huì)返回空字符串,同時(shí)會(huì)創(chuàng)建這個(gè)元素并將其值設(shè)置為空字符串。

awk '
  BEGIN{
    arr[-1]=3;
    print length(arr);  # 1
    print arr[1];
    print length(arr)   # 2
  }'

awk數(shù)組長(zhǎng)度

awk提供了length()函數(shù)來獲取數(shù)組的元素個(gè)數(shù),它也可以用于獲取字符串的字符數(shù)量。還可以獲取數(shù)值轉(zhuǎn)換成字符串后的字符數(shù)量。

awk 'BEGIN{arr[1]=1;arr[2]=2;print length(arr);print length("hello")}'

awk刪除數(shù)組元素

  • delete arr[idx]:刪除數(shù)組arr[idx]元素
    • 刪除不存在的元素不會(huì)報(bào)錯(cuò)
  • delete arr:刪除數(shù)組所有元素
$ awk 'BEGIN{arr[1]=1;arr[2]=2;arr[3]=3;delete arr[2];print length(arr)}'
2

awk檢測(cè)是否是數(shù)組

isarray(arr)可用于檢測(cè)arr是否是數(shù)組,如果是數(shù)組則返回1,否則返回0。

typeof(arr)可返回?cái)?shù)據(jù)類型,如果arr是數(shù)組,則其返回"array"。

awk 'BEGIN{
    arr[1]=1;
    print isarray(arr);
    print (typeof(arr) == "array")
}'

awk測(cè)試元素是否在數(shù)組中

不要使用下面的方式來測(cè)試元素是否在數(shù)組中:

if(arr["x"] != ""){...}

這有兩個(gè)問題:

  • 如果不存在arr["x"],則會(huì)立即創(chuàng)建該元素,并將其值設(shè)置為空字符串
  • 有些元素的值本身就是空字符串

應(yīng)當(dāng)使用數(shù)組成員測(cè)試操作符in來測(cè)試:

# 注意,idx不要使用index,它是一個(gè)內(nèi)置函數(shù)
if (idx in arr){...}

它會(huì)測(cè)試索引idx是否在數(shù)組中,如果存在則返回1,不存在則返回0。

awk '
    BEGIN{
    # 本文來自駿馬金龍:www.junmajinlong.com
        arr[1]=1;
        arr[2]=2;
        arr[3]=3;

        arr[1]="";
        delete arr[2];

        print (1 in arr);  # 1
        print (2 in arr);  # 0
    }'

awk遍歷數(shù)組

awk提供了一種for變體來遍歷數(shù)組:

for(idx in arr){print arr[idx]}

因?yàn)閍wk數(shù)組是關(guān)聯(lián)數(shù)組,元素是不連續(xù)的,也就是說沒有順序。遍歷awk數(shù)組時(shí),順序是不可預(yù)測(cè)的。

例如:

# 本文來自駿馬金龍:www.junmajinlong.com
awk '
    BEGIN{
        arr["one"] = 1
        arr["two"] = 2
        arr["three"] = 3
        arr["four"] = 4
        arr["five"] = 5

        for(i in arr){
            print i " -> " arr[i]
        }
    }
'

此外,不要隨意使用for(i=0;i<length(arr);i++)來遍歷數(shù)組,因?yàn)閍wk數(shù)組是關(guān)聯(lián)數(shù)組。但如果已經(jīng)明確知道數(shù)組的所有元素索引都位于某個(gè)數(shù)值范圍內(nèi),則可以使用該方式進(jìn)行遍歷。

例如:

# 本文來自駿馬金龍:www.junmajinlong.com
awk '
    BEGIN{
        arr[1] = "one"
        arr[2] = "two"
        arr[3] = "three"
        arr[4] = "four"
        arr[5] = "five"
        arr[10]= "ten"

        for(i=0;i<=10;i++){
            if(i in arr){
                print arr[i]
            }
        }
    }
'

awk復(fù)雜索引的數(shù)組

在awk中,很多時(shí)候單純的一個(gè)數(shù)組只能存放兩個(gè)信息:一個(gè)索引、一個(gè)值。但在一些場(chǎng)景下,這樣簡(jiǎn)單的存儲(chǔ)能力在處理復(fù)雜需求的時(shí)候可能會(huì)捉襟見肘。

為了存儲(chǔ)更多信息,方式之一是將第3份、第4份等信息全部以特殊方式存放到值中,但是這樣的方式在實(shí)際使用過程中并不方便,每次都需要去分割值從而取出各部分的值。

另一種方式是將第3份、第4份等信息存放在索引中,將多份數(shù)據(jù)組成一個(gè)整體構(gòu)成一個(gè)索引。

gawk中提供了將多份數(shù)據(jù)信息組合成一個(gè)整體當(dāng)作一個(gè)索引的功能。默認(rèn)方式為arr[x,y],其中x和y是要結(jié)合起來構(gòu)建成一個(gè)索引的兩部分?jǐn)?shù)據(jù)信息。逗號(hào)稱為下標(biāo)分隔符,在構(gòu)建索引時(shí)會(huì)根據(jù)預(yù)定義變量SUBSEP的值將多個(gè)索引組合起來。所以arr[x,y]其實(shí)完全等價(jià)于arr[x SUBSEP y]。

例如,如果SUBSEP設(shè)置為"@",那么arr[5,12] = 512存儲(chǔ)時(shí),其真實(shí)索引為5@12,所以要訪問該元素需使用arr["5@12"]

SUBSEP的默認(rèn)值為\034,它是一個(gè)不可打印的字符,幾乎不可能會(huì)出現(xiàn)在字符串當(dāng)中。

如果我們?cè)敢獾脑?,我們也可以自己將多份?shù)據(jù)組合起來去構(gòu)建成一個(gè)索引,例如arr[x" "y]。但是awk提供了這種更為簡(jiǎn)便的方式,直接用即可。

為了測(cè)試這種復(fù)雜數(shù)組的索引是否在數(shù)組中,可以使用如下方式:

arr["a","b"] = 12
if (("a", "b") in arr){...}

例如,順時(shí)針倒轉(zhuǎn)下列數(shù)據(jù):

1 2 3 4 5 6
2 3 4 5 6 1
3 4 5 6 1 2
4 5 6 1 2 3

結(jié)果:
4 3 2 1
5 4 3 2
6 5 4 3
1 6 5 4
2 1 6 5
3 2 1 6
{
  nf = NF
  nr = NR
  for(i=1;i<=NF;i++){
    arr[NR,i] = $i
  }
}

END{
  for(i=1;i<=nf;i++){
    for(j=nr;j>=1;j--){
      if(j%nr == 1){
        printf "%s\n", arr[j,i]
      }else {
        printf "%s ", arr[j,i]
      }
    }
  }
}

awk子數(shù)組

子數(shù)組是指數(shù)組中的元素也是一個(gè)數(shù)組,即Array of Array,它也稱為子數(shù)組(subarray)。

awk也支持子數(shù)組,在效果上即是嵌套數(shù)組或多維數(shù)組。

a[1][1] = 11
a[1][2] = 12
a[1][3] = 13
a[2][1] = 21
a[2][2] = 22
a[2][3] = 23
a[2][4][1] = 241
a[2][4][2] = 242
a[2][4][1] = 241
a[2][4][3] = 243

通過如下方式遍歷二維數(shù)組:

一篇文章玩透awk

awk指定數(shù)組遍歷順序

由于awk數(shù)組是關(guān)聯(lián)數(shù)組,默認(rèn)情況下,for(idx in arr)遍歷數(shù)組時(shí)順序是不可預(yù)測(cè)的。

但是gawk提供了PROCINFO["sorted_in"]來指定遍歷的元素順序。它可以設(shè)置為兩種類型的值:

  • 設(shè)置為用戶自定義函數(shù)
  • 設(shè)置為下面這些awk預(yù)定義好的值:
    • @unsorted:默認(rèn)值,遍歷時(shí)無序
    • @ind_str_asc:索引按字符串比較方式升序遍歷
    • @ind_str_desc:索引按字符串比較方式降序遍歷
    • @ind_num_asc:索引強(qiáng)制按照數(shù)值比較方式升序遍歷。所以無法轉(zhuǎn)換為數(shù)值的字符串索引將當(dāng)作數(shù)值0進(jìn)行比較
    • @ind_num_desc:索引強(qiáng)制按照數(shù)值比較方式降序遍歷。所以無法轉(zhuǎn)換為數(shù)值的字符串索引將當(dāng)作數(shù)值0進(jìn)行比較
    • @val_type_asc:按值升序比較,此外數(shù)值類型出現(xiàn)在前面,接著是字符串類型,最后是數(shù)組類(即認(rèn)為num<str<arr)
    • @val_type_desc:按值降序比較,此外數(shù)組類型出現(xiàn)在前面,接著是字符串類型,最后是數(shù)值型(即認(rèn)為num<str<arr)
    • @val_str_asc:按值升序比較,數(shù)值轉(zhuǎn)換成字符串再比較,而數(shù)組出現(xiàn)在尾部(即認(rèn)str<arr)
    • @val_str_desc:按值降序比較,數(shù)值轉(zhuǎn)換成字符串再比較,而數(shù)組出現(xiàn)在頭部(即認(rèn)str<arr)
    • @val_num_asc:按值升序比較,字符串轉(zhuǎn)換成數(shù)值再比較,而數(shù)組出現(xiàn)在尾部(即認(rèn)num<arr)
    • @val_num_desc:按值降序比較,字符串轉(zhuǎn)換成數(shù)值再比較,而數(shù)組出現(xiàn)在頭部(即認(rèn)為num<arr)

例如:

awk '
  BEGIN{
    arr[1] = "one"
    arr[2] = "two"
    arr[3] = "three"
    arr["a"] ="aa"
    arr["b"] ="bb"
    arr[10]= "ten"

    #PROCINFO["sorted_in"] = "@ind_num_asc"
    #PROCINFO["sorted_in"] = "@ind_str_asc"
    PROCINFO["sorted_in"] = "@val_str_asc"
    for(idx in arr){
      print idx " -> " arr[idx]
    }
}'

a -> aa
b -> bb
1 -> one
2 -> two
3 -> three
10 -> ten

# 本文來自駿馬金龍:www.junmajinlong.com

如果指定為用戶自定義的排序函數(shù),其函數(shù)格式為:

function sort_func(i1,v1,i2,v2){
    ...
    return <0;0;>0
}

其中,i1和i2是每次所取兩個(gè)元素的索引,v1和v2是這兩個(gè)索引的對(duì)應(yīng)值。

如果返回值小于0,則表示i1在i2前面,i1先被遍歷。如果等于0,則表示i1和i2具有等值關(guān)系,它們的遍歷順序不可保證。如果大于0,則表示i2先于i1被遍歷。

例如,對(duì)數(shù)組元素按數(shù)值大小比較來決定遍歷順序。

awk '
function cmp_val_num(i1, v1, i2, v2){
  if ((v1 - v2) < 0) {
    return -1
  } else if ((v1 - v2) == 0) {
    return 0
  } else {
    return 1
  }
  # return (v1-v2)
}

NR > 1 {
  arr[$0] = $4
}

END {
  PROCINFO["sorted_in"] = "cmp_val_num"
  for (i in arr) {
    print i
  }
}' a.txt

再比如,按數(shù)組元素值的字符大小來比較。

function cmp_val_str(i1,v1,i2,v2) {
    v1 = v1 ""
    v2 = v2 ""
    if(v1 < v2){
        return -1
    } else if(v1 == v2){
        return 0
    } else {
        return 1
    }
    # return (v1 < v2) ? -1 : (v1 != v2)
}

NR>1{
    arr[$0] = $2
}

END{
    PROCINFO["sorted_in"] = "cmp_val_str"
    for(line in arr)
    {
        print line
    }
}

再比如,對(duì)元素值按數(shù)值升序比較,且相等時(shí)再按第一個(gè)字段ID進(jìn)行數(shù)值降序比較。

awk '
function cmp_val_num(i1,v1,i2,v2,    a1,a2) {
    if (v1<v2) {
        return - 1
    } else if(v1 == v2){
        split(i1, a1, SUBSEP)
        split(i2, a2, SUBSEP)
        return a2[2] - a1[2]
    } else {
        return 1
    }
}

NR>1{
    arr[$0,$1] = $4
}

END{
    PROCINFO["sorted_in"] = "cmp_val_num"
    for(str in arr){
        split(str, a, SUBSEP)
        print a[1]
    }
}

' a.txt

上面使用的arr[x,y]來存儲(chǔ)額外信息,下面使用arr[x][y]多維數(shù)組的方式來存儲(chǔ)額外信息實(shí)現(xiàn)同樣的排序功能。

NR>1{
  arr[NR][$0] = $4
}

END{
  PROCINFO["sorted_in"] = "cmp_val_num"
  for(nr in arr){
    for(line in arr[nr]){
      print line
    }
  # 本文來自駿馬金龍:www.junmajinlong.com
  }
}

function cmp_val_num(i1,v1,i2,v2,   ii1,ii2){
  # 獲取v1/v2的索引,即$0的值
  for(ii1 in v1){ }
  for(ii2 in v2){ }

  if(v1[ii1] < v2[ii2]){
    return -1
  }else if(v1[ii1] > v2[ii2]){
    return 1
  }else{
    return (i2 - i1)
  }
}

此外,gawk還提供了兩個(gè)內(nèi)置函數(shù)asort()和asorti()來對(duì)數(shù)組進(jìn)行排序。

awk ARGC和ARGV

預(yù)定義變量ARGV是一個(gè)數(shù)組,包含了所有的命令行參數(shù)。該數(shù)組使用從0開始的數(shù)值作為索引。

預(yù)定義變量ARGC初始時(shí)是ARGV數(shù)組的長(zhǎng)度,即命令行參數(shù)的數(shù)量。

ARGV數(shù)組的數(shù)量和ARGC的值只有在awk剛開始運(yùn)行的時(shí)候是保證相等的。

$ awk -va=1 -F: '
  BEGIN{
    print ARGC;
    for(i in ARGV){
      print "ARGV[" i "]= " ARGV[i]
    }
}' b=3 a.txt b.txt

4
ARGV[0]= awk
ARGV[1]= b=3
ARGV[2]= a.txt
ARGV[3]= b.txt

awk讀取文件是根據(jù)ARGC的值來進(jìn)行的,有點(diǎn)類似于如下偽代碼形式:

while(i=1;i<ARGC;i++){
    read from ARGV[i]
}

默認(rèn)情況下,awk在讀完ARGV中的一個(gè)文件時(shí),會(huì)自動(dòng)從它的下一個(gè)元素開始讀取,直到讀完所有文件。

直接減小ARGC的值,會(huì)導(dǎo)致awk不會(huì)讀取尾部的一些文件。此外,增減ARGC的值,都不會(huì)影響ARGV數(shù)組,僅僅只是影響awk讀取文件的數(shù)量。

# 不會(huì)讀取b.txt
awk 'BEGIN{ARGC=2}{print}' a.txt b.txt

# 讀完b.txt后自動(dòng)退出
awk 'BEGIN{ARGC=5}{print}' a.txt b.txt

可以將ARGV中某個(gè)元素賦值為空字符串"",awk在選擇下一個(gè)要讀取的文件時(shí),會(huì)自動(dòng)忽略ARGV中的空字符串元素。

也可以delete ARGV[i]的方式來刪除ARGV中的某元素。

用戶手動(dòng)增、刪ARGV元素時(shí),不會(huì)自動(dòng)修改ARGC,而awk讀取文件時(shí)是根據(jù)ARGC值來確定的。所以,在增加ARGV元素之后,要手動(dòng)的去增加ARGC的值。

# 不會(huì)讀取b.txt文件
$ awk 'BEGIN{ARGV[2]="b.txt"}{print}' a.txt

# 會(huì)讀取b.txt文件
$ awk 'BEGIN{ARGV[2]="b.txt";ARGC++}{print}' a.txt 

對(duì)awk ARGC和ARGV進(jìn)行操刀

awk判斷命令行中給定文件是否可讀

awk命令行中可能會(huì)給出一些不存在或無權(quán)限或其它原因而無法被awk讀取的文件名,這時(shí)可以判斷并從中剔除掉不可讀取的文件。

  1. 排除命令行尾部(非選項(xiàng)型參數(shù))的var=val、-、和/dev/stdin這3種特殊情況
  2. 如果不可讀,則從ARGV中刪除該參數(shù)
  3. 剩下的都是可在main代碼段正常讀取的文件
BEGIN{
  for(i=1;i<ARGC;i++){
    if(ARGV[i] ~ /[a-zA-Z_][a-zA-Z0-9_]*=.*/ \
    || ARGV[i]=="-" || ARGV[i]=="/dev/stdin"){
      continue
    } else if((getline var < ARGV[i]) < 0){
      delete ARGV[i]
    } else{
      close(ARGV[i])
    }
  }
}

awk 自定義函數(shù)

可以定義一個(gè)函數(shù)將多個(gè)操作整合在一起。函數(shù)定義之后,可以到處多次調(diào)用,從而方便復(fù)用。

使用function關(guān)鍵字來定義函數(shù):

function func_name([parameters]){
    function_body
}

對(duì)于gawk來說,也支持func關(guān)鍵字來定義函數(shù)。

func func_name(){}

函數(shù)可以定義在下面使用下劃線的地方:

awk '_ BEGIN{} _ MAIN{} _ END{} _'

無論函數(shù)定義在哪里,都能在任何地方調(diào)用,因?yàn)閍wk在BEGIN之前,會(huì)先編譯awk代碼為內(nèi)部格式,在這個(gè)階段會(huì)將所有函數(shù)都預(yù)定義好。

例如:

awk '
    BEGIN{
        f()
        f()
        f()
    }
    function f(){
        print "星期一"
        print "星期二"
        print "星期三"
        print "星期四"
        print "星期五"
        print "星期六"
        print "星期日"
    }
'

awk 函數(shù)的return語句

如果想要讓函數(shù)有返回值,那么需要在函數(shù)中使用return語句。

return語句也可以用來立即結(jié)束函數(shù)的執(zhí)行。

例如:

awk '
    function add(){
        return 40
    }
    BEGIN{
        print add()
        res = add() 
        print res
    }
'

如果不使用return或return沒有參數(shù),則返回值為空,即空字符串。

awk '
  function f1(){        }
  function f2(){return  }
  function f3(){return 3}
  BEGIN{
    print "-"f1()"-"
    print "-"f2()"-"
    print "-"f3()"-"
  }
'

awk函數(shù)參數(shù)

為了讓函數(shù)和調(diào)用者能夠進(jìn)行數(shù)據(jù)的交互,可以使用參數(shù)。

awk '
  function f(a,b){
    print a
    print b
    return a+b
  }
  BEGIN{
    x=10
    y=20
    res = f(x,y)
    print res
    print f(x,y)
  }
'

例如,實(shí)現(xiàn)一個(gè)重復(fù)某字符串指定次數(shù)的函數(shù):

awk '
    function repeat(str,cnt  ,res_str){
        for(i=0;i<cnt;i++){
            res_str = res_str""str
        }
        return res_str
    }
    BEGIN{
        print repeat("abc",3)
        print repeat("-",30)
    }
'

調(diào)用函數(shù)時(shí),實(shí)參數(shù)量可以比形參數(shù)量少,也可以比形參數(shù)量多。但是,在多于形參數(shù)量時(shí)會(huì)給出警告信息。

awk '
  function f(a,b){
    print a
    print b
    return a+b
  }
  BEGIN{
    x=10
    y=20
    
    print "---1----"
    print "-"f()"-"          # 不傳遞參數(shù)

    print "---2----"
    print "-"f(30)"-"        # 傳遞1個(gè)參數(shù)

    print "---3----"
    print "-"f(10,20,30)"-"  # 傳遞多個(gè)參數(shù)
  }
'

awk函數(shù)參數(shù)數(shù)據(jù)類型沖突問題

如果函數(shù)內(nèi)部使用參數(shù)的類型和函數(shù)外部變量的類型不一致,會(huì)出現(xiàn)數(shù)據(jù)類型不同而導(dǎo)致報(bào)錯(cuò)。

awk '
    function f(a){
        a[1]=30
    }
    BEGIN{
        a="hello world"
        f(a)   # 報(bào)錯(cuò)

        f(x)
        x=10   # 報(bào)錯(cuò)
    }
'

函數(shù)內(nèi)部參數(shù)對(duì)應(yīng)的是數(shù)組,那么外面對(duì)應(yīng)的也必須是數(shù)組類型。

awk參數(shù)按值傳遞還是按引用傳遞

在調(diào)用函數(shù)時(shí),將數(shù)據(jù)作為函數(shù)參數(shù)傳遞給函數(shù)時(shí),有兩種傳遞方式:

一篇文章玩透awk

# 傳遞普通變量:按值拷貝
awk '
  function modify(a){
    a=30
    print a
  }
  BEGIN{
    a=40
    modify(a)
    print a
  }
'

# 傳遞數(shù)組:按引用拷貝
awk '
  function modify(a){
    a[1]=20
  }

  BEGIN{
    a[1]=10
    modify(a)
    print a[1]
  }
'

awk作用域問題

awk只有在函數(shù)參數(shù)中才是局部變量,其它地方定義的變量均為全局變量。

函數(shù)內(nèi)部新增的變量是全局變量,會(huì)影響到全局,所以在函數(shù)退出后仍然能訪問。例如上面的e變量。

awk '
  function f(){
    a=30  # 新增的變量,是全局變量
    print "in f: " a
  }
  BEGIN{
    a=40
    f()
    print a  # 30
  }
'

函數(shù)參數(shù)會(huì)遮掩全局同名變量,所以在函數(shù)執(zhí)行時(shí),無法訪問到或操作與參數(shù)同名的全局變量,函數(shù)退出時(shí)會(huì)自動(dòng)撤掉遮掩,這時(shí)才能訪問全局變量。所以,參數(shù)具有局部效果。

awk '
  function f(a){
    print a    # 50,按值拷貝,和全局a已經(jīng)沒有關(guān)系
    a=40
    print a    # 40
  }
  BEGIN{
    a=50
    f(a)
    print a     # 50,函數(shù)退出,重新訪問全局變量
  }
'

由于函數(shù)內(nèi)部新增變量均為全局變量,awk也沒有提供關(guān)鍵字來修飾一個(gè)變量使其成為局部變量。所以,awk只能將本該出現(xiàn)在函數(shù)體內(nèi)的局部變量放在參數(shù)列表中,只要調(diào)用函數(shù)時(shí)不要為這些參數(shù)傳遞數(shù)據(jù)即可,從而實(shí)現(xiàn)局部變量的效果。

awk '
  function f(a,b       ,c,d){
  
    # a,b是參數(shù),調(diào)用時(shí)需傳遞兩個(gè)參數(shù)
    # c,d是局部變量,調(diào)用時(shí)不要給c和d傳遞數(shù)據(jù)
    a=30
    b=40
    c=50
    d=60
    e=70  # 全局變量

    print a,b,c,d,e  # 30 40 50 60 70
  }
  BEGIN{
    a=31
    b=41
    c=51
    d=61
    f(a,b)  # 調(diào)用函數(shù)時(shí)值傳遞兩個(gè)參數(shù)
    print a,b,c,d,e  # 31 41 51 61 70
  }
'

所以,awk對(duì)函數(shù)參數(shù)列表做了兩類區(qū)分:

  • arguments:調(diào)用函數(shù)時(shí)傳遞的參數(shù)
  • local variables:調(diào)用函數(shù)時(shí)省略的參數(shù)

一篇文章玩透awk

local variables是awk實(shí)現(xiàn)真正局部變量的技巧,只是因?yàn)楹瘮?shù)內(nèi)部新增的變量都是全局變量,所以退而求其次將其放在參數(shù)列表上來實(shí)現(xiàn)局部變量。

自定義函數(shù)示例

1.一次性讀取一個(gè)文件所有數(shù)據(jù)

function readfile(file    ,rs_bak,data){
  rs_bak=RS
  RS="^$"
  if ( (getline data < file) < 0 ){
    print "read file failed"
    exit 1
  }
  close(file)
  RS=rs_bak
  return data
}


/^1/{
  print $0
  content = readfile("c.txt")
  print content
}

將RS設(shè)置為^$是永遠(yuǎn)不可能出現(xiàn)的分隔符,除非這個(gè)文件為空文件。

2.重讀文件

實(shí)現(xiàn)一個(gè)rewind()功能來重置文件偏移指針,從而模擬實(shí)現(xiàn)重讀當(dāng)前文件。

function rewind(    i){
    # 將當(dāng)前正在讀取的文件添加到ARGV中當(dāng)前文件的下一個(gè)元素
    for(i=ARGC;i>ARCIND;i--){
        ARGV[i] = ARGV[i-1]
    }

    # 隨著增加ARGC,以便awk能夠讀取到因ARGV增加元素后的最后一個(gè)文件
    ARGC++

    # 直接進(jìn)入下一個(gè)文件
    nextfile
}

要注意可能出現(xiàn)無限遞歸的場(chǎng)景:

awk -f rewind.awk 'NR==3{rewind()}{print FILENAME, FNR, $0}' a.txt

# 下面這個(gè)會(huì)無限遞歸,因?yàn)镕NR==3很可能每次重讀時(shí)都會(huì)為真
awk -f rewind.awk 'FNR==3{rewind()}{print FILENAME, FNR, $0}' a.txt

3.格式化數(shù)組的輸出

實(shí)現(xiàn)一個(gè)a2s()函數(shù)。

BEGIN{
  arr["zhangsan"]=21
  arr["lisi"]=22
  arr["wangwu"]=23
  print a2s(arr)
}

function a2s(arr       ,content,i,cnt){
  for(i in arr){
    if(cnt){
      content=content""(sprintf("\t%s:%s\n",i,arr[i]))
    } else {
      content=content""(sprintf("\n\t%s:%s\n",i,arr[i]))
    }
    cnt++
  }
  return "{"content"}"
}

4.禁用命令行尾部的賦值語句

awk '{}' ./a=b a.txt中,a=b會(huì)被awk識(shí)別為變量賦值操作。但是,如果用戶想要處理的正好是包含了等號(hào)的文件名,則應(yīng)當(dāng)去禁用該賦值操作。

禁用的方式很簡(jiǎn)單,只需為其加上一個(gè)路徑前綴./即可。

為了方便控制,可通過-v設(shè)置一個(gè)flag類型的選項(xiàng)標(biāo)記。

function disable_assigns(argc,argv,    i){
    for(i=1;i<argc;i++){
        if(argv[i] ~ /[[:alpha:]_][[:alnum:]_]*=.*/){
            argv[i] = ("./"argv[i])
        }
    }
}

BEGIN{
    if(assign_flag){
        disable_assigns(ARGC,ARGV)
    }
}

那么,調(diào)用awk時(shí)采用如下方式:

awk -v assign_flag=1 -f assigns.awk '{print}' a=b.txt a.txt

awk選項(xiàng)、內(nèi)置變量

選項(xiàng)

-e program-text
--source program-text
指定awk程序表達(dá)式,可結(jié)合-f選項(xiàng)同時(shí)使用
在使用了-f選項(xiàng)后,如果不使用-e,awk program是不會(huì)執(zhí)行的,它會(huì)被當(dāng)作ARGV的一個(gè)參數(shù)

-f program-file
--file program-file
從文件中讀取awk源代碼來執(zhí)行,可指定多個(gè)-f選項(xiàng)

-F fs
--field-separator fs
指定輸入字段分隔符(FS預(yù)定義變量也可設(shè)置)

-n
--non-decimal-data
識(shí)別文件輸入中的8進(jìn)制數(shù)(0開頭)和16進(jìn)制數(shù)(0x開頭)
echo '030' | awk -n '{print $1+0}'

-o [filename]
格式化awk代碼。
不指定filename時(shí),則默認(rèn)保存到awkprof.out
指定為`-`時(shí),表示輸出到標(biāo)準(zhǔn)輸出

-v var=val
--assign var=val
在BEGIN之前,聲明并賦值變量var,變量可在BEGIN中使用

預(yù)定義變量

預(yù)定義變量分為兩類:控制awk工作的變量和攜帶信息的變量。

第一類:控制AWK工作的預(yù)定義變量

  • RS:輸入記錄分隔符,默認(rèn)為換行符\n,參考RS
  • IGNORECASE:默認(rèn)值為0,表示所有的正則匹配不忽略大小寫。設(shè)置為非0值(例如1),之后的匹配將忽略大小寫。例如在BEGIN塊中將其設(shè)置為1,將使FS、RS都以忽略大小寫的方式分隔字段或分隔record
  • FS:讀取記錄后,劃分為字段的字段分隔符。參考FS
  • FIELDWIDTHS:以指定寬度切割字段而非按照FS。參考FIELDWIDTHS
  • FPAT:以正則匹配匹配到的結(jié)果作為字段,而非按照FS劃分。參考FPAT
  • OFS:print命令輸出各字段列表時(shí)的輸出字段分隔符,默認(rèn)為空格" "
  • ORS:print命令輸出數(shù)據(jù)時(shí)在尾部自動(dòng)添加的記錄分隔符,默認(rèn)為換行符\n
  • CONVFMT:在awk中數(shù)值隱式轉(zhuǎn)換為字符串時(shí),將根據(jù)CONVFMT的格式按照sprintf()的方式自動(dòng)轉(zhuǎn)換為字符串。默認(rèn)值為"%.6g
  • OFMT:在print中,數(shù)值會(huì)根據(jù)OFMT的格式按照sprintf()的方式自動(dòng)轉(zhuǎn)換為字符串。默認(rèn)值為"%.6g

第二類:攜帶信息的預(yù)定義變量

  • ARGCARGV:awk命令行參數(shù)的數(shù)量、命令參數(shù)的數(shù)組。參考ARGC和ARGV
  • ARGIND:awk當(dāng)前正在處理的文件在ARGV中的索引位置。所以,如果awk正在處理命令行參數(shù)中的某文件,則ARGV[ARGIND] == FILENAME為真
  • FILENAME:awk當(dāng)前正在處理的文件(命令行中指定的文件),所以在BEGIN中該變量值為空
  • ENVIRON:保存了Shell的環(huán)境變量的數(shù)組。例如ENVIRON["HOME"]將返回當(dāng)前用戶的家目錄
  • NR:當(dāng)前已讀總記錄數(shù),多個(gè)文件從不會(huì)重置為0,所以它是一直疊加的
    • 可以直接修改NR,下次讀取記錄時(shí)將在此修改值上自增
  • FNR:當(dāng)前正在讀取文件的第幾條記錄,每次打開新文件會(huì)重置為0
    • 可以直接修改FNR,下次讀取記錄時(shí)將在此修改值上自增
  • NF:當(dāng)前記錄的字段數(shù),參考NF
  • RT:在讀取記錄時(shí)真正的記錄分隔符,參考RT
  • RLENGTH:match()函數(shù)正則匹配成功時(shí),所匹配到的字符串長(zhǎng)度,如果匹配失敗,該變量值為-1
  • RSTART:match()函數(shù)匹配成功時(shí),其首字符的索引位置,如果匹配失敗,該變量值為0
  • SUBSEParr[x,y]中下標(biāo)分隔符構(gòu)建成索引時(shí)對(duì)應(yīng)的字符,默認(rèn)值為\034,是一個(gè)不太可能出現(xiàn)在字符串中的不可打印字符。參考復(fù)雜數(shù)組

awk預(yù)定義內(nèi)置函數(shù)

預(yù)定義函數(shù)分為幾類:

  • 數(shù)值類內(nèi)置函數(shù)
  • 字符串類內(nèi)置函數(shù)
  • 時(shí)間類內(nèi)置函數(shù)
  • 位操作內(nèi)置函數(shù)
  • 數(shù)據(jù)類型相關(guān)內(nèi)置函數(shù):isarray()、typeof()
  • IO類內(nèi)置函數(shù):close()、system()、fflush()

awk數(shù)值類內(nèi)置函數(shù)

int(expr)     截?cái)酁檎麛?shù):int(123.45)和int("123abc")都返回123,int("a123")返回0
sqrt(expr)    返回平方根
rand()        返回[0,1)之間的隨機(jī)數(shù),默認(rèn)使用srand(1)作為種子值
srand([expr]) 設(shè)置rand()種子值,省略參數(shù)時(shí)將取當(dāng)前時(shí)間的epoch值(精確到秒的epoch)作為種子值

例如:

$ awk 'BEGIN{srand();print rand()}'
0.0379114
$ awk 'BEGIN{srand();print rand()}'
0.0779783
$ awk 'BEGIN{srand(2);print rand()}'
0.893104
$ awk 'BEGIN{srand(2);print rand()}'
0.893104

生成[10,100]之間的隨機(jī)整數(shù)。

awk 'BEGIN{srand();print 10+int(91*rand())}'

awk字符串類內(nèi)置函數(shù)

注意,awk中涉及到字符索引的函數(shù),索引位都是從1開始計(jì)算,和其它語言從0開始不一樣。

基本函數(shù)

  • sprintf(format, expression1, ...):返回格式化后的字符串,參考sprintf

    • a=sprintf("%s\n","abc")
  • length():返回字符串字符數(shù)量、數(shù)組元素?cái)?shù)量、或數(shù)值轉(zhuǎn)換為字符串后的字符數(shù)量

    awk '
        BEGIN{
            print length(1.23)     # 4   # CONVFMT %.6g
    
            print 1.234567         # 1.23457
            print length(1.234567) # 7 
            print length(122341223432.1213241234)  # 11
        }'
    
  • strtonum(str):將字符串轉(zhuǎn)換為十進(jìn)制數(shù)值

    • 如果str以0開頭,則將其識(shí)別為8進(jìn)制
    • 如果str以0x或0X開頭,則將其識(shí)別為16進(jìn)制
  • tolower(str):轉(zhuǎn)換為小寫

  • toupper(str):轉(zhuǎn)換為大寫

  • index(str,substr):從str中搜索substr(子串),返回搜索到的索引位置(索引從1開始),搜索不到則返回0

awk substr()

  • substr(string,start[,length]):從string中截取子串

start是截取的起始索引位(索引位從1開始而非0),length表示截取的子串長(zhǎng)度。如果省略length,則表示從start開始截取剩余所有字符。

awk '
    BEGIN{
        str="abcdefgh"
        print substr(str,3)   # cdefgh
        print substr(str,3,3) # cde
    }
'

如果start值小于1,則將其看作為1對(duì)待,如果start大于字符串的長(zhǎng)度,則返回空字符串。

如果length小于或等于0,則返回空字符串。

awk split()和patsplit()

  • split(string, array [, fieldsep [, seps ] ]):將字符串分割后保存到數(shù)組array中,數(shù)組索引從1開始存儲(chǔ)。并返回分割得到的元素個(gè)數(shù)

其中fieldsep指定分隔符,可以是正則表達(dá)式方式的。如果不指定該參數(shù),則默認(rèn)使用FS作為分隔符,而FS的默認(rèn)值又是空格。

seps是一個(gè)數(shù)組,保存了每次分割時(shí)的分隔符。

例如:

split("abc-def-gho-pq",arr,"-",seps)

其返回值為4。同時(shí)得到的數(shù)組a和seps為:

arr[1] = "abc"
arr[2] = "def"
arr[3] = "gho"
arr[4] = "pq"

seps[1] = "-"
seps[2] = "-"
seps[3] = "-"

split在開始工作時(shí),會(huì)先清空數(shù)組,所以,將split的string參數(shù)設(shè)置為空,可以用于清空數(shù)組。

awk 'BEGIN{arr[1]=1;split("",arr);print length(arr)}'  # 0

如果分隔符無法匹配字符串,則整個(gè)字符串當(dāng)作一個(gè)數(shù)組元素保存到數(shù)組array中。

awk 'BEGIN{split("abcde",arr,"-");print arr[1]}' # abcde
  • patsplit(string, array [, fieldpat [, seps ] ]):用正則表達(dá)式fieldpat匹配字符串string,將所有匹配成功的部分保存到數(shù)組array中,數(shù)組索引從1開始存儲(chǔ)。返回值是array的元素個(gè)數(shù),即匹配成功了多少次

如果省略fieldpat,則默認(rèn)采用預(yù)定義變量FPAT的值。

awk '
    BEGIN{
        patsplit("abcde",arr,"[a-z]")
        print arr[1]   # a
        print arr[2]   # b
        print arr[3]   # c
        print arr[4]   # d
        print arr[5]   # e
    }
'

awk match()

  • match(string,reg[,arr]):使用reg匹配string,返回匹配成功的索引位(從1開始計(jì)數(shù)),匹配失敗則返回0。如果指定了arr參數(shù),則arr[0]保存的是匹配成功的字符串,arr[1]、arr[2]、...保存的是各個(gè)分組捕獲的內(nèi)容

match匹配時(shí),同時(shí)會(huì)設(shè)置兩個(gè)預(yù)定義變量:RSTART和RLENGTH

  • 匹配成功時(shí):
    • RSTART賦值為匹配成功的索引位,從1開始計(jì)數(shù)
    • RLENGTH賦值為匹配成功的字符長(zhǎng)度
  • 匹配失敗時(shí):
    • RSTART賦值為0
    • RLENGTH賦值為-1

例如:

awk '
    BEGIN{
        where = match("foooobazbarrrr","(fo+).*(bar*)",arr)
        print where   # 1
        print arr[0]  # foooobazbarrrr
        print arr[1]  # foooo
        print arr[2]  # barrrr
        print RSTART  # 1
        print RLENGTH # 14
    }
'

因?yàn)閙atch()匹配成功時(shí)返回值為非0,而匹配失敗時(shí)返回值為0,所以可以直接當(dāng)作條件判斷:

awk '
  {
    if(match($0,/A[a-z]+/,arr)){
      print NR " : " arr[0]
    }
  }
' a.txt

awk sub()和gsub()

  • sub(regexp, replacement [, target])
  • gsub(regexp, replacement [, target]):sub()的全局模式

sub()從字符串target中進(jìn)行正則匹配,并使用replacement對(duì)第一次匹配成功的部分進(jìn)行替換,替換后保存回target中。返回替換成功的次數(shù),即0或1。

target必須是一個(gè)可以賦值的變量名、$N或數(shù)組元素名,以便用它來保存替換成功后的結(jié)果。不能是字符串字面量,因?yàn)樗鼰o法保存數(shù)據(jù)。

如果省略target,則默認(rèn)使用$0。

需要注意的是,如果省略target,或者target是$N,那么替換成功后將會(huì)使用OFS重新計(jì)算$0

awk '
    BEGIN{
        str="water water everywhere"
        #how_many = sub(/at/, "ith", str)
        how_many = gsub(/at/, "ith", str)
        print how_many   # 1
        print str        # wither water everywhere
    }
'

在replacement參數(shù)中,可以使用一個(gè)特殊的符號(hào)&來引用匹配成功的部分。注意sub()和gsub()不能在replacement中使用反向引用\N

awk '
    BEGIN{
        str = "daabaaa"
        gsub(/a+/,"C&C",str)
        print str  # dCaaCbaaa
    }
'

如果想要在replacement中使用&純字符,則轉(zhuǎn)義即可。

sub(/a+/,"C\\&C",str)

兩根反斜線:
因?yàn)閍wk在正則開始工作時(shí),首先會(huì)掃描所有awk代碼然后編譯成awk的內(nèi)部格式,掃描期間會(huì)解析反斜線轉(zhuǎn)義,使得\\變成一根反斜線。當(dāng)真正開始運(yùn)行后,sub()又要解析,這時(shí)\&才表示的是對(duì)&做轉(zhuǎn)義。
掃描代碼階段稱為詞法解析階段,運(yùn)行解析階段稱為運(yùn)行時(shí)解析階段。

awk gensub()

gawk支持的gensub(),完全可以取代sub()和gsub()。

  • gensub(regexp, replacement, how [, target])

可以替代sub()和gsub()。

how指定替換第幾個(gè)匹配,例如指定為1表示只替換第一個(gè)匹配。此外,還可以指定為gG開頭的字符串,表示全局替換。

一篇文章玩透awk

awk 'BEGIN{
    a = "abc def"
    b = gensub(/(.+) (.*)/, "\\2 \\1, \\0 , &", "g", a)
    print b  # def abc, abc def , abc def
}'

awk asort()和asorti()

  • asort(src,[dest [,how]])
  • asorti(src,[dest [,how]])

asort對(duì)數(shù)組src的值進(jìn)行排序,然后將排序后的值的索引改為1、2、3、4...序列。返回src中的元素個(gè)數(shù),它可以當(dāng)作排序后的索引最大值。

asorti對(duì)數(shù)組src的索引進(jìn)行排序,然后將排序后的索引值的索引改為1、2、3、4...序列。返回src中的元素個(gè)數(shù),它可以當(dāng)作排序后的索引最大值。

arr["last"] = "de"
arr["first"] = "sac"
arr["middle"] = "cul"

asort(arr)得到:

arr[1] = "cul"
arr[2] = "de"
arr[3] = "sac"

asorti(arr)得到:

arr[1] = "first"
arr[2] = "last"
arr[3] = "middle"

如果指定dest,則將原始數(shù)組src備份到dest,然后對(duì)dest進(jìn)行排序,而src保持不變。

how參數(shù)用于指定排序時(shí)的方式,其值指定方式和PROCINFO["sorted_in"]一致:可以是預(yù)定義的排序函數(shù),也可以是用戶自定義的排序函數(shù)。參考指定數(shù)組遍歷順序。

IO類內(nèi)置函數(shù)

  • close(filename [, how]):關(guān)閉文件或命令,參考close
  • system(command):執(zhí)行Shell命令,參考system
  • fflush([filename]):gawk會(huì)按塊緩沖模式來緩沖輸出結(jié)果,使用fflush()會(huì)將緩沖數(shù)據(jù)刷出

從gawk 4.0.2之后的版本(不包括4.0.2),無參數(shù)fflush()將刷出所有緩沖數(shù)據(jù)。

此外,終端設(shè)備是行緩沖模式,此時(shí)不需要fflush,而重定向到文件、到管道都是塊緩沖模式,此時(shí)可能需要fflush()。

此外,system()在運(yùn)行時(shí)也會(huì)flush gawk的緩沖。特別的,如果system的參數(shù)為空字符串system(""),則它不會(huì)去啟動(dòng)一個(gè)shell子進(jìn)程而是僅僅執(zhí)行flush操作。

一篇文章玩透awk

使用system()來flush:

awk '{print "first";system("echo system");print "second"}' | cat
awk '{print "first";system("");print "second"}' | cat

也可以使用stdbuf -oL命令來強(qiáng)制gawk按行緩沖而非默認(rèn)的按塊緩沖。

stdbuf -oL awk '{print "first";print "second"}' | cat

fflush()也可以指定文件名或命令,表示只刷出到該文件或該命令的緩沖數(shù)據(jù)。

# 刷出所有流向到標(biāo)準(zhǔn)輸出的緩沖數(shù)據(jù)
awk '{print "first";fflush("/dev/stdout");print "second"}' | cat

最后注意,fflush()刷出緩沖數(shù)據(jù)不代表發(fā)送EOF標(biāo)記。

數(shù)據(jù)類型內(nèi)置函數(shù)

  • isarray(var):測(cè)試var是否是數(shù)組,返回1(是數(shù)組)或0(不是數(shù)組)
  • typeof(var):返回var的數(shù)據(jù)類型,有以下可能的值:
    • "array":是一個(gè)數(shù)組
    • "regexp":是一個(gè)真正表達(dá)式類型,強(qiáng)正則字面量才算是正則類型,如@/a.*ef/
    • "number":是一個(gè)number
    • "string":是一個(gè)string
    • "strnum":是一個(gè)strnum,參考strnum類型
    • "unassigned":曾引用過,但未賦值,例如"print f;print typeof(f)"
    • "untyped":從未引用過,也從未賦值過

例如,輸出awk進(jìn)程的內(nèi)部信息,但跳過數(shù)組

awk '
  BEGIN{
    for(idx in PROCINFO){
      if(typeof(PROCINFO[idx]) == "array"){
        continue
      }
      print idx " -> "PROCINFO[idx]
    }
  }'

時(shí)間類內(nèi)置函數(shù)

awk常用于處理日志,它支持簡(jiǎn)單的時(shí)間類操作。有下面3個(gè)內(nèi)置的時(shí)間函數(shù):

  • mktime("YYYY MM DD HH mm SS [DST]"):構(gòu)建一個(gè)時(shí)間,返回這個(gè)時(shí)間點(diǎn)的秒級(jí)epoch,構(gòu)建失敗則返回-1
  • systime():返回當(dāng)前系統(tǒng)時(shí)間點(diǎn),返回的是秒級(jí)epoch值
  • strftime([format [, timestamp [, utc-flag] ] ]):將時(shí)間按指定格式轉(zhuǎn)換為字符串并返回轉(zhuǎn)的結(jié)果字符串

注意,awk構(gòu)建時(shí)間時(shí)都是返回秒級(jí)的epoch值,表示從1970-01-01 00:00:00開始到指定時(shí)間已經(jīng)過的秒數(shù)。

awk 'BEGIN{print systime();print mktime("2019 2 29 12 32 59")}'
1572364974
1551414779

awk mktime()

mktime在構(gòu)建時(shí)間時(shí),如果傳遞的DD給定的值超出了月份MM允許的天數(shù),則自動(dòng)延申到下個(gè)月。例如,指定"2019 2 29 12 30 59"中2月只有28號(hào),所以構(gòu)建出來的時(shí)間是2019-03-01 12:30:59

此外,其它部分也不限定必須在范圍內(nèi)。例如,2019 2 23 12 32 65的秒超出了59,那么多出來的秒數(shù)將進(jìn)位到分鐘。

awk 'BEGIN{
    print mktime("2019 2 23 12 32 65") | "xargs -i date -d@{} +\"%F %T\""
}'
2019-02-23 12:33:05

如果某部位的數(shù)值為負(fù)數(shù),則表示在此時(shí)間點(diǎn)基礎(chǔ)上減幾。例如:

# 2019-02-23 12:00:59基礎(chǔ)上減1分鐘
$ awk 'BEGIN{print mktime("2019 2 23 12 -1 59") | "xargs -i date -d@{} +\"%F %T\""}'  
2019-02-23 11:59:59

# 2019-02-23 00:32:59基礎(chǔ)上減1小時(shí)
$ awk 'BEGIN{print mktime("2019 2 23 -1 32 59") | "xargs -i date -d@{} +\"%F %T\""}'  
2019-02-22 23:32:59

awk strftime()

strftime([format [, timestamp [, utc-flag] ] ])

將指定的時(shí)間戳tiemstamp按照給定格式format轉(zhuǎn)換為字符串并返回這個(gè)字符串。

如果省略timestamp,則對(duì)當(dāng)前系統(tǒng)時(shí)間進(jìn)行格式化。

如果省略format,則采用PROCINFO["strftime"]的格式,其默認(rèn)格式為%a %b %e %H:%M%:S %Z %Y。該格式對(duì)應(yīng)于Shell命令date的默認(rèn)輸出結(jié)果。

$ awk 'BEGIN{print strftime()}'
Wed Oct 30 00:20:01 CST 2014

$ date
Wed Oct 30 00:20:04 CST 2014

$ awk 'BEGIN{print strftime(PROCINFO["strftime"], systime())}'
Wed Oct 30 00:24:00 CST 2014

支持的格式包括:

%a 星期幾的縮寫,如Mon、Sun Wed Fri
%A 星期幾的英文全名,如Monday
%b 月份的英文縮寫,如Oct、Sep
%B 月份的英文全名,如February、October
%C 2位數(shù)的世紀(jì),例如1970對(duì)應(yīng)的世紀(jì)是19
%y 2位數(shù)的年份(00–99),通過年份模以100取得,例如2019/100的余數(shù)位19
%Y 四位數(shù)年份(如2015)
%m 月份(01–12)
%j 年中天(001–366)
%d 月中天(01–31)
%e 空格填充的月中天
%H 24小時(shí)制的小時(shí)(00–23)
%I 12小時(shí)制的小時(shí)(01–12)
%p 12小時(shí)制時(shí)的AM/PM
%M 分鐘數(shù)(00–59)
%S 秒數(shù)(00–60)
%u 數(shù)值的星期幾(1–7),1表示星期一
%w 數(shù)值的星期幾(0–6),0表示星期日
%W 年中第幾周(00–53)
%z 時(shí)區(qū)偏移,格式為"+HHMM",如"+0800"
%Z 時(shí)區(qū)偏移的英文縮寫,如CST

%k 24小時(shí)制的時(shí)間(0-23),1位數(shù)的小時(shí)使用空格填充
%l 12小時(shí)制的時(shí)間(1-12),1位數(shù)的小時(shí)使用空格填充
%s 秒級(jí)epoch

##### 特殊符號(hào)
%n 換行符
%t 制表符
%% 百分號(hào)%

##### 等價(jià)寫法:
%x 等價(jià)于"%A %B %d %Y"
%F 等價(jià)于"%Y-%m-%d",用于表示ISO 8601日期格式
%T 等價(jià)于"%H:%M:%S"
%X 等價(jià)于"%T"
%r 12小時(shí)制的時(shí)間部分格式,等價(jià)于"%I:%M:%S %p"
%R 等價(jià)于"%H:%M"
%c 等價(jià)于"%A %B %d %T %Y",如Wed 30 Oct 2015 12:34:48 AM CST
%D 等價(jià)于"%m/%d/%y"
%h 等價(jià)于"%b"

例如:

$ awk 'BEGIN{print strftime("%s", mktime("2077 11 12 10 23 32"))}'
3403909412

$ awk 'BEGIN{print strftime("%F %T %Z", mktime("2077 11 12 10 23 32"))}' 
2077-11-12 10:23:32 CST

$ awk 'BEGIN{print strftime("%F %T %z", mktime("2077 11 12 10 23 32"))}' 
2077-11-12 10:23:32 +0800

awk將字符串轉(zhuǎn)換為時(shí)間:strptime1()

例如:

2019-11-11T03:42:42+08:00

1.將日期時(shí)間字符串中的年月日時(shí)分秒全都單獨(dú)保存起來
2.將年月日時(shí)分秒構(gòu)建成mktime()的字符串格式"YYYY MM DD HH mm SS"
3.使用mktime()可以構(gòu)建出時(shí)間點(diǎn)

function strptime(str,    time_str,arr,Y,M,D,H,m,S){
    time_str = gensub(/[-T:+]/," ","g",str)
    split(time_str, arr, " ")
    Y = arr[1]
    M = arr[2]
    D = arr[3]
    H = arr[4]
    m = arr[5]
    S = arr[6]
    # mktime失敗返回-1
    return mktime(sprintf("%d %d %d %d %d %d", Y,M,D,H,m,S))
}

BEGIN{
  str = "2019-11-11T03:42:42+08:00"
  print strptime(str)
}

awk將字符串轉(zhuǎn)換為時(shí)間:strptime2()

下面是更難一點(diǎn)的,月份使用的是英文或英文縮寫,日期時(shí)間分隔符也比較特殊。

Sat 26. Jan 15:36:24 CET 2013
function strptime(str,     time_str,arr,Y,M,D,H,m,S){
    time_str = gensub(/[.:]+/, " ", "g", str)
    split(time_str, arr, " ")
    Y = arr[8]
    M = month_map(arr[3])
    D = arr[2]
    H = arr[4]
    m = arr[5]
    S = arr[6]
    return mktime(sprintf("%d %d %d %d %d %d", Y,M,D,H,m,S))
}

function month_map(str,   mon){
    # mon = substr(str,1,3)
    # return (((index("JanFebMarAprMayJunJelAugSepOctNovDec", mon)-1)/3)+1)
    mon["Jan"] = 1
    mon["Feb"] = 2
    mon["Mar"] = 3
    mon["Apr"] = 4
    mon["May"] = 5
    mon["Jun"] = 6
    mon["Jul"] = 7
    mon["Aug"] = 8
    mon["Sep"] = 9
    mon["Oct"] = 10
    mon["Nov"] = 11
    mon["Dec"] = 12
    return mon[str]
}

BEGIN{
    str = "Sat 26. Jan 15:36:24 CET 2013"
    print strptime(str)
}

幾個(gè)常見的gawk擴(kuò)展

使用擴(kuò)展的方式:

awk -l ext_name 'BEGIN{}{}END{}'
awk '@load "ext_name";BEGIN{}{}END{}'

1.文件相關(guān)的擴(kuò)展

awk和文件相關(guān)的擴(kuò)展是"filefuncs"。

它支持chdir()、stat()函數(shù)。

2.awk文件名匹配擴(kuò)展

"fnmatch"擴(kuò)展提供文件名通配。

@load "fnmatch"
result = fnmatch(pattern, string, flags)

3.awk原處修改文件

awk通過加載inplace.awk,也可以實(shí)現(xiàn)sed -i類似的功能,即內(nèi)容直接修改源文件(其本質(zhì)是先寫入臨時(shí)文件,寫完后將臨時(shí)文件重命名為源文件進(jìn)行覆蓋)。

例如:

一篇文章玩透awk

4.awk多進(jìn)程擴(kuò)展

"fork"擴(kuò)展提供多進(jìn)程相關(guān)功能。

@load "fork"

pid = fork()
創(chuàng)建一個(gè)子進(jìn)程,對(duì)子進(jìn)程返回值為0,對(duì)父進(jìn)程返回值為子進(jìn)程的PID,返回-1表示錯(cuò)誤。
在子進(jìn)程中,PROCINFO["pid"]和PROCINFO["ppid"]會(huì)隨之更新。

ret = waitpid(pid)
等待某個(gè)子進(jìn)程退出。awk的waitpid是非阻塞的,如果等待的進(jìn)程還未退出,則返回值為0,等待的進(jìn)程已經(jīng)退出,則返回該進(jìn)程pid。

ret = wait()
等待任意一個(gè)子進(jìn)程退出。wait()是阻塞的,必須等待到一個(gè)子進(jìn)程退出,同時(shí)返回該子進(jìn)程PID。

例如:

awk '
    @load "fork"
    BEGIN{
        if( (pid=fork()) == 0 ){
            print "Child Process"
            print "CHILD PID: "PROCINFO["pid"]
            print "CHILD PPID: "PROCINFO["ppid"]
            system("sleep 1")
        } else {
            while(waitpid(pid) == 0){
                system("sleep 1")
            }
            print "Parent PID: "PROCINFO["pid"]
            print "Parent PPID: "PROCINFO["ppid"]
            print "Parent Process"
        }
    }
'

5.awk日期時(shí)間擴(kuò)展

"time"擴(kuò)展提供了兩個(gè)函數(shù)。

@load "time"

the_time = gettimeofday()
    獲取當(dāng)前系統(tǒng)時(shí)間,以浮點(diǎn)數(shù)方式返回,精確的浮點(diǎn)小數(shù)位由操作系統(tǒng)決定  

res = sleep(sec)
    睡眠指定時(shí)間,可以是小數(shù)秒
$ awk '@load "time";BEGIN{printf "%.9f\n",gettimeofday()}'
1572422333.740148067

$ awk '@load "time";BEGIN{printf "%.19f\n",gettimeofday()}'
1572422391.5475890636444091797

睡眠是很好用的功能:文章來源地址http://www.zghlxwxcb.cn/news/detail-741311.html

awk '@load "time";BEGIN{sleep(1.2);print "hello world"}'

到了這里,關(guān)于一篇文章玩透awk的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • Jenkins 插件下載速度慢安裝失???這篇文章可能解決你頭等難題!

    Jenkins 插件下載速度慢安裝失敗?這篇文章可能解決你頭等難題!

    Jenkins部署完畢,如果不安裝插件的話,那它就是一個(gè)光桿司令,啥事也做不了! ? 所以首先要登陸管理員賬號(hào)然后點(diǎn)擊系統(tǒng)管理再點(diǎn)擊右邊的插件管理安裝CI/CD必要插件。 但是問題來了,jenkins下載插件速度非常慢,而且經(jīng)常提示下載插件失敗,真是令人心痛! 本文主旨真正

    2024年02月13日
    瀏覽(26)
  • ElasticSearch篇——認(rèn)識(shí)、安裝和使用IK分詞器插件,一篇文章帶你徹底拿下!

    ElasticSearch篇——認(rèn)識(shí)、安裝和使用IK分詞器插件,一篇文章帶你徹底拿下!

    一、什么是IK分詞器 所謂分詞,即把一段中文或者別的劃分成一個(gè)個(gè)的,我們?cè)谒阉鲿r(shí)會(huì)把自己的信息進(jìn)行分詞,會(huì)把數(shù)據(jù)庫中或者索引庫中的數(shù)據(jù)進(jìn)行分詞,然后進(jìn)行一個(gè)匹配的操作,默認(rèn)的中文分詞器是將每一個(gè)字看成一個(gè)詞,比如“我愛中國(guó)”會(huì)被分成“我”、

    2024年02月03日
    瀏覽(29)
  • 【Stable Diffusion WebUI】一篇文章教你如何安裝和使用Stable Diffusion WebUI

    【Stable Diffusion WebUI】一篇文章教你如何安裝和使用Stable Diffusion WebUI

    1.1 下載 stable-diffusion-webui 1.2 運(yùn)行 webui.sh 第一次卡住一般是在 Installing gfpgan ,第二次卡住一般是在 Installing open_clip . 如果卡住或者報(bào)錯(cuò),就直接pip install [package] -i https://pypi.tuna.tsinghua.edu.cn/simple 然后在執(zhí)行 bash webui.sh -i https://pypi.tuna.tsinghua.edu.cn/simple ,一直重復(fù)上述過程,直

    2024年02月09日
    瀏覽(58)
  • 怎樣在一臺(tái)電腦安裝多個(gè)版本的JDK并切換使用?一篇文章教會(huì)你所有細(xì)節(jié)

    怎樣在一臺(tái)電腦安裝多個(gè)版本的JDK并切換使用?一篇文章教會(huì)你所有細(xì)節(jié)

    目錄 1. 下載安裝JDK版本 2. 配置環(huán)境變量 2. 1 配置環(huán)境變量的步驟 2.2 需要注意的細(xì)節(jié)點(diǎn) 2.3 JDK8,11,17版本切換測(cè)試 a . JDK8 下載鏈接: Java Downloads | Oracle https://www.oracle.com/java/technologies/downloads/#java8-windows b.? 這里我先插一句,因?yàn)槲覀円惭b多個(gè)JDK版本,所以我們最好提前創(chuàng)

    2024年04月16日
    瀏覽(20)
  • 一篇文章教你解決node-sass 4.12.0 安裝失敗,一勞永逸

    一篇文章教你解決node-sass 4.12.0 安裝失敗,一勞永逸

    已知: 使用mac電腦 使用的node版本是v14.20.0 問題:在安裝node-sass 4.12.0的時(shí)候報(bào)錯(cuò)如下 ?看到這一堆錯(cuò)誤,千萬不要立馬復(fù)制粘貼到瀏覽器去搜,感覺像無頭蒼蠅亂撞,好歹稍微看一下什么意思。 顯而易見是有一個(gè)文檔404not found,那么我們具體看一下 ?https://github.com/sass/node

    2024年02月01日
    瀏覽(32)
  • stable diffusion webui界面布局(很多大佬一鍵安裝包的樣式,自己部署卻沒有?那就看這篇文章吧!)

    stable diffusion webui界面布局(很多大佬一鍵安裝包的樣式,自己部署卻沒有?那就看這篇文章吧!)

    自己部署stable diffusion界面布局(很多大佬一鍵安裝包的樣式,自己部署卻沒有?那就看這篇文章吧?。?如下圖,使用一鍵部署的項(xiàng)目,有【外掛vae模型】【跳過CLIP部署】,且【采樣方法】的部署不是下拉列表,而是所有采樣方法都放出來了 如下圖:這是不適用一鍵部署包,

    2024年02月16日
    瀏覽(21)
  • Linux-一篇文章,速通Hadoop集群之偽分布式,完全分布式,高可用搭建(附zookeeper,jdk介紹與安裝)。

    Linux-一篇文章,速通Hadoop集群之偽分布式,完全分布式,高可用搭建(附zookeeper,jdk介紹與安裝)。

    文章較長(zhǎng),附目錄,此次安裝是在VM虛擬環(huán)境下進(jìn)行。文章第一節(jié)主要是介紹Hadoop與Hadoop生態(tài)圈并了解Hadoop三種集群的區(qū)別,第二節(jié)和大家一起下載,配置Linux三種集群以及大數(shù)據(jù)相關(guān)所需的jdk,zookeeper,只需安裝配置的朋友可以直接跳到文章第二節(jié)。同時(shí),希望我的文章能幫

    2024年03月19日
    瀏覽(28)
  • 一篇文章打好SQL基礎(chǔ),熟悉數(shù)據(jù)庫的基礎(chǔ)操作和方法,以及安裝MySQL軟件包和Python操作MySQL基礎(chǔ)使用

    一篇文章打好SQL基礎(chǔ),熟悉數(shù)據(jù)庫的基礎(chǔ)操作和方法,以及安裝MySQL軟件包和Python操作MySQL基礎(chǔ)使用

    SQL的全稱:Structured Query Language,結(jié)構(gòu)化查詢語言,用于 訪問和處理數(shù)據(jù)庫的標(biāo)準(zhǔn)計(jì)算機(jī)語言 。 SQL語言1974年有Boyce和Chamberlin提出的,并且首先在IBM公司研制的關(guān)系數(shù)據(jù)庫系統(tǒng)SystemR上實(shí)現(xiàn)。 經(jīng)過多年發(fā)展,SQL已經(jīng)成為數(shù)據(jù)庫領(lǐng)域同意的數(shù)據(jù)操作標(biāo)準(zhǔn)語言,可以說幾乎市面上所

    2024年02月08日
    瀏覽(104)
  • 一篇文章完成Hbase入門

    一篇文章完成Hbase入門

    HBase是一種分布式、可擴(kuò)展、支持海量數(shù)據(jù)存儲(chǔ)的NoSQL數(shù)據(jù)庫。 1、數(shù)據(jù)模型結(jié)構(gòu) 邏輯上,HBase的數(shù)據(jù)模型同關(guān)系型數(shù)據(jù)庫很類似,數(shù)據(jù)存儲(chǔ)在一張表中,有行有列。但從HBase的底層物理存儲(chǔ)結(jié)構(gòu)(K-V)來看,HBase更像是一個(gè)multi-dimensional map(多維地圖) HBase邏輯結(jié)構(gòu) 2、物理存

    2024年01月16日
    瀏覽(21)
  • 一篇文章學(xué)會(huì)高級(jí)IO

    一篇文章學(xué)會(huì)高級(jí)IO

    IO是數(shù)據(jù)在傳輸時(shí)的一種動(dòng)作描述,分為輸入數(shù)據(jù)(I)和輸出數(shù)據(jù)(O)兩種動(dòng)作。和一般而言,IO都需要維護(hù)一個(gè)收發(fā)數(shù)據(jù)的緩沖區(qū),例如read、recv函數(shù)和write、send函數(shù),它們的數(shù)據(jù)緩沖區(qū)都是由系統(tǒng)幫助創(chuàng)建的。對(duì)于C語言中常用到的scanf函數(shù)和printf函數(shù),同樣不需要用戶自

    2024年02月05日
    瀏覽(28)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包