"三劍客"之awk心中無劍

一寸HUI發表於2020-11-24

一、awk介紹

  awk 是一種程式語言. 它具有一般程式語言常見的功能.
  因awk語言具有某些特點, 如 : 使用直譯器(Interpreter)不需先行編譯; 變數無型別之分(Typeless), 可使用文字當陣列的下標(Associative Array)...等特色. 因此, 使用awk撰寫程式比起使用其它語言更簡潔便利且節省時間. awk還具有一些內建功能, 使得awk擅於處理具資料行(Record), 欄位(Field)型態的資料; 此外, awk內建有pipe的功能, 可將處理中的資料傳送給外部的 Shell命令加以處理, 再將Shell命令處理後的資料傳回awk程式, 這個特點也使得awk程式很容易使用系統資源。

1.1、awk讀取資料設定

awk讀取輸入檔案時,每次讀取一條記錄(record)(預設情況下按行讀取,所以此時記錄就是行)。每讀取一條記錄,將其儲存到$0中,然後執行一次main程式碼段
在讀取資料時,可設定表示輸入記錄分隔符的預定義變數RS(Record Separator)來改變每次讀取的記錄模式

awk 'BEGIN{RS="\n"}'預設情況下使用\n換行符進行分隔讀取。
RS="":按段落讀取
RS="\0":一次性讀取所有資料,但有些特殊檔案中包含了空字元\0
RS="^$":真正的一次性讀取所有資料,因為非空檔案不可能匹配成功
RS="\n+":按行讀取,但忽略所有空行

所以在讀取資料前,可以改變RS來改變每次讀取資料的模式

1.2、awk的執行過程

  1. 自動從指定的資料檔案中讀取一個資料行.
  2. 自動更新(Update)相關的內建變數之值. 如 : NF, NR, $0...
  3. 依次執行程式中 所有 的 Pattern { Actions } 指令.
  4. 當執行完程式中所有 Pattern { Actions } 時, 若資料檔案中還有未讀取的資料, 則反覆執行步驟1到步驟4.

1.3、本文例項資料

ID      name    gender  age     email           phone
1       zhangs  female  34      aaa@163.com     13423394013
2       lisi    male    45      abb@gmail.com   15608492523
3       wmzi    female  61      ccc@sohu.com    18848796505
4       wangw   female  31      acc@sohu.com    18848796506
5       liub    male    56      gdd@163.com     18848796507
6       zhangf  female  34      adb@139.com     18848796573
7       guany   male    56      eab@189.com     17048796508
8       zhaoy   female  35      189@163.com     17058796503
9       huangz  male    65      dd@189.com      13648796593
10      lvbu    male    43      byy@sohu.com    18148796803
11      sunwk   male    99      suk@sohu.com    18148706803

二、awk使用語法和選項

2.1、使用語法

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

program-txt是awk命令列中的awk程式碼,一般使用單引號包圍
-f program-file表示將awk程式碼寫在檔案中,然後使用-f選項去執行該檔案
-e program-text也用於指定awk程式碼,如果要結合檔案和命令列一起使用,必須使用-e和-f,不能使用(1)

2.2、使用選項

-e program-text
--source program-text
指定awk程式表示式,可結合-f選項同時使用
在使用了-f選項後,如果不使用-e,awk program是不會執行的,它會被當作ARGV的一個引數

-f program-file
--file program-file
從檔案中讀取awk原始碼來執行,可指定多個-f選項

-F fs
--field-separator fs
指定輸入欄位分隔符(FS預定義變數也可設定)

-n
--non-decimal-data
識別檔案輸入中的8進位制數(0開頭)和16進位制數(0x開頭)
echo '030' | awk -n '{print $1+0}'

-o [filename]
格式化awk程式碼。
不指定filename時,則預設儲存到awkprof.out
指定為`-`時,表示輸出到標準輸出

-v var=val
--assign var=val
在BEGIN之前,宣告並賦值變數var,變數可在BEGIN中使用

2.3、awk程式語法

awk程式中主要語法是 Pattern { Actions}
Pattern:為匹配模式,即判定某一行是否符合該模式,然後才執行Actions
Actions:為執行邏輯,其中多個action如果寫在同一行,則需使用分號分隔


pattern和action都可以省略

  • 省略pattern,等價於對每一行資料都執行action
  • 例如:awk '{print $0}' test.txt

省略程式碼塊{action},等價於{print}即輸出所有行

  • 例如:awk '/Alice/' test.txt等價於awk '/Alice/{print $0}' test.txt
  • 省略程式碼塊中的action,表示對篩選的行什麼都不做
  • 例如:awk '/Alice/{}' test.txt

pattern{action}任何一部分都可以省略

  • 例如:awk '' test.txt

pattern中使用邏輯與、非、或

pattern && pattern # 邏輯與 3>2 && 3>1 {action}
pattern || pattern # 邏輯或 3>2 || 3<1 {action}
! pattern # 邏輯取反 !/a.*ef/{action}

常用的Pattern

