Разбивать большой текстовый файл csv на основе значения столбца

У меня есть файлы CSV, которые имеют несколько столбцов, которые сортируются. Например, у меня могут быть такие строки:

19980102,,PLXS,10032,Q,A,,,15.12500,15.00000,15.12500,2
19980105,,PLXS,10032,Q,A,,,14.93750,14.75000,14.93750,2
19980106,,PLXS,10032,Q,A,,,14.56250,14.56250,14.87500,2
20111222,,PCP,63830,N,A,,,164.07001,164.09000,164.12000,1
20111223,,PCP,63830,N,A,,,164.53000,164.53000,164.55000,1
20111227,,PCP,63830,N,A,,,165.69000,165.61000,165.64000,1

Я хотел бы разделить файл на основе третьего столбца, например. помещать записи PLXS и PCP в свои собственные файлы, называемые PLXS.csv и PCP.csv. Поскольку файл предварительно отсортирован, все записи PLXS находятся перед входами PCP и т.д.

Я вообще делаю такие вещи, как это на С++, потому что тот язык, который я знаю лучше всего, но в этом случае мой входной CSV файл имеет несколько гигабайт и слишком большой для загрузки в память на С++.

Может кто-нибудь показать, как это можно сделать? Решения Perl/Python/php/ bash все в порядке, они просто должны иметь возможность обрабатывать огромный файл без чрезмерного использования памяти.

Ответ 1

С++ отлично, если вы знаете это лучше всего. Почему вы все равно пытаетесь загрузить весь файл в память?

Поскольку вывод зависит от прочитанного столбца, вы можете легко хранить буферы для выходных файлов и записывать запись в соответствующий файл по мере процесса обработки, очищая по мере того, как вы двигаетесь, чтобы сохранить размер памяти относительно небольшим.

Я делаю это (хотя и в java), когда нужно брать массивные выдержки из базы данных. Записи вставляются в поток файлового буфера, и все в памяти очищается, поэтому след программы никогда не растет выше того, из чего она начинается.

Летайте на сиденье псевдокода моих штанов:

  • Создайте список для хранения буферов выходных файлов
  • Откройте поток в файле и начните чтение в содержимом одной строки за раз
  • Мы столкнулись с записью с открытым файловым потоком для этого типа контента?
    • Да -
      • Получить сохраненный файловый поток
      • сохранить запись в этот файл
      • очистить поток
    • Нет -
      • создать поток и сохранить его в нашем списке потоков
      • сохранить запись в потоке
      • очистить поток
  • Повторное ополаскивание...

В основном продолжить эту обработку до тех пор, пока мы не закончим файл.

Так как мы никогда не храним больше указателей на потоки, и мы стираем, как только мы пишем в потоки, мы никогда не держим ничего в памяти приложения, отличного от одной записи из входного файла. Таким образом, след остается регулируемым.

Ответ 2

Вот старая строка для вас (просто замените >> на > для усечения выходных файлов при каждом запуске):

awk -F, '{print >> ($3".csv")}' input.csv

Из-за популярного спроса (и зуда, который у меня только что был), я также написал версию, которая будет дублировать строки заголовка для всех файлов:

awk -F, '{fn=$3".csv"} NR==1 {hdr=$0} NR>1&&!($3 in p) {p[$3]; print hdr > fn} NR>1 {print >> fn}' input.csv

Но вы можете просто начать с этого и закончить первым awk:

HDR=$(head -1 input.csv); for fn in $(tail -n+2 input.csv | cut -f3 -d, | sort -u); do echo $HDR > $fn.csv; done

В большинстве современных систем включен двоичный файл awk, но если у вас его нет, вы можете найти exe в Gawk для Windows

Ответ 3

perl -F, -ane '`echo $_ >> $F[2].csv`' < file

Используются следующие параметры командной строки:

  • -n цикл вокруг каждой строки входного файла
  • -l удаляет новые строки перед обработкой и добавляет их обратно
  • -a автоматический режим - разделение входных строк на массив @F. По умолчанию разбивается на пробелы.
  • -e выполнить код perl
  • -F модификатор авторасширения, в этом случае разбивается на ,

@F - это массив слов в каждой строке, индексированный начиная с $F[0]


Если вы хотите сохранить заголовок, вам потребуется более сложный подход.

perl splitintofiles.pl file

Содержимое splitintofiles.pl:

open $fh, '<', $ARGV[0];
while ($line = <$fh>) {
    print $line;
    if ($. == 1) {
        $header = $line;
    } else {
        # $fields[2] is the 3rd column
        @fields = split /,/, $line;
        # save line into hash %c
        $c{"$fields[2].csv"} .= $line;
    }
}
close $fh;
for $file (keys %c) {
    print "$file\n";
    open $fh, '>', $file;
    print $fh $header;
    print $fh $c{$file};
    close $fh;
}

ввод:

a,b,c,d,e,f,g,h,i,j,k,l
19980102,,PLXS,10032,Q,A,,,15.12500,15.00000,15.12500,2
19980105,,PLXS,10032,Q,A,,,14.93750,14.75000,14.93750,2
19980106,,PLXS,10032,Q,A,,,14.56250,14.56250,14.87500,2
20111222,,PCP,63830,N,A,,,164.07001,164.09000,164.12000,1
20111223,,PCP,63830,N,A,,,164.53000,164.53000,164.55000,1
20111227,,PCP,63830,N,A,,,165.69000,165.61000,165.64000,1

вывод PCP.csv

a,b,c,d,e,f,g,h,i,j,k,l
20111222,,PCP,63830,N,A,,,164.07001,164.09000,164.12000,1
20111223,,PCP,63830,N,A,,,164.53000,164.53000,164.55000,1
20111227,,PCP,63830,N,A,,,165.69000,165.61000,165.64000,1

вывод PLXS.csv

a,b,c,d,e,f,g,h,i,j,k,l
19980102,,PLXS,10032,Q,A,,,15.12500,15.00000,15.12500,2
19980105,,PLXS,10032,Q,A,,,14.93750,14.75000,14.93750,2
19980106,,PLXS,10032,Q,A,,,14.56250,14.56250,14.87500,2

Ответ 5

Если в первых трех столбцах вашего файла нет цитируемых запятых, простой однострочный:

cat file | perl -e 'while(<>){@a=split(/,/,$_,4);$key=$a[2];open($f{$key},">$key.csv") unless $f{$key};print {$f{$key}} $_;} for $key (keys %f) {close $f{$key}}'

Он не потребляет много памяти (сохраняются только отдельные символы (3rd_column) → file-handle), и строки могут поступать в любом порядке.

Если столбцы более сложны (например, содержат запятые), используйте Text::CSV.

Ответ 6

Если во входном файле нет строки заголовка

awk -F, '
{fn = $3".csv"
 print > fn}' bigfile.csv

Если есть строка заголовка, которая должна быть передана разделенным файлам

awk -F, '
NR==1 {hdr=$0; next}
{fn = $3".csv"}
!seen[$3]++{print hdr > fn}
{print > fn}' bigfile.csv