Смущает вывод sed при использовании N. Может кто-нибудь объяснить эти результаты?

8

Я учусь Сед. Казалось, что все идет хорошо, пока я не наткнулся на N (многострочный следующий). Я создал этот файл (guide.txt) для практики / понимания / контекста. Вот содержимое указанного файла ...

This guide is meant to walk you through a day as a Network
Administrator. By the end, hopefully you will be better
equipped to perform your duties as a Network Administrator
and maybe even enjoy being a Network Administrator that much more.
Network Administrator
Network Administrator
I'm a Network Administrator

Поэтому моя цель - заменить ВСЕ экземпляры «Администратор сети» на «Системный пользователь». Поскольку первый экземпляр «Администратора сети» отделен новой строкой (\ n), мне нужен многострочный оператор следующего оператора (N), чтобы добавить строку, начинающуюся с «Администратор», с предыдущей строкой, заканчивающейся «Сеть \ n» , Нет проблем. Но я также хочу перехватить все другие однострочные экземпляры «Администратор сети».

Из моего исследования я узнал, что мне понадобятся две команды замещения; один для новой строки, разделенной строкой, и один для остальных. Кроме того, произошел некоторый джайв из-за последней строки, содержащей совпадение подстановки и следующей многострочной. Так что я создаю это ...

$ sed '
> s/Network Administrator/System User/
> N
> s/Network\nAdministrator/System\nUser/
> ' guide.txt

Это возвращает эти результаты ...

This guide is meant to walk you through a day as a System
User. By the end, hopefully you will be better
equipped to perform your duties as a System User
and maybe even enjoy being a Network Administrator that much more.
System User
Network Administrator
I'm a System User

Я думал, что однострочная подстановка перехватит все «нормальные» экземпляры «Network Administrator» и поменяет их на «System User», тогда как многострочный оператор сработает с магией на экземпляре, отделенном символом новой строки, но, как вы могу видеть, что это вернуло, что я считаю, неожиданные результаты.

После некоторой возни я приземлился на это ...

$ sed '
> s/Network Administrator/System User/
> N
> s/Network\nAdministrator/System\nUser/
> s/Network Administrator/System User/
> ' guide.txt

И вуаля, я получаю желаемый результат ...

This guide is meant to walk you through a day as a System
User. By the end, hopefully you will be better
equipped to perform your duties as a System User
and maybe even enjoy being a System User that much more.
System User
System User
I'm a System User

Почему это работает, а оригинальный скрипт sed - нет? Я очень хочу это понять.

Заранее благодарю за любую помощь.

dlowrie290
источник
Хорошо, что вы изучили Sed! Я использовал Sed для решения очень похожего вопроса ранее на этом сайте; может представлять интерес.
Wildcard
И два других хитрых примера Sed: unix.stackexchange.com/a/277375/135943 , unix.stackexchange.com/a/257913/135943
подстановочный

Ответы:

6

Пока вы sedучитесь, я потрачу время, чтобы добавить ответ @ John1024:

1) Обратите внимание, что вы используете \nв строке замены. Это работает в GNU sed, но не является частью POSIX, поэтому он вставит обратную косую черту и nво многие другие sed(используя \nв шаблоне переносимый, кстати).

Вместо этого я предлагаю сделать s/Network\([[:space:]]\)Administrator/System\1Us‌​er/g: The [[:space:]]будет соответствовать новой строке или пробелу, поэтому вам не нужно две sкоманды, но объедините их в одну. Окружив его, \(...\)вы можете ссылаться на него в замене: он \1будет заменен тем, что было найдено в первой паре \(\).

2) Чтобы правильно сопоставить шаблоны по двум строкам, вы должны знать N;P;Dшаблон:

 sed '$!N;s/Network\([[:space:]]\)Administrator/System\1User/g;P;D'

NВсегда добавьте следующую строку (для последней строки , за исключением, поэтому это «имя» с $!(= если не последняя строка, вы всегда должны рассмотреть , чтобы предшествовать Nс , $!чтобы избежать случайного окончания сценария) Затем , после замены в. PТолько печатает первая строка в пространстве шаблона и Dудаляет эту строку и начинает следующий цикл с остатками пространства шаблона (без чтения следующей строки). Это, вероятно, то, что вы изначально хотели.

Запомните этот шаблон, он вам часто понадобится.

3) Еще один полезный шаблон для многострочного редактирования, особенно когда задействовано более двух строк: задержать сбор проб, как я предложил Джону:

sed 'H;1h;$!d;g;s/Network\([[:space:]]\)Administrator/System\1Us‌​er/g'

Я повторяю это, чтобы объяснить это: Hдобавляет каждую строку к пробелу. Так как это приведет к дополнительному переводу строки перед первой строкой, необходимо добавить первую строку вместо добавления 1h. Следующее $!dозначает «для всех строк, кроме последней, удалите пробел и начните сначала». Таким образом, остальная часть сценария выполняется только для последней строки. На этом этапе весь файл собирается в удерживающем пространстве (поэтому не используйте его для очень больших файлов!) И gперемещает его в пространство образца, так что вы можете выполнять все замены сразу, как вы можете с -zопцией GNU sed.