# 1.根據行號篩選
NR==2   # 篩選出第二行
NR>=2   # 輸出第2行和之後的行

# 2.根據正規表示式篩選整行
/qq.com/       # 輸出帶有qq.com的行
$0 ~ /qq.com/  # 等價於上面命令
/^[^@]+$/      # 輸出不包含@符號的行
!/@/           # 輸出不包含@符號的行

# 3.根據欄位來篩選行
($4+0) > 24{print $0}  # 輸出第4欄位大於24的行
$5 ~ /qq.com/          # 輸出第5欄位包含qq.com的行

# 4.將多個篩選條件結合起來進行篩選
NR>=2 && NR<=7             #輸出行數大於等於2並且小於等於7
$3=="male" && $6 ~ /^170/  #輸出第三個欄位等於male並且第六個欄位不用170開頭    
$3=="male" || $6 ~ /^170/  #輸出第三個欄位等於male或者第六個欄位不用170開頭

# 5.按照範圍進行篩選 flip flop
# pattern1,pattern2{action}
NR==2,NR==7         # 輸出第2到第7行
NR==2,$6 ~ /^170/   # 輸出第2行到匹配第六個欄位170開頭的行

action有很多,例如:

awk的 I/O指令 : print, printf( ), getline...
awk的 流程控制指令 : if(...){..} else{..}, while(...){...}...

例項:

[root@lgh test]# awk '' test.txt #action和pattern都省了
[root@lgh test]# awk '/163/{print $0}' test.txt  #匹配行中有163的行,並輸出
1       zhangs  female  34      aaa@163.com     13423394013
5       liub    male    56      gdd@163.com     18848796507
8       zhaoy   female  35      189@163.com     17058796503
[root@lgh test]# awk '/163/' test.txt  #省略action,預設輸出$0
1       zhangs  female  34      aaa@163.com     13423394013
5       liub    male    56      gdd@163.com     18848796507
8       zhaoy   female  35      189@163.com     17058796503
[root@lgh test]# awk '{print $0}' test.txt #省略pattern,輸出全部行
ID      name    gender  age     email           phone
1       zhangs  female  34      aaa@163.com     13423394013
2       lisi    male    45      abb@gmail.com   15608492523
3       wmzi    female  61      ccc@sohu.com    18848796505
4       wangw   female  31      acc@sohu.com    18848796506
5       liub    male    56      gdd@163.com     18848796507
6       zhangf  female  34      adb@139.com     18848796573
7       guany   male    56      eab@189.com     17048796508
8       zhaoy   female  35      189@163.com     17058796503
9       huangz  male    65      dd@189.com      13648796593
10      lvbu    male    43      byy@sohu.com    18148796803
11      sunwk   male    99      suk@sohu.com    18148706803

三、awk內建變數

預定義變數分為兩類:控制awk工作的變數和攜帶資訊的變數

第一類:控制AWK工作的預定義變數

$0,$1,$2,...$NF:其中$0表示整行資料,$1,$2,...,$NF分別表示該行的第一個欄位、第二個欄位,...,最後一個欄位
RS:輸入記錄分隔符,預設為換行符\n IGNORECASE:預設值為0,表示所有的正則匹配不忽略大小寫。設定為非0值(例如1),之後的匹配將忽略大小寫。例如在BEGIN塊中將其設定為1,將使FS、RS都以忽略大小寫的方式分隔欄位或分隔record FS:讀取記錄後,劃分為欄位的欄位分隔符。 FIELDWIDTHS:以指定寬度切割欄位而非按照FS。 FPAT:以正則匹配匹配到的結果作為欄位,而非按照FS劃分。 OFS:print命令輸出各欄位列表時的輸出欄位分隔符,預設為空格" " ORS:print命令輸出資料時在尾部自動新增的記錄分隔符,預設為換行符\n CONVFMT:在awk中數值隱式轉換為字串時,將根據CONVFMT的格式按照sprintf()的方式自動轉換為字串。預設值為”
%.6g OFMT:在print中,數值會根據OFMT的格式按照sprintf()的方式自動轉換為字串。預設值為”%.6g

第二類:攜帶資訊的預定義變數

ARGC和ARGV:awk命令列引數的數量、命令引數的陣列。
ARGIND:awk當前正在處理的檔案在ARGV中的索引位置。所以,如果awk正在處理命令列引數中的某檔案,則ARGV[ARGIND] == FILENAME為真
FILENAME:awk當前正在處理的檔案(命令列中指定的檔案),所以在BEGIN中該變數值為空
ENVIRON:儲存了Shell的環境變數的陣列。例如ENVIRON["HOME"]將返回當前使用者的家目錄
NR:當前已讀總記錄數,多個檔案從不會重置為0,所以它是一直疊加的,可以直接修改NR,下次讀取記錄時將在此修改值上自增
FNR:當前正在讀取檔案的第幾條記錄,每次開啟新檔案會重置為0,可以直接修改FNR,下次讀取記錄時將在此修改值上自增
NF:當前記錄的欄位數,
RT:在讀取記錄時真正的記錄分隔符,
RLENGTH:match()函式正則匹配成功時,所匹配到的字串長度,如果匹配失敗,該變數值為-1
RSTART:match()函式匹配成功時,其首字元的索引位置,如果匹配失敗,該變數值為0
SUBSEP:arr[x,y]中下標分隔符構建成索引時對應的字元,預設值為\034,是一個不太可能出現在字串中的不可列印字元。

