使用 awk 只寫入重複項

使用 awk 只寫入重複項

使用 awk 刪除重複項非常常見且簡單。但當我們只比較一列時,我只需要列印那些重複的行。我嘗試了這個命令:

awk 'seen[$2]++'

但正如你所看到的,它有缺陷。它會列印重複項,但僅限於它們第二次出現後。我才剛開始習慣 unix 和 bash,所以如果你能向我解釋解決方案那就太好了。

答案1

我可以看到有兩種方法可以做到這一點:

  1. 迭代文件兩次:

    在第一次迭代中,計算每個 $2 出現的次數。
    在第二次迭代中,僅列印計數大於 1 的行

    awk 'NR == FNR {count[$2]++; next} count[$2] > 1' file file
    
  2. 資料的單次迭代:

    你需要統計每個$2出現的次數,記住每 2 美元發生了哪些行。

    這個答案使用 GNU awk 來表示陣列的陣列。輸出的順序不可能與輸入資料相同。它還必須將整個文件儲存在記憶體中。

    gawk '
        { lines[$2][++count[$2]] = $0 }
        END {
            for (x in lines)
                if (count[x] > 1)
                    for (i=1; i<=count[x]; i++)
                        print lines[x][i]
        }
    ' file
    

使用輸入檔進行測試:

$ cat file
a b
b b
c b
a c
a d
b d
a e

和預期產出

a b
b b
c b
a d
b d

答案2

使用相同的樣本輸入格倫傑克曼的回答

$ awk '$2 in seen{if(c[$2]--){print fl[$2]} print} !seen[$2]++{fl[$2]=$0; c[$2]=1}' file
a b
b b
c b
a d
b d
  • !seen[$2]++如果$2之前沒有遇過:
    • fl[$2]=$0保存第一行,我假設輸入未排序並且重複項可能出現在文件中的任何位置,因此基於$2 而不是僅臨時變數保存它
    • c[$2]=1類似地,將計數變數初始化為 1
  • $2 in seen如果$2之前發生過:
    • if(c[$2]--){print fl[$2]}首先列印上一行,計數器遞減,以便後續匹配的條件將失敗
    • print然後列印當前行


與一些其他輸入

$ cat ip.txt 
6.2  : 897 : bar
3.1  : 32  : foo
1.2  : 123 : xyz
2.3  : 32  : baz
7.5  : 897 : boo

$ awk -F: '$2 in seen{if(c[$2]--){print fl[$2]} print} !seen[$2]++{fl[$2]=$0; c[$2]=1}' ip.txt 
3.1  : 32  : foo
2.3  : 32  : baz
6.2  : 897 : bar
7.5  : 897 : boo

請注意,順序取決於重複發生的方式

答案3

當您迭代同一個檔案兩次時,您可以使用行號作為方便的索引;它可以使邏輯更清晰。

awk 'NR == FNR {if ($2 in z) { y[z[$2]]; y[FNR] } z[$2]=FNR; next} (FNR in y)' file file

我在回答這個問題時使用了類似的技巧:


這個技巧的基礎是 Awk 將簡單地透過引用來創建一個變量,並且該index in arrayname構造根據是否已使用指定索引創建了數組元素來返回 true 或 false。

相關內容