linux awk 一看就懂

forest7707發表於2017-05-17

awk是什麼

awk是linux環境下的一個命令列工具,但是由於awk強大的能力,我們可以為awk工具傳遞一個字串,該字串的內容類似一種程式語言的語法,我們可以稱其為Awk語言,而awk工具本身則可以看作是Awk語言的解析器。就好比python解析器與Python語言的關係。我們一般使用awk來做什麼,awk又適合做什麼工作呢。由於awk天生提供對檔案中文字分列進行處理,所以如果一個檔案中的每行都被特定的分隔符(常見的是空格)隔開,我們可以將這個檔案看成是由很多列的文字組成,這樣的檔案最適合用awk進行處理,其實awk在工作中很多時候被用來處理log檔案,進行一些統計工作等。

awk命令的一般組成

awk最常用的工作一般是遍歷一個檔案中的每一行,然後分別對檔案的每一行進行處理,一個完整的awk命令形式如下:

awk  [options]  'BEGIN{ commands } pattern{ commands } END{ commands }'  file

其中options表示awk的可選的命令列選項,其中最常用的恐怕是 -F 它指定將檔案中每一行分隔成列的分隔符號。而緊接著後面的單引號裡面的所有內容是awk的程式指令碼,awk需要對檔案每一行分割後的每一列做處理。file則是awk要處理的檔名稱。讓我們通過demo來體會awk的功能。

awk對每一行進行分割處理

echo '11 22 33 44' | awk '{print $3" "$2" "$1}'
輸出:33 22 11

我們將字串 11 22 33 44 通過管道傳遞給awk命令,相當於awk處理一個檔案,該檔案的內容就是 11 22 33 44 上面的命令中我們並沒有新增 -F 指定分割符號,實際上預設情況下awk使用空格分割每一行,如果需要指定別的字元則使用-F顯示指定。上面的命令是將 11 22 33 44 通過空格(不管列之間有多少個空格都將當作一個空格處理)分割成4列,在awk中有一種通過 $數字 引用的變數,這種變數引用的內容就是當前行中分割的每一列的內容,數字的序號從1開始,例如$1表示第1列的內容,$2表示第二列,以此類推。$0 表示當前整行的內容。print是awk的內建函式,用於列印出變數的值。 而我們在$3 $2 $1 之間新增了用雙引號引起來的空格,如果沒有,則這些變數的值列印出來會連在一起。這裡的awk命令中{}裡面的內容實際上是我們上面完整模式的中間部分,我們省略了上面的BEGIN塊,END塊,並且中間的程式塊我們也省略掉了pattern部分,也就是如果不新增BEGIN或者END說明那麼該程式塊就是上面完整模式中的中間的那個程式塊,該中間的程式塊所執行的操作就是迴圈處理檔案內容的每一行,如果檔案有10行,那麼中間的程式塊要執行10次,每一次處理一行的內容,並且處理完當前行之後,下次迴圈會自動依次處理接下來的行內容。

我們來看看,如果有兩列是什麼效果呢,例如:
    
echo -e '11 22 33 44\naa bb cc dd' | awk '{print $3" "$2" "$1}'
輸出:
33 22 11
cc bb aa

注意這裡echo命令使用了-e選項的目的就是為了保持字串中的\n的格式能夠生效,否則該換行將被忽略。那麼上面的命令是如何執行的呢,我們模擬一下awk的執行過程,首先awk讀取第一行的內容,使用空格分隔該行中的列,並將字串11賦值給$1,22賦值給$2,33賦值給$3,44賦值給$4。然後通過print列印出來。接著讀取第二行的內容,同樣執行上面的操作。

使用parttern部分

我們已經學習了awk最簡單的命令,下面我們再加一點東西進去,在程式塊的前面新增pattern部分,例如:

echo -e '1 2 3 4\n5 6 7 8' | awk '$1>2{print $3" "$2" "$1}'
輸出:7 6 5

該程式與上面的程式幾乎一樣,只不過我們在程式塊前面新增了  $1>2 表示如果當前行的第1列的值大於2則處理當前行,否則不處理。說白了pattern部分是用來從檔案中篩選出需要處理的行進行處理的,如果沒有則迴圈處理檔案中的所有行。pattern部分可以是任何條件表示式的判斷結果,例如>,<,==,>=,<=,!= 同時還可以使用+,-,*,/運算與條件表示式相結合的複合表示式,邏輯 &&,||,! 同樣也可以使用進來。另外pattern部分還可以使用 /正則/ 選擇需要處理的行。

