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


10

С bashоболочкой, в файле со строками, подобными следующим

first "line"
<second>line and so on

Я хотел бы, чтобы заменить один или несколько вхождений "line"\n<second>с other charactersи получить каждый раз , когда :

first other characters line and so on

Поэтому я должен заменить строку как специальными символами, такими как "и, так <и символом новой строки.

После поиска между другими ответами я обнаружил, что sedможет принимать переводы строк в правой части команды (так, в other charactersстроке), но не в левой.

Есть ли способ (проще, чем этот ) получить этот результат с помощью sedили grep?


ты работаешь с Mac? \nзаявление ewline вы делаете почему я спрашиваю. люди редко спрашивают, могут ли они сделать то s//\n/же самое, что и вы с GNU sed, хотя большинство других sedотклонят этот побег с правой стороны. тем не менее, \nescape будет работать слева в любом POSIX, sedи вы можете переносить их, как y/c/\n/будто это будет иметь тот же эффект, что s/c/\n/gи не всегда так полезно.
— mikeserv

Ответы:


3

Три разные sedкоманды:

sed '$!N;s/"[^"]*"\n<[^>]*>/other characters /;P;D'

sed -e :n -e '$!N;s/"[^"]*"\n<[^>]*>/other characters /;tn'

sed -e :n -e '$!N;/"$/{$!bn' -e '};s/"[^"]*"\n<[^>]*>/other characters /g'

Все они s///основаны на основной команде ubstitution:

s/"[^"]*"\n<[^>]*>/other characters /

Они также все стараются позаботиться об обработке последней строки, так как seds имеют тенденцию различаться по своему выводу в крайних случаях. Это значение $!адреса, соответствующего каждой строке, которая !не является $последней.

Все они также используют команду Next, чтобы добавить следующую строку ввода к \nпробелу шаблона после символа ewline. Любой, кто занимался sedкакое-то время, научится полагаться на \nперсонажа ewline - потому что единственный способ получить его - это явно поместить его туда.

Все три делают некоторую попытку прочитать как можно меньше входных данных, прежде чем предпринимать какие-либо действия - sedдействуют так быстро, как это возможно, и не нужно читать весь входной файл перед этим.

Хотя они делают все N, они все три отличаются по своим методам рекурсии.

Первая команда

Первая команда использует очень простой N;P;Dцикл. Эти три команды встроены в любую POSIX-совместимую систему sedи прекрасно дополняют друг друга.

  • N- как уже упоминалось, добавляет Nстроку ввода ext в шаблонное пространство после вставленного \nразделителя ewline.
  • P- как p; он Pзапечатлевает шаблонное пространство - но только до первого встречающегося \nсимвола ewline. И так, с учетом следующего ввода / команды:

    • printf %s\\n one two | sed '$!N;P;d'
  • sed Pзвонит только один . Тем не менее, с ...

  • D- как d; он Dвыбирает шаблонное пространство и начинает другой цикл строки. В отличие от d , Dудаляет только до первой \nвстречной линии в шаблонном пространстве. Если после \nсимвола ewline в шаблонном пространстве больше, sedначинается следующий цикл строки с тем, что остается. Если dв предыдущем примере было заменено на D, например, sedбудет Pнабирать как один, так и два .

Эта команда повторяется только для строк, которые не соответствуют s///выражению ubstitution. Поскольку s///ubstitution удаляет \newline, добавленный с помощью N, при sed Dвыборке шаблон-пространства ничего не остается .

Можно выполнить тесты для применения Pи / или Dвыборочно, но есть и другие команды, которые лучше подходят для этой стратегии. Поскольку рекурсия реализована для обработки последовательных строк , которые соответствуют только части правила замены, последовательные последовательности линий , соответствующих оба конца на s///ubstitution не работают хорошо .:

Учитывая этот вклад:

first "line"
<second>"line"
<second>"line"
<second>line and so on

... это печатает ...

first other characters "line"
<second>other characters line and so on

Это, однако, обрабатывать

first "line"
second "line"
<second>line

...просто хорошо.

Вторая команда

Эта команда очень похожа на третью. Оба используют ярлык :bранчо / test (как также продемонстрировано в ответе Джозефа Р. здесь ) и возвращаются к нему при определенных условиях.

  • -e :n -e- переносимые sedсценарии разграничивают определение :метки либо с помощью \newline, либо с помощью нового встроенного -eоператора xecution.
    • :n- определяет метку с именем n. Это может быть возвращено в любое время с помощью bnили tn.
  • tn- команда test возвращается к указанной метке (или, если она не указана , выходит из сценария для текущего цикла строки), если s///возникла какая-либо замена, поскольку либо метка была определена, либо поскольку она в последний раз называлась tуспешной проверкой.

В этой команде рекурсия происходит для совпадающих строк. Если sedуспешно заменить шаблон с другими символами , sedвозвращается к :nметке и пытается снова. Если s///замена не выполняется, sedавтоматически печатается шаблонное пространство и начинается следующий цикл строки.

Это имеет тенденцию обрабатывать последовательные последовательности лучше. Там, где последний провалился, это печатает:

first other characters other characters other characters line and so on

Третья команда

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

  • /"$/bn- это sedтест. Поскольку команда bранчо является функцией этого адреса, sedона bвернется на ранчо только :nпосле добавления \newline, и пространство шаблона все еще заканчивается "двойной кавычкой .

Между Nи bкак можно меньше делается - таким образом sedможно очень быстро собрать ровно столько информации, сколько необходимо, чтобы гарантировать, что следующая строка не может соответствовать вашему правилу. В s///ubstitution отличается здесь в том , что она использует gЛОБАЛЬНЫЙ флаг - и поэтому он будет делать все необходимые замены сразу. При одинаковом вводе эта команда выводит идентично последнему.


Извините за тривиальный вопрос, но в чем смысл DATAи как вы получаете ввод текста?
— BowPark

@BowPark - в этом примере <<\DATA\ntext input\nDATA\nзапекается, но это только текст, передаваемый sedоболочкой в документе здесь . Это будет работать так же, как sed 'script' filenameили process that writes to stdout | sed 'script'. Это помогает?
— mikeserv

Да, спасибо! Почему без Dкаждой модифицированной строки двойная? (Вы использовали это по мере необходимости; возможно, я не sedочень хорошо знаю )
— BowPark

1
@BowPark - вы получаете удвоения при пропуске, Dпотому что в Dпротивном случае Dвыводится из вывода то, что вы теперь видите удвоенным. Я только что сделал правку - и я могу расширить это также в ближайшее время.
— mikeserv

1
@BowPark - хорошо, я обновил его и предоставил опции. Это может быть немного легче читать / понимать сейчас. Я также явно обратился к этой Dвещи.
— mikeserv

7

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

  1. Perl

    Для того, чтобы заменить каждое вхождение "line"\n<second>с other characters, использованием:

    $ perl -00pe 's/"line"\n<second>/other characters /g' file
    first other characters line and so on
    

    Или, чтобы рассматривать несколько последовательных вхождений "line"\n<second>как одно и заменить все из них одним other characters, используйте:

    perl -00pe 's/(?:"line"\n<second>)+/other characters /g' file
    

    Пример:

    $ cat file
    first "line"
    <second>"line"
    <second>"line"
    <second>line and so on
    $ perl -00pe 's/(?:"line"\n<second>)+/other characters /g' file
    first other characters line and so on
    

    -00Приводит к Perl , чтобы прочитать файл в режиме «пункт» , который означает , что «линия» определяется путем \n\nвместо \n, по сути, каждый пункт рассматриваются как линия. Таким образом, подстановка совпадает с новой строкой.

  2. AWK

    $  awk -v RS="\n\n" -v ORS="" '{
          sub(/"line"\n<second>/,"other characters ", $0)
          print;
        }' file 
    first other characters line and so on
    

    По той же самой основной идее мы устанавливаем разделитель записей ( RS), чтобы он \n\nхранил весь файл, затем разделитель выходных записей - ничто (в противном случае выводится дополнительная новая строка), а затем используем sub()функцию для выполнения замены.


2
@mikeserv? Который из? Второй, как предполагается, ФП сказал, что они хотят «заменить одно или несколько вхождений», поэтому употребление этого абзаца вполне может оказаться тем, что они ожидают.
— Terdon

очень хороший момент. Полагаю, что я больше фокусировался и получаю каждый раз , но не совсем ясно, должна ли это быть одна замена на вхождение или одна замена на последовательность вхождений ... @BowPark?
— mikeserv

Требуется одна замена в каждом случае.
— BowPark

@BowPark Хорошо, тогда первый подход perl или awk должны работать. Разве они не дают желаемый результат?
— Тердон

Это работает, спасибо, но третья строка awkдолжна быть print;}' file. Мне нужно избегать Perl и предпочтительно использовать sed, в любом случае вы предложили хорошие альтернативы.
— BowPark

6

прочитайте весь файл и сделайте глобальную замену:

sed -n 'H; ${x; s/"line"\n<second>/other characters /g; p}' <<END
first "line"
<second> line followed by "line"
<second> and last
END
first other characters  line followed by other characters  and last

Да. Это работает, но что, если у меня есть несколько случаев?
— BowPark

Да, верно. Исправлено
— Гленн Джекман

1
Извините, что придираюсь еще раз, но ${cmds}это специфично для GNU - большинству других sedтребуется \newline или -eразрыв между pи }. Вы можете полностью избежать скобок - и переносимо - и даже не вставлять дополнительный \nсимвол ewline в первую строку, например:sed 'H;1h;$!d;x;s/"line"\n<second>/other characters /g'
— mikeserv

Я проверил это, и это кажется не портативным. Он печатает дополнительную новую строку в начале вывода, но результат корректен в GNU.
— BowPark

Чтобы удалить ведущий sed -n '1{h;n};H; ${x; s/"line"\n<second>/other characters /g; p}'символ новой строки: - однако это становится неосуществимым.
— Гленн Джекман

3

Вот вариант ответа glenn, который будет работать, если у вас есть несколько последовательных вхождений (работает sedтолько с GNU ):

sed ':x /"line"/N;s/"line"\n<second>/other characters/;/"line"/bx' your_file

Это :xпросто метка для ветвления. По сути, это то, что он проверяет строку после подстановки и, если она все еще совпадает "line", возвращается к :xметке (вот что bxделает), добавляет еще одну строку в буфер и начинает обрабатывать ее.


@mikeserv Пожалуйста, уточните, что вы имеете в виду. Это сработало для меня.
— Джозеф Р.

@mikeserv Извините, я действительно не знаю, о чем вы говорите. Я скопировал вышеупомянутую строку кода обратно в мой терминал, и она работала правильно.
— Джозеф Р.

1
retracted - это, очевидно, работает в GNU, sedкоторый обрабатывает не-POSIX-метки достаточно далеко, чтобы принять пробел в качестве разделителя для объявления метки. Тем не менее, вы должны отметить, что любой другой sedтам потерпит неудачу - и потерпит неудачу N. GNU sedнарушает правила POSIX для печати пространства шаблонов перед выходом Nв последней строке, но POSIX дает понять, что если Nкоманда читается в последней строке, ничего печатать не следует.
— mikeserv

Если вы отредактируете пост, указав GNU, я откажусь от своего голосования и удалю эти комментарии. Кроме того, возможно, стоит узнать о vкоманде GNU, которая разбивается на части, sedно не работает в GNU версии 4 и выше.
— mikeserv

1
в этом случае я буду предлагать один больше - это может быть сделано переносимо , как: sed -e :x -e '/"line"/{$!N' -e '};s/"line"\n<second>/other characters/;/"line"/bx'.
— mikeserv
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.