如果你能把这些行保留在内存中
如果足够多的数据可以放入内存,那么steve的awk
解决方案非常简洁,无论您是通过内部管道写入命令,还是简单地将未修饰的输出通过管道传输到shell 级别。sort
awk
awk
sort
如果您有 100 GiB 的数据,其中可能有 3% 的重复,那么您需要能够在内存中存储 100 GiB 的数据。这是很多主内存。64 位系统可能会使用虚拟内存来处理它,但它可能运行得相当慢。
如果密钥适合内存
如果您无法在内存中容纳足够多的数据,那么前面的任务就会困难得多,并且至少需要对文件进行两次扫描。我们需要假设,pro tem,您至少可以将每个键放在内存中,并计算该键出现的次数。
- 扫描1:读取文件。
- 计算每个键在输入中出现的次数。
- 在
awk
,使用icount[$1]++
。
- 扫描 2:重新读取文件。
- 计算每个键出现的次数;
ocount[$1]++
.
- 如果
icount[$1] == ocount[$1]
,则打印该行。
(这假设您可以存储键和计数两次;另一种方法是icount
(仅)在两次扫描中使用,在扫描 1 中递增,在扫描 2 中递减,当计数减为零时打印值。)
我可能会为此使用 Perl 而awk
不是awk
.
连钥匙都不配?
如果您甚至无法将键及其计数放入内存中怎么办?那么您将面临一些严重的问题,尤其是因为脚本语言可能无法像您希望的那样干净利落地向您报告内存不足的情况。在证明有必要之前,我不会尝试过这座桥。如果有必要,我们需要一些关于文件集的统计数据来了解可能的情况:
- 记录的平均长度。
- 不同键的数量。
- 对于 N = 1, 2, ... max中的每一个,具有 N 次出现的不同键的数量。
- 密钥的长度。
- 键数加上可以装入内存的计数。
可能还有其他一些人……所以,正如我所说,在证明有必要之前,我们不要尝试过那座桥。
Perl 解决方案
示例数据
$ cat x000.csv
abc,123,def
abd,124,deg
abe,125,deh
$ cat x001.csv
abc,223,xef
bbd,224,xeg
bbe,225,xeh
$ cat x002.csv
cbc,323,zef
cbd,324,zeg
bbe,325,zeh
$ perl fixdupcsv.pl x???.csv
abd,124,deg
abe,125,deh
abc,223,xef
bbd,224,xeg
cbc,323,zef
cbd,324,zeg
bbe,325,zeh
$
注意没有千兆字节的测试!
fixdupcsv.pl
这使用“向上计数,向下计数”技术。
#!/usr/bin/env perl
#
# Eliminate duplicate records from 100 GiB of CSV files based on key in column 1.
use strict;
use warnings;
# Scan 1 - count occurrences of each key
my %count;
my @ARGS = @ARGV; # Preserve arguments for Scan 2
while (<>)
{
$_ =~ /^([^,]+)/;
$count{$1}++;
}
# Scan 2 - reread the files; count down occurrences of each key.
# Print when it reaches 0.
@ARGV = @ARGS; # Reset arguments for Scan 2
while (<>)
{
$_ =~ /^([^,]+)/;
$count{$1}--;
print if $count{$1} == 0;
}
' while (<>)
' 符号会破坏@ARGV
(因此在执行任何其他操作之前复制到@ARGS
),但这也意味着如果您重置@ARGV
为原始值,它将再次运行文件。在 Mac OS X 10.7.5 上使用 Perl 5.16.0 和 5.10.0 进行测试。
这是 Perl;TMTOWTDI。你可以使用:
#!/usr/bin/env perl
#
# Eliminate duplicate records from 100 GiB of CSV files based on key in column 1.
use strict;
use warnings;
my %count;
sub counter
{
my($inc) = @_;
while (<>)
{
$_ =~ /^([^,]+)/;
$count{$1} += $inc;
print if $count{$1} == 0;
}
}
my @ARGS = @ARGV; # Preserve arguments for Scan 2
counter(+1);
@ARGV = @ARGS; # Reset arguments for Scan 2
counter(-1);
可能也有压缩循环主体的方法,但我发现那里的内容相当清晰,并且更喜欢清晰而不是极端简洁。
调用
您需要以fixdupcsv.pl
正确的顺序显示带有文件名的脚本。由于您的文件编号从 1.csv 到大约 2000.csv,因此不要按字母数字顺序列出它们很重要。其他答案建议ls -v *.csv
使用 GNUls
扩展选项。如果可用,那是最好的选择。
perl fixdupcsv.pl $(ls -v *.csv)
如果这不可用,那么您需要对名称进行数字排序:
perl fixdupcsv.pl $(ls *.csv | sort -t. -k1.1n)
awk 解决方案
awk -F, '
BEGIN {
for (i = 1; i < ARGC; i++)
{
while ((getline < ARGV[i]) > 0)
count[$1]++;
close(ARGV[i]);
}
for (i = 1; i < ARGC; i++)
{
while ((getline < ARGV[i]) > 0)
{
count[$1]--;
if (count[$1] == 0) print;
}
close(ARGV[i]);
}
}'
这会忽略awk
' 固有的 'read' 循环并显式执行所有读取(您可以将 BEGIN 替换为 END 并获得相同的结果)。该逻辑在许多方面都基于 Perl 逻辑。awk
在带有 BSD和 GNU的 Mac OS X 10.7.5 上测试awk
。有趣的是,GNUawk
坚持在close
BSDawk
没有的调用中使用括号。在第close()
一个循环中调用是必要的,以使第二个循环完全工作。第二个循环中的close()
调用是为了保持对称和整洁——但当您在一次运行中处理几百个文件时,它们也可能是相关的。