前言
關于函式的基本概念,在學習bash的函式的時候已經大致講解過了,加上本人大學時期也學習過C語言(雖然都忘記了),因此這里就不再對函式做過多冗余的介紹了,
awk大致將函式分成了自定義函式和內置函式,不過其本質上沒有區別,自己寫的函式就叫做自定義函式,而官方寫好的嵌入在awk本身的我們直接拿來用的函式就叫做內置函式,關于內置函式的介紹請見這里,
本博文學習的內容是了解函式,學會如何創建與使用它們,也就是學會自定義函式,雖然內置函式可以拿來直接使用,不需要了解其內部實作,但是學習自定義函式就是在學習函式的基礎,
函式的定義
function funcName([arg, ...]){ ... function body ... } func funcName([arg, ...]){ ... function body ... }
awk的函式可以定義在代碼的任意位置,沒有先后順序之分,例如定義在下面的下劃線位置:
awk '_BEGIN{}_main{}_main{}_END{}_' ...
這是因為awk在執行BEGIN代碼塊執行,awk就會將代碼編碼成內部格式,而在這一步中就會去識別代碼中的函式定義了,這在awk的作業流程中就有講述到了,
注意:別把函式定義在main代碼塊中即可,畢竟不是每次內部回圈一次就要定義一次函式,
因此可以在任意位置呼叫任意位置定義好的函式,
# awk 'BEGIN{f()}function f(){print "hello world"}' hello world
函式的回傳值
函式使用return陳述句來回傳回傳值,一旦遇到return陳述句,在return陳述句后面的函式內部陳述句就不會執行,
# awk 'func re(){return 100;print "hello world"} BEGIN{a=re();print a;print re()}' 100 100
注意:回傳值也可以是字串,
# awk 'func re(){return "abc";print "hello world"} BEGIN{a=re();print a;print re()}' abc abc
如果函式沒有return陳述句或者return陳述句沒有具體的回傳值,則回傳空字串,
# awk 'func f(){} BEGIN{print "---"f()"---"}' ------ # awk 'func f(){return} BEGIN{print "---"f()"---"}' ------ # awk 'func f(){return 100} BEGIN{print "---"f()"---"}' ---100---
函式的引數
函式可以不帶引數,不過大多數時候是帶引數的,這樣使得函式在呼叫時更加靈活,
# cat funcArg.awk func f(a,b){ print a print b return a+b } BEGIN{ x=10;y=20 res=f(x,y) # 呼叫函式時列印了x和y的值,并將回傳值賦值給res print res print f(x,y) # 在列印函式回傳值的同時由于呼叫了函式,而函式本身包含了列印x和y的值,所以先列印x和y的值,再列印回傳值, } # awk -f funcArg.awk 10 20 30 10 20 30
使用函式來重復連接字串,函式接受2個引數,其一是想要串聯的字串,其二是想要串聯的次數,
# cat funcCatStr.awk func cat(str,count){ for(i=1;i<=count;i++){ newStr=newStr""str } return newStr } BEGIN{print cat("-",5)} # awk -f funcCatStr.awk -----
在編程語言中,函式的引數有兩種,形式引數和實際引數,
在函式定義時用來定義函式可接受的引數的引數稱為形式引數,簡稱形參,在函式呼叫時實際向函式傳遞的引數成為實際引數,簡稱實參,
f(x,y){} # 定義形式引數x和y, a=10;b=5 f(a,b) # 傳遞實際引數a和b,
在計算機英語中,我們使用parameter來表示形參,使用argument來表示實參,如果某種情況下沒有實參和形參之分的話,那么parameter和argument都可以用來表示引數之意,
在函式呼叫時,實參和形參的個數可以不一致,但是,如果實引數量多于形引數量,那么awk會回傳警告資訊,
# awk 'func f(a,b){} BEGIN{f(1,2,3)}' awk: cmd. line:1: warning: function `f' called with more arguments than declared
引數型別沖突
實參和形參的變數型別需要一致,否則會報錯,
# 實參是數值變數,而形參是陣列, # awk 'func f(a){a["name"]="alongdidi"} BEGIN{x=10;f(x)}' awk: cmd. line:1: fatal: attempt to use scalar parameter `a' as an array
# 首先進行函式的呼叫,實參x被識別為形參中的陣列,因此在BEGIN的后續想要將實參x當作數值變數來使用會報錯, # awk 'func f(a){a["name"]="alongdidi"} BEGIN{f(x);x=10}' awk: cmd. line:1: fatal: attempt to use array `x' in a scalar context
引數的傳遞方式
首先我們回顧一下bash中的變數的概念,變數的名稱,起始是指向某個記憶體空間的地址,我們參考變數,就是參考對應地址的記憶體空間中的資料,
在函式引數傳遞時,有兩種傳參方式:
- 先找到地址對應的記憶體空間中的資料,將該資料復制一份放入新的記憶體空間中,將該值作為引數傳遞就是將形參指向該新記憶體空間,這種方式叫做按值傳遞,會產生新的記憶體空間,
- 直接將實參所對應的記憶體空間的地址傳遞給形參,使得實參和形參同時指向了同一個記憶體空間,這種指向應該是基于指標的概念,這種方式叫做按參考傳遞,
由此可見,按值傳遞使用不同的記憶體空間,因此即便實參和形參的變數名稱相同,其指向的記憶體地址也回是不同的,
因此按值傳遞的函式內部的變數修改不會影響到函式外部,反之亦然,
在awk中,如果傳遞的引數的變數型別是數值或者字串,則是按值傳遞,
# awk 'func f(a){a=10} BEGIN{a=5;print a;f(a);print a}' 5 5 # awk 'func f(a){a="alonggege"} BEGIN{a="alongdidi";print a;f(a);print a}' alongdidi alongdidi
按參考傳遞由于使用了相同的記憶體空間,并且傳參時傳遞的是記憶體的地址,因此按參考傳遞的函式內部的變數修改會影響到函式外部,反之亦然,如果傳遞的引數的是陣列,則是按參考傳遞,
# awk 'func f(a){a["name"]="alonggege"} BEGIN{a["name"]="alongdidi";print a["name"];f(a);print a["name"]}' alongdidi alonggege
函式中變數的作用域
我們先來回顧funcCatStr.awk代碼,
# cat funcCatStr.awk func cat(str,count){ for(i=1;i<=count;i++){ newStr=newStr""str } return newStr } BEGIN{print cat("-",5)} # awk -f funcCatStr.awk -----
我們新增一部分代碼,
func cat(str,count){ for(i=1;i<=count;i++){ newStr=newStr""str } return newStr } BEGIN{print cat("-",5);print cat("+",5)} # 紅色字體為新增部分,
此時我們可能會理所應當地認為輸出的結果應該是:
----- +++++
但是:
# awk -f funcCatStr.awk ----- -----+++++
造成這種結果的原因和變數的作用域有關,在awk中,函式內部定義的變數屬于全域變數,因此在函式內部的變數newStr是一個全域變數,經過第一次函式呼叫以后,它的值是“-----”,函式回傳以后,由于它是全域變數,因此該變數不會被釋放,在第二次函式呼叫時會在“----”的基礎之上進行操作,
我們可以在第一次函式呼叫后print看看,
# cat funcCatStr.awk ... ... BEGIN{print cat("-",5);print newStr;print cat("+",5)} # awk -f funcCatStr.awk ----- ----- -----+++++
在awk中,沒有顯式定義區域變數的關鍵詞,如果希望將某個變數具有區域變數的特性的話,可以將變數置于函式定義時的引數的位置,即形參的位置,
# cat funcCatStr.awk func cat(str,count ,newStr){ for(i=1;i<=count;i++){ newStr=newStr""str } return newStr } BEGIN{print cat("-",5);print newStr;print cat("+",5)} # awk -f funcCatStr.awk ----- +++++
由于newStr并不是真實的引數,放在形參的位置僅僅是因為我們希望使其具備區域變數的特性罷了,因此真實的形參現在前面,作為區域變數的假形參寫在后面,并使用多個空格分隔,
如果我們看到一個函式的定義是這樣的,我們就應該明白該函式僅支持2個引數,不要向c和d傳遞引數,因為它們僅僅作為區域變數存在,
func f(a,b ,c,d){...}
到這里我們應該也會明白所有的形參,無論是作為真實的形參還是區域變數,它們都具備有區域變數的特性,
cat funcCatStr.awk func cat(str,count ,newStr){ for(i=1;i<=count;i++){ newStr=newStr""str } return newStr } BEGIN{print cat("-",5);print cat("+",5);print str;print count} [root@c7-server awk]# awk -f funcCatStr.awk ----- +++++ # 列印形參str,結果為空字串, # 列印形參count,結果為空字串,
實戰
寫一個一次性讀取檔案的所有資料的函式
# cat funcReadFile.awk func readFile(file ,RSBak,data){ RSBak=RS RS="^$" if((getline data<file)<=0){ print "Reading file error!" exit 1 } close("c.txt") RS=RSBak return data } /^1/{ print $0 content=readFile("c.txt") print content } # awk -f funcReadFile.awk a.txt 1 Bob male 28 [email protected] 18023394012 abc def ABC DEF 10 Bruce female 27 bcbd@139.com 13942943905 abc def ABC DEF
寫一個可以重讀檔案的函式
在處理某個檔案的時候,如果遇到某些條件(比如讀取到第3行),我們就要求重新讀取一遍該檔案,
PS:個人覺得這個示例怪怪的,需求都怪怪的,
# cat funcRewind.awk func rewind(){ for(i=ARGC;i>ARGIND;i--){ ARGV[i]=ARGV[i-1] } ARGC++ nextfile } NR==3{ # 這里如果改成FNR的話,會陷入死回圈, print rewind() } { print } # awk -f funcRewind.awk a.txt ID name gender age email phone 1 Bob male 28 [email protected] 18023394012 2 Alice female 24 [email protected] 18084925203 ID name gender age email phone 1 Bob male 28 [email protected] 18023394012 2 Alice female 24 [email protected] 18084925203 3 Tony male 21 aaa@163.com 17048792503 4 Kevin male 21 bbb@189.com 17023929033 5 Alex male 18 [email protected] 18185904230 6 Andy female 22 ddd@139.com 18923902352 7 Jerry female 25 exdsa@189.com 18785234906 8 Peter male 20 [email protected] 17729348758 9 Steven female 23 [email protected] 15947893212 10 Bruce female 27 bcbd@139.com 13942943905
格式化陣列的輸出
當我們有一個陣列的時候,我們是無法直接使用print陳述句將其全部輸出的,
# awk 'BEGIN{arr["name"]="alongdidi";arr["age"]=29;arr["gender"]="male";print arr}' awk: cmd. line:1: fatal: attempt to use array `arr' in a scalar context
現在我們撰寫一個自定義的函式,來輸出這個陣列的資料,輸出的格式如下,
{ arr["name"]="alongdidi" arr["age"]=29 arr["gender"]="male" }
# cat funcA2S.awk func a2s(arr ,str){ for(i in arr){ str=str""(sprintf("\tarr[\"%s\"]=%s\n",i,arr[i])) } return "{\n"str"}" } BEGIN{ arr["name"]="alongdidi" arr["age"]=29 arr["gender"]="male" print a2s(arr) } # awk -f funcA2S.awk { arr["age"]=29 arr["name"]=alongdidi arr["gender"]=male }
識別檔案名帶等于號的檔案
一般來說,如果出現了這種CLI,那么awk會將a=b識別變數賦值,倘若“a=b”真的是一個檔案的話,我們只需要在為其帶上相對路徑即可,
awk -f xxx.awk a=b a.txt c.txt awk -f xxx.awk ./a=b a.txt c.txt
# awk '{print}' a=b ^C # awk '{print}' ./a=b aaa bbb ccc
思路:
- 所有的CLI的引數保存在ARGV中,因此從中尋找檔案名形似變數賦值的檔案,
- 變數賦值的規律:
- 等于號,
- 變數名包含數字、字母和下劃線并且只能以字母或者下劃線打頭,
- 找到后替換ARGV中對應的引數,
- 要提供一個開關選項,畢竟不是每次都會遇到檔案名形如變數賦值的形式,
# cat funcRecogAssignFile.awk func recog(argv,argc ,i){ for(i=1;i<argc;i++){ if(argv[i]~/^[[:alpha:]_][[:alnum:]_]*=.*/){ argv[i]="./"argv[i] } } } BEGIN{ if(open){ recog(ARGV,ARGC) } } {print} # awk -f funcRecogAssignFile.awk a=b ^C # awk -v open=1 -f funcRecogAssignFile.awk a=b aaa bbb ccc
識別時間
在學習了時間類內置函式以后,我們可以基于已有的時間類內置函式來識別一些在日志檔案中常見的時間格式,將其轉換為epoch值(即時間戳),這樣有助于我們的后續深入的運維作業,
在運維作業中,一般遇到的日志檔案中的時間格式一般都形如以下兩種:
2019-11-11T03:42:42+08:00 Sat 26. Jan 15:36:24 CET 2013
這種日期時間格式無法直接拿來比較,必須先轉換成epoch值,但是這種格式也無法直接被mktime()轉換成epoch值,需要先做處理,
mktime("YYYY MM DD HH MM SS [DST]"[,utc-flag])
因此我們可以自定義兩個函式來將上面的兩種格式轉換為epoch值,
str1ToTime()
2019-11-11T03:42:42+08:00
思路:
- 將字串轉換成mktime()可識別的格式“Y M D H m S”,注意:需要使用sprintf()來構建這種格式,
- 然后使用mktime()輸出時間戳,
# cat str1ToTime.awk func str1ToTime(str ,newStr,Y,M,D,H,m,S,arr){ newStr=gensub("[-:T+]+"," ","g",str) # 2019 11 11 03 42 42 08 00 split(newStr,arr) Y=arr[1] M=arr[2] D=arr[3] H=arr[4] m=arr[5] S=arr[6] # print mktime(Y M D H m S) # Do not write like this, otherwise mktime() return -1!!! # Use sprintf() instead!!! return mktime(sprintf("%d %d %d %d %d %d",Y,M,D,H,m,S)) } BEGIN{ print str1ToTime("2019-11-11T03:42:42+08:00") print str1ToTime("2021-11-11T03:42:42+08:00") print (str1ToTime("2019-11-11T03:42:42+08:00") < str1ToTime("2021-11-11T03:42:42+08:00")) } # awk -f str1ToTime.awk 1573414962 1636573362 1
需要注意,日期和時間資訊存入陣列以后不能直接拿來用,必須要使用sprintf()轉換才可以,
mktime(Y M D H m S) # 這樣會使得mktime()接收6個引數,實際上它只能接收2個,其中一個還是可選的, mktime("Y M D H m S") # 這樣寫的話,就無法變數替換了,而是識別了字串字面量,
str2ToTime()
Sat 26. Jan 15:36:24 CET 2013
思路:
- 與str1ToTime()類似,區別在于需要識別月份“Jan”,因此需要事先寫一個映射函式,
# cat str2ToTime.awk func str2ToTime(str ,Y,M,D,H,m,S,arr){ patsplit(str,arr,"[[:alnum:]]+") Y=arr[8] M=monthMap(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)) } func monthMap(mon ,mrr){ mrr["Jan"]=1 mrr["Feb"]=2 mrr["Mar"]=3 mrr["Apr"]=4 mrr["May"]=5 mrr["Jun"]=6 mrr["Jul"]=7 mrr["Aug"]=8 mrr["Sep"]=9 mrr["Oct"]=10 mrr["Nov"]=11 mrr["Dec"]=12 return mrr[mon] } BEGIN{ print str2ToTime("Sat 26. Jan 15:36:24 CET 2013") print mktime("2013 01 26 15 36 24") } # awk -f str2ToTime.awk 1359185784 1359185784
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/255801.html
標籤:Linux
上一篇:每日一課——Linux命令——洗掉檔案與檔案夾——rm命令
下一篇:linux內核版本最新
