Альтернатива Sed для поиска и замены на очень длинные строки

9

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

sed -e 's/}{/}\n{/g'

Проблема заключается в том, что входные файлы имеют размер в несколько гигабайт, и поэтому строки ввода для sed имеют длину в несколько ГБ. sed пытается удерживать строку в памяти, которая в этом случае не работает. Я попробовал --unbufferedвариант, но это, казалось, замедляло его и не позволяло закончить правильно.

Том Паннинг
источник
Можно ли загрузить пример входного файла где-нибудь для нас, чтобы попробовать некоторые идеи?
MKC
3
Может быть, вы могли бы сначала использовать trдля перевода }в \nи затем использовать, sedчтобы добавить }в конце каждой строки? Как это:tr '}' '\n' < your_file.txt| sed 's/$/}/'
user43791
Помогает ли вообще добавление новой строки в конце файла? Нравится:printf "\n" >> file
няня
1
@Ketan, я предполагаю, что написания файла с 78 мусорными символами, за которым следуют }{повторения, пока его длины не станет несколько гигабайт, будет достаточно.
няня
@nanny - хорошая мысль - но где ты достаешь 78? Если записи уже заблокированы, то dd if=file cbs=80 conv=unblockсделал бы это - но это редко бывает так просто.
mikeserv

Ответы:

7

Вы можете использовать другой инструмент, который позволяет вам установить разделитель входных записей. Например

  • Perl

    perl -pe 'BEGIN{ $/="}{" } s/}{/}\n{/g' file
    

    Специальная переменная $/является разделителем входных записей. Установка его }{определяет строки как заканчивающиеся на }{. Таким образом, вы можете достичь того, что вы хотите, не читая все это в памяти.

  • ястреб или ястреб

    awk -v RS="}{" -vORS= 'NR > 1 {print "}\n{"}; {print}' file 
    

    Это та же идея. RS="}{"устанавливает разделитель записей на, }{а затем вы печатаете }, новую строку {(кроме первой записи) и текущую запись.

Тердон
источник
3

Perl на помощь:

perl -i~ -e ' $/ = \1024;
              while (<>) {
                  print "\n" if $closing and /^{/;
                  undef $closing;
                  s/}{/}\n{/g;
                  print;
                  $closing = 1 if /}$/;
              } ' input1 input2

Установка $/для \1024прочтет файл в кусках 1024 байт. В $closingпеременной обрабатывает случай , когда кусок заканчивается в , }а следующий начинается с {.

choroba
источник
1
+1, наверное, лучшее решение; другие perl / awk-решения тоже работают нормально, но что, если первый разделитель записей появляется после примерно 17 ГБ символов?
don_crissti
2

Ты должен сделать:

{ <infile tr \} \\n;echo {; } | paste -d'}\n' - /dev/null >outfile

Это, наверное, самое эффективное решение.

Это позволяет {}защитить любые возможные конечные данные. С помощью еще одного trпроцесса вы можете поменять его местами и сделать пустую строку в начале первого {поля. Подобно...

tr {} '}\n'| paste -d{\\0 /dev/null - | tr {}\\n \\n{}

Итак, первое, с примерами Дона, делает:

printf '{one}{two}{three}{four}' |
{ tr \} \\n; echo {; }           |
paste -d'}\n' - /dev/null
{one}
{two}
{three}
{four}
{}

... а второй делает ...

printf '{one}{two}{three}{four}'      |
tr {} '}\n'| paste -d{\\0 /dev/null - |
tr {}\\n \\n{}
#leading blank
{one}
{two}
{three}
{four}

Для второго примера нет завершающего символа новой строки, хотя для первого - один.

mikeserv
источник
0

Бинарная sedутилита под названиемbbe

Я считаю, что в этом случае проще всего придерживаться подобного sed синтаксиса.

Я много предпочитаю с помощью bbeутилиты (доступна через ваш {универа, Linų} Установка пакета иксы, эк apt-get). Или здесь, если вы один из мерзавцев, хотя я лично не проверял эту конкретную ссылку.

1. Поддерживает s/before/after/идиому

Это «Binary Block Editor», который поддерживает sed-подобные (среди прочего) операции. Это включает в себя супер распространенную s/before/after/идиому замены, которая вам нужна. Обратите внимание, поскольку bbeс точки зрения самой по себе нет строк, в конце команды нет «глобального g».

В качестве быстрого теста (обратите внимание на обязательное -e):

$ echo hello | bbe -e 's/l/(replaced)/'

производит:

he(replaced)(replaced)o

2. В вашем конкретном случае }{для }\n{конвертации

Поэтому, если бы у нас был массивный файл, заполненный миллионами чисел в (скажем) формате {1}{2}{3}... {1000000}без возврата каретки, мы могли бы легко обмениваться }{с ними }\n{и иметь все числа по одному в строке.

Это было бы с этой bbeкомандой:

bbe -e 's/}{/}\n{/'

Как проверено в этом цикле zsh, который мы берем только за хвост:

$ for ((num=0; num<1000000; num++)) do; echo -n "{$num}"; done | bbe -e 's/}{/}\n{/' | tail

Который будет производить это:

{999990}
{999991}
{999992}
{999993}
{999994}
{999995}
{999996}
{999997}
{999998}
{999999}

(конечно, без возврата каретки)

tgm1024 - с Моникой плохо обращались
источник