Заменить первое совпадение строк в 1-строчном 2ГБ файле в Linux

Я пытаюсь заменить только первое совпадение одной строки в огромном файле только одной строкой (2,1 ГБ), эта замена произойдет в задании сценария оболочки. Большая проблема в том, что машина, на которой будет запускаться этот скрипт, имеет только 1 ГБ памяти (примерно 300 МБ свободной), поэтому мне нужна буферизованная стратегия, которая не переполняет мою память. Я уже пробовала sed, perl и python подход, но все они возвращают меня из ошибок памяти. Вот мои попытки (обнаруженные в других вопросах):

# With perl
perl -pi -e '!$x && s/FROM_STRING/TO_STRING/ && ($x=1)' file.txt

# With sed
sed '0,/FROM_STRING/s//TO_STRING/' file.txt > file.txt.bak

# With python (in a custom script.py file)
for line in fileinput.input('file.txt', inplace=True):
    print line.replace(FROM_STRING, TO_STRING, 1)
    break

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

РЕДАКТИРОВАТЬ (РЕШЕНИЕ):

Я проверил три решения ответов, все они решили проблему, спасибо всем вам. Я проверил производительность с Linux time и все они тоже занимают примерно одинаковое время, примерно до 10 секунд... Но я выбираю решение @Miller, потому что оно проще (просто использует perl).

5 ответов

Решение

Если вы уверены, что строка, которую вы пытаетесь заменить, находится только в первых 100 символах, тогда должна работать следующая строка perl:

perl -i -pe 'BEGIN {$/ = \1024} s/FROM_STRING/TO_STRING/ .. undef' file.txt

Объяснение:

Переключатели:

  • -i: Редактировать <> файлы на месте (делает резервную копию, если расширение предоставлено)
  • -p: Создает while(<>){...; print} цикл для каждой "строки" во входном файле.
  • -e: Говорит perl выполнить код в командной строке.

Код:

  • BEGIN {$/ = \1024}: Установите $INPUT_RECORD_SEPARATOR равным количеству символов, которые нужно прочитать для каждой "строки"
  • s/FROM/TO/ .. undef: Используйте триггер, чтобы выполнить регулярное выражение только один раз. Мог бы также использовать if $. == 1,

Поскольку вы знаете, что ваша строка всегда находится в первом фрагменте файла, вы должны использовать dd за это. Вам также понадобится временный файл для работы, как в tmpfile="$(mktemp)"

Сначала скопируйте первый блок файла в новое временное местоположение:dd bs=32k count=1 if=file.txt of="$tmpfile"

Затем выполните замену в этом блоке:sed -i 's/FROM_STRING/TO_STRING/' "$tmpfile"

Затем объедините новый первый блок с остальной частью старого файла, снова используя dd:dd bs=32k if=file.txt of="$tmpfile" seek=1 skip=1


РЕДАКТИРОВАТЬ: В соответствии с предложением Марка Сетчелла, я добавил спецификацию bs=32k на эти команды, чтобы ускорить темп dd операции. Это настраивается в соответствии с вашими потребностями, но если вы настраиваете отдельные команды по-отдельности, вам, возможно, следует быть осторожным с изменениями семантики между различными размерами входного и выходного блоков.

  • Учитывая, что тогда строка для замены находится в первых 100 байтах,
  • Учитывая, что Perl IO работает медленно, если вы не начнете использовать sysread читать большие блоки,
  • Предполагая, что подстановка изменяет размер файла[1], и
  • При условии, что binmode не требуется[2],

Я бы использовал

( head -c 100 | perl -0777pe's/.../.../' && cat ) <file.old >file.new

  1. Более быстрое решение для этого существует.
  2. Хотя это легко добавить в случае необходимости.

Не проверено, но я бы сделал:

perl -pi -we 'BEGIN{$/=\65536} s/FROM_STRING/TO_STRING/ if 1..1' file.txt

читать кусками по 64к.

Практичным (не очень компактным, но эффективным) будет разделение файла, поиск-замена и объединение: например:

head -c 100 myfile | sed 's/FROM/TO/' > output.1
tail -c +101 myfile > output.2
cat output.1 output.2 > output && /bin/rm output.1 output.2

Или в одну строку:

( ( head -c 100 myfile | sed 's/FROM/TO/' ) && (tail -c +101 myfile ) ) > output
Другие вопросы по тегам