1、$0,$1,$2,...,$NF

[root@lgh test]# awk '{print $0,$1,$2,$NF}' test.txt #輸出整行,然後輸出第一,第二,和最後一個欄位的值
ID      name    gender  age     email           phone ID name phone
1       zhangs  female  34      aaa@163.com     13423394013 1 zhangs 13423394013
2       lisi    male    45      abb@gmail.com   15608492523 2 lisi 15608492523
3       wmzi    female  61      ccc@sohu.com    18848796505 3 wmzi 18848796505
4       wangw   female  31      acc@sohu.com    18848796506 4 wangw 18848796506
5       liub    male    56      gdd@163.com     18848796507 5 liub 18848796507
6       zhangf  female  34      adb@139.com     18848796573 6 zhangf 18848796573
7       guany   male    56      eab@189.com     17048796508 7 guany 17048796508
8       zhaoy   female  35      189@163.com     17058796503 8 zhaoy 17058796503
9       huangz  male    65      dd@189.com      13648796593 9 huangz 13648796593
10      lvbu    male    43      byy@sohu.com    18148796803 10 lvbu 18148796803
11      sunwk   male    99      suk@sohu.com    18148706803 11 sunwk 18148706803

2、RS、IGNORECASE

RS有兩種情況:
RS為單個字元:直接使用該字元作為分隔符進行分割記錄
RS為多個字元:將其當做正規表示式,只要匹配正規表示式的符號,都用來做分割記錄,設定預定義變數INGORECASE為非零值,表示正則忽略大小寫

[root@lgh test]# echo "bababaABAB" |  awk 'BEGIN{IGNORECASE=1;RS="a+"}{print $0}' #正則忽略大小寫切分讀取
b
b
b
B
B
[root@lgh test]# echo "bababaABAB" |  awk 'BEGIN{IGNORECASE=0;RS="a+"}{print $0}' #正則不忽略大小寫切分讀取
b
b
b
ABAB
[root@lgh test]# echo "bababaABAB" |  awk 'BEGIN{IGNORECASE=1;RS="a"}{print $0}' #非正則,IGNORECASE無效
b
b
b
ABAB

3、分隔符FS、FIELDWIDTHS

  • FS為單個字元時,該字元即為欄位分隔符
  • FS為多個字元時,則採用正規表示式模式作為欄位分隔符
  • 特殊的,也是FS預設的情況,FS為單個空格時,將以連續的空白(空格、製表符、換行符)作為欄位分隔符
  • 特殊的,FS為空字串””時,將對每個字元都進行分隔,即每個字元都作為一個欄位
  • 設定預定義變數IGNORECASE為非零值,正則匹配時表示忽略大小寫(隻影響正則,所以FS為單字時無影響)
[root@lgh test]# awk -F" +|@" '{print $1,$2,$3,$4,$5,$6,$7}' test.txt #正則,一個空格或者多個空格,或者@符進行分割
ID name gender age email phone
1 zhangs female 34 aaa 163.com 13423394013
2 lisi male 45 abb gmail.com 15608492523
3 wmzi female 61 ccc sohu.com 18848796505
4 wangw female 31 acc sohu.com 18848796506
5 liub male 56 gdd 163.com 18848796507
6 zhangf female 34 adb 139.com 18848796573
7 guany male 56 eab 189.com 17048796508
8 zhaoy female 35 189 163.com 17058796503
9 huangz male 65 dd 189.com 13648796593
10 lvbu male 43 byy sohu.com 18148796803
11 sunwk male 99 suk sohu.com 18148706803
[root@lgh test]#  awk  '{print $1,$2}' FS=":" /etc/passwd | head -2 #使用FS指定:為分割分
root x
bin x
[root@lgh test]# awk -F":" '{print $1,$2}' /etc/passwd | head -2 #使用-F指定:分隔符
root x
bin x

預定義變數FIELDWIDTHS按字元寬度分割欄位
FIELDWIDTHS="1 2 3 4" :表示第一個欄位1個字元,第二個2個字元,第三個3個字元,第四個4個字元

[root@lgh test]# cat name.txt #檔案內容
ID      name    gender  age     email           phone
1               female  34      aaa@163.com     13423394013
2       lisi    male    45      abb@gmail.com   15608492523
[root@lgh test]#  awk '{print $2}' name.txt #獲取第二列,其中female是第三列的內容
name
female
lisi
[root@lgh test]# awk 'BEGIN{FIELDWIDTHS="8 8"}{print $2}' name.txt #正確的獲取第二列
name

