使用 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。

相关内容