разделить большой текстовый файл CSV на основе значения столбца - PullRequest
10 голосов
/ 31 марта 2012

У меня есть 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

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

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

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

Ответы [ 6 ]

32 голосов
/ 03 апреля 2012

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

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

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

awk -F, 'NR==1 {h=$0; next} {f=$3".csv"} !($3 in p) {p[$3]; print h > f} {print >> f}' 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

1 голос
/ 13 ноября 2015
perl -F, -ane '`echo $_ >> $F[2].csv`' < file

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

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

@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
1 голос
/ 31 марта 2012

C ++ хорошо, если вы знаете это лучше всего. Зачем пытаться загрузить весь файл в память в любом случае?

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

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

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

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

По сути, продолжаем эту обработку, пока мы не находимся в конце файла.

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

0 голосов
/ 11 января 2019

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

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
0 голосов
/ 12 июня 2012

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

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.

0 голосов
/ 31 марта 2012

Альтернативным решением было бы загрузить CSV в индекс Solr и затем сгенерировать файлы CSV на основе ваших пользовательских критериев поиска.

Вот базовый HOWTO:

Создать отчет и загрузить на сервер для загрузки

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...