lisi
[root@lgh test]# echo 'bababaABAB' | awk 'BEGIN{FIELDWIDTHS="1 2 3 4"}{print $1,$2,$3,$4}' #使用指定字元長度分割
b ab aba ABAB

4、FS、OFS

FS是讀取行時的欄位分割,OFS是輸出時的欄位分隔符

[root@lgh test]# awk 'BEGIN{OFS="="}{print $1,$2}' test.txt
ID=name
1=zhangs
2=lisi
3=wmzi
4=wangw
5=liub
6=zhangf
7=guany
8=zhaoy
9=huangz
10=lvbu
11=sunwk
[root@lgh test]# awk 'BEGIN{OFS="-"}{$1=$1;print $0}' test.txt #重組$0,修改$1,$2,...,$NF中的內容,以及增大或者減少NF的值,都會產生$0的重組
ID-name-gender-age-email-phone
1-zhangs-female-34-aaa@163.com-13423394013
2-lisi-male-45-abb@gmail.com-15608492523
3-wmzi-female-61-ccc@sohu.com-18848796505
4-wangw-female-31-acc@sohu.com-18848796506
5-liub-male-56-gdd@163.com-18848796507
6-zhangf-female-34-adb@139.com-18848796573
7-guany-male-56-eab@189.com-17048796508
8-zhaoy-female-35-189@163.com-17058796503
9-huangz-male-65-dd@189.com-13648796593
10-lvbu-male-43-byy@sohu.com-18148796803
11-sunwk-male-99-suk@sohu.com-18148706803

5、RS、ORS

RS是讀取時的行分割,ORS是輸出時的行分割

[root@lgh test]# echo -e "a\nb" |awk '{print $1}' #RS預設\n
a
b
[root@lgh test]# echo -e "a\nb" |awk 'BEGIN{ORS="hello word"}{print $1}' #使用hello word代替\n進行輸出
ahello wordbhello word

6、OFMT

變數OFMT(Output format)定義的格式按照sprintf()相同的方式進行格式化。OFMT預設值為%.6g,表示有效位(整數部分加小數部分)最多為6。

[root@lgh test]# awk 'BEGIN{OFMT="%.3f";print 3.141592678}' #會進行四捨五入
3.142
[root@lgh test]# awk 'BEGIN{print 3.141592678}'
3.14159
常用格式符:
%c :將ASCII碼轉換為字元,
%d :轉為整數
%e :科學計數方法輸出
%o :轉為8進位制輸出
%s :轉為字串
%x :轉16進位制

7、NR、FNR

NR是該程式讀取的記錄數,FNR是程式讀取某個檔案的的記錄數

[root@lgh test]# awk '{print NR,FNR,$0}' test.txt test.txt #NR一直遞增,FNR根據檔案重新設定1,然後遞增
1 1 ID      name    gender  age     email           phone
2 2 1       zhangs  female  34      aaa@163.com     13423394013
3 3 2       lisi    male    45      abb@gmail.com   15608492523
4 4 3       wmzi    female  61      ccc@sohu.com    18848796505
5 5 4       wangw   female  31      acc@sohu.com    18848796506
6 6 5       liub    male    56      gdd@163.com     18848796507
7 7 6       zhangf  female  34      adb@139.com     18848796573
8 8 7       guany   male    56      eab@189.com     17048796508
9 9 8       zhaoy   female  35      189@163.com     17058796503
10 10 9       huangz  male    65      dd@189.com      13648796593
11 11 10      lvbu    male    43      byy@sohu.com    18148796803
12 12 11      sunwk   male    99      suk@sohu.com    18148706803
13 1 ID      name    gender  age     email           phone
14 2 1       zhangs  female  34      aaa@163.com     13423394013
15 3 2       lisi    male    45      abb@gmail.com   15608492523
16 4 3       wmzi    female  61      ccc@sohu.com    18848796505
17 5 4       wangw   female  31      acc@sohu.com    18848796506
18 6 5       liub    male    56      gdd@163.com     18848796507
19 7 6       zhangf  female  34      adb@139.com     18848796573
20 8 7       guany   male    56      eab@189.com     17048796508
21 9 8       zhaoy   female  35      189@163.com     17058796503
22 10 9       huangz  male    65      dd@189.com      13648796593
23 11 10      lvbu    male    43      byy@sohu.com    18148796803
24 12 11      sunwk   male    99      suk@sohu.com    18148706803

8、ARGS、ARGV

預定義變數ARGV是一個陣列,包含了所有的命令列引數。該陣列使用從0開始的數值作為索引。
預定義變數ARGC初始時是ARGV陣列的長度,即命令列引數的數量。
ARGV陣列的數量和ARGC的值只有在awk剛開始執行的時候是保證相等的。

[root@lgh test]# awk 'BEGIN{print ARGC;for(i in ARGV){print "ARGV[" i "]= " ARGV[i]}}' a b c
4
ARGV[0]= awk
ARGV[1]= a
ARGV[2]= b
ARGV[3]= c

四、BEGIN和END

