Заменить строку, содержащую новую строку в огромном файле

16

Кто-нибудь знает инструмент, не основанный на строках, для «бинарного» поиска / замены строк в некотором смысле эффективным способом памяти? Смотрите и этот вопрос .

У меня есть текстовый файл + 2 ГБ, который я хотел бы обработать аналогично тому, как это выглядит:

sed -e 's/>\n/>/g'

Это означает, что я хочу удалить все новые строки, которые появляются после >, но нигде больше, так что это исключает tr -d.

Эта команда (которую я получил из ответа на аналогичный вопрос ) не выполняется с couldn't re-allocate memory:

sed --unbuffered ':a;N;$!ba;s/>\n/>/g'

Итак, есть ли другие методы, не прибегая к C? Я ненавижу Perl, но готов сделать исключение в этом случае :-)

Я точно не знаю ни одного символа, который не встречается в данных, поэтому временную замену \nдругим символом я бы хотел избежать, если это возможно.

Любые хорошие идеи, кто-нибудь?

MattBianco
источник
Вы пробовали вариант --unbuffered?
Ctrl-Alt-Delor
С --unbufferedпамятью или без
нее
Что делает $!?
Ctrl-Alt-Delor
Что не так с первой командой sed. Второй, кажется, читает все в шаблонном пространстве, $!хотя я не знаю, что это такое. Это Я ожидаю , что потребуется МНОГО памяти.
Ctrl-Alt-Delor
Проблема в том, что sed читает все в виде строк, поэтому первая команда не удаляет символы новой строки, поскольку она выводит текст построчно. Вторая команда - это просто обходной путь. Я думаю, что sedэто не правильный инструмент в этом случае.
MattBianco

Ответы:

14

Это действительно тривиально в Perl, вы не должны ненавидеть это!

perl -i.bak -pe 's/>\n/>/' file

объяснение

  • -i: отредактируйте файл на месте и создайте резервную копию оригинала file.bak. Если вы не хотите резервное копирование, просто используйте perl -i -peвместо этого.
  • -pe: читать входной файл построчно и печатать каждую строку после применения скрипта, заданного как -e.
  • s/>\n/>/: замена, просто как sed.

И вот awkподход:

awk  '{if(/>$/){printf "%s",$0}else{print}}' file2 
Тердон
источник
3
+1. awk golf:awk '{ORS=/>$/?"":"\n"}1'
Гленн Джекман
1
Почему я не люблю Perl в целом - это та же самая причина, по которой я выбрал этот ответ (или фактически ваш комментарий к ответу Gnouc): удобочитаемость. Использование perl -pe с простым «шаблоном sed» более читабельно, чем сложное выражение sed.
MattBianco
3
@ MattBianco достаточно честно, но, как вы знаете, это не имеет никакого отношения к Perl. Заглядывание назад, которое использовал Gnouc, является особенностью некоторых языков регулярных выражений (включая, но не ограничиваясь ими, PCRE), а не ошибкой Perl. Кроме того, после того, как вы включили ':a;N;$!ba;s/>\n/>/g'в свой вопрос это чудовище , вы отказались от права жаловаться на удобочитаемость! : P
terdon
@glennjackman хорошо! Я играл с foo ? bar : bazконструктом, но не смог заставить его работать.
Тердон
@terdon: Да, моя ошибка. Удали это.
cuonglm
7

perlРешение:

$ perl -pe 's/(?<=>)\n//'

Explaination

  • s/// используется для подстановки строк.
  • (?<=>) это шаблон
  • \n соответствует новой строке.

Весь шаблон означает удаление всех символов новой строки, находящихся >перед ним.

cuonglm
источник
2
не хотите прокомментировать, что делает часть программы? Я всегда ищу учиться.
MattBianco
2
Зачем беспокоиться о взгляде сзади? Почему не просто s/>\n/>/?
Тердон
1
или s/>\K\n//также будет работать
Гленн Джекман
@terdon: Только первое, что я хочу удалить вместо замены
cuonglm
@glennjackman: хорошая мысль!
cuonglm
3

Как насчет этого:

sed ':loop
  />$/ { N
    s/\n//
    b loop
  }' file

Для GNU sed вы также можете попробовать добавить опцию -u( --unbuffered) в соответствии с вопросом. GNU sed также доволен этим как простой однострочник:

sed ':loop />$/ { N; s/\n//; b loop }' file
Graeme
источник
Это не удаляет последнее, \nесли файл заканчивается >\n, но это, вероятно, предпочтительнее в любом случае.
Стефан Шазелас
@ StéphaneChazelas, почему закрытие }должно быть в отдельном выражении? это не будет работать как многострочное выражение?
Грэм,
1
Это будет работать в POSIX SEDs с b loop\n}или , -e 'b loop' -e '}'но не так b loop;}и , конечно , не так , b loop}потому что }и ;действительны в именах меток (хотя никто в здравом уме не будет использовать. А это означает , что GNU SED не POSIX совместимый) и }потребность команды должны быть разделено из bкоманды.
Стефан Шазелас
@ StéphaneChazelas, GNU sedдоволен всем вышеперечисленным, даже с --posix! Стандарт также имеет следующие выражения для скобок - The list of sed functions shall be surrounded by braces and separated by <newline>s. Не означает ли это, что точки с запятой следует использовать только за скобками?
Грэм
@mikeserv, цикл необходим для обработки последовательных строк, оканчивающихся на >. У оригинала никогда не было такового, на это указал Стефан.
Грэм
1

Вы должны быть в состоянии использовать sedс Nкомандой, но хитрость будет состоять в том, чтобы удалять одну строку из пространства образца каждый раз, когда вы добавляете другую (так, чтобы пространство образца всегда содержало только 2 последовательные строки, вместо того, чтобы пытаться читать полностью). файл) - попробуй

sed ':a;$!N;s/>\n/>/;P;D;ba'

РЕДАКТИРОВАТЬ: после перечитывания объяснения знаменитых Sed One-Liners Петерис Круминьш я думаю, что лучшее sedрешение будет

sed -e :a -e '/>$/N; s/\n//; ta'

который добавляет только следующую строку в том случае, если >в конце уже выполнено совпадение, и должен условно вернуться назад, чтобы обработать случай последовательных совпадающих строк (это 39 Крумина. Добавить строку к следующей, если она заканчивается обратной косой чертой «\» именно для замещения исключением >для \как присоединиться характер, и тот факт , что присоединиться символ сохраняется на выходе).

steeldriver
источник
2
Это не сработает, если две последовательные строки заканчиваются >(это также специфично для GNU)
Стефан Шазелас
1

sedне обеспечивает способ вывода вывода без окончательного перевода строки. Ваш подход, основанный на использовании, в Nосновном работает, но хранит неполные строки в памяти и, следовательно, может потерпеть неудачу, если строки становятся слишком длинными (внедрения sed обычно не предназначены для обработки очень длинных строк).

Вместо этого вы можете использовать awk.

awk '{if (/<$/) printf "%s", $0; else print}'

Альтернативный подход - использовать trдля замены символа новой строки «скучный», часто встречающийся символ. Здесь может сработать пробел - выберите символ, который имеет тенденцию появляться в каждой строке или, по крайней мере, в значительной части строк в ваших данных.

tr ' \n' '\n ' | sed 's/> />/g' | tr '\n ' ' \n'
Жиль "ТАК - перестань быть злым"
источник
Оба метода уже продемонстрированы здесь для лучшего эффекта в других ответах. И его подход с sedне работает без буфера 2,5 ГБ.
mikeserv
Кто-нибудь упоминал awk? О, я пропустил это, я только почему-то заметил perl в ответе Тердона. Никто не упомянул этот trподход - mikeserv, вы опубликовали другой (действительный, но менее общий) подход, который также используется tr.
Жиль "ТАК - перестань быть злым"
правильные, но менее общие звуки для меня, как вы только что назвали это рабочим, целевым решением. я думаю, что трудно утверждать, что такая вещь не полезна, что странно, потому что у нее 0 голосов. Самое большое различие, которое я вижу между моим собственным решением и вашим более общим предложением, заключается в том, что мое решение конкретно решает проблему, а ваше - вообще. Это может сделать его стоящим - и я могу даже поменять свой голос - но есть и надоедливый вопрос о 7 часах между ними и повторяющаяся тема ваших ответов, подражая другим. Вы можете это объяснить?
mikeserv
1

что насчет использования ed?

ed -s test.txt <<< $'/fruits/s/apple/banana/g\nw'

(через http://wiki.bash-hackers.org/howto/edit-ed )

андрей
источник
отредактировано, больше нет зависимости от веб-сайта
andrej
-1

Есть много способов сделать это, и большинство из них действительно хороши, но я думаю, что это мой любимый:

tr '>\n' '\n>' | sed 's/^>*//;H;/./!d;x;y/\n>/>\n/'

Или даже:

tr '>\n' '\n>' | sed 's/^>*//' | tr '\n>' '>\n'
mikeserv
источник
Я не могу получить ваш первый ответ на работу вообще. Хотя я восхищаюсь элегантностью второго, я считаю, что вам нужно удалить *. Теперь он удалит все пустые строки, следующие за строкой, заканчивающейся на >. ... хм. Оглядываясь назад на вопрос, я вижу, что это немного неоднозначно. Вопрос говорит: «Я хочу , чтобы удалить все переводы строк , которые происходят после того, как >...» Я расцениваю , что означает , что >\n\n\n\n\nfooдолжно быть изменено \n\n\n\nfoo, но я полагаю , fooможет быть желаемым результатом.
Скотт
@ Скотт - я проверил с вариациями на следующее: printf '>\n>\n\n>>\n>\n>>>\n>\nf\n\nff\n>\n' | tr '>\n' '\n>' | sed 's/^>*//;H;/./!d;x;y/\n>/>\n/'- это дает >>>>>>>>>>f\n\nff\n\nмне первый ответ. Мне любопытно, что вы делаете, чтобы сломать это, потому что я хотел бы это исправить. Что касается второго пункта - я не согласен, что это неоднозначно. OP не просит , чтобы удалить все > предшествующие на \newline, но вместо того, чтобы удалить все \n ewlines следующего> .
mikeserv
1
Да, но правильная интерпретация заключается в том, что >\n\n\n\n\nтолько после первого символа новой строки следует после >; все остальные следуют другим переводам. Обратите внимание, что предложение ОП «это то, что я хочу, если бы это сработало» sed -e 's/>\n/>/g', не было sed -e 's/>\n*/>/g'.
Скотт
1
@ Скотт - предложение не сработало и никогда не могло. Я не верю, что предложение о коде того, кто не полностью понимает код, может считаться допустимым интерпретирующим аргументом, как простой язык, который этот человек также использует. И кроме того, выход - если он действительно работал - от s/>\n/>/по - >\n\n\n\n\nпрежнему будет то , что s/>\n/>/будет править.
mikeserv