Regex-Wechsel / oder Operator (foo | bar) in GNU oder BSD Sed


28

Ich kann es scheinbar nicht zum Laufen bringen. Die GNU sed-Dokumentation sagt, dass man aus der Pipe fliehen soll, aber das funktioniert nicht und auch nicht, wenn man eine gerade Pipe ohne Flucht benutzt. Das Hinzufügen von Parens macht keinen Unterschied.

$ echo 'cat
dog
pear
banana
cat
dog' | sed 's/cat|dog/Bear/g'
cat
dog
pear
banana
cat
dog

$ echo 'cat
dog
pear
banana
cat
dog' | sed 's/cat\|dog/Bear/g'
cat
dog
pear
banana
cat
dog

Antworten:


33

Standardmäßigsed werden POSIX Basic Regular Expressions verwendet , die den |Alternationsoperator nicht enthalten . Viele Versionen sed, einschließlich GNU und FreeBSD, unterstützen den Wechsel zu Extended Regular Expressions , die auch |Alternation enthalten. Wie Sie das tun, ist unterschiedlich: GNU sed verwendet-r , während FreeBSD , NetBSD , OpenBSD und OS X sed verwenden -E. Andere Versionen unterstützen es meist überhaupt nicht. Sie können verwenden:

echo 'cat dog pear banana cat dog' | sed -E -e 's/cat|dog/Bear/g'

und es wird auf diesen BSD-Systemen und sed -rmit GNU funktionieren .


GNU sedscheint völlig undokumentiert zu sein, aber es funktioniert. -EWenn Sie also ein Multi-Plattform-Skript haben, das auf das Obige beschränkt ist, ist dies Ihre beste Option. Da es nicht dokumentiert ist, können Sie sich wahrscheinlich nicht wirklich darauf verlassen.

In einem Kommentar wird darauf hingewiesen, dass die BSD-Versionen auch -rundokumentierte Aliasnamen unterstützen. OS X funktioniert heute noch nicht und die älteren NetBSD- und OpenBSD-Maschinen, auf die ich Zugriff habe, auch nicht, aber die NetBSD 6.1-Version. Die kommerziellen Unices, die ich erreichen kann, gibt es nicht. Trotzdem wird die Frage nach der Portabilität an dieser Stelle ziemlich kompliziert, aber die einfache Antwort ist, zu wechseln,awk wenn Sie es brauchen, und überall EREs zu verwenden.


Die drei BSDs, die Sie alle erwähnt haben, unterstützen die -rOption als Synonym -Efür Kompatibilität mit GNU sed. OpenBSDs und OS Xs sed -Einterpretieren die Escape-Pipe als Literal-Pipe und nicht als Alternationsoperator. Hier ist ein funktionierender Link zur NetBSD-Manpage und hier einer für OpenBSD, der nicht zehn Jahre alt ist.
Damien



9

Dies liegt daran, dass (a|b)es sich um einen erweiterten regulären Ausdruck handelt, nicht um einen regulären Basisausdruck. Nutzen Sie die -EOption, um damit umzugehen.

echo 'cat
dog
pear
banana
cat
dog'|sed -E 's/cat|dog/Bear/g'

Von der sedManpage:

 -E      Interpret regular expressions as extended (modern) regular
         expressions rather than basic regular expressions (BRE's).

Beachten Sie, dass dies -rein weiteres Flag für dasselbe ist, jedoch -Eportabler ist und sogar in der nächsten Version der POSIX-Spezifikationen enthalten sein wird.


6

Die übertragbare und effizientere Möglichkeit hierfür sind Adressen. Du kannst das:

printf %s\\n cat dog pear banana cat dog |
sed -e '/cat/!{/dog/!b' -e '};cBear'

Auf diese Weise wird die aktuelle Zeile automatisch ausgedruckt , wenn die Zeile nicht die Zeichenfolge cat enthält und die Zeichenfolge dog sed b ranches nicht aus dem Skript entfernt wurde, und die nächste Zeile wird eingezogen, um den nächsten Zyklus zu beginnen. Es führt daher nicht den nächsten Befehl aus, der in diesem Beispiel cdie gesamte Zeile zum Lesen von Bear ändert, aber alles tun kann.

Es ist wahrscheinlich auch erwähnenswert, dass jede Anweisung, die !bauf diesen sedBefehl folgt , nur mit einer Zeile übereinstimmen kann , die entweder den String enthält, dogoder cat- so können Sie weitere Tests durchführen, ohne die Gefahr einer Übereinstimmung mit einer Zeile, die nicht übereinstimmt - was bedeutet, dass Sie jetzt Regeln anwenden können auch nur zu dem einen oder anderen.

Aber das ist der nächste. Hier ist die Ausgabe des obigen Befehls:

###OUTPUT###
Bear
Bear
pear
banana
Bear
Bear

Sie können eine Nachschlagetabelle mit Rückverweisen auch portabel implementieren.

printf %s\\n cat dog pear banana cat dog |
sed '1{x;s/^/ cat dog /;x
};G;s/^\(.*\)\n.* \1 .*/Bear/;P;d'