awk的所有程式碼(目前這麼認為)都是寫在語句塊中的。
每個語句塊前面可以有pattern,比如:pattern1{action1}pattern2{action3;action4;...}
語句塊可分為3類:BEGIN語句塊、END語句塊和main語句塊。其中BEGIN語句塊和END語句塊都是的格式分別為BEGIN{...}和END{...},而main語句塊是一種統稱,它的pattern部分沒有固定格式,也可以省略,main程式碼塊是在讀取檔案的每一行的時候都執行的程式碼塊。

BEGIN程式碼塊:
    在讀取檔案之前執行,且執行一次
    在BEGIN程式碼塊中,無法使用$0或其它一些特殊變數

main程式碼塊:
    讀取檔案時迴圈執行,(預設情況)每讀取一行,就執行一次main程式碼塊
    main程式碼塊可有多個

END程式碼塊:
    在讀取檔案完成之後執行,且執行一次
    有END程式碼塊,必有要讀取的資料(可以是標準輸入)
    END程式碼塊中可以使用$0等一些特殊變數,只不過這些特殊變數儲存的是最後一輪awk迴圈的資料
[root@lgh test]# awk 'BEGIN{print "hello word"}/^1/{print $0}END{print "END hello word"}' test.txt
hello word
1       zhangs  female  34      aaa@163.com     13423394013
10      lvbu    male    43      byy@sohu.com    18148796803
11      sunwk   male    99      suk@sohu.com    18148706803
END hello word

例項如上:begin和end均只輸出一次,main程式碼中匹配1開頭的行並輸出

五、運算子和邏輯運算子

5.1、運算子

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

5.2、邏輯運算子

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

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

# 注:
# 1. && ||會短路運算
# 2. !優先順序高於&&和||
#    所以`! expr1 && expr2`等價於`(! expr1) && expr2`

六、輸出和重定向

輸出格式:print elem1,elem2,elem3...
逗號分隔要列印的欄位列表,各欄位都會自動轉換成字串格式,然後通過預定義變數OFS值(其預設值為空格)連線各欄位進行輸出
在print輸出時會自動在尾部加上輸出記錄分隔符,輸出記錄分隔符的預定義變數為ORS,其預設值為\n
sprintf()採用和printf相同的方式格式化字串,但是它不會輸出格式化後的字串,而是返回格式化後的字串。所以,可以將格式化後的字串賦值給某個變數。

[root@lgh test]# awk 'BEGIN{var=sprintf("%.3f",3.141592678);print var}' #使用sprintf進行格式化複製變數,然後輸出
3.142
[root@lgh test]# awk 'BEGIN{var=sprintf("%d",3.141592678);print var}'
3
[root@lgh test]# awk 'BEGIN{OFMT="%.3f";print 3.141592678}' #使用OFMT指定格式進行輸出
3.142
[root@lgh test]# awk 'BEGIN{OFMT="%d";print 3.141592678}'
3

 重定向:

>filename時,如果檔案不存在,則建立,如果檔案存在則首先截斷。之後再輸出到該檔案時將不再截斷。
>>filename時,將追加資料,檔案不存在時則建立。
awk中只要不close(),任何檔案都只會在第一次使用時開啟,之後都不會再重新開啟。

[root@lgh test]# awk '{print $1,$2 > "name.txt"}' test.txt #重定向到name.txt檔案
[root@lgh test]# cat name.txt
ID name
1 zhangs
2 lisi
3 wmzi
4 wangw
5 liub
6 zhangf
7 guany
8 zhaoy
9 huangz
10 lvbu
11 sunwk

七、陣列

awk陣列特性:

  • awk的陣列是關聯陣列(即key/value方式的hash資料結構),索引下標可為數值(甚至是負數、小數等),也可為字串
  • 在內部,awk陣列的索引全都是字串,即使是數值索引在使用時內部也會轉換成字串
  • awk的陣列元素的順序和元素插入時的順序很可能是不相同的
  • awk陣列支援陣列的陣列
  • 通過索引的方式訪問陣列中不存在的元素時,會返回空字串,同時會建立這個元素並將其值設定為空字串。

陣列的訪問和賦值都是通過制定下標就行賦值和訪問,下標可以為整數,負數,字串,小數
例如:

[root@lgh test]# cat a.awk
{
        arr[i]="a"
        arr[-1]="b"
        print arr[i]
        print arr[-1]
        print length(arr)
        print arr[1]  #輸出不存在的陣列下標內容
        print "################"
        print length(arr)  #長度+1
}
[root@lgh test]# echo 0 | awk -f a.awk
a
b
2

################
3

陣列遍歷、刪除、判讀某key是否在arr中:

[root@lgh test]# cat a.awk
{
        arr["x"]="a"
        arr[-1]="b"
        arr[4.5]=1
        arr[4]=5
        for(idx in arr){ #遍歷
           print arr[idx]
        }
        print "###############"
        delete arr[4]  #刪除元素,也可以刪除整個陣列delete arr
        for(idx in arr){ #遍歷無序,與建立陣列時輸入的元素順序不一定一致
           print idx,arr[idx]
        }
        print "##############"
        print (-1 in arr) #判斷key=-1,是否在陣列中,是返回1,不是返回0
        print (99 in arr)
}
[root@lgh test]# echo 0 | awk -f a.awk
5
b
a
1
###############
-1 b
x a
4.5 1
##############
1
0

