仅根据分号分隔文件中的一列列出重复行?

时间:2009-09-20 02:01:45

标签: linux

我有一堆行文件。 这些行中的每一行都有8个以分号分隔的列。

我如何(在Linux中)返回重复行但仅基于第2列? 我应该使用grep还是别的什么?

9 个答案:

答案 0 :(得分:17)

在awk脚本中查看我的评论

$ cat data.txt 
John Thomas;jd;301
Julie Andrews;jand;109
Alex Tremble;atrem;415
John Tomas;jd;302
Alex Trebe;atrem;416

$ cat dup.awk 
BEGIN { FS = ";" }

{
    # Keep count of the fields in second column
    count[$2]++;

    # Save the line the first time we encounter a unique field
    if (count[$2] == 1)
        first[$2] = $0;

    # If we encounter the field for the second time, print the
    # previously saved line
    if (count[$2] == 2)
        print first[$2];

    # From the second time onward. always print because the field is
    # duplicated
    if (count[$2] > 1)
        print
}

示例输出:

$ sort -t ';' -k 2 data.txt | awk -f dup.awk

John Thomas;jd;301
John Tomas;jd;302
Alex Tremble;atrem;415
Alex Trebe;atrem;416

这是我的解决方案#2:

awk -F';' '{print $2}' data.txt |sort|uniq -d|grep -F -f - data.txt

这个解决方案的优点是它保留了行顺序,但却牺牲了一起使用许多工具(awk,sort,uniq和fgrep)。

awk命令打印出第二个字段,然后对其输出进行排序。接下来,uniq -d命令选出重复的字符串。此时,标准输出包含重复的第二个字段的列表,每行一个。然后我们将该列表输入fgrep。 ' -f - '标志告诉fgrep从标准输入中查找这些字符串。

是的,您可以使用命令行全力以赴。我喜欢第二种解决方案,更好地用于锻炼许多工具和更清晰的逻辑(至少对我而言)。缺点是工具的数量和可能使用的内存。此外,第二种解决方案是低效的,因为它扫描数据文件两次:第一次使用awk命令,第二次使用fgrep命令。这种考虑仅在输入文件很大时才有意义。

答案 1 :(得分:7)

有一个令人费解的awk脚本。

awk 'BEGIN { FS=";" } { c[$2]++; l[$2,c[$2]]=$0 } END { for (i in c) { if (c[i] > 1) for (j = 1; j <= c[i]; j++) print l[i,j] } }' file.txt

它的工作原理是保留第二个字段中每个值的所有出现的计数器,以及具有该值的行,然后打印出计数器大于1的行。

$2的所有实例替换为您需要的字段编号,并使用您的文件名替换最后的file.txt

答案 2 :(得分:3)

正如@mjv推测的那样 - awk(或Perl或Python)是更好的选择:

awk -F';' ' {
    if (assoc[$2]) {          # This field 2 has been seen before
        if (assoc[$2] != 1) { # The first occurrence has not been printed
            print assoc[$2];  # Print first line with given $2
            assoc[$2] = 1;    # Reset array entry so we know we've printed it;
                              # a full line has 8 fields with semi-colons and
                              # cannot be confused with 1.
        }
        print $0;             # Print this duplicate entry
    }
    else {
        assoc[$2] = $0;       # Record line in associative array, indexed by
                              # second field.  
    }
}' <<!
a;b;c;d;e;f;g;h
a;c;c;d;e;f;g;h
a;1;c;d;e;f;g;h
a;1;c;d;e;f;g;h
a;2;c;d;e;f;g;h
a;z;c;d;e;f;g;h
a;q;c;d;e;f;g;h
a;4;c;d;e;f;g;h
a;1;c;d;e;f;g;h
a;1;c;d;e;f;g;h
a;x;c;d;e;f;g;h
a;c;c;d;e;f;g;h
a;1;c;d;e;f;g;h
a;q;c;d;e;f;g;h
a;4;c;d;e;f;g;h
!

这样可以,但它可以对数据进行轻微重新排序 - 因为它会在第二次出现时打印出第一次出现的重复行。示例输出为:

a;1;c;d;e;f;g;h
a;1;c;d;e;f;g;h
a;1;c;d;e;f;g;h
a;1;c;d;e;f;g;h
a;c;c;d;e;f;g;h
a;c;c;d;e;f;g;h
a;1;c;d;e;f;g;h
a;q;c;d;e;f;g;h
a;q;c;d;e;f;g;h
a;4;c;d;e;f;g;h
a;4;c;d;e;f;g;h

awk脚本的这个变体重新排序测试,导致更紧凑的表示法。它还明确忽略了不包含由分号分隔的8个字段的格式错误的数据行。它被打包为shell脚本,但没有任何选项处理,因此您只能提供要扫描的文件列表(如果没有列出文件,则读取标准输入)。我在脚本中删除了Perl-ish分号; awk不需要它们。

#!/bin/sh

awk -F';' '
NF == 8 {
    if (!assoc[$2]) assoc[$2] = $0
    else if (assoc[$2] != 1)
    {
        print assoc[$2]
        assoc[$2] = 1
        print $0
    }
    else print $0
}' "$@"

另外,@ mjv评论说,如果输入很大,可能存在解决方案的内存问题,因为它保留了关联数组'assoc'中每个不同字段2值的记录。我们可以消除这种情况,如果输入awk的数据已经排序,我们当然可以确保使用sort。这是一个处理怪异输入的变体脚本(因为sort在必要时将数据溢出到磁盘以保存中间结果):

sort -t';' -k 2,2 "$@" |
awk -F';' '
BEGIN { last = ";"; line = "" }
NF == 8 {
    if ($2 != last)
    {
        last = $2
        line = $0
    }
    else if (line != "")
    {
        print line
        line = ""
        print $0
    }
    else print $0;
}'

这只保留一行输入的副本。当然,样本数据的输出是按排序顺序给出的。

答案 3 :(得分:2)

借用 Hai Vu

% cat data.txt
John Thomas;jd;301
Julie Andrews;jand;109
Alex Tremble;atrem;415
John Tomas;jd;302
Alex Trebe;atrem;416

有一种非常简单的方法(使用gnu-sort&amp; gawk):
(虽然这会重新输出输出!)
(警告:没有 - 稳定,排序可以重新排序行,所以第二次出现在第一次出现之前。请注意!)

cat data.txt | sort -k2,2 -t';' --stable | gawk -F';' '{if ( $2==old ) { print $0 }; old=$2; }'

还有perl方式......

cat data.txt | perl -e 'while(<>) { @data = split(/;/); if ( defined( $test{$data[1]} ) ) { print $_; } $test{$data[1]} = $_; }'

答案 4 :(得分:1)

grep可能会这样做,但我猜你用 awk (在某些系统上也称为gawk)会更容易。

用于满足您需求的有效链/脚本取决于一些额外的信息。例如,输入文件是否易于排序,输入有多大(或者更大或者是流)...

假设已排序的输入(最初或通过排序管道),awk脚本看起来像这样:(注意未经测试)

检查Jonathan Leffler或Hai Vu提供的解决方案,以获得实现相同而无预排序要求的方法。

#!/usr/bin/awk
# *** Simple AWK script to output duplicate lines found in input ***
#    Assume input is sorted on fields

BEGIN {
    FS = ";";   #delimiter
    dupCtr = 0;       # number of duplicate _instances_
    dupLinesCtr = 0;  # total number of duplicate lines

    firstInSeries = 1;   #used to detect if this is first in series

    prevLine = "";
    prevCol2 = "";  # use another string in case empty field is valid
}

{
  if ($2 == prevCol2) {
    if (firstInSeries == 1) {
      firstInSeries = 0;
      dupCtr++;
      dupLinesCtr++;
      print prevLine
    }
    dupLinesCtr++;
    print $0
  }
  else
     firstInSeries = 1
  prevCol2 = $2
  prevLine = $0
}

END { #optional display of counts etc.
  print "*********"
  print "Total duplicate instances = " iHits "   Total lines = " NR;
}

答案 5 :(得分:1)

简单awk仅根据第2列删除唯一行(或根据第2列返回重复行);您可能需要更改为预期的目标列或多列$X$Y的组合。

awk -F\; 'NR==FNR{s[$2]++;next} (s[$2]>1)' infile infile

答案 6 :(得分:0)

怎么样:

 sort -t ';' -k 2 test.txt | awk -F';' 'BEGIN{curr="";prev="";flag=0} \
                     NF==8{ prev=curr;
                            curr=$2;
                            if(prev!=curr){flag=1}
                            if(flag!=0 && prev==curr)flag++ ; 
                            if(flag==2)print $0}'

我还尝试了uniq命令,它有显示重复行“-d”的选项,但无法确定是否可以与字段一起使用。

答案 7 :(得分:0)

我假设您不依赖于输入的任何特定顺序(它可能没有在键(第二个)字段上预先排序)并且您更愿意保留输入的顺序输出中的输入行...打印第一个和所有后续行的副本,其中包含第二个字段中的重复值。

这是我在Python中可以提出的最快的代码片段:

    import fileinput
    seen = dict()
    for line in fileinput.input():
        fields = line.split(';')
        key = fields[1]
        if key in seen:
            if not seen[key][0]:
                print seen[key][1],
                seen[key] = (True, seen[key][1])
            print line,
        else:
            seen[key] = (False, line)

fileinput模块允许我们以类似于默认awk文件/输入处理的方式处理输入行...或者Perl的-n命令行开关的语义。

从那里我们只是跟踪我们在第二个字段中看到的第一行,其中有一个唯一值,还有一个标志,指示我们之前是否打印过这个。当我们第一次找到副本时,我们打印出具有该键的第一行,并将其标记为已打印,然后我们打印当前行。对于所有后续重复项,我们只打印当前行。显然,对于任何非欺骗,我们只是将其作为我们词典的一个条目发布。

可能有一种更优雅的方式来处理“第一个欺骗”布尔......但这对我来说是最明显的,不应该造成任何撤消额外的开销。创建一个非常简单的对象/类具有自己的状态(我已被打印)将是一个选项。但我认为这会使代码的整体要点更难理解。

很明显,这可以在支持关联数组的任何脚本或编程语言中完成(哈希,字典,表格,无论您的首选语言是什么)。这个代码和我在这个帖子中看到的大多数其他例子之间的唯一区别在于我对你的要求做出的假设(你更喜欢保留输入和输出行的相对顺序)。 / p>

答案 8 :(得分:0)

一个没有排序的衬纸:

awk '{d[$2][a[$2]++]=$0} END{for (i in a) {if (a[i] > 1) for (j in d[i]) {print d[i][j]}}}'