Почему `sed expr1 | sed expr2` отличается от `sed -e expr1 -e expr2`

10

Я разделил вывод, idчтобы обеспечить более читаемый построчный список групп, членом которых является пользователь:

id roaima | sed 's/,/\n\t/g'
uid=1001(roaima) gid=1001(roaima) groups=1001(roaima)
    24(cdrom)
    25(floppy)
    ...
    822413650 (international (uk) location)

Я хотел отделить номер группы от имени в скобках, поэтому я расширил выражение следующим образом

id roaima | sed -e 's/,/\n\t/g' -e '2,$s/(/ (/'

Однако, это не действовало, как я первоначально ожидал. Второе выражение, казалось, не имело никакого эффекта.

Вместо этого, чтобы получить желаемый результат, мне нужно было выполнить две отдельные sedкоманды, например:

id roaima | sed -e 's/,/\n\t/g' | sed '2,$s/(/ (/'
uid=1001(roaima) gid=1001(roaima) groups=1001(roaima)
    24 (cdrom)
    25 (floppy)
    ...
    822413650 (international (uk) location)

Зачем мне нужны две sedкоманды в конвейере, а не одна с несколькими инструкциями? Или, если я могу сделать это с одним sed, как бы я это сделал?

Что мне особенно хотелось бы, так это иметь один пробел между значением UID / GID и его именем в скобках для каждого отдельного элемента (включая UID и GID в первой строке), но предостережение заключается в том, что в моих реальных данных я могу иметь группы содержащие скобки в своих именах, и я не хочу, чтобы имена сами были искажены.

roaima
источник

Ответы:

14

sed, вроде awkили cutили perl -neработает в каждой строке индивидуально один за другим.

sed -e code1 -e code2

на самом деле работает как:

while(patternspace = getline()) {
  linenumber++
  code1
  code2
} continue {print patternspace}

Если ваш код2 есть 2,$ s/foo/bar/, это:

if (linenumber >= 2) sub(/foo/, "bar", patternspace)

Поскольку ваш ввод содержит только одну строку, sub()он никогда не будет запущен.

Вставка символов новой строки в пространство шаблона code1не приводит к linenumberувеличению.

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

s/\(\n[^(]*\)(/\1 (/g

Хотя здесь, конечно, вы могли бы также выполнить две операции за один раз:

id | sed 's/,\([^(]*\)(/\n\t\1 (/g'
Стефан Шазелас
источник
awk и perl -n / p работают с каждой записью, которая по умолчанию является строкой, но может быть изменена; в этом случае -vRS=,или -054может помочь.
dave_thompson_085
5

Если у вас есть GNU sed, вы можете использовать

id username | sed 's/(/ (/4g; s/,/\n\t/g'

который добавляет пробел перед 4-й и последующими открытыми скобками, а затем заменяет запятые.

Гленн Джекман
источник
1
Это выглядит интересно. К сожалению, это также влияет на имена групп, которые содержат скобки, такие как мой пример international (uk) location, путем вставки нежелательного пробела в само имя.
роайма
Затем используйте, s/\([[:digit:]]\+\)(/\1 (/4gкоторый будет добавлять пробел только если есть цифры перед скобкой.
Гленн Джекман
1

То, что сказал @ stéphane-chazelas, верно, но вы всегда можете сначала добавить пробел и разбить на строки после этого:

sed -e 's:\([,=][0-9]*\):\1 :g' -e 's:,:\n\t:g'

Или в одном сценарии sed (без -e):

sed 's:\([,=][0-9]*\):\1 :g; s:,:\n\t:g'

Обычно мы используем " /" в качестве разделителя командного поиска (й), но он также принимает любой символ, поэтому иногда легче читать, используя другие символы типа " :", чтобы избежать комбинаций типа " /\".

WPomier
источник