Разбивать большой текстовый файл 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
Ответ 4
Альтернативным решением было бы загрузить CSV в индекс Solr, а затем сгенерировать CSV файлы на основе ваших пользовательских критериев поиска.
Здесь основной HOWTO:
Создать отчет и загрузить на сервер для загрузки
Ответ 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