Die Einrichtung dieses einfachen Beispielfalls ist sehr viel aufwändiger, kann jedoch sedlangfristig zu wesentlich flexibleren Skripten führen.

In der ersten Zeile xändere ich Hold Space und Pattern Space und füge dann den String <space>cat ein<space> Dog<space> in den Hold Space ein, bevorx .

Von da an und in jeder folgenden Zeile Ghalte ich das Leerzeichen an das Leerzeichenmuster angehängt und überprüfe dann, ob alle Zeichen vom Zeilenanfang bis zur neuen Zeile, die ich gerade am Ende hinzugefügt habe, mit einer von Leerzeichen umgebenen Zeichenfolge übereinstimmen. Wenn ja, ersetze ich die ganze Partie durch Bär und wenn nicht, wird da kein Schaden angerichtet, weil ich weiter macheP nur bis zur ersten im Musterbereich auftretenden Zeile weiterdrucke, und dlösche dann alles.

###OUTPUT###
Bear
Bear
pear
banana
Bear
Bear

Und wenn ich "flexibel" sage, dann meine ich das auch so. Hier ersetzt es Katze durch Braunbär und Hund durch Schwarzbär :

printf %s\\n cat dog pear banana cat dog |
sed '1{x;s/^/ 1cat Brown 2dog Black /;x
};G;s/^\(.*\)\n.* [0-9]\1 \([^ ]*\) .*/\2Bear/;P;d'

###OUTPUT###
BrownBear
BlackBear
pear
banana
BrownBear
BlackBear

Natürlich können Sie den Inhalt der Nachschlagetabelle erheblich erweitern - ich habe die Idee aus Greg Ubbens Usenet-E-Mails zu diesem Thema aufgegriffen, als er in den 90er Jahren beschrieb, wie er aus einer einzigen sed s///Anweisung einen groben Taschenrechner konstruierte .


1
Puh, +1. Sie haben eine Vorliebe für
ungewöhnliches

@ 1_CR - Siehe meine letzte Bearbeitung - nicht meine Idee - was nicht heißt, dass ich das nicht schätze und es als Kompliment betrachte. Aber ich gebe gerne Anerkennung, wo es fällig ist.
mikeserv

1

Dies ist eine ziemlich alte Frage, aber für den Fall, dass jemand es versuchen möchte, gibt es einen relativ geringen Aufwand, dies in sed mit sed-Dateien zu tun. Jede Option kann in einer separaten Zeile aufgeführt werden, und sed bewertet jede Option. Es ist eine logische Entsprechung von oder. So entfernen Sie beispielsweise Zeilen, die einen bestimmten Code enthalten:

Sie können sagen : sed -E '/^\/\*!(40103|40101|40111).*\/;$/d'

oder fügen Sie dies in Ihre sed-Datei ein:

/^\/\*!40103.*\/;$/d
/^\/\*!40101.*\/;$/d
/^\/\*!40111.*\/;$/d

0

Hier ist eine Technik , die Verwendung jeglicher Implementierung spezifische Optionen nicht machen sed(zB -E, -r). Anstatt das Muster als einen einzelnen regulären Ausdruck zu beschreiben cat|dog, können wir einfach sedzweimal ausführen :

echo 'cat
dog
pear
banana
cat
dog' | sed 's/cat/Bear/g' | sed 's/dog/Bear/g'

Es ist eine offensichtliche Problemumgehung, aber es lohnt sich zu teilen. Es wird natürlich auf mehr als zwei Musterketten verallgemeinert, obwohl eine sehr lange Kette von sed's nicht besonders gut aussieht.

Ich verwende häufig sed -i(was in allen Implementierungen gleich funktioniert), um Änderungen an Dateien vorzunehmen. Hier kann eine lange Liste von Musterzeichenfolgen integriert werden, da jedes temporäre Ergebnis in der Datei gespeichert wird:

for pattern in cat dog owl; do
    sed -i "s/${pattern}/Bear/g" myfile
done
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.