八、流程控制

8.1、判斷、選擇

if判斷:

if(condition){statement }
if(condition){statement }else{statement }
if(condition){statement }else if(condition){statement }else{statement }

三目運算

expr1 ? expr2 : expr3

switch選擇:

switch (expression) {
    case value1|regex1 : statements1
    case value2|regex2 : statements2
    case value3|regex3 : statements3
    ...
    [ default: statement ]
}
[root@lgh test]# awk 'NR>1{$1>5? var="B": var="A";print $1,var}' test.txt
1 A
2 A
3 A
4 A
5 A
6 B
7 B
8 B
9 B
10 B
11 B
[root@lgh test]# awk 'NR>1{if($1<5){print $1,"A"}else{print $1,"B"}}' test.txt
1 A
2 A
3 A
4 A
5 B
6 B
7 B
8 B
9 B
10 B
11 B

8.2、迴圈

while和do…while

while(condition){
    statements
}

do {
    statements
} while(condition)

for迴圈

for (expr1; expr2; expr3) {
    statement
}

for (idx in array) {
    statement
}
awk '{i=1;while(i<=NF){print $i;i++}}' test.txt 
awk '{for(i=1;i<=NF;i++) print $i}' test.txt

8.3、關鍵字

break:break可退出for、while、do…while、switch語句。
continue:continue可讓for、while、do…while進入下一輪迴圈。
next:next會在當前語句處立即停止後續操作,並讀取下一行,進入迴圈頂部。
nextfile:nextfile會在當前語句處立即停止後續操作,並直接讀取下一個檔案,並進入迴圈頂部。
exit:直接退出awk程式,注意,END語句塊也是exit操作的一部分,所以在BEGIN或main段中執行exit操作,也會執行END語句塊。如果真的想直接退出整個awk,則可以先設定一個flag變數,然後在END語句塊的開頭檢查這個變數再exit。

 

[root@lgh test]# awk 'NR==3{next}{print  $0}' test.txt  #匹配到了第3行,然後跳過
ID      name    gender  age     email           phone
1       zhangs  female  34      aaa@163.com     13423394013
3       wmzi    female  61      ccc@sohu.com    18848796505
4       wangw   female  31      acc@sohu.com    18848796506
5       liub    male    56      gdd@163.com     18848796507
6       zhangf  female  34      adb@139.com     18848796573
7       guany   male    56      eab@189.com     17048796508
8       zhaoy   female  35      189@163.com     17058796503
9       huangz  male    65      dd@189.com      13648796593
10      lvbu    male    43      byy@sohu.com    18148796803
11      sunwk   male    99      suk@sohu.com    18148706803
[root@lgh test]# awk 'NR==3{nextfile}{print  $0}' test.txt test.txt #匹配到第3行,跳過該檔案,執行下一個檔案
ID      name    gender  age     email           phone
1       zhangs  female  34      aaa@163.com     13423394013
ID      name    gender  age     email           phone
1       zhangs  female  34      aaa@163.com     13423394013
2       lisi    male    45      abb@gmail.com   15608492523
3       wmzi    female  61      ccc@sohu.com    18848796505
4       wangw   female  31      acc@sohu.com    18848796506
5       liub    male    56      gdd@163.com     18848796507
6       zhangf  female  34      adb@139.com     18848796573
7       guany   male    56      eab@189.com     17048796508
8       zhaoy   female  35      189@163.com     17058796503
9       huangz  male    65      dd@189.com      13648796593
10      lvbu    male    43      byy@sohu.com    18148796803
11      sunwk   male    99      suk@sohu.com    18148706803
[root@lgh test]# awk 'BEGIN{ print "hello work";exit}END{print "god job"}'  #預設會執行END中的程式碼
hello work
god job
[root@lgh test]# awk 'BEGIN{ print "hello work";;exit flag=1}END{if(flag) exit ;print "god job"}' #使用flag跳出
hello work

九、變數

awk中語句塊沒有作用域,都是全域性變數(除非是函式中的形參)

變數的賦值:

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

awk中宣告變數的位置:

在BEGIN或main或END程式碼段中直接引用或賦值
使用-v var=val選項,可定義多個,必須放在awk程式碼的前面
它的變數宣告早於BEGIN塊
普通變數:awk -v age=123 'BEGIN{print age}'
使用shell變數賦值:awk -v age=$age 'BEGIN{print age}'

在awk程式碼後面使用var=val引數
它的變數宣告在BEGIN之後
    awk '{print n}' n=3 a.txt n=4 b.txt
    awk '{print $1}' FS=' ' a.txt FS=":" /etc/passwd
使用Shell變數賦值:awk '{print age}' age=$age a.txt