awk的BEGIN語句塊

BEGIN語句塊是在匹配檔案第一行之前執行的語句塊。由於是匹配第一行之前執行,實際上在BEGIN語句塊中 $n 是不可用的。一般情況下可以在BEGIN語句塊中做一些變數(awk中可以自定義變數,直接為一個變數賦值就定義了一個變數,awk中沒有專門定義變數的關鍵字)初始化的工作,以及一些只需要在開始僅列印一次的輸出資訊(例如輸出表的表頭)。例如:

echo -e '1 2 3 4\n5 6 7 8' | awk 'BEGIN{print "c1 c2 c3";print ""}{print $3"  "$2"  "$1}'  
輸出為:
c1 c2 c3

3  2  1
7  6  5

注意一個語句塊(花括號包圍)中可以有多條語句,使用分號隔開,這與C語言一樣。如果需要單獨列印空行,需要使用 print "" 我們上面就實現了輸出表頭的效果。

awk的END語句塊

END語句塊是在awk迴圈執行完所有行的處理之後,才執行的,與BEGIN一樣,END語句塊也只執行一次,我們看看完整的例子。

echo -e '1\n2\n3' | awk 'BEGIN{print "begin"}{print $1}END{print "end"}'
輸出:
begin
1
2
3
end

awk定義變數對列求和

test.txt 的內容如下:
11 22 33
23 45 34
22 32 43

awk 'BEGIN{sum=0}{sum+=$1}END{print sum}' test.txt
輸出結果:56

首先在BEGIN語句塊中為變數sum賦值0,然後在迴圈語句塊中將每一行的第1列加到sum中,當檔案的所有行全部迴圈處理完成之後,列印出sum變數的值。當然這個例子中BEGIN語句塊是可以省略的,我們可以直接在迴圈語句塊中使用sum變數,此時sum第一次使用,該變數會自動被建立,預設的初始值是0。

awk中的判斷語句

awk的所有語句塊中都可以使用判斷語句,其判斷語句語法與C語言一樣。

//test.txt內容如下
1 2 3
4 5 6
7 8 9
10 11 12

awk '{if($1%2==0)print $1" "$2" "$3}' test.txt
輸出:
4 5 6
10 11 12

awk中的迴圈

//while迴圈
awk 'BEGIN{count=0;while(count<5){print count;count ++;}}'
輸出:
0
1
2
3
4
可以看出awk的一個語句塊中可以有比較複雜的複合語句,其使用與C語言幾乎差不多,多條語句之間用分號隔開,複合語句塊用花括號括起來做分隔。

//do..while迴圈
awk 'BEGIN{count=0;do{print count;count++}while(count<5)}'
輸出:
0
1
2
3
4

//for 迴圈
awk 'BEGIN{for(count=0;count<5;count++)print count}'
輸出:與上面一樣

可以看到這幾種迴圈的形式與C語言是一樣的,對於我們理解沒有任何障礙。awk中也使用break退出迴圈,使用continue跳過本次迴圈,其含義與C語言一樣。

使用陣列分組求和,for..in迴圈

awk中的陣列基本上可以看作是字典,看下面的例子:
//test.txt的檔案內容
zhangsan 2 3
lisi 5 6
zhangsan 8 9
lisi 11 12
wangwu 33 11

將所有第一列相同的分成一個組,並將該組中的第二列求和。
awk '{sum[$1]+=$2}END{for(k in sum)print k" "sum[k]}' test.txt
輸出:
zhangsan 10
lisi 16
wangwu 33
這個例子裡面使用了for..in迴圈來遍歷陣列的key,同時通過key來得到陣列的值。對於key不是數字的陣列,是不能通過普通的for迴圈來以數字索引訪問陣列元素的。我們可以通過length()函式來獲得陣列的元素個數,例如length(array)

awk中使用shell變數值

有的時候我們在shell中計算出來的變數值需要被awk命令使用,我們當然不能在awk中直接使用 $VAR,因為美元符號在awk中本來就是特殊符號,在awk中可以使用 $n 引用當前行的第n列的值,所以直接這麼使用是不行的,awk提供了一個選項 -v 來指定變數,在awk中有兩種變數,一種是 $n 形式的變數,這個是在迴圈檔案的行的時候,用來引用當前行的第n列的值,還有一種變數,不用定義可以直接使用,不需要用美元符號來引用。下面看看shell中的變數值如何在awk中使用:

