AWK教程

通俗的來講awk更像是一個行列處理器。
awk、sed、grep更適合的方向:
grep 更適合單純的查找或匹配文本
sed 更適合編輯匹配到的文本
awk 更適合格式化文本,對文本進行較複雜格式處理

基礎知識

讀(Read)
AWK 從輸入流(文件、管道或者標準輸入)中讀入一行然後將其存入記憶體中。

執行(Execute)
對於每一行輸入,所有的 AWK 命令按順執行。 默認情況下,AWK 命令是針對於每一行輸入,但是我們可以將其限制在指定的模式中。

重複(Repeate)
一直重複上述兩個過程直到文件結束。

開始塊 BEGIN BLOCK

BEGIN {awk-commands}

類似於java中的static塊,在程式啟動時執行,且整個過程只執行一次
一般用於初始化一些變數(如分隔符),BEGIN是AWK的關鍵詞,必須大寫。
開始塊部分是可選的,一個程式可以沒有開始塊部分。

主體塊 BODY BLOCK

/pattern/ {awk-commands}

如上方工作流中展示的那樣,每個輸入的行都會執行一次主體塊的命令
注意,主體塊部分沒有關鍵字

結束塊 END BOLCK

END {awk-commands}

結束塊是在程式結束時執行的程式碼。 END 也是 AWK 的關鍵字,它也必須大寫。 與開始塊相似,結束塊也是可選的。

awk命令行

awk [option] file...
# 列印日誌文件
awk '{print}' info.log

$0

此變數表示整個輸入記錄。

[jerry]$ awk '{print $0}' marks.txt
#執行上面的命令可以得到如下的結果:

1)    Amit     Physics    80
2)    Rahul    Maths      90
3)    Shyam    Biology    87
4)    Kedar    English    85
5)    Hari     History    89

$n

此變數表示當前輸入記錄的第 n 個域,這些域之間由 FS 分割。

[jerry]$ awk '{print $3 "\t" $4}' marks.txt
執行上面的命令可以得到如下的結果:

Physics    80
Maths      90
Biology    87
English    85
History    89

AWK程式文件

除了使用命令外,awk還可以通過讀入文本文件中的命令執行
在test.awk文本中寫入{print}
執行awk命令

#列印日誌文件
awk -f test.awk info.log

基本命令

awk默認分隔符為空格或tab
元素下表從1開始
示例文本

# text
2 this is a test
3 Are you like awk
This's a test
10 There are orange,apple,mongo
awk '{[pattern] action}' {filenames}   # 行匹配語句 awk '' 只能用單引號

匹配文本某一列

awk '{print $1,$4}' log.txt #每行按空格或TAB分割,輸出文本中的1、4項
#結果
 2 a
 3 like
 This's
 10 orange,apple,mongo
 
# 格式化輸出
 $ awk '{printf "%-8s %-10s\n",$1,$4}' log.txt
 ---------------------------------------------
 2        a
 3        like
 This's
 10       orange,apple,mongo

通過匹配模式輸出某一行

awk '/ABCacheLoader/ {print$0}' info.log

如果某行中含有/parttern/中的字元串,則該行會被輸出。
如果沒有主體塊——默認的動作是輸出記錄(行)。因此上面的效果也可以使用下面簡略方式實現,它們會得到相同的結果:

 awk '/ABCacheLoader/' info.log

通過匹配模式輸出某一列

前面我們已經看到了,當模式串匹配成功後, AWK 默認會輸出整個記錄。不過,我們可以讓 AWK 只輸出特定的域(列)的內容。 例如,下面的這個例子中當模式串匹配成功後只會輸出第1列與第3列的內容:

 awk '/ABCacheLoader/ {print$1 "\t" $3}' info.log

其中$1$3的順序可以隨意地調換,即我們能以任意順序輸出各列

計算匹配次數並輸出

