仅列出分号分隔文件中基于一列的重复行?

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

我怎样才能(在Linux中)返回重复的行,但只基于列号2? 我应该使用grep还是其他的东西?

Solutions Collecting From Web of "仅列出分号分隔文件中基于一列的重复行?"

在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命令。 这个考虑只有在输入文件很大时才有意义。

有一个令人费解的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

正如@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 -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; }' 

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

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; } 

海武借:

 % 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&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]} = $_; }' 

怎么样:

  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”的选项,但无法弄清楚是否可以使用字段。

我假设你不是依赖任何特定的输入顺序(它可能没有预先排序在关键字(第二)字段),你宁愿保持输入行的顺序在你的输出…打印在第二个字段中包含重复值的第一个和所有后续行的副本。

以下是我可以用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命令行开关的语义。

从那里我们只需要跟踪我们看到的第一行,在第二个字段中有一个唯一的值,还有一个标志,表示我们之前是否打印过这一行。 当我们第一次找到一个重复的时候,我们打印出第一个有这个键的行,并将其标记为已经打印,然后打印当前行。 对于所有后续重复,我们只是打印当前行。 显然,对于任何非欺骗,我们只是把它作为我们的字典入口。

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

很明显,这可以用支持联合数组(散列,字典,表,无论你的首选语言称为它们)的任何脚本或编程语言来完成。 这个代码和我在这个线程中看到的大多数其他示例之间的唯一区别在于我正在对您的需求做出的假设(您宁愿保留输入和输出行的相对顺序)。

简单awk唯一的方法来删除基于列#2的唯一行(或基于列#2返回重复的行); 您可能需要更改为预期的目标列或多个列$X$Y

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