十、函式和自定義函式

10.1、自定義函式

使用function關鍵字來定義函式:

function func_name([parameters]){
    function_body
}

函式可以定義的位置:

awk '_ BEGIN{} _ MAIN{} _ END{} _'  #可以定義在任何下劃線的地方
[root@lgh test]# cat a.awk
BEGIN{
        f()
    }
    function f(){
        print "自定義函式"
    }
{}#main
END{}
[root@lgh test]# echo 0 | awk -f a.awk
自定義函式
[root@lgh test]# vim a.awk
[root@lgh test]# cat a.awk
function f(){
        print "自定義函式"
    }

BEGIN{
        f()
    }
{}#main
END{}
[root@lgh test]# echo 0 | awk -f a.awk
自定義函式
[root@lgh test]# vim a.awk
[root@lgh test]# cat a.awk
BEGIN{
        f()
    }
{}#main
END{}
function f(){
        print "自定義函式"
    }
[root@lgh test]# echo 0 | awk -f a.awk
自定義函式

帶引數和return的自定義函式:

[root@lgh test]# cat a.awk
BEGIN{
        print f(1,2)
    }
{}#main
END{}
function f(a,b){
        print "自定義函式"
        return a+b
    }

[root@lgh test]# echo 0 | awk -f a.awk
自定義函式
3

引數的傳遞形式:

傳遞普通變數時,是按值拷貝傳遞

  • 直接拷貝普通變數的值到函式中
  • 函式內部修改不會影響到外部

傳遞陣列時,是按引用傳遞

  • 函式內部修改會影響到外部

 

awk變數作用域:

  • awk只有在函式引數中才是區域性變數,其它地方定義的變數均為全域性變數。
  • 函式內部新增的變數是全域性變數,會影響到全域性,所以在函式退出後仍然能訪問
  • 函式引數會遮掩全域性同名變數,所以在函式執行時,無法訪問到或操作與引數同名的全域性變數,函式退出時會自動撤掉遮掩,這時才能訪問全域性變數。所以,引數具有區域性效果。
[root@lgh test]# cat a.awk
BEGIN{
        a=50 #全域性
        print a #50
        print f(a,2) #42
        print a #50
    }
{}#main
END{}
function f(a,b){
        print "自定義函式"
        a=40 #覆蓋了全域性變數a
        print "區域性",a
        return a+b #40+2
    }

[root@lgh test]# echo 0 | awk -f a.awk
50
自定義函式
區域性 40
42
50

10.2、getline函式

getline函式用於從檔案、標準輸入或管道中讀取資料,並按情況設定變數的值。getline可以自動不斷的載入下一行。如果能讀取記錄,則getline的返回值為1,遇到輸入流的尾部時,返回值為0,不能讀取記錄(如檔案沒有讀取許可權、檔案不存在)時,返回值為“-1"。

其中:

  1. getline:會從主輸入檔案中讀取記錄。會同時設定$0,NF,NR,FNR。
  2. getline var:會從主輸入檔案中讀取記錄,並將讀取的記錄賦值給變數var。會同時設定var,NR,FNR。
  3. getline <file:從外部檔案file中讀取記錄。同時會設定$0,NF。
  4. getline var <file:從外部檔案file中讀取記錄,並將讀取的記錄賦值給變數var。會同時設定var。
  5. cmd | getline:從管道中讀取記錄。會同時設定$0,NF。
  6. cmd | getline var:從管道中讀取記錄,並將讀取的記錄賦值給變數var。會同時設定var。

getline與netx的區別
getline:讀取下一行之後,繼續執行getline後面的程式碼
next:讀取下一行,立即回頭awk迴圈的頭部,不會再執行next後面的程式碼

[root@lgh test]# awk '/^1/{print;getline;print}' test.txt #匹配1開頭的行,在1開頭的時候,獲取到了下一行,也列印了出來
1       zhangs  female  34      aaa@163.com     13423394013
2       lisi    male    45      abb@gmail.com   15608492523
10      lvbu    male    43      byy@sohu.com    18148796803
11      sunwk   male    99      suk@sohu.com    18148706803
[root@lgh test]# awk '/^1/{print;getline;print;exit}' test.txt #執行了getline,print,然後exit了
1       zhangs  female  34      aaa@163.com     13423394013
2       lisi    male    45      abb@gmail.com   15608492523
[root@lgh test]# awk '/^1/{print;if((getline var)<0)exit;print var}' test.txt #getline賦值給了var
1       zhangs  female  34      aaa@163.com     13423394013
2       lisi    male    45      abb@gmail.com   15608492523
10      lvbu    male    43      byy@sohu.com    18148796803
11      sunwk   male    99      suk@sohu.com    18148706803
[root@lgh test]# awk '/^1/{print;if((getline var)<0)exit;print $0}' test.txt #getline賦值給了var,但是沒有設定$0
1       zhangs  female  34      aaa@163.com     13423394013
1       zhangs  female  34      aaa@163.com     13423394013
10      lvbu    male    43      byy@sohu.com    18148796803
10      lvbu    male    43      byy@sohu.com    18148796803
[root@lgh test]# awk '/^2/{while((getline var <"a.awk")>0)print var;close("a.awk")}' test.txt #匹配2開頭的行,只有一行,所以後面只執行一次,把a.awk裡面的內容賦值給var變數,然後輸出
BEGIN{
        a=50
        print a
        print f(a,2)
        print a
    }
{}#main
END{}
function f(a,b){
        print "自定義函式"
        a=40
        print "區域性",a
        return a+b
    }


