Если вы можете сохранить строки в памяти
Если в памяти помещается достаточно данных, awk
решение by steve довольно аккуратно, пишете ли вы команду sort
по каналу внутри awk
или просто передаете вывод без украшений от awk
до sort
на уровне оболочки.
Если у вас есть 100 ГБ данных с дублированием, возможно, 3%, вам нужно будет хранить 100 ГБ данных в памяти. Это много основной памяти. 64-битная система может справиться с виртуальной памятью, но она, скорее всего, будет работать довольно медленно.
Если ключи помещаются в памяти
Если вы не можете уместить достаточно данных в памяти, то предстоящая задача намного сложнее и потребует как минимум двух сканирований файлов. Мы должны заранее предположить, что вы можете, по крайней мере, поместить каждый ключ в память, а также подсчитать, сколько раз ключ появлялся.
- Scan 1: read the files.
- Count the number of times each key appears in the input.
- В
awk
используйте icount[$1]++
.
- Scan 2: reread the files.
- Count the number of times each key has appeared;
ocount[$1]++
.
- Если
icount[$1] == ocount[$1]
, то вывести строку.
(Это предполагает, что вы можете сохранить ключи и счетчики дважды; альтернативой является использование icount
(только) в обоих сканированиях, увеличение при сканировании 1 и уменьшение при сканировании 2, печать значения, когда счетчик уменьшается до нуля.)
Я бы, вероятно, использовал для этого Perl, а не awk
, хотя бы потому, что в Perl будет легче перечитывать файлы, чем в awk
.
Даже ключи не подходят?
А что, если вы даже не можете поместить ключи и их количество в память? Тогда вы столкнетесь с некоторыми серьезными проблемами, не в последнюю очередь из-за того, что языки сценариев могут не сообщать вам о состоянии нехватки памяти так четко, как вам хотелось бы. Я не буду пытаться пересечь этот мост, пока не будет доказано, что это необходимо. И если это необходимо, нам понадобятся некоторые статистические данные о наборах файлов, чтобы знать, что может быть возможно:
- Средняя длина записи.
- Количество отдельных ключей.
- Количество различных ключей с N вхождениями для каждого из N = 1, 2, ... max.
- Длина ключа.
- Количество ключей плюс счетчики, которые можно поместить в память.
И, возможно, некоторые другие... так что, как я уже сказал, давайте не будем пытаться пересечь этот мост, пока не будет показано, что это необходимо.
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
до исходного значения, он будет проходить через файлы во второй раз. Протестировано с Perl 5.16.0 и 5.10.0 в Mac OS X 10.7.5.
Это Перл; 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
использовать параметр расширения GNU ls
. Если он доступен, это лучший выбор.
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
и делает все чтение явно (вы можете заменить BEGIN на END и получить тот же результат). Логика во многих отношениях тесно связана с логикой Perl. Протестировано на Mac OS X 10.7.5 с BSD awk
и GNU awk
. Интересно, что GNU awk
настаивал на скобках в вызовах close
, тогда как BSD awk
этого не делал. Вызовы close()
необходимы в первом цикле, чтобы второй цикл вообще работал. Вызовы close()
во втором цикле нужны для сохранения симметрии и аккуратности, но они также могут быть важны, когда вы обрабатываете несколько сотен файлов за один прогон.
person
Jonathan Leffler
schedule
15.10.2012
sort -o one_sorted.csv *.csv
будет занимать меньше места на диске и быстрее, чем вариантcat
+sort
. - person Jonathan Leffler   schedule 15.10.2012