Warum ist `sed expr1 | sed expr2` unterscheidet sich von `sed -e expr1 -e expr2`

10

Ich habe die Ausgabe von aufgeteilt id, um eine besser lesbare zeilenweise Liste der Gruppen bereitzustellen, zu denen ein Benutzer gehört:

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

Ich wollte die Gruppennummer von ihrem Namen in Klammern trennen, also habe ich den Ausdruck so erweitert

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

Dies hat jedoch nicht so funktioniert, wie ich es ursprünglich erwartet hatte. Der zweite Ausdruck schien keine Wirkung zu haben.

Um das gewünschte Ergebnis zu erzielen, musste ich stattdessen zwei separate sedBefehle ausführen:

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)

Warum brauche ich zwei sedBefehle in einer Pipe anstatt einen mit mehreren Anweisungen? Oder wenn ich das mit einem machen kann sed, wie würde ich das machen?

Was ich besonders möchte, ist, dass für jedes einzelne Element (einschließlich der UID und der GIDs in der ersten Zeile) ein Leerzeichen zwischen dem UID / GID-Wert und dem Namen in Klammern steht. Die Einschränkung besteht jedoch darin, dass ich in meinen realen Daten Gruppen haben kann Ich möchte nicht, dass die Namen selbst verstümmelt werden.

Roaima
quelle

Antworten:

14

sed, wie awkoder cutoder perl -nearbeitet in jeder Zeile einzeln nacheinander.

sed -e code1 -e code2

wird tatsächlich ausgeführt als:

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

Wenn Ihr Code2 ist 2,$ s/foo/bar/, ist das:

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

Da Ihre Eingabe nur eine Zeile enthält, sub()wird die niemals ausgeführt.

Das Einfügen von Zeilenumbrüchen in den Musterbereich in erhöht code1die linenumberErhöhung nicht.

Stattdessen haben Sie einen Musterbereich mit mehreren Zeilen, während Sie die erste und einzige Eingabezeile verarbeiten. Wenn Sie Änderungen an der zweiten Zeile und darüber des mehrzeiligen Musterbereichs vornehmen möchten, müssen Sie Folgendes tun:

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

Natürlich können Sie hier auch die beiden Operationen auf einmal ausführen:

id | sed 's/,\([^(]*\)(/\n\t\1 (/g'
Stéphane Chazelas
quelle
awk und perl -n / p funktionieren für jeden Datensatz, der standardmäßig eine Zeile enthält, aber geändert werden kann. in diesem Fall -vRS=,oder -054könnte helfen.
Dave_thompson_085
5

Wenn Sie GNU sed haben, könnten Sie verwenden

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

Dies fügt ein Leerzeichen vor dem 4. und den nachfolgenden offenen Klammern hinzu und ersetzt dann die Kommas.

Glenn Jackman
quelle
1
Das sieht interessant aus. Leider wirkt es sich auch auf Gruppennamen aus, die Klammern wie mein Beispiel enthalten, international (uk) locationindem ein unerwünschtes Leerzeichen in den Namen selbst eingefügt wird .
Roaima
Verwenden Sie dann s/\([[:digit:]]\+\)(/\1 (/4g, dass nur dann ein Leerzeichen hinzugefügt wird, wenn vor der Klammer Ziffern stehen.
Glenn Jackman
1

Was @ stéphane-chazelas gesagt hat, ist wahr, aber Sie können das Leerzeichen immer zuerst hinzufügen und danach wie folgt in Zeilen aufteilen:

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

Oder in einem einzigen sed-Skript (ohne -e):

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

Normalerweise verwenden wir " /" als Trennzeichen für Befehlssuche (n), aber es akzeptiert auch jedes Zeichen. Daher ist es manchmal einfacher, mit anderen Zeichen wie " :" zu lesen , um Kombinationen wie " /\" zu vermeiden .

WPomier
quelle