Linie nach dem ersten Match mit sed einfügen

245

Aus irgendeinem Grund kann ich keine eindeutige Antwort darauf finden und bin im Moment in einer Zeitkrise. Wie würde ich vorgehen, um eine ausgewählte Textzeile nach der ersten Zeile einzufügen, die mit dem sedBefehl einer bestimmten Zeichenfolge entspricht? Ich habe ...

CLIENTSCRIPT="foo"
CLIENTFILE="bar"

Und ich möchte eine Zeile nach der CLIENTSCRIPT=Zeile einfügen, die zu ...

CLIENTSCRIPT="foo"
CLIENTSCRIPT2="hello"
CLIENTFILE="bar"
user2150250
quelle

Antworten:

379

Versuchen Sie dies mit GNU sed:

sed '/CLIENTSCRIPT="foo"/a CLIENTSCRIPT2="hello"' file

wenn Sie ersetzen wollen in-place , Einsatz

sed -i '/CLIENTSCRIPT="foo"/a CLIENTSCRIPT2="hello"' file

Ausgabe

CLIENTSCRIPT="foo"
CLIENTSCRIPT2="hello"
CLIENTFILE="bar"

Doc

  • siehe sed doc und suchen \a(anhängen)
Gilles Quenot
quelle
Vielen Dank! Und damit es in die Datei zurückschreibt, ohne den Inhalt der Datei zu drucken?
user2150250
11
Beachten Sie, dass beide GNU annehmen sed. Dies ist keine Standardsyntax sedund funktioniert mit keiner anderen sedImplementierung.
Stephane Chazelas
Ich hatte Probleme, dies für mich zum Laufen zu bringen, weil ich ein anderes Trennzeichen verwendete. Nach RTFM'ing wurde mir klar, dass ich den regulären Ausdruck mit einem \ und dann mit dem Trennzeichen beginnen musste.
Christy
10
Wie gilt das NUR für das erste Spiel? Es ist klar, dass nach dem Spiel Text angehängt wird, aber woher weiß es nur, dass es zum ersten Spiel passt?
Mohammed Noureldin
7
Dies fügt für mich den Text nach jedem Spiel hinzu.
schoppenhauer
49

Beachten Sie den Standard sed (wie in POSIX, die von allen konformen sedImplementierungen (GNU, OS / X, BSD, Solaris ...) unterstützt wird):

sed '/CLIENTSCRIPT=/a\
CLIENTSCRIPT2="hello"' file

Oder in einer Zeile:

sed -e '/CLIENTSCRIPT=/a\' -e 'CLIENTSCRIPT2="hello"' file

(-e xpressions (und der Inhalt von -files) werden mit Zeilenumbrüchen verbunden, um die sed-Skriptinterpretationen zu bilden sed).

Die -iOption für die direkte Bearbeitung ist auch eine GNU-Erweiterung, einige andere Implementierungen (wie die von FreeBSD) unterstützen dies -i ''.

Alternativ können Sie aus Gründen der Portabilität verwenden perl stattdessen :

perl -pi -e '$_ .= qq(CLIENTSCRIPT2="hello"\n) if /CLIENTSCRIPT=/' file

Oder Sie könnten verwenden edoder ex:

printf '%s\n' /CLIENTSCRIPT=/a 'CLIENTSCRIPT2="hello"' . w q | ex -s file
Stephane Chazelas
quelle
2
Ich kann mich irren, aber der Strom sed -e '/CLIENTSCRIPT=/a\' -e 'CLIENTSCRIPT2="hello"' fileentgeht dem Anführungszeichen am Ende des ersten Parameters und bricht den Befehl.
Verlassener Wagen
1
@AbandonedCart in Muscheln der Bourne cshoder der rcFamilie '...'sind starke Zitate, in denen Backslash nichts Besonderes ist. Die einzige Ausnahme, die ich kenne, ist die fishShell.
Stephane Chazelas
1
Eine Ausnahme bildet anscheinend auch das Terminal unter MacOS (und anschließend ein im Terminal ausgeführtes Skript). Ich habe eine alternative Syntax gefunden, aber trotzdem danke.
Verlassener Wagen
1
@ BandonedCart, das ist etwas anderes. Das ist, dass macOS hier sednicht POSIX-kompatibel ist. Das macht meine Aussage darüber, dass es portabel ist, falsch (für die einzeilige Variante). Ich werde die offene Gruppe um Bestätigung bitten, wenn es sich tatsächlich um eine Nichtkonformität oder eine Fehlinterpretation des Standards meinerseits handelt.
Stephane Chazelas
19

Eine POSIX-kompatible Datei mit dem folgenden sBefehl:

sed '/CLIENTSCRIPT="foo"/s/.*/&\
CLIENTSCRIPT2="hello"/' file
SLePort
quelle
1
... das unterstützt sogar das Einfügen neuer Zeilen. Ich liebe es!
Stephan Henningsen
1
Siehe auch, sed -e '/.../s/$/\' -e 'CLI.../'was Probleme mit Zeilen vermeiden würde, die Byte-Sequenzen enthalten, die keine gültigen Zeichen bilden.
Stephane Chazelas
14

Sed-Befehl, der unter MacOS (mindestens OS 10) und Unix gleichermaßen funktioniert (dh erfordert keine Gnu-Sed wie Gilles (derzeit akzeptiert)):

sed -e '/CLIENTSCRIPT="foo"/a\'$'\n''CLIENTSCRIPT2="hello"' file

Dies funktioniert in Bash und möglicherweise auch in anderen Shells, die den $ '\ n' Bewertungszitatstil kennen. Alles kann in einer Zeile stehen und in älteren / POSIX sed-Befehlen funktionieren. Wenn möglicherweise mehrere Zeilen mit CLIENTSCRIPT = "foo" (oder Ihrem Äquivalent) übereinstimmen und Sie die zusätzliche Zeile nur beim ersten Mal hinzufügen möchten, können Sie sie wie folgt überarbeiten:

sed -e '/^ *CLIENTSCRIPT="foo"/b ins' -e b -e ':ins' -e 'a\'$'\n''CLIENTSCRIPT2="hello"' -e ': done' -e 'n;b done' file

(Dies erzeugt eine Schleife nach dem Zeileneinfügungscode, die nur den Rest der Datei durchläuft und nie wieder zum ersten sed-Befehl zurückkehrt.)

Möglicherweise stellen Sie fest, dass ich dem übereinstimmenden Muster ein '^ *' hinzugefügt habe, falls diese Zeile beispielsweise in einem Kommentar angezeigt oder eingerückt wird. Es ist nicht 100% perfekt, deckt aber einige andere Situationen ab, die wahrscheinlich häufig sind. Nach Bedarf anpassen ...