統計模式串成功匹配文件中行的次數

 awk '/OfflineTaskCacheLoader/{++cnt}END{print "count=",cnt}' info.log

上面這個例子中,每次成功的匹配我們都會增加計數器的值,並在結束塊中將該計數器的值輸出。 請注意,與其它程式語言不一樣的地方在於, AWK 在使用一個變數前不需要特意地聲明這個變數。

輸出字元數大於18的行

awk 'length($0)>18' info.log

AWK 提供了內置的 length 函數。該函數返回字元串的長度。變數 $0 表示整行,缺失的主體塊會執行默認動作,例如,列印輸出。 因此,如果一行中字元數超過 18, 則比較的結果為真,該行則被輸出。

內置變數

ARGC 參數個數

ARGC 表示在命令行提供的參數的個數。

awk 'BEGIN {print "argc = " ,ARGC}' info.log info.log
argc =  3

ARGV 輸入參數的數組

這個變數表示存儲命令行輸入參數的數組。數組的有效索引是從 1 到 ARGC。其中ARGV[0] = awk

awk 'BEGIN {for (i=0; i<ARGC;i++) {printf "ARGV[%d] = %s\n",i,ARGV[i]}}' one two three
ARGV[0] = awk
ARGV[1] = one
ARGV[2] = two
ARGV[3] = three

ENVIRON 環境變數

獲取環境變數
具體的環境變數可以通過系統中的 env 命令查詢其它環境變數的名字

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

FILENAME 文件名

值得注意的是在開始塊中FILENAME是未定義的

awk 'END{print FILENAME}' info.log
info.log

FS (Field Separator 欄位分隔符)

此變數表示輸入的數據域之間的分隔符,其默認值是空格。 你可以使用 -F 命令行選項 或者在 BEGIN塊中設置分割符 改變它的默認值。

awk 'BEGIN{FS=":"}{print $2}' info.log
#上下等價
awk -F ":" '{print $2}' info.log
awk 'BEGIN{FS=":";print "FS ="FS}' info.log
FS =:

FS和OFS命令
FS:Field Separator,欄位分隔符(對輸入生效)
OFS:Out of Field Separator,輸出欄位分隔符(對輸出生效)

awk 'BEGIN{ FS=" ";OFS="," }{ print $1,$2,$3,$4 }' file2.txt > file3.txt

NF(Number of Filed)

此變數表示當前輸入記錄中域的數量。例如,下面這個例子只輸出超過兩個域的行:
echo -e 激活轉義字元。使用-e選項時,若字元串中出現轉義字元,則特別加以處理,不會當做一般文字輸出

echo -e "one two\nthree\none two three" | awk 'NF >2'
one two three

NR(Number of Row)

該變數記錄當前處理的記錄的數量。下面例子會輸出讀入的前2行(NR<3)。

cat info.log|awk 'NR < 3'
#上下等價
awk '{if(NR<3){print}}' info.log

FNR(File Number of Row)

該變數與 NR 類似,不過它是相對於當前文件而言的。此變數在處理多個文件輸入時有重要的作用。每當從新的文件中讀入時 FNR 都會被重新設置為 0。

awk '{print NR,FNR}' info.log  newtable
# 新文件NR繼續計數,而FNR重0開始計數
1029 1029
1030 1030
1031 1
1032 2

OFS(Output Field Separator 輸出欄位分割符)

此變數表示輸出域之間的分割符,其默認為空格
對 {print $1,$2,$3}生效 ,即只對print中的逗號(,)生效

awk 'BEGIN{OFS = "---"}{print $1,$2}' info.log
-----
2020-06-15---20:00:02.446
2020-06-15---20:00:02.457
2020-06-15---20:00:02.462

RS (Row Separator 輸入行分隔符)

此變數表示輸入記錄(行)之間的分隔符,默認為換行符

#以:作為行分割
awk 'BEGIN{RS=":"}{print $1}' info.log | more
-------
2020-06-15
00
02.446
]
49]
00
02.457
]
67]

ORS(行分隔符)

此變數表示輸出記錄(行)之間的分割符,其默認值是換行符。

awk 'BEGIN{RS=":";ORS="---"}{print $1}' info.log | more
2020-06-15---00---02.446---]---49]---00---02.457---]---67]---00---02.462---]---56]---00---02.468---]

RSTART(匹配字元串)

此變數表示由 match 函數匹配的字元串的第一個字元的位置。

awk 'BEGIN { if (match("One Two Three", "Thre")) { print RSTART } }'
#執行上面的命令可以得到如下的結果:
9

操作符

正則表達式操作符
下面將介紹兩種正則表達式操作符:

匹配(Match)

匹配運算符為~。它用於搜索包含匹配模式字元串的域。下面的示例中將輸出包括 9 的行:

[jerry]$ awk '$0 ~ 9' marks.txt
執行上面的命令可以得到如下的結果:

2)  Rahul   Maths   90
5)  Hari    History 89

不匹配(Not match)

不匹配操作符為 !~。 此操作符用於搜索不匹配指定字元串的域。如下示例輸出不包含 9 的行:

[jerry]$ awk '$0 !~ 9' marks.txt
執行上面的命令可以得到如下的結果:

1)  Amit    Physics 80
3)  Shyam   Biology 87
4)  Kedar   English 85

點(Dot)

點字元(.)可以匹配除了行結束字元的所有字元。比如下面的便子就可以匹配 fin, fun, fan 等等。

echo -e "cat\nbat\nfun\nfin\nfan" | awk '/f.n/'
#輸出
fun
fin
fan

行開始

行開始符(^)匹配一行的開始。下面的示例將輸出所有以字元串 The 開始的行。

echo -e "this is lipengtest" | awk '/^t/'
this is lipengtest

行結束

行結束符($)匹配一行的結束。下面的例子中將輸出所有以字元 t 結束的行:

echo -e "this is lipengtest" | awk '/t$/'
this is lipengtest

匹配字符集

匹配字符集用於匹配集合(由方括弧表示)中的一個字元。如下例子中,匹配 Call 與 Tall 而不會匹配 Ball。
echo -e "Call\nTall\nBall" | awk '/[CT]all/'
執行上面的命令可以得到如下結果:
fun
fin
fan

排除集

正則匹配時會排除集合中的字元。如下例子中只會輸出 Ball。
注意,^符號只有在集合內才會起作用,且如果放在開頭作用範圍為整個集合

#放在集合開頭
echo -e "Call\nTall\nBall" | awk '/[^CT]all/'
執行上面的命令可以得到如下結果:
Ball
#放在集合中間
echo -e "Call\nTall\nBall" | awk '/[C^T]all/'
Call
Tall
#放在集合外,用作行開始
echo -e "Call\nTall\nBall" | awk '/^[CT]all/'
Call
Tall

豎線(|)允許正則表達式實現邏輯或運算. 下面例子將會輸出 Ball 與 Call 。

echo -e "Call\nTall\nBall\nSmall\nShall" | awk '/Call|Ball/'
執行上面的命令可以得到如下結果:
Call
Ball

最多出現一次

該符號(?)前面的字元不出現或者出現一次。如下示例匹配 Colour 與 Color。 使用 ? 使得 u 變成了可選字元 。

echo -e "Colour\nColor" | awk '/Colou?r/'
執行上面的命令可以得到如下結果:
Colour
Color

分組

括弧用於分組而字元 | 用於提供多種選擇。如下的正則表達式會匹配所有包含 Apple Juice 或 Aplle Cake 的行。

[jerry]$ echo -e "Apple Juice\nApple Pie\nApple Tart\nApple Cake" | awk '/Apple (Juice|Cake)/'
#這裡的空格是會算進表達式里的
#執行上面的命令可以得到如下結果:
Apple Juice
Apple Cake

控制流

IF-ELSE語句

if-else語句中允許在條件為假時執行另外一組的動作。下面為 if-else 的語法格式:

if (condition)
    action-1
else
    action-2

其中,條件為真時執行 action-1,條件為假時執行 action-2。下面是使用該語句判斷數字是否為偶數的例子:

awk 'BEGIN {num = 11; 
   if (num % 2 == 0) printf "%d is even number.\n", num; 
   else printf "%d is odd number.\n", num 
                    }'

執行上面的操作可以得到如下的結果:

11 is odd number.

EXIT

Exit 用於結束腳本程式的執行。該函數接受一個整數作為參數表示 AWK 進程結束狀態。 如果沒有提供該參數,其默認狀態為 0 。下面例子中當和大於 50 時結束 AWK 程式。

awk 'BEGIN {
    sum = 0; for (i = 0; i < 20; ++i) {
        sum += i; if (sum > 50) exit(10); else print "Sum =", sum 
                                     } 
                    }'
Sum = 0
Sum = 1
Sum = 3
Sum = 6
Sum = 10
Sum = 15
Sum = 21
Sum = 28
Sum = 36
Sum = 45

讓我們檢查一下腳本執行後的返回狀態:

echo $?
執行上面的命令可以得到如下的結果:

19

實際例子

篩選出第n個列並輸出到文件中

awk 'BEGIN{FS=";"}{print $1 >> "result1.sql"}' result.txt

傳入參數並根據參數匹配輸出

#!/bin/bash

#########
#
# 目標:查找文件中對應的sql和riskey
# author: lipeng01_dxm
#
#########


# host="10.21.181.103"
# Port=8888
# user="db_user_rw"
# password="EDCIdek25"
# database="mask"

riskey_array=(
"1"
"2"
"3"
"4"
"5"
"6"
)
shell_var="abc"
for ((i=0; i<6; i++)); do
    shell_var=${riskey_array[i]}
    # awk 'BEGIN{riskey="'$riskey_array[i]'";if($2 == riskey) printf $2}'
    awk 'BEGIN{riskey = "'$shell_var'";}{if($2 == riskey) print $0 >> "/Users/dxm/Desktop/result.txt"}' /Users/dxm/Desktop/rieskeyallsql.txt 
done

匹配模式輸出

awk 'BEGIN{FS=";"}{if($1!~/mdm_per_detail_user_info/) {print "--\n" $1 >> /Users/dxm/Desktop/result2.sql}}' result.txt

文件對比

#在awk中,FNR指的是當前文件中的記錄號(通常是行號)並NR引用總記錄號。運算符==是一個比較運算符,當兩個周圍的操作數相等時返回true。
#這意味著條件NR==FNR僅適用於第一個文件,因為FNR每個文件的第一行重置為1,但NR會繼續增加。
#此模式通常用於僅對第一個文件執行操作。在next塊內是指任何進一步的命令被跳過,所以它們僅在比所述第一其他文件運行。
#該條件FNR==NR比較了相同的兩個操作數NR==FNR,因此它的行為方式相同。
awk -F',' 'NR==FNR{a[$1]=$1;next}NR!=FNR{if($1 in a){}else{print $0}}' 5568.csv 5569.csv   
awk 'BEGIN{FS = ","}{if(NR==FNR){a[$1]=$1}else{if($1 in a){}else{print $0}}}' 2370.csv  5568.csv

curl命令轉換

awk '{print "curl -L -X POST \047//10.157.23.145:8307/mask/_doc/" $1 "\047 -H \047Authorization: Basic ZWxhc3RpYzptYXNrX3Jk\047 -H \047Content-Type: application/json\047 --data-raw \047{}\047"}' 2020-08-12-01.txt > ./2020-08-12-01.sh 
sh ./2020-08-12-01.sh 

單引號因為是關鍵字,所以需要使用8進位的\047來代替