回到: "Linux系列文章" "Shell系列文章" "Awk系列文章" getline用法詳解 除了可以從標準輸入或非選項型參數所指定的文件中讀取數據,還可以使用getline從其它各種渠道獲取需要處理的數據,它的用法有很多種。 getline的返回值: 如果可以讀取到數據,返回1 如果遇到了E ...
回到:
getline用法詳解
除了可以從標準輸入或非選項型參數所指定的文件中讀取數據,還可以使用getline從其它各種渠道獲取需要處理的數據,它的用法有很多種。
getline的返回值:
- 如果可以讀取到數據,返回1
- 如果遇到了EOF,返回0
- 如果遇到了錯誤,返回負數。如-1表示文件無法打開,-2表示IO操作需要重試(retry)。在遇到錯誤的同時,還會設置
ERRNO
變數來描述錯誤
為了健壯性,getline時強烈建議進行判斷。例如:
上面的getline的括弧儘量加上,因為getline < 0
表示的是輸入重定向,而不是和數值0進行小於號的比較。
無參數的getline
getline無參數時,表示從當前正在處理的文件中立即讀取下一條記錄保存到$0
中,併進行欄位分割,然後繼續執行後續代碼邏輯。
此時的getline會設置NF、RT、NR、FNR、$0和$N。
next也可以讀取下一行。
getline:讀取下一行之後,繼續執行getline後面的代碼
next:讀取下一行,立即回頭awk迴圈的頭部,不會再執行next後面的代碼
它們之間的區別用偽代碼描述,類似於:
# next
exec 9<> filename
while read -u 9 line;do
...code...
continue # next
...code... # 這部分代碼在本輪迴圈當中不再執行
done
# getline
while read -u 9 line;do
...code...
read -u 9 line # getline
...code...
done
例如,匹配到某行之後,再讀一行就退出:
awk '/^1/{print;getline;print;exit}' a.txt
為了更健壯,應當對getline的返回值進行判斷。
awk '/^1/{print;if((getline)<=0){exit};print}' a.txt
一個參數的getline
沒有參數的getline是讀取下一條記錄之後將記錄保存到$0
中,並對該記錄進行欄位的分割。
一個參數的getline是將讀取的記錄保存到指定的變數當中,並且不會對其進行分割。
getline var
此時的getline只會設置RT、NR、FNR變數和指定的變數var。因此$0和$N以及NF保持不變。
awk '
/^1/{
if((getline var)<=0){exit}
print var
print $0"--"$2
}' a.txt
從指定文件中讀取數據
filename需使用雙引號包圍表示文件名字元串,否則會當作變數解析getline < "c.txt"
。此外,如果路徑是使用變數構建的,則應該使用括弧包圍路徑部分。例如getline < dir "/" filename
中使用了兩個變數構建路徑,這會產生歧義,應當寫成getline <(dir "/" filename)
。
註意,每次從filename讀取之後都會做好位置偏移標記,下次再從該文件讀取時將根據這個位置標記繼續向後讀取。
例如,每次行首以1開頭時就讀取c.txt文件的所有行。
awk '
/^1/{
print;
while((getline < "c.txt")>0){print};
close("c.txt")
}' a.txt
上面的close("c.txt")
表示在while(getline)
讀取完文件之後關掉,以便後面再次讀取,如果不關掉,則文件偏移指針將一直在文件結尾處,使得下次讀取時直接遇到EOF。
從Shell命令輸出結果中讀取數據
cmd | getline
:從Shell命令cmd的輸出結果中讀取一條記錄保存到$0
中- 會進行欄位劃分,設置變數
$0 NF $N RT
,不會修改變數NR FNR
- 會進行欄位劃分,設置變數
cmd | getline var
:從Shell命令cmd的輸出結果中讀取數據保存到var中- 除了var和RT,其它變數都不會設置
如果要再次執行cmd並讀取其輸出數據,則需要close關閉該命令。例如close("seq 1 5")
,參見下麵的示例。
例如:每次遇到以1開頭的行都輸出seq命令產生的1 2 3 4 5
。
awk '/^1/{print;while(("seq 1 5"|getline)>0){print};close("seq 1 5")}' a.txt
再例如,調用Shell的date命令生成時間,然後保存到awk變數cur_date中:
awk '
/^1/{
print
"date +\"%F %T\""|getline cur_date
print cur_date
close("date +\"%F %T\"")
}' a.txt
可以將cmd保存成一個字元串變數。
awk '
BEGIN{get_date="date +\"%F %T\""}
/^1/{
print
get_date | getline cur_date
print cur_date
close(get_date)
}' a.txt
更為複雜一點的,cmd中可以包含Shell的其它特殊字元,例如管道、重定向符號等:
awk '
/^1/{
print
if(("seq 1 5 | xargs -i echo x{}y 2>/dev/null"|getline) > 0){
print
}
close("seq 1 5 | xargs -i echo x{}y 2>/dev/null")
}' a.txt
awk中的coprocess
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產生的數據。
例如,不想使用awk的substr()來取子串,而是使用sed命令來替換。
awk '
BEGIN{
CMD="sed -nr \"s/.*@(.*)$/\\1/p\"";
}
NR>1{
print $5;
print $5 |& CMD;
close(CMD,"to");
CMD |& getline email_domain;
close(CMD);
print email_domain;
}' a.txt
對於awk_print |& cmd; cmd |& getline
的使用,須註意的是:
awk_print |& cmd
會直接將數據寫進管道,cmd可以從中獲取數據
- 強烈建議在awk_print寫完數據之後加上
close(cmd,"to")
,這樣表示向管道中寫入一個EOF標記,避免某些要求讀完所有數據再執行的cmd命令被永久阻塞
- 如果cmd是按塊緩衝的,則getline可能會陷入阻塞。這時可將cmd部分改寫成
stdbuf -oL cmd
以強制其按行緩衝輸出數據CMD="stdbuf -oL cmdline";awk_print |& CMD;close(CMD,"to");CMD |& getline
對於那些要求讀完所有數據再執行的命令,例如sort命令,它們有可能需要等待數據已經完成後(遇到EOF標記)才開始執行任務,對於這些命令,可以多次向coprocess中寫入數據,最後close(CMD,"to")
讓coprocess運行起來。
例如,對age欄位(即$4
)使用sort命令按數值大小進行排序:
awk '
BEGIN{
CMD="sort -k4n";
}
# 將所有行都寫進管道
NR>1{
print $0 |& CMD;
}
END{
close(CMD,"to"); # 關閉管道通知sort開始排序
while((CMD |& getline)>0){
print;
}
close(CMD);
} ' a.txt
close()
close(filename)
close(cmd,[from | to]) # to參數只用於coprocess的第一個階段
如果close()關閉的對象不存在,awk不會報錯,僅僅只是讓其返回一個負數返回值。
close()有兩個基本作用:
awk中任何文件都只會在第一次使用時打開,之後都不會再重新打開。只有關閉之後,再使用才會重新打開。
例如一個需求是只要在a.txt中匹配到1開頭的行就輸出另一個文件x.log的所有內容,那麼在第一次輸出x.log文件內容之後,文件偏移指針將在x.log文件的結尾處,如果不關閉該文件,則後續所有讀取x.log的文件操作都從結尾處繼續讀取,但是顯然總是得到EOF異常,所以getline返回值為0,而且也讀取不到任何數據。所以,必須關閉它才能在下次匹配成功時再次從頭讀取該文件。
awk '
/^1/{
print;
while((getline var <"x.log")>0){
print var
}
close("x.log")
}' a.txt
在處理Coprocess的時候,close()可以指定第二個參數"from"或"to",它們都針對於coproc而言,from時表示關閉coproc |& getline
的管道,使用to時,表示關閉print something |& coproc
的管道。
awk '
BEGIN{
CMD="sed -nr \"s/.*@(.*)$/\\1/p\"";
}
NR>1{
print $5;
print $5 |& CMD;
close(CMD,"to"); # 本次close()是必須的
CMD |& getline email_domain;
close(CMD);
print email_domain;
}' a.txt
上面的第一個close是必須的,否則sed會一直阻塞。因為sed一直認為還有數據可讀,只有關閉管道發送一個EOF,sed才會開始處理。