Linux 是否仅基于分号分隔文件中的一列列出重复行?
我有一个有很多行的文件。 每行有8个分号分隔的列 如何(在Linux中)返回重复的行,但仅基于第2列?Linux 是否仅基于分号分隔文件中的一列列出重复行?,linux,Linux,我有一个有很多行的文件。 每行有8个分号分隔的列 如何(在Linux中)返回重复的行,但仅基于第2列? 我应该使用grep或其他什么吗?grep可能会这样做,但我猜你会更容易使用awk(在某些系统上也称为gawk) 根据您的需要使用的有效链/脚本取决于一些额外的信息。例如,输入文件是否容易排序,输入有多大(或者更确切地说是巨大还是流) 假设已排序的输入(最初或从管道到排序),awk脚本将如下所示:(注意:未测试) 检查Jonathan Leffler或Hai Vu提供的解决方案,以了解在不满足预
我应该使用
grep
或其他什么吗?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;
}
有一个复杂的
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
awk'BEGIN{FS=“;”}{c[$2]++;l[$2,c[$2]]=$0}END{for(c中的i){如果(c[i]>1)for(j=1;j如@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
!
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
来确保这一点ant脚本,它处理异常的输入(因为排序在需要保存中间结果时将数据溢出到磁盘):
这只保留一行输入的副本。当然,示例数据的输出是按排序顺序给出的。请参见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、sort、uniq和fgrep)为代价来保持行顺序
awk命令打印出第二个字段,然后对其输出进行排序。接下来,uniq-d命令将挑选出重复的字符串。此时,标准输出包含重复的第二个字段的列表,每行一个。然后将该列表导入fgrep。“-f-”标志告诉fgrep从sta中查找这些字符串标准输入
是的,你可以完全使用命令行。我更喜欢第二种解决方案,因为它使用了很多工具,逻辑更清晰(至少对我来说)。缺点是使用的工具数量和可能的内存。此外,第二种解决方案效率低下,因为它会扫描数据文件两次:第一次使用awk命令,第二次使用fgrep命令。只有当输入文件较大时,这一考虑才重要。如何:
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”的选项,但无法确定是否可以与字段一起使用。借用了海武:
% cat data.txt
John Thomas;jd;301
Julie Andrews;jand;109
Alex Tremble;atrem;415
John Tomas;jd;302
Alex Trebe;atrem;416
有一种非常简单的方法(使用gnu排序和gawk):
(尽管这将重新排序输出!)
(注意:如果没有--stable,排序可以对行重新排序,以便第二次出现在第一次出现之前。请注意!)
还有perl方法
cat data.txt | perl -e 'while(<>) { @data = split(/;/); if ( defined( $test{$data[1]} ) ) { print $_; } $test{$data[1]} = $_; }'
cat data.txt | perl-e'while(){@data=split(/;/);if(defined($test{$data[1]})){print$\}$test{$data[1]}=$\}'
.我假设您不依赖输入的任何特定顺序(它可能没有在键(第二个)字段上预先排序),并且您更希望保留输出中输入行的顺序…打印第一行和所有后续行的副本,这些行在第二个字段中包含重复值
下面是我能用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
命令行开关的语义
从这里,我们只需跟踪第一行,在第二个字段中有一个唯一的值,并有一个标志指示我们以前是否打印过这一行。当我们第一次找到一个副本时,我们打印有该键的第一行,并将其标记为已打印,然后打印当前行。对于所有后续副本,我们只打印当前行。显然,对于任何非重复项,我们只是将其作为一个条目发布到我们的字典中
可能有一种更优雅的方法来处理“第一次重复”布尔值…但这对我来说是最明显的,不应该造成任何额外的开销。创建一个具有自己状态(我已经打印)的非常简单的对象/类是一种选择。但我认为这会使代码的整体要点更难理解
显然,这可以在任何支持关联数组的脚本或编程语言中实现(哈希、字典、表,无论您的首选语言如何调用)。此代码与我在本线程中看到的大多数其他示例之间的唯一区别在于我对您的需求所做的假设(即您希望保留输入和输出行的相对顺序)。Simpleawk
基于列2删除唯一行的唯一方法(或基于列#2返回重复的行);您可能需要更改为预期的目标列或
% cat data.txt
John Thomas;jd;301
Julie Andrews;jand;109
Alex Tremble;atrem;415
John Tomas;jd;302
Alex Trebe;atrem;416
cat data.txt | sort -k2,2 -t';' --stable | gawk -F';' '{if ( $2==old ) { print $0 }; old=$2; }'
cat data.txt | perl -e 'while(<>) { @data = split(/;/); if ( defined( $test{$data[1]} ) ) { print $_; } $test{$data[1]} = $_; }'
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)
awk '{d[$2][a[$2]++]=$0} END{for (i in a) {if (a[i] > 1) for (j in d[i]) {print d[i][j]}}}'