[AWK] NR==FNR 的意思

簡介

很多時候,我們都會看到 awk 指令中含有 NR==FNR 這個短句,其實這昰一個 awk 中的常用語法。

NRFNR 是兩個 awk 中的內置變量,NR 即是 total Number of input Records seen so far(到目前為止看到的總輸入記錄數),而 FNR 則是 Number of input Record in the current input File(當前文件中,到目前為止看到的輸入記錄數),輸入記錄通常是指一行,所以輸入記錄數通常是 line number(行號碼),當我們只處理一個檔案時,我們通常不會理會 NR,因為當我們處理第一個檔案時, NR 必定會等於 FNR

內置變量意思
NRThe total Number of input Records seen so far.
到目前為止看到的總輸入記錄數
FNRThe Number of input Record in the current input File seen so far.
當前文件中,到目前為止看到的輸入記錄數

查看 ‘NR’ 和 ‘FNR’

在以下例子中,我們可以直接看到 NRFNR,好讓我們更清楚它們是甚麼。

  1. 首先,我們要創建兩個檔案。
noob@learnfromnoobs:~$ cat file1.txt 
file1-line1
file1-line2
file1-line3
noob@learnfromnoobs:~$ cat file2.txt 
file2-line1
file2-line2
file2-line3
  1. 我們可以利用以下指令顯示出我們剛建立的檔案的檔案名、NRFNR 以及當前的行。
noob@learnfromnoobs:~$ awk '{print FILENAME, NR, FNR, $0}' file1.txt file2.txt 
file1.txt 1 1 file1-line1
file1.txt 2 2 file1-line2
file1.txt 3 3 file1-line3
file2.txt 4 1 file2-line1
file2.txt 5 2 file2-line2
file2.txt 6 3 file2-line3

由以上輸出中,我們可以看到 NRFNR 兩者都會隨著 awk 處理新一行而增加,唯一不同的是當 awk 處理另一個檔案時,FNR 會重置為 1 。

因此,NR==FNR 這個短句其實就在檢查我們是否正在處理引數(argument)中的第一個檔案。


常見實用例子

當我們需要處理兩個或以上的檔案時,NR==FNR 就會變很非常有用,以下是兩個常見的實用例子。(注意,其實還有其他方法可以實現跟以下例子相同的目標,我們只是討論在這些情況下可以如何利用 awk,你應該根據自己的情況選擇合適的工具。)

在進入討論前,讓我們先修改一下 file1.txtfile2.txt 旳內容。

noob@learnfromnoobs:~$ cat file1.txt 
John
Tom
Tony
Alex
Michael
Kalvin
noob@learnfromnoobs:~$ cat file2.txt 
Tony
Alex
Chris

1. 顯示兩個文件共有的行

我們可以利用 awk 顯示出兩個文件共有的行。

noob@learnfromnoobs:~$ awk 'NR==FNR { array[$0]; next } $0 in array' file1.txt file2.txt
Tony
Alex

這指令把第一個檔案中的所有行儲存在一個陣列(array)中,如果 file2.txt 的行跟陣列中的容內重覆,我們就會直接把它顯示出來(這是 awk 的默認操作),如果你仍然感到難以理解,我們可以把指令分拆成幾個部分:

  1. NR==FNR { array[$0]; next } 的意思是,如果當前的行是來自第一個檔案(file1.txt)的,就把它儲存在一個陣列中,否則,則直接跳過。
  2. $0 in array 會查看 file2.txt 的行是否存在於我們剛建立的陣列(file1.txt 的行)中,如果是這樣的話,我們就會直接把該行顯示出來。

2. 顯示文件1 中的所有行,但不包括那些同時存在於文件2、文件3 的行

這篇文章中,我們已經討論了如何利用 awk 在兩個文件中,只顯示文件1 特有的行,現在,讓我們擴展已有的知識,使我們可以過濾更多文件中的行。

讓我們創建 file3.txt 並執行我們的指令。

noob@learnfromnoobs:~$ cat file3.txt
Tony
John
noob@learnfromnoobs:~$ awk 'NR==FNR { array[$0]; next } { delete array[$0] } END { for (key in array) { print key } }' file1.txt file2.txt file3.txt 
Tom
Michael
Kalvin

我們可以把指令分拆成幾個部分:

  1. NR==FNR { array[$0]; next } 的意思是,如果當前的行是來自第一個檔案(file1.txt)的,就把它儲存在一個陣列中,否則,則直接跳過。
  2. 當我們處理 file1.txt 以外文件中的行時,awk 就會執行 { delete array[$0] } ,若該行已存在於陣列當中,awk 就會將該行從陣列中刪除。
  3. 當 awk 已經完成閱讀所有的輸入記錄, awk 就會執行 END { for (key in array) { print key } } 把所有仍然在陣列中的 key (鍵)顯示出來,亦即文件1 特有的行。

我們也可以重新命名我們的檔案,令我們的指令更容易理解。

noob@learnfromnoobs:~$ cp file1.txt all.txt
noob@learnfromnoobs:~$ cp file2.txt remove1.txt
noob@learnfromnoobs:~$ cp file3.txt remove2.txt
noob@learnfromnoobs:~$ awk 'NR==FNR { array[$0]; next } { delete array[$0] } END{for (key in array) { print key } }' all.txt remove*.txt
Tom
Michael
Kalvin

把檔案重新命名後,我們更清楚知道這指令會顯示 all.txt 中的所有行,但不包括那些同時存在於 remove*.txt 的行。


總結

在這篇文章中,我們討論了:

  1. NRFNR 是甚麼
  2. NR==FNR 的意思是甚麼
  3. NR==FNR 的常見實用例子

我希望你喜歡這篇文章,並從中得到了新知識。

記得要不斷學習,have fun!

Leave a Reply

Your email address will not be published. Required fields are marked *