Это еще одна полезная модель, которую я предлагаю иметь в виду.

Philippos
источник
Вот Это Да! Отличное объяснение! Это в сочетании с ответом Джона действительно дало мне лучшее понимание этой проблемы и успокоило в целом. Похоже, мне нужно многому научиться. Я хотел бы проверить оба ваших решения в качестве ответов. Большое спасибо за ваши усилия. Они очень ценятся.
dlowrie290
7

Во-первых, обратите внимание, что ваше решение на самом деле не работает. Рассмотрим этот тестовый файл:

$ cat test1
Network
Administrator Network
Administrator

И затем выполните команду:

$ sed '
 s/Network Administrator/System User/
 N
 s/Network\nAdministrator/System\nUser/
 s/Network Administrator/System User/
 ' test1
System
User Network
Administrator

Проблема в том, что код не заменяет последний Network\nAdministrator.

Это решение работает:

$ sed ':a; /Network$/{$!{N;ba}}; s/Network\nAdministrator/System\nUser/g; s/Network Administrator/System User/g' test1
System
User System
User

Мы также можем применить это к вашему guide.txt:

$ sed ':a; /Network$/{$!{N;ba}}; s/Network\nAdministrator/System\nUser/g; s/Network Administrator/System User/g' guide.txt 
This guide is meant to walk you through a day as a System
User. By the end, hopefully you will be better
equipped to perform your duties as a System User
and maybe even enjoy being a System User that much more.
System User
System User
I'm a System User

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

Примечание о совместимости: все вышеперечисленное используется \nв тексте замены. Это требует GNU sed. Это не будет работать на седе BSD / OSX.

[Шляпа на Филиппосе .]

Многострочная версия

Если это поможет уточнить, вот та же команда, разделенная на несколько строк:

$ sed ':a
    /Network$/{
       $!{
           N
           ba
       }
    }
    s/Network\nAdministrator/System\nUser/g
    s/Network Administrator/System User/g
    ' filename

Как это работает

  1. :a

    Это создает ярлык a.

  2. /Network$/{ $!{N;ba} }

    Если эта строка заканчивается на Network, то, если это не последняя строка ( $!), прочитайте и добавьте следующую строку ( N) и вернитесь к label a( ba).

  3. s/Network\nAdministrator/System\nUser/g

    Сделайте замену с промежуточным переводом строки.

  4. s/Network Administrator/System User/g

    Сделайте замену с промежуточным пробелом.

Более простое решение (только GNU)

С GNU sed ( не BSD / OSX) нам нужна только одна команда замены:

$ sed -zE 's/Network([[:space:]]+)Administrator/System\1User/g' test1
System
User System
User

И в guide.txtфайле:

$ sed -zE 's/Network([[:space:]]+)Administrator/System\1User/g' guide.txt 
This guide is meant to walk you through a day as a System
User. By the end, hopefully you will be better
equipped to perform your duties as a System User
and maybe even enjoy being a System User that much more.
System User
System User
I'm a System User

В этом случае -zговорит sed читать до первого NUL-символа. Поскольку текстовые файлы никогда не имеют нулевого символа, это приводит к чтению всего файла за один раз. Затем мы можем сделать замену, не беспокоясь о пропущенной строке.

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

Решение, которое работает как на GNU, так и на BSD sed

Как предположил Филлипос , следующее решение является переносимым:

sed 'H;1h;$!d;x;s/Network\([[:space:]]\)Administrator/System\1Us‌​er/g'
John1024
источник
1
Отличная информация, Джон! Спасибо, что пролили свет на это, и ваше альтернативное решение очень приятно. При этом я до сих пор не понимаю, почему мое решение не является решением. Похоже, что работает, но с вашим файлом test.txt это не так. Почему мое решение работает, но не работает? Большое спасибо за помощь.
dlowrie290
1
@ dlowrie290 Ваше решение читается в строках попарно. Если Network Administratorразделить между первой и второй строкой этой пары, ваше решение успешно выполнит замену. Затем он печатает эти две строки и читает следующую пару. Однако, если вторая строка первой пары заканчивается, Networkа первая строка второй пары начинается с Administrator, код пропускает ее. Мой код избегает этого, читая в строках, пока не найдет тот, который не заканчивается Network.
John1024
2
Обратите внимание, что ваше первое многострочное решение также зависит от расширений GNU sed: \nв замене не определено в стандарте. sed 'H;1h;$!d;x;s/Network\([[:space:]]\)Administrator/System\1User/g'это портативный способ сделать это.
Филиппос
@Philippos Отличные очки. Ответ обновлен, чтобы включить портативное решение.
John1024
1
Спасибо за разъяснения, Джон! Опять же, отличные вещи и ваше время / усилия очень ценятся!
dlowrie290