[root@lgh test]# awk '/^2/{while((getline<"a.awk")>0){print};close("a.awk")}' test.txt #匹配2開頭的行,只有一行,所以只執行一次,輸出a.awk中的內容
BEGIN{
        a=50
        print a
        print f(a,2)
        print a
    }
{}#main
END{}
function f(a,b){
        print "自定義函式"
        a=40
        print "區域性",a
        return a+b
    }
[root@lgh test]# awk '/^2/{"date"|getline;print}' test.txt #使用linux的date命令,通過管道,使用getline讀取
Tue Nov 24 19:44:28 CST 2020
[root@lgh test]# awk '/^2/{"date"|getline var ;print var}' test.txt
Tue Nov 24 19:44:41 CST 2020
[root@lgh test]# awk '/^1/{"date"|getline var ;print var}' test.txt
Tue Nov 24 19:44:48 CST 2020
Tue Nov 24 19:44:48 CST 2020
Tue Nov 24 19:44:48 CST 2020

awk雖然強大,但是有些資料仍然不方便處理,這時可將資料交給Shell命令去幫助處理,然後再從Shell命令的執行結果中取回處理後的資料繼續awk處理。awk通過|&符號來支援coproc。

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

這表示awk通過print輸出的資料將傳遞給Shell的命令Shell_Cmd去執行,然後awk再從Shell_Cmd的執行結果中取回Shell_Cmd產生的資料

[root@lgh test]# cat a.awk
BEGIN{
      CMD="sed -nr \"s/.*@(.*)$/\\1/p\""; #獲取郵箱@符號後面的內容
    }
    NR>1{
        print $5; #輸出郵箱名
        print $5 |& CMD; #擷取郵箱@符號後面的內容
        close(CMD,"to");
        CMD |& getline email_domain; #把郵箱@後面的內容賦值給變數email_domain
        close(CMD);
        print email_domain; #輸出email_domain


}
[root@lgh test]# awk -f a.awk test.txt
aaa@163.com
163.com
abb@gmail.com
gmail.com
ccc@sohu.com
sohu.com
acc@sohu.com
sohu.com
gdd@163.com
163.com
adb@139.com
139.com
eab@189.com
189.com
189@163.com
163.com
dd@189.com
189.com
byy@sohu.com
sohu.com
suk@sohu.com
sohu.com

10.3、內建字串函式

index(str1,str2):返回子串str2在字串str1中第一次出現的位置。如果沒有指定str1,則返回0。
length(str1):返回字串str1的長度。如果未給定str1,則表示計算"$0"的長度。
substr(str1,p):返回str1中從p位置開始的字尾字串。
substr(str1,p,n):返回str1中從p位置開始,長度為n的子串。
match(str1,regexp):如果regexp能匹配str1,則返回匹配起始位置。否則返回0。它會設定內建變數RSTART和RLENGTH的值。
split(str1,array,sep):使用欄位分隔符sep將str1分割到陣列array中,並返回陣列的元素個數。如果未指定sep則採用FS的值。因此該函式用於切分欄位到陣列中,下標從1開始。
sprintf(fmt,expr):根據printf的格式fmt,返回格式化後的expr。
sub(regexp,rep,str2):將str2中第一個被regexp匹配的字串替換成rep,替換成功則返回1(表示替換了1次),否則返回0。注意是貪婪匹配。
sub(regexp,rep):將"$0"中第一個被regexp匹配的字串替換成rep,替換成功則返回1,否則返回0。注意是貪婪匹配。
gsub(regexp,rep,str2):將str2中所有被regexp匹配的內容替換成rep,並返回替換的次數。
gsub(regexp,rep):將"$0"中所有被regexp匹配的內容替換成rep,並返回替換的次數。
toupper(str):將str轉換成大寫字母,並返回新串。
tolower(str):將str轉換成小寫字母,並返回新串。

10.4、內建數值函式

int(expr)     截斷為整數:int(123.45)和int("123abc")都返回123,int("a123")返回0
sqrt(expr)    返回平方根
rand()        返回[0,1)之間的隨機數,預設使用srand(1)作為種子值
srand([expr]) 設定rand()種子值,省略引數時將取當前時間的epoch值(精確到秒的epoch)作為種子值

 

參考:

https://www.cnblogs.com/f-ck-need-u/p/7509812.html#9-awk-

https://www.cnblogs.com/f-ck-need-u/p/12688355.html

https://www.cnblogs.com/hdk1993/p/4637525.html

相關文章