Diese beiden Lösungen umgehen auch das Problem (für die generische Lösung zum Hinzufügen einer Zeile), dass, wenn Ihre neu eingefügte Zeile nicht gekapselte Backslashes oder kaufmännisches Und enthält, diese von sed interpretiert werden und wahrscheinlich nicht gleich herauskommen, genau wie die \n is - z.\0wäre die erste übereinstimmende Zeile. Besonders praktisch, wenn Sie eine Zeile hinzufügen, die aus einer Variablen stammt, in der Sie sonst alles zuerst mit $ {var //} oder einer anderen sed-Anweisung usw. maskieren müssten.

Diese Lösung ist in Skripten etwas weniger chaotisch (das Zitieren und \ n ist jedoch nicht einfach zu lesen), wenn Sie den Ersatztext für den Befehl a nicht am Anfang einer Zeile einfügen möchten, wenn Sie beispielsweise in einer Funktion stehen mit eingerückten Linien. Ich habe ausgenutzt, dass $ '\ n' von der Shell als Zeilenumbruch ausgewertet wird, nicht in regulären '\ n' Werten in einfachen Anführungszeichen.

Es wird jedoch lang genug, dass ich denke, Perl / sogar Awk könnte gewinnen, weil es besser lesbar ist.

Breezer
quelle
1
Danke für die Antwort! Erstens funktioniert man auch unter AIX OS wie ein Zauber.
Abhishek
Wie geht das, außer beim mehrzeiligen Einfügen?
Tatsu
1
POSIX sed unterstützt wörtliche Zeilenumbrüche in der Ersatzzeichenfolge, wenn Sie sie mit einem Backslash ( Quelle ) "maskieren" . Also: s/CLIENTSCRIPT="foo"/&\ [Enter] CLIENTSCRIPT2="hello"/ . Der Backslash muss das allerletzte Zeichen in der Zeile sein (kein Leerzeichen danach), anders als es in diesem Kommentar aussieht.
TheDudeAbides
1
@tatsu Zeilenumbrüche in der Ersatzzeichenfolge von s///sowie in den Befehlen aund ikönnen mit der gleichen Methode, die ich in meinem obigen Kommentar beschrieben habe, " maskiert " werden.
TheDudeAbides
4

Vielleicht etwas spät, um eine Antwort darauf zu veröffentlichen, aber ich fand einige der oben genannten Lösungen etwas umständlich.

Ich habe versucht, einen einfachen Saitenwechsel in sed durchzuführen, und es hat funktioniert:

sed 's/CLIENTSCRIPT="foo"/&\nCLIENTSCRIPT2="hello"/' file

& Zeichen spiegelt die übereinstimmende Zeichenfolge wider. Anschließend fügen Sie \ n und die neue Zeile hinzu.

Wie bereits erwähnt, wenn Sie dies vor Ort tun möchten:

sed -i 's/CLIENTSCRIPT="foo"/&\nCLIENTSCRIPT2="hello"/' file

Etwas anderes. Sie können mit einem Ausdruck übereinstimmen:

sed -i 's/CLIENTSCRIPT=.*/&\nCLIENTSCRIPT2="hello"/' file

Hoffe das hilft jemandem

Siwesam
quelle
Dies funktioniert gut unter Linux, wo GNU sed standardmäßig verwendet wird, da es \nin der Ersatzzeichenfolge verstanden wird. Plain-Vanilla (POSIX) sedist nicht verstehen \nin der Ersatzzeichenfolge jedoch so das wird nicht funktionieren auf BSD oder macOS-es sei denn , Sie haben installiert GNU irgendwie sed. Mit Vanille sed Sie können Zeilenumbrüche durch „Flucht“ sie das einzubetten, ist die Zeile mit einem Backslash, dann drücken Sie [Enter] ( Referenz , unter der Überschrift [2addr]s/BRE/replacement/flags). Dies bedeutet, dass Ihr sed-Befehl mehrere Zeilen im Terminal umfassen muss.
TheDudeAbides
Eine weitere Option, um einen wörtlichen Zeilenumbruch in der Ersatzzeichenfolge zu erhalten, ist die Verwendung der ANSI-C-Anführungszeichenfunktion in Bash, wie in einer der anderen Antworten erwähnt .
TheDudeAbides
4

Die awk-Variante:

awk '1;/CLIENTSCRIPT=/{print "CLIENTSCRIPT2=\"hello\""}' file
Corentin Limier
quelle
1
Für alle, die sich über das wundern, lesen 1;Sie Warum druckt "1" in awk die aktuelle Zeile?
cherdt
1

Ich hatte eine ähnliche Aufgabe und konnte die oben genannte Perl-Lösung nicht zum Laufen bringen.

Hier ist meine Lösung:

perl -i -pe "BEGIN{undef $/;} s/^\[mysqld\]$/[mysqld]\n\ncollation-server = utf8_unicode_ci\n/sgm" /etc/mysql/my.cnf

Erläuterung:

Verwendet einen regulären Ausdruck, um in meiner Datei /etc/mysql/my.cnf nach einer Zeile zu suchen, die nur diese enthält [mysqld]und durch diese ersetzt

[mysqld] collation-server = utf8_unicode_ci

effektiv die collation-server = utf8_unicode_ciZeile nach der Zeile hinzufügen, die enthält [mysqld].

Caleb
quelle
0

Ich musste dies kürzlich auch für Mac- und Linux-Betriebssysteme tun. Nachdem ich viele Beiträge durchgesehen und viele Dinge ausprobiert hatte, kam ich meiner Meinung nach nie dahin, wo ich wollte, nämlich: eine einfach genug zu verstehende Lösung mit bekannten und Standardbefehle mit einfachen Mustern, einzeilig, tragbar, erweiterbar, um weitere Einschränkungen hinzuzufügen. Dann habe ich versucht, es aus einer anderen Perspektive zu betrachten. Dann wurde mir klar, dass ich auf die Option "Einzeiler" verzichten kann, wenn ein "Zweiliner" den Rest meiner Kriterien erfüllt. Am Ende habe ich mir diese Lösung ausgedacht, die sowohl unter Ubuntu als auch unter Mac funktioniert und die ich mit allen teilen wollte:

insertLine=$(( $(grep -n "foo" sample.txt | cut -f1 -d: | head -1) + 1 ))
sed -i -e "$insertLine"' i\'$'\n''bar'$'\n' sample.txt

Im ersten Befehl sucht grep nach Zeilennummern, die "foo" enthalten, cut / head wählt das erste Vorkommen aus, und die arithmetische Operation erhöht die Zeilennummer des ersten Vorkommens um 1, da ich sie nach dem Vorkommen einfügen möchte. Im zweiten Befehl handelt es sich um eine direkte Dateibearbeitung, "i" zum Einfügen: eine Antwort, die eine neue Zeile, "bar" und dann eine weitere neue Zeile zitiert. Das Ergebnis ist das Hinzufügen einer neuen Zeile mit "bar" nach der Zeile "foo". Jeder dieser beiden Befehle kann auf komplexere Operationen und Übereinstimmungen erweitert werden.

momonari8
quelle