Dlaczego `sed expr1 | sed expr2` różni się od `sed -e expr1 -e expr2`

10

Dzieliłem dane wyjściowe, idaby zapewnić bardziej czytelną listę grup po linii, do której należy użytkownik:

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

Chciałem oddzielić numer grupy od nazwy w nawiasach, więc rozszerzyłem wyrażenie w ten sposób

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

Nie działało to jednak tak, jak początkowo oczekiwałem. Drugie wyrażenie wydawało się nie mieć żadnego efektu.

Zamiast tego, aby uzyskać pożądany wynik, musiałem uruchomić dwa osobne sedpolecenia, takie jak to:

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)

Dlaczego potrzebuję dwóch sedpoleceń w potoku zamiast jednego z wieloma instrukcjami? A jeśli mogę to zrobić za pomocą jednego sed, jak mam to zrobić?

Szczególnie chciałbym mieć pojedynczą spację między wartością UID / GID a jej nazwą w nawiasach kwadratowych dla każdego pojedynczego elementu (w tym UID i GID w pierwszym wierszu), ale zastrzeżeniem jest to, że w moich rzeczywistych danych mogę mieć grupy zawierające w nazwach nawiasy klamrowe i nie chcę, aby same nazwy były zniekształcone.

roaima
źródło

Odpowiedzi:

14

sed, awklub cutlub perl -nedziała na każdej linii osobno jedna po drugiej.

sed -e code1 -e code2

jest faktycznie uruchamiany jako:

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

Jeśli twój kod2 to 2,$ s/foo/bar/, to:

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

Ponieważ dane wejściowe mają tylko jedną linię, sub()nigdy nie zostaną uruchomione.

Wstawianie znaków nowej linii w przestrzeni wzoru code1nie powoduje linenumberzwiększenia.

Zamiast tego masz jedną przestrzeń wzorcową z kilkoma liniami w trakcie przetwarzania pierwszego i jedynego wiersza wprowadzania. Jeśli chcesz wykonać modyfikacje w drugiej linii i powyżej tego wieloliniowego obszaru wzorów, musisz zrobić coś takiego:

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

Chociaż tutaj oczywiście równie dobrze możesz wykonać dwie operacje za jednym razem:

id | sed 's/,\([^(]*\)(/\n\t\1 (/g'
Stéphane Chazelas
źródło
awk i perl -n / p działa na każdym rekordzie, który domyślnie ma linię, ale można go zmienić; w tym przypadku -vRS=,lub -054może pomóc.
dave_thompson_085
5

Jeśli masz GNU sed, możesz użyć

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

który dodaje spację przed 4. i kolejnymi otwartymi nawiasami, a następnie zastępuje przecinki.

Glenn Jackman
źródło
1
To wygląda interesująco. Niestety wpływa to również na nazwy grup zawierające nawiasy, takie jak mój przykład international (uk) location, poprzez wstawienie niechcianej spacji w samej nazwie.
roaima
Następnie użyj, s/\([[:digit:]]\+\)(/\1 (/4gktóra doda spację tylko wtedy, gdy przed nawiasiem znajdują się cyfry.
glenn jackman
1

To, co powiedział @ stéphane-chazelas, jest prawdą, ale zawsze możesz najpierw dodać spację i podzielić na linie w następujący sposób:

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

Lub w jednym skrypcie sed (bez -e):

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

Zwykle używamy „ /” jako separatora wyszukiwania poleceń, ale akceptuje on także dowolny znak, więc czasami łatwiej jest czytać przy użyciu innych znaków, takich jak „ :”, aby uniknąć kombinacji takich jak „ /\”.

WPomier
źródło