Linux檔案處理三劍客之awk

joshliu發表於2023-11-13

背景

awk語言,出自三位創始人姓氏的首字母(Alfred Aho 、Peter Weinberger 和 Brian Kernighan)。在2011年針對發起者Alfred Aho的採訪中,問起為何會創作了awk語言,Alfred的回答是在上世紀80年代初,為了追蹤經費預算或者追蹤學生的成績,同時避免用C或者其它語言長篇的語句來實現,所以創造了awk語言,旨在用少量的程式碼行數就能實現如此的功能。同樣,與後期Larry Wall創作了Perl語言一樣,這些大師們出於某個實際應用,又不想寫一堆程式碼,所以正是在這種程式設計師獨有的“懶惰”與“完美主義”的雙重人格下,極簡而又優雅的語言,就被創造了出來。

awk是一種優良的文字處理工具,它不僅是 Linux 中也是任何環境中現有的最強大的資料處理引擎之一,提供了極其強大的功能:樣式裝入、流控制、數學運算子、程式控制語句等等。而且內建了許多變數和函式,可以便於使用者在用awk處理文字的時候,簡潔而又方便得透過少量程式碼就能獲取所需資訊。


格式

    在awk中,格式會嚴重影響輸出的正確性,通常而言,兩種常見的格式如下:

awk '{pattern + action}'  input_file
awk 'pattern {action}' input_file

    比如說,如果檔案中第一列匹配關鍵字abc,那就列印最後一列:

  •     第一種格式,awk '{if($1 ~ /abc/) print $NF}' input_file

  •     第二種格式,awk '$1 ~ /abc/ {print $NF}' input_file


引數

    通常情況下, awk命令常用引數有-F與-v

    -F 輸入內容的分隔符(field)

    -v 某個可以傳入的引數(var=val)

    比如輸入內容分隔符是豎線(|),如果要列印每一行的列數,可以執行

awk -F '|' '{print NF}' input_file

    如果要透過-v列印出“列數是”的關鍵字,可以執行

awk -F '|' -v 'dp=列數是' '{print dp, NF}' input_file


特殊變數

    細心的讀者在上述用例中,已經發現了NF這樣的關鍵字,其實在awk中,除了NF,還有很多其它類似的特殊變數:

  • $0:代表整行

  • $1:代表分隔後第一列;同理$2代表第二列,以此類推

  • NF:number of fields,代表一行中列的個數,所以$NF表示最後一列

  • NR:number of rows,代表該行的行號

  • FS:field separator,代表輸入檔案的列分隔符

  • OFS:output field separator,代表輸出內容的列分隔符

    所以,如果要列印某個檔案的第2行,透過awk可以如下執行:

awk 'NR == 2 {print}' input_file #透過格式二書寫

    如果要將input_file(分隔符是'|')的第一列替換成test:

awk -F '|' '{$1="test";print}' input_file

    細心的讀者同樣也會發現,輸出內容的分隔符與原始檔案不一致,所以如果要將上述輸出內容依舊保持檔案最開始的分隔符'|':

awk -F '|' '{$1="test";OFS=FS;print}' input_file


內建函式

    為了文書處理更加便捷,awk開發了一系列的內建函式,透過這些函式能極大簡化程式碼的行數,本文選取常用函式進行介紹:

  • length,長度

  • gsub,替換

  • substr,擷取

  • split,分隔

length

    例如,用'|'作為分隔符,將input_file檔案每一行的長度以及第二列的長度列印出來:

awk -F '|' '{print length,length($2)}' input_file

    注:上述length如果代表的是一行長度,會包括分隔符'|'


gsub

    對gsub函式而言,有兩種格式,分別是:

  • gsub("a","b"),將單行中所有的"a"替換為"b"

  • gsub("a","b",sth),將sth中所有的"a"替換為"b"

    比如,如果要將input_file中,所有的"i"字元,替換為"%",可以執行:

awk -F '|' '{gsub("s","%");OFS=FS;print}' input_file

    如果要將第二列的所有的"s"替換為"%",可以執行:

awk -F '|' '{gsub("s","%",$2);OFS=FS;print}' input_file



substr

    與gsub類似,substr也有兩種格式

  • substr(sth,num),將sth從num開始截圖到最後

  • substr(sth,num1,num2),將sth從num1開始往後擷取num2位

    注:上述sth可以是某個字串,也可以是某個變數或者$1/$2這樣某一個列

    例如將以'|'作為分隔符的第一列,從第2位開始擷取到最後:

awk -F '|' '{a=substr($1,2);print a}' input_file

    或者從第2位開始往後擷取2位:

awk -F '|' '{a=substr($1,2,2);print a}' input_file


split

    split主要作用是將某一串字串,根據某個關鍵字來分隔,並將分割完的結果儲存到某個陣列中,格式如下:

  • split(sth,陣列名,"分隔符")

    表示將sth按照“分隔符”切分,並將切分後的結果儲存到陣列“陣列名”中。

    比如將字串"this|is|a|test"按照"|"切分,並列印第二部分內容

echo "this|is|a|test" | awk '{split($0,arr,"|");print arr[1]}'

    注:上述arr為人為定義的一個陣列名稱,並且陣列的下標從1開始


運算與判斷

    在任何一門程式語言中,離不開各式各樣的運算以及判斷,awk支援多種運算,這些運算與C語言提供的基本相同:如+、-、*、/、%等等,同時awk也支援C語言中類似++、--、+=、-=、=+、=-之類的功能,這給熟悉C語言的使用者編寫awk程式帶來了極大的方便。作為對運算功能的一種擴充套件,awk還提供了一系列內建的運算函式(如log、sqr、cos、sin等等)和一些用於對字串進行操作(運算)的函式(如length、substr等等)。這些函式的引用大大的提高了awk的運算功能。

    在判斷上,awk也提供了相當多功能,如常用的==(等於)、!=(不等於)、>(大於)、>=(大於等於)、<=(小於等於)等等,同時作為樣式匹配,還提供了~(匹配於)和!~(不匹配於)判斷。

    作為對測試的一種擴充,awk也支援用邏輯運算子:!(非)、&&;(與)、||(或)和括號()進行多重判斷,這大大增強了awk的功能。


BEGIN/END

    BEGIN和END的作用是給程式賦予初始狀態和在程式結束之後執行一些掃尾的工作。任何在BEGIN之後列出的操作(在{}內)將在awk開始掃描輸入之前執行,而END之後列出的操作將在掃描完全部的輸入之後執行。因此,通常使用BEGIN來顯示變數和預置(初始化)變數,使用END來輸出最終結果。

    我們知道透過ps aux可以查詢系統中程式相關資訊,並且顯示每個程式的CPU以及MEM使用:

    如果想要知道系統中所有程式使用的總CPU與總MEM之和,透過awk可以如下實現:

ps aux | awk 'BEGIN{cpusum=0;memsum=0} {cpusum=cpusum+$3;memsum=memsum+$4} END{print cpusum,memsum}'

流程控制

    awk提供了2種型別的流程控制,如下:

  •     條件判斷

  •     迴圈

    

條件判斷

    在awk中,條件判斷一般為if .. else或者if .. else if .. else兩種形式,使用方式與C語言相同。

    比如判斷系統中每個程式CPU使用是否大於0,並列印對應資訊,命令如下:

ps aux | awk 'NR>1 {if ($3>0) {print "程式"$2"CPU使用大於0"} else {print "程式"$2"CPU使用等於0"}}'

    

迴圈

    awk允許使用while、do..while和for語句來進行迴圈操作,同時使用break、continue語句來控制流程走向,也允許使用exit這樣的語句來退出,使用方式與C語言類似。

    比如,分別透過while、do .. while以及for來列印0-9:

  •     awk 'BEGIN{i=0;while(i<10) {print i;i++}}'

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

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

    如果上述i等於5,則退出迴圈:

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


部分示例

日誌篩選

    在對日誌進行篩選的時候,比如說/var/log/messages這樣的系統日誌,如果我們想選取出11月12日時間在3點10分到3點14分之間的日誌,可執行如下命令:

awk '$1=="Nov" && $2 == 12 && $3 >="03:10" && $3 <="03:14" {print}' /var/log/messages


檔案中某列最大長度

    資料庫從業者一定會碰到將某個資料檔案載入到資料庫這樣的場景,有時候會碰到檔案中某一列長度過長,從而導致無法載入進資料庫(資料庫端報錯通常為char或者varchar定義長度不夠),此時就需要檢視該列最長的字元有多長,從而可以在資料庫對應的表中設定合適的char(n)或者varchar(n)這樣的n值,為了檢視檔案中該列最長的字元,可以執行:

awk 'BEGIN{max=0} {tmp=length($NF);if(tmp>max) {max=tmp}} END{print max}' input_file #本示例中以求最後一列$NF最大長度為例


檔案內容替換

    在檔案處理時,有時候需要將滿足條件的行(比如說行號在10-100,或者第三列匹配abc選取出來的行),對這部分行的第二列內容,將所有的"test"字元替換為"test1"字元,命令如下:

awk '$3 ~ /abc/ {gsub("test","test1",$2);print}' input_file

總結

    awk是Linux上強大的檔案處理工具,其主要作用是對所有行或者部分行,進行列上的各種操作,其中$0代表整行資料,$1代表第一列,$2代表第二列資料。透過-F引數可以指定輸入內容的分隔符(同樣支援多分隔符),並支援NF、NR、FS、OFS等特殊的變數,便於使用者獲取檔案內的各種資訊(列數、行號、輸入分隔符、輸出分隔符)等。同時內建了很多函式,length/gsub/substr/split/sprintf等等,簡化了檔案處理的難度。為了處理檔案時能有預置條件或者結束動作,awk提供了BEGIN與END的功能,同時在條件判斷上支援與C語言相同的if..else用法。有時候為了實現更加複雜的功能,awk也支援迴圈,能讓使用者像C語言一樣進行迭代計算或者遍歷陣列。

    總之,awk本身就是一種語言,可以毫不誇張的說,awk可以實現Linux上絕大部分的文字處理,儘管有時候不最完美的實現方式,不過對檔案處理來說,awk已經足夠了。




來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69985104/viewspace-2994860/,如需轉載,請註明出處,否則將追究法律責任。

相關文章