У меня большой текстовый файл (~ 50Gb, когда gz'ed). Файл содержит 4*N
строки или N
записи; то есть каждая запись состоит из 4 строк. Я хотел бы разделить этот файл на 4 файла поменьше, каждый размером примерно 25% от входного файла. Как я могу разделить файл на границе записи?
Наивным подходом было бы zcat file | wc -l
получить количество строк, разделить это число на 4 и затем использовать split -l <number> file
. Тем не менее, это происходит по файлу дважды, и счетчик строк очень медленный (36 минут). Есть ли способ лучше?
Это близко, но это не то, что я ищу. Принятый ответ также делает счетчик строк.
РЕДАКТИРОВАТЬ:
Файл содержит данные последовательности в формате fastq. Две записи выглядят так (анонимно):
@NxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxGCGA+ATAGAGAG
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxTTTATGTTTTTAATTAATTCTGTTTCCTCAGATTGATGATGAAGTTxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+
AAAAA#FFFFFFFFFFFFAFFFFF#FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF<AFFFFFFFFFFAFFFFFFFFFFFFFFFFFFF<FFFFFFFFFAFFFAFFAFFAFFFFFFFFAFFFFFFAAFFF<FAFAFFFFA
@NxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxGCGA+ATAGAGAG
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxCCCTCTGCTGGAACTGACACGCAGACATTCAGCGGCTCCGCCGCCxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+
AAAAA#FFFFF7FFFFFFAFFFFA#F7FFFFFFFFF7FFFFFAF<FFFFFFFFFFFFFFAFFF.F.FFFFF.FAFFF.FFFFFFFFFFFFFF.)F.FFA))FFF7)F7F<.FFFF.FFF7FF<.FFA<7FA.<.7FF.FFFAFF
Первая строка каждой записи начинается с @
.
EDIT2:
zcat file > /dev/null
занимает 31мин.
EDIT3:
только первая строка начинается с @
. Ни один из других никогда не будет. Смотрите здесь . Записи должны оставаться в порядке. Нельзя добавлять что-либо в полученный файл.
zcat file > /dev/null
?@
того, что в каждой записи есть 4 строки. Являются ли оба эти абсолютными? - а строки 2,3,4 могут начинаться с@
? и есть ли в файле какие-либо не записываемые заголовки строк нижнего колонтитула?Ответы:
Я не думаю, что вы можете сделать это - не надежно и не так, как вы просите. Дело в том, что степень сжатия архива, вероятно, не будет равномерно распределена от головы к хвосту - алгоритм сжатия будет применяться к некоторым частям лучше, чем к другим. Вот только как это работает. И поэтому вы не можете учесть размер вашего разделения на размер сжатого файла.
Более того,
gzip
просто не поддерживается сохранение исходного размера сжатых файлов размером более 4 ГБ - он не может с этим справиться. И поэтому вы не можете запросить архив, чтобы получить надежный размер - потому что он вас обманет.4 строки - это довольно просто, правда. Дело в 4 файлах - я просто не знаю, как вы могли бы сделать это надежно и с равномерным распределением без предварительного извлечения архива, чтобы получить его несжатый размер. Я не думаю, что вы можете, потому что я пытался.
Однако, что вы можете сделать, это установить максимальный размер для разделенных выходных файлов и убедиться, что они всегда нарушаются при барьерах записи. Это вы можете легко сделать. Вот небольшой скрипт, который сделает это путем извлечения
gzip
архива и передачи содержимого через несколько явныхdd
конвейерных буферов с конкретнымиcount=$rpt
аргументами, прежде чем передать егоlz4
для распаковки / повторного сжатия каждого файла на лету. Я также добавил несколько маленькихtee
хитростей, чтобы напечатать последние четыре строки для каждого сегмента в stderr.Это будет продолжаться до тех пор, пока не будет обработан весь ввод. Он не пытается разделить его на некоторый процент - который он не может получить - но вместо этого он разделяет его на максимальное количество необработанных байтов за разделение. И в любом случае, большая часть вашей проблемы заключается в том, что вы не можете получить надежный размер для вашего архива, потому что он слишком большой - что бы вы ни делали, не делайте этого снова - сделайте сплиты менее 4 ГБ на кусок, так что , может быть. Этот маленький скрипт, по крайней мере, позволяет вам делать это без необходимости записывать несжатый байт на диск.
Ниже приведена более короткая версия, которая не включает в себя все элементы отчета:
Он делает все то же самое, что и первый, в основном, ему просто нечего сказать об этом. Кроме того, там меньше беспорядка, так что легче понять, что происходит, может быть.
Все
IFS=
дело в том, чтобы обрабатывать однуread
строку на одну итерацию. Мыread
один, потому что нам нужно, чтобы наш цикл заканчивался, когда ввод заканчивается. Это зависит от размера вашей записи - который, по вашему примеру, составляет 354 байта на. Я создал 4 + ГБgzip
архив с некоторыми случайными данными, чтобы проверить его.Случайные данные были получены следующим образом:
... но, может быть, вам не нужно беспокоиться об этом, так как у вас уже есть данные и все такое. Вернуться к решению ...
В основном
pigz
- который, кажется, распаковывает немного быстрее, чем делаетzcat
- передает несжатый поток иdd
буферы, которые выводят в блоки записи, размер которых определенно кратен 354 байтам. Цикл будет один раз в каждой итерации теста , что ввод еще прибывающего, который он будет потом на перед другим , называется для чтения размеров блоков конкретно на кратна 354-байт - для синхронизации с буферным процесса - в течение всего срока. Будет одно короткое чтение за каждую итерацию из-за начального - но это не имеет значения, потому что мы печатаем это в нашем процессе сбора - в любом случае.read
$line
printf
printf
lz4
dd
dd
read $line
lz4
Я настроил его так, что каждая итерация будет считывать примерно 1 ГБ несжатых данных и сжимать их в потоке примерно до 650 МБ или около того.
lz4
гораздо быстрее, чем любой другой полезный метод сжатия - вот почему я выбрал его здесь, потому что я не люблю ждать.xz
возможно, будет гораздо лучше работать при фактическом сжатии. Но есть одна вещьlz4
, которая заключается в том, что он часто может распаковываться со скоростью, близкой к скорости ОЗУ, а это означает, что вы можете распаковыватьlz4
архив так же быстро, как и в любом случае.Большой делает несколько отчетов за одну итерацию. Оба
dd
цикла напечатают отчет о количестве переданных необработанных байтов, скорости и т. Д. Большой цикл также будет печатать последние 4 строки ввода за цикл и количество байтов для него, а такжеls
каталог, в который я записываюlz4
архивы. Вот несколько раундов вывода:источник
gzip -l
работает только для несжатых файлов <2GiB IIRC (что-то меньше, чем файл OP в любом случае).Разделить файлы по границам записи на самом деле очень легко, без какого-либо кода:
Это создаст выходные файлы по 10000 строк каждый с именами output_name_aa, output_name_ab, output_name_ac, ... При таком большом размере ввода, вы получите много выходных файлов. Замените
10000
на любое число, кратное четырем, и вы можете сделать выходные файлы такими большими или маленькими, как вам нравится. К сожалению, как и в случае с другими ответами, нет хорошего способа гарантировать, что вы получите желаемое количество (приблизительно) одинакового размера выходных файлов без каких-либо предположений о вводе. (Или, на самом деле, все это проясняетwc
.) Если ваши записи примерно одинакового размера (или, по крайней мере, примерно равномерно распределены), вы можете попытаться составить такую оценку:Это скажет вам сжатый размер первых 1000 записей вашего файла. Исходя из этого, вы, вероятно, можете составить оценку того, сколько строк в каждом файле вы хотите получить в результате из четырех файлов. (Если вы не хотите, чтобы вырожденный пятый файл был оставлен, обязательно немного увеличьте свою оценку или будьте готовы прикрепить пятый файл к хвосту четвертого.)
Изменить: Вот еще один трюк, если вы хотите сжатые выходные файлы:
Это создаст много небольших файлов, а затем быстро объединит их. (Возможно, вам придется настроить параметр -l в зависимости от длины строк в ваших файлах.) Предполагается, что у вас относительно свежая версия GNU coreutils (для split --filter) и около 130% размера входного файла в свободное место на диске. Замените gzip / zcat на pigz / unpigz, если у вас их нет. Я слышал, что некоторые программные библиотеки (Java?) Не могут обрабатывать сцепленные таким образом файлы gzip, но у меня до сих пор не было проблем с этим. (pigz использует тот же трюк для распараллеливания сжатия.)
источник
Из того, что я собрал после проверки google -phere и дальнейшего тестирования
.gz
файла размером 7,8 ГБ , кажется, что метаданные исходного размера несжатого файла не являются точными (то есть неправильными ) для больших.gz
файлов (больше 4 ГБ (может быть 2 ГБ для некоторых). версииgzip
).Re. мой тест метаданных gzip:
Таким образом, кажется, что невозможно определить несжатый размер, не распаковав его (что немного грубовато, если не сказать больше!)
Во всяком случае, вот способ разбить несжатый файл на границах записи, где каждая запись содержит 4 строки .
Он использует размер файла в байтах (через
stat
) и сawk
подсчетом байтов (не символов). Являются ли окончание строкиLF
|CR
|CRLF
этот скрипт обрабатывает длину конца строки через встроенную переменнуюRT
).Ниже приведен тест, который я использовал для проверки количества строк в каждом файле
mod 4 == 0
Тестовый вывод:
myfile
был сгенерирован:источник
Это не должно быть серьезным ответом! Я просто играл,flex
и это, скорее всего, не будет работать с входным файлом с ~ 50 Гб (если вообще, с большими входными данными, чем мой тестовый файл):Это работает для меня на входном файле ~ 1 ГБ :
Учитывая
flex
входной файл splitter.l :генерировать lex.yy.c и компилировать его в
splitter
двоичный файл с помощью:Применение:
Продолжительность 1Гб input.txt :
источник
getc(stream)
и примените простую логику. Кроме того, вы знаете, что. (точка) регулярное выражение в (f) lex соответствует любому символу, кроме новой строки , верно? Тогда как эти записи многострочные.@
символ, а затем позволить правилу по умолчанию копировать данные. Теперь ваше правило копирует часть данных в виде одного большого токена, а затем правило по умолчанию получает вторую строку по одному символу за раз.txr
?Вот решение в Python, которое делает один проход по входному файлу, записывая выходные файлы по мере продвижения.
Особенность использования
wc -l
заключается в том, что вы предполагаете, что все записи здесь имеют одинаковый размер. Это может быть правдой здесь, но решение ниже работает, даже если это не так. Это в основном использованиеwc -c
или количество байтов в файле. В Python это делается через os.stat ()Итак, вот как работает программа. Сначала мы вычисляем идеальные точки разделения как смещения байтов. Затем вы читаете строки записи входного файла в соответствующий выходной файл. Когда вы увидите, что вы достигли оптимальной следующей точки разделения, и вы находитесь на границе записи, закройте последний выходной файл и откройте следующий.
Программа оптимальна в этом смысле, она считывает байты входного файла один раз; Получение размера файла не требует чтения данных файла. Необходимое хранилище пропорционально размеру строки. Но Python или система предположительно имеют разумные файловые буферы для ускорения ввода-вывода.
Я добавил параметры для количества файлов, которые нужно разделить, и каков размер записи на случай, если вы захотите изменить это в будущем.
И, очевидно, это можно перевести и на другие языки программирования.
Еще одна вещь, я не уверен, что Windows с ее crlf правильно обрабатывает длину строки, как в Unix-системах. Если len () отключен на единицу, я надеюсь, что это очевидно, как настроить программу.источник
printf %s\\n {A..Z}{A..Z}{A..Z}{A..Z}—{1..4}
Пользователь FloHimself, казалось, интересовался решением TXR . Вот тот, который использует встроенный TXR Lisp :
Ноты:
По той же причине
pop
- важно, чтобы каждый кортеж из ленивого списка кортежей был важен, так что ленивый список используется. Мы не должны сохранять ссылку на начало этого списка, потому что тогда память будет расти по мере продвижения по файлу.(seek-stream fo 0 :from-current)
это безоперационный случайseek-stream
, который делает себя полезным, возвращая текущую позицию.Производительность: не упоминайте об этом. Можно использовать, но не принесет домой никаких трофеев.
Так как мы проверяем размер только через 1000 кортежей, мы можем просто сделать размер кортежа 4000 строк.
источник
Если вам не нужно, чтобы новые файлы были смежными кусками исходного файла, вы можете сделать это полностью
sed
следующим образом:Он
-n
останавливает печать каждой строки, и каждый из-e
сценариев по сути делает одно и то же.1~16
соответствует первой строке и каждой 16-й строке после.,+3
означает сопоставлять следующие три строки после каждой из них.w1.txt
говорит, что записать все эти строки в файл1.txt
. Это берет каждую 4-ю группу из 4 строк и записывает ее в файл, начиная с первой группы из 4 строк. Другие три команды делают то же самое, но каждая из них сдвинута вперед на 4 строки и записывает в другой файл.Это ужасно сломается, если файл не совсем соответствует спецификации, которую вы выложили, но в противном случае он должен работать так, как вы предполагали. Я не профилировал его, поэтому не знаю, насколько он будет эффективным, но
sed
он достаточно эффективен при редактировании потока.источник