a=22
b=33
awk -v x=$a -v y=$b 'BEGIN{print x" "y}' 

可以看到我們只需要在使用awk的時候通過 -v 指定awk中將會用到的變數即可,而變數值則可以通過引用shell變數得到,也就是說我們只能在awk的options部分引用shell的變數,在awk的語句塊中使用美元符號引用變數會被awk解析成自己的變數而不是shell的變數。

awk中的操作符與優先順序列表

11

awk的內建函式

awk定義了很多內建函式,下面我們根據函式型別列出常用的函式,下面的函式只是一部分,完整的函式列表則需要查閱awk的官方文件。

算術:
atan2(y,x) 返回 y/x 的反正切。
cos(x) 返回 x 的餘弦;x 是弧度。
sin(x) 返回 x 的正弦;x 是弧度。
exp(x) 返回 x 冪函式。
log(x) 返回 x 的自然對數。
sqrt(x) 返回 x 平方根。
int(x) 返回 x 的截斷至整數的值。
rand() 返回任意數字 n,其中 0 <= n < 1。
srand([expr]) 將 rand 函式的種子值設定為 Expr 引數的值,或如果省略 Expr 引數則使用某天的時間。返回先前的種子值。

字串:
gsub(reg,str1,str2) 使用str1替換所有str2中符合正規表示式reg的子串
sub(reg,str1,str2) 含義與gsub相同,只不過gsub是替換所有匹配,sub只替換第一個匹配
index(str,substr) 返回substr在str中第一次出現的索引,注意索引從1開始計算,如果沒有則返回0
length(str) 返回str字串的長度,length函式還可以返回陣列元素的個數
blength(str) 返回字串的位元組數
match(str,reg) 與index函式一樣,只不過reg使用正規表示式,例如match("hello",/lo/)
split(str,array,reg)將str分隔成陣列儲存到array中,分隔使用正則reg,或者字串都可以,返回陣列長度
tolower(str) 轉換為小寫
toupper(str) 轉換為大寫
substr(str,start,length) 擷取字串,從start索引開始的length個字元,如不指定length則擷取到末尾,索引從1開始

其他:
system(command) 執行系統命令,返回退出碼
mktime( YYYY MM dd HH MM ss[ DST]) 生成時間格式
strftime(format,timestamp) 格式化時間輸出,將時間戳轉換為時間字串
systime() 得到時間戳,返回從1970年1月1日開始到當前時間(不計閏年)的整秒數

awk的內建變數

awk中同樣定義了很多內建變數,我們可以直接像使用普通變數一樣使用他們,由於awk的版本眾多,有些內建變數並不是得到所有awk版本的支援。

說明:[A][N][P][G]表示支援該變數的工具,[A]=awk、[N]=nawk、[P]=POSIXawk、[G]=gawk
$n 當前記錄的第n個欄位,比如n為1表示第一個欄位,n為2表示第二個欄位。
$0 這個變數包含執行過程中當前行的文字內容。
[N] ARGC 命令列引數的數目。
[G] ARGIND 命令列中當前檔案的位置(從0開始算)。
[N] ARGV 包含命令列引數的陣列。
[G] CONVFMT 數字轉換格式(預設值為%.6g)。
[P] ENVIRON 環境變數關聯陣列。
[N] ERRNO 最後一個系統錯誤的描述。
[G] FIELDWIDTHS 欄位寬度列表(用空格鍵分隔)。
[A] FILENAME 當前輸入檔案的名。
[P] FNR 同NR,但相對於當前檔案。
[A] FS 欄位分隔符(預設是任何空格)。
[G] IGNORECASE 如果為真,則進行忽略大小寫的匹配。
[A] NF 表示欄位數,在執行過程中對應於當前的欄位數。
[A] NR 表示記錄數,在執行過程中對應於當前的行號。
[A] OFMT 數字的輸出格式(預設值是%.6g)。
[A] OFS 輸出欄位分隔符(預設值是一個空格)。
[A] ORS 輸出記錄分隔符(預設值是一個換行符)。
[A] RS 記錄分隔符(預設是一個換行符)。
[N] RSTART 由match函式所匹配的字串的第一個位置。
[N] RLENGTH 由match函式所匹配的字串的長度。
[N] SUBSEP 陣列下標分隔符(預設值是34)。

awk官方文件:
https://www.gnu.org/software/gawk/manual/gawk.html

 

您如果覺得寫的還不錯,給個讚唄

 

相關文章