У меня есть огромный (70 ГБ), одна строка , текстовый файл, и я хочу заменить строку (токен) в нем. Я хочу заменить токен <unk>
другим фиктивным токеном ( проблема с перчатками ).
Я пробовал sed
:
sed 's/<unk>/<raw_unk>/g' < corpus.txt > corpus.txt.new
но выходной файл corpus.txt.new
имеет нулевые байты!
Я также пытался использовать Perl:
perl -pe 's/<unk>/<raw_unk>/g' < corpus.txt > corpus.txt.new
но я получил ошибку нехватки памяти.
Для файлов меньшего размера обе вышеуказанные команды работают.
Как мне заменить строку таким файлом? Это связанный вопрос, но ни один из ответов не сработал для меня.
Изменить : Как насчет разделения файла на куски по 10 ГБ (или что-то еще) каждый и применения sed
к каждому из них, а затем объединить их с cat
? Имеет ли это смысл? Есть ли более элегантное решение?
text-processing
sed
large-files
Христос Базиотис
источник
источник
split
с-b
опцией, определяющей размеры файла чанка в байтах. Обработайте каждый по очереди, используяsed
и собирая заново. Существует риск того, что<unk>
его можно разделить на два файла и не найти ...Ответы:
Обычные инструменты обработки текста не предназначены для обработки строк, которые не помещаются в ОЗУ. Они имеют тенденцию работать, читая одну запись (одну строку), манипулируя ею и выводя результат, затем переходя к следующей записи (строке).
Если есть символ ASCII, который часто появляется в файле и отсутствует в
<unk>
или<raw_unk>
, то вы можете использовать его как разделитель записей. Поскольку большинство инструментов не допускают использование пользовательских разделителей записей, поменяйте местами этот символ и символы новой строки.tr
обрабатывает байты, а не строки, поэтому не имеет значения размер записи. Предположим, что;
работает:Вы также можете привязать первый символ искомого текста, предполагая, что он не повторяется в тексте поиска и появляется достаточно часто. Если файл может начинаться с
unk>
, измените команду sed,sed '2,$ s/…
чтобы избежать ложного совпадения.В качестве альтернативы используйте последний символ.
Обратите внимание, что этот метод предполагает, что sed работает незаметно для файла, который не заканчивается новой строкой, то есть обрабатывает последнюю частичную строку, не обрезая ее и не добавляя заключительный символ новой строки. Работает с GNU sed. Если вы можете выбрать последний символ файла в качестве разделителя записей, вы избежите проблем переносимости.
источник
awk -v RS=, -v ORS=, '{gsub(/<unk>/, "<raw_unk>"); print}'
Нет?-0
и восьмеричным значением символа, или же внутри скрипта его можно установить с помощью специальной переменной$/
awk
избегайте передачи потока дваждыtr
. Так будет ли это еще медленнее?tr
очень быстро и трубу можно даже распараллелить.Для такого большого файла одна возможность - Flex. Позвольте
unk.l
быть:Затем скомпилируйте и выполните:
источник
make
для этого есть правила по умолчанию, вместо flex / cc вы можете добавить в%option main
качестве первой строки unk.l, а затем простоmake unk
. Я более-менее рефлексивно пользуюсь%option main 8bit fast
, и имеюexport CFLAGS='-march=native -pipe -Os'
по моему.bashrc
.%option main
+make
+CFLAGS
это очень хороший трюк !! Является ли-march=native
поведение по умолчанию?Таким образом, у вас недостаточно физической памяти (ОЗУ) для одновременного хранения всего файла, но в 64-разрядной системе у вас достаточно виртуального адресного пространства для сопоставления всего файла. Виртуальные отображения могут быть полезны как простой взлом в таких случаях.
Все необходимые операции включены в Python. Есть несколько раздражающих тонкостей, но при этом не нужно писать C-код. В частности, необходимо соблюдать осторожность, чтобы не копировать файл в память, что могло бы полностью устранить проблему. С другой стороны, вы получаете сообщения об ошибках бесплатно (python «исключения») :).
источник
search
может содержать символ NUL. И я заметил, что другая версия C здесь не поддерживает символы NULreplace
.). Вы можете получить версию C для сравнения. Однако помните, что моя версия включает базовые отчеты об ошибках для выполняемых операций. Версия C будет, по крайней мере, более раздражающей для чтения IMO, если в нее включены отчеты об ошибках.В
replace
пакете mariadb-server / mysql-server есть утилита. Он заменяет простые строки (не регулярных выражений) и в отличие от Grep / SED / AWKreplace
не заботится о\n
и\0
. Потребление памяти постоянно с любым входным файлом (около 400 КБ на моей машине).Конечно, вам не нужно запускать сервер mysql для его использования
replace
, он упакован таким образом только в Fedora. Другие дистрибутивы / операционные системы могут быть упакованы отдельно.источник
Я думаю, что версия C может работать намного лучше:
РЕДАКТИРОВАТЬ: Изменено в соответствии с предложениями из комментариев. Также исправлена ошибка с рисунком
<<unk>
.источник
memcpy
Скорость (то есть узкое место в памяти) составляет что-то вроде 12 ГБ / с на недавнем процессоре x86 (например, Skylake). Даже с учетом издержек системного вызова stdio + для файла объемом 30 МБ, горячего в дисковом кеше, можно ожидать 1 ГБ / с для эффективной реализации. Вы компилировали с отключенной оптимизацией, или ввод-вывод по одному символу за раз действительно такой медленный?getchar_unlocked
/putchar_unlocked
может помочь, но определенно лучше читать / записывать порциями, может быть, 128 кБ (половина размера кэша L2 на большинстве процессоров x86, так что вы, в основном, работаете с L2 во время цикла после чтения)fix
К программе по -"<<unk>"
прежнему не работает , еслиpattern
начинается с повторяющейся последовательностью символов (т.е. она не будет работать , если вы пытаетесь заменить Aardvark с зеброй и вы внесли свой вклад в aaardvak, или вы пытаетесь заменить ababc и был введен abababc). Как правило, вы не можете двигаться вперед по количеству прочитанных вами символов, если только вы не знаете, что совпадение, начинающееся с прочитанных вами символов, невозможно.GNU
grep
может показывать смещение совпадений в «двоичных» файлах, не считывая целые строки в памяти. Затем вы можете использоватьdd
для считывания до этого смещения, пропустить совпадение, а затем продолжить копирование из файла.Для скорости я разделил
dd
на большое чтение размера блока 1048576 и меньшее чтение на 1 байт за раз, но эта операция все еще будет немного медленной для такого большого файла.grep
Выход, например,13977:<unk>
и это делится на толстой кишке путем чтения в переменныхoffset
иpattern
. Мы должны отслеживать,pos
сколько байтов уже скопировано из файла.источник
Вот еще одна командная строка UNIX, которая может работать лучше, чем другие опции, потому что вы можете «искать» «размер блока», который работает хорошо. Чтобы это было надежно, вам нужно знать, что у вас есть хотя бы один пробел в каждых символах X, где X - ваш произвольный «размер блока». В приведенном ниже примере я выбрал «размер блока» из 1024 символов.
Здесь, fold будет захватывать до 1024 байтов, но -s гарантирует, что он будет разбит на пробел, если есть хотя бы один с момента последнего разрыва.
Команда sed ваша и делает то, что вы ожидаете.
Затем команда tr "раскроет" файл, преобразуя новые строки, которые были вставлены обратно, в ничто.
Вы должны попробовать использовать блоки большего размера, чтобы увидеть, работает ли он быстрее. Вместо 1024 вы можете попробовать 10240 и 102400 и 1048576 для опции -w сгиба.
Вот пример с разбивкой по каждому шагу, который преобразует все N в нижний регистр:
Вам нужно будет добавить новую строку в самый конец файла, если он есть, потому что команда tr удалит его.
источник
С помощью
perl
Управление своими собственными буферами
Вы можете использовать
IO::Handle
ssetvbuf
для управления буферами по умолчанию, или вы можете управлять своими собственными буферами с помощьюsysread
иsyswrite
. Проверьтеperldoc -f sysread
иperldoc -f syswrite
для получения дополнительной информации, по существу, они пропускают буферизованные io.Здесь мы запускаем наш собственный буферный ввод-вывод, но мы делаем это вручную и произвольно на 1024 байтах. Мы также открываем файл для RW, поэтому делаем все это одновременно на одном и том же FH.
Если вы собираетесь идти по этому маршруту
<unk>
и<raw_unk>
имеют тот же размер байт.CHUNKSIZE
границу, если вы заменяете более 1 байта.источник
<unk>
падает на границу между кусками?Вы можете попробовать bbe ( редактор двоичных блоков ), «
sed
для двоичных файлов».Я имел хороший успех, используя его в текстовом файле размером 7 ГБ без
EOL
символов, заменив несколько вхождений строки одной длины. Без попыток какой-либо оптимизации средняя скорость обработки составила> 50 МБ / с.источник
С помощью
perl
вы можете работать с записями фиксированной длины, такими как:И надеюсь, что
<unk>
эти две записи по 100 МБ не будут охватывать вас.источник
while read -N 1000 chunk;
(1000
выбранный в качестве примера). Решение<unk>
, разбитое между блоками, состоит в двух проходах по файлу: первый с блоками 100 МБ, а второй с блоками «100 МБ + 5 байт». Но это не оптимальное решение в случае файла 70 ГБ.<unk>
.<unk>
вхождения далеко аппарт, если нет, то используйте$/ = ">"
иs/<unk>\z/<raw_unk>/g
) быть правильным.Вот небольшая программа Go, которая выполняет задачу (
unk.go
):Просто создайте его
go build unk.go
и запустите как./unk <input >output
.РЕДАКТИРОВАТЬ:
Извините, я не читал, что все в одной строке, поэтому я пытался читать файл символ за символом.
РЕДАКТИРОВАТЬ II:
Применяется то же исправление, что и к программе на Си.
источник
scanner.Split(bufio.ScanRunes)
делает волшебство.go doc bufio.MaxScanTokenSize
размер буфера по умолчанию.C
программа, это не работает для замены aardvark на zebra с вводом aaardvark.Это может быть излишним для файла объемом 70 ГБ и простым поиском и заменой, но среда Hadoop MapReduce решит вашу проблему прямо сейчас без каких-либо затрат (выберите опцию «Единый узел» при настройке для ее локального запуска) - и это может быть масштабируется до бесконечной емкости в будущем без необходимости изменять ваш код.
Официальное руководство по адресу https://hadoop.apache.org/docs/stable/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.html использует (чрезвычайно простую) Java, но вы можете найти клиентские библиотеки для Perl или какой бы язык вы не хотели использовать.
Таким образом, если позже вы обнаружите, что выполняете более сложные операции с текстовыми файлами объемом 7000 ГБ - и должны делать это 100 раз в день - вы можете распределить рабочую нагрузку по нескольким узлам, которые вы предоставляете или которые автоматически предоставляются вам облачным на основе кластера Hadoop.
источник
Все предыдущие предложения требуют чтения всего файла и записи всего файла. Это не только занимает много времени, но и требует 70 ГБ свободного места.
1) Если я вас понимаю конкретный случай, правильно было бы приемлемо заменить <УНК> с какой-либо другой строки той же длины?
2а) Есть ли несколько случаев? 2b) Если да, то знаете, сколько?
Я уверен, что вы уже решили эту проблему года плюс, и я хотел бы знать, какое решение вы использовали.
Я бы предложил решение (скорее всего, на С), которое бы считывало БЛОКИ файла, ища по каждой строке, с учетом возможного пересечения блоков. Как только найден, замените строку с той же длины чередуется и записать только этот блок. Продолжая в течение известного числа случаев или до конца файла. Это потребует всего лишь записи числа событий и не более чем вдвое (если каждое вхождение было разделено между 2 блоками). Это не потребует никакого дополнительного места!
источник
Если у нас есть минимальное количество
<unk>
(как ожидается, согласно закону Ципфа),источник
sed
читает строку за раз в памяти независимо. Он не сможет соответствовать этой линии.sed
не будет делать буферизацию ввода / вывода при использовании этого флага. Я не вижу, что он будет читать частичные строки.