精通awk系列(12):awk getline用法詳解


回到:linux


getline用法詳解

除了能夠從標準輸入或非選項型參數所指定的文件中讀取數據,還可使用getline從其它各類渠道獲取須要處理的數據,它的用法有不少種。shell

getline的返回值:dom

  • 若是能夠讀取到數據,返回1
  • 若是遇到了EOF,返回0
  • 若是遇到了錯誤,返回負數。如-1表示文件沒法打開,-2表示IO操做須要重試(retry)。在遇到錯誤的同時,還會設置ERRNO變量來描述錯誤

爲了健壯性,getline時強烈建議進行判斷。例如:指針

上面的getline的括號儘可能加上,由於getline < 0表示的是輸入重定向,而不是和數值0進行小於號的比較。code

無參數的getline

getline無參數時,表示從當前正在處理的文件中當即讀取下一條記錄保存到$0中,並進行字段分割,而後繼續執行後續代碼邏輯對象

此時的getline會設置NF、RT、NR、FNR、$0和$N。blog

next也能夠讀取下一行。排序

  • getline:讀取下一行以後,繼續執行getline後面的代碼字符串

  • next:讀取下一行,當即回頭awk循環的頭部,不會再執行next後面的代碼get

它們之間的區別用僞代碼描述,相似於:

# 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纔會開始處理。

相關文章
相關標籤/搜索