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

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?

BowPark
источник
ты работаешь с 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ЛОБАЛЬНЫЙ флаг - и поэтому он будет делать все необходимые замены сразу. При одинаковом вводе эта команда выводит идентично последнему.

mikeserv
источник
Извините за тривиальный вопрос, но в чем смысл 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