最简单的方法是遍历文件两次,计算第一次出现一行的频率,并在第二次遇到唯一的行时打印它们。
如果你有足够的内存(这需要相当多的时间),你可以使用
awk 'NR == FNR { seen[$0]++; next } seen[$0] == 1' file file
这需要多少内存取决于文件中行的平均长度。如果行很短,哈希映射的开销将使内存使用量远远超过纯输入数据所需的 1GB。我最近有一个类似的用例,其中 awk 最终使用超过 8GB 的 RAM 来处理约 300 MB 的输入数据,其中行的平均长度约为 8 个字符。用 C++ 重写代码使问题不那么严重,但仍然不切实际。
我们最终用 sqlite 解决了这个问题,用 RAM 交易速度。对于您的用例,这可能最终会是
rm lcount.db
awk -v q=\' '
NR == 1 {
print "CREATE TABLE lines (line text PRIMARY KEY, counter INTEGER, nr INTEGER);"
}
{
sub(q, q q); # hacky way to sanitize lines with quotes in them
print "INSERT OR IGNORE INTO lines VALUES (" q $0 q ", 0, " NR ");";
print "UPDATE lines SET counter = counter + 1 WHERE line = " q $0 q ";"
}
END {
print "SELECT line FROM lines WHERE counter = 1 ORDER BY nr;"
}' file | sqlite3 lcount.db
令人惊讶的是,这仍然相当快。它的速度又取决于您的可用 RAM —— sqlite 进程将只使用几兆字节,但速度很大程度上取决于用于数据库文件的文件系统缓存的可用空间。
请注意,我对 SQL 卫生状况不太满意;我不相信如果输入数据来自不可靠的来源是完全安全的。如果担心,您可以使用以下方法:
perl -MDBI -e'
my $dbh = DBI->connect("dbi:SQLite:dbname=lcount.db", "", "", { PrintError=>0, RaiseError=>1 });
$dbh->do("CREATE TABLE lines (line TEXT PRIMARY KEY, counter INTEGER, nr INTEGER)");
my $ins_sth = $dbh->prepare("INSERT OR IGNORE INTO lines VALUES (?, 0, ?)");
my $upd_sth = $dbh->prepare("UPDATE lines SET counter = counter + 1 WHERE line = ?");
while (<>) {
$ins_sth->execute($_, $.);
$upd_sth->execute($_);
}
my $sth = $dbh->prepare("SELECT line FROM lines WHERE counter = 1 ORDER BY nr");
print while ($_) = $sth->fetchrow_array;
' file