Wie füge ich eine neue Zeile vor einem Muster ein?

138

Wie füge ich eine neue Zeile vor einem Muster innerhalb einer Zeile ein?

Dadurch wird beispielsweise eine neue Zeile hinter dem Regex-Muster eingefügt .

sed 's/regex/&\n/g'

Wie kann ich das gleiche tun, aber vor dem Muster?

In dieser Beispiel-Eingabedatei ist das Muster, mit dem die Übereinstimmung übereinstimmt, die Telefonnummer.

some text (012)345-6789

Soll werden

some text
(012)345-6789
Dennis
quelle
Wiederholen Sie einfach die Antwort auf Wie füge ich eine neue Zeile / einen Zeilenumbruch nach einer Zeile mit sed hier ein:sed '/regex/G'
Adam Schmideg
1
@NilsvonBarth, warum ist eine einfache Frage eine schlechte Frage?
Josh

Antworten:

177

Dies funktioniert unter bashund zshunter Linux und OS X getestet:

sed 's/regexp/\'$'\n/g'

Im Allgemeinen wird, $gefolgt von einem Zeichenfolgenliteral in einfachen Anführungszeichen, basheine Backslash-Ersetzung im C-Stil durchgeführt, z. B. $'\t'wird sie in eine Literal-Registerkarte übersetzt. Plus, sed will Ihr Newline wörtlichen mit einem Backslash geschützt werden, damit die \vor $. Und schließlich sollte das Dollarzeichen selbst nicht in Anführungszeichen gesetzt werden, damit es von der Shell interpretiert wird. Deshalb schließen wir das Anführungszeichen vor dem $und öffnen es dann erneut.

Bearbeiten : Wie in den Kommentaren von @ mklement0 vorgeschlagen, funktioniert dies auch:

sed $'s/regexp/\\\n/g'

Was hier passiert, ist: Der gesamte Befehl sed ist jetzt eine Zeichenfolge im C-Stil. Dies bedeutet, dass der Backslash, den sed vor dem neuen Zeilenliteral platzieren muss, jetzt mit einem anderen Backslash maskiert werden soll. Obwohl besser lesbar, können Sie in diesem Fall keine Shell-String-Ersetzungen vornehmen (ohne es erneut hässlich zu machen.)

Mojuba
quelle
7
Dies gibt mir unter OSX "uneingeschränkte Zeilenumbruch innerhalb des Ersatzmusters".
Matt Gibson
@ Matt Gibson, das ist sehr seltsam, weil "unescaped newline" nur angegeben wird, wenn Sie eine echte Newline ohne Backslash innerhalb des Substitutionsmusters haben. Mein obiger Code funktioniert tatsächlich auch in einigen anderen Shells, z. B. zsh, ksh.
Mojuba
3
@ Matt Gibson ... oder wenn Sie den Backslash vor '$' \ n in meinem Code vergessen.
Mojuba
7
Wie geschrieben, ersetzen diese Ausdrücke den regulären Ausdruck vollständig durch eine neue Zeile, anstatt wie gewünscht eine neue Zeile in die Mitte einer vorhandenen Zeile einzufügen. So habe ich eine modifizierte Form dieser Antwort verwendet, um eine neue Zeile zwischen zwei übereinstimmenden Mustern einzufügen : sed '\(first match\)\(second match\)/\1\'$'\n''\2/g'. Beachten Sie die beiden einfachen Anführungszeichen nach dem \ n. Der erste schließt den $Abschnitt " " ab, damit der Rest der Zeile davon nicht betroffen ist. Ohne diese Anführungszeichen wurde die \ 2 ignoriert.
David Ravetti
12
Eine andere Option ist die Verwendung einer einzelnen Zeichenfolge in ANSI C-Anführungszeichen : sed $'s/regexp/\\\n/g'Dies verbessert die Lesbarkeit. Die einzige Einschränkung besteht darin, dass Sie dann alle Literalzeichen verdoppeln müssen\ .
mklement0
43

Einige der anderen Antworten funktionierten für meine Version von sed nicht. Position wechseln &und \nhat funktioniert.

sed 's/regexp/\n&/g' 

Bearbeiten: Dies scheint unter OS X nicht zu funktionieren, es sei denn, Sie installieren gnu-sed.

Dennis
quelle
9
Ich bin nicht sicher, ob dies in allen Versionen von sed funktioniert. Ich habe dies auf meinem Mac versucht und das \ n wird nur als 'n' ausgegeben
Todd Gamblin
3
Verbrachte 15 Minuten auf dem Mac bei meiner Arbeit, bevor ich Ihre Antwort las. Gehen Sie Apple!
Rick77
1
Für diejenigen, die Homebrew verwenden: brew install gnu-sedgefolgt vongsed 's/regexp/\n&/g'
aaaaaa
1
... gefolgt vonecho 'alias sed=gsed' >> ~/.bashrc
Proximo
36

In sed können Sie dem Ausgabestream nicht einfach Zeilenumbrüche hinzufügen. Sie müssen eine Fortsetzungszeile verwenden, was umständlich ist, aber funktioniert:

$ sed 's/regexp/\
&/'

Beispiel:

$ echo foo | sed 's/.*/\
&/'

foo

Siehe hier für Details. Wenn Sie etwas weniger umständliches wollen, können Sie versuchen, es perl -pemit Matchgruppen anstelle von sed zu verwenden:

$ echo foo | perl -pe 's/(.*)/\n$1/'

foo

$1 bezieht sich auf die erste übereinstimmende Gruppe im regulären Ausdruck, wobei Gruppen in Klammern stehen.

Todd Gamblin
quelle
Warum können Sie keine Zeilenumbrüche hinzufügen? Sie können einfach sed 's / regexp / & \ n / g' machen. Das war's
Andres
2
Dies ist die am wenigsten hackig aussehende Sache, die Sie auf einem Mac tun können, um Zeilenumbrüche einzufügen (\ n funktioniert auf einem Mac nicht)
Pylinux
Die Perl-Version kann geändert werden, um sie direkt zu bearbeitenperl -pi -e 's/(.*)/\n$1/' foo
gleichnamig
2
@Andres: (Meistens) Sed-Implementierungen nur mit POSIX-Funktionen wie die BSD-Version, die auch mit OS X geliefert wird, unterstützen keine Escape-Sequenzen für Steuerzeichen im Substitutionsteil eines sFunktionsaufrufs (im Gegensatz zur GNU Sed-Implementierung, die dies tut). . Die obige Antwort funktioniert mit beiden Implementierungen. Eine Übersicht aller Unterschiede finden Sie hier .
mklement0
29

Auf meinem Mac wird im Folgenden ein einzelnes 'n' anstelle einer neuen Zeile eingefügt:

sed 's/regexp/\n&/g'

Dies wird durch Zeilenumbruch ersetzt:

sed "s/regexp/\\`echo -e '\n\r'`/g"
Brüllen Sie Skullestad
quelle
Ich habe eine Inline-Bearbeitung durchgeführt sed -i '' -e ...und hatte Probleme damit, dass ein ^MCaret M (Strg + M) in die Datei geschrieben wurde. Am Ende habe ich Perl mit den gleichen Parametern verwendet.
Steve Tauber
2
Bitte beachten Sie, dass der zweite Code einen speziellen Zeilenumbruchcode LF CR (Rückseite des MS-DOS CR LF) einfügt! Sowohl Unix-ähnliche Betriebssysteme als auch Mac OS X verwenden nur LF ( \n).
Pabouk
Etwas anderes in meinem sed-Ausdruck verursachte so viel Unglück (obwohl es ohne die echo...und newline gut funktionierte), dass ich dies nur in vim tat.
Ahmed Fasih
1
Oder einfach: sed "s/regexp/`echo`/g"- Dies erzeugt einen einzelnen LF anstelle von LF-CR
Mojuba
2
@mojuba: Nein: `echo`führt zu einer leeren Zeichenfolge , da Befehlsersetzungen immer alle nachfolgenden Zeilenumbrüche kürzen. Es gibt keine Möglichkeit, eine Befehlsersetzung zu verwenden, um eine einzelne neue Zeile direkt einzufügen (und das Einfügen \n\r- dh eines zusätzlichen CR - ist eine schreckliche Idee).
mklement0
15
echo one,two,three | sed 's/,/\
/g'
vivek
quelle
1
+1 funktionierte perfekt und ziemlich geradlinig / leicht zu merken
gMale
2
Diese Antwort ist eigentlich eher eine Sed- Lösung als eine Bash- Lösung. Alles, was Konstrukte wie verwendet, $'\n'verlässt sich auf die Shell, um die neue Zeile zu generieren. Solche Lösungen sind möglicherweise nicht tragbar. Dieser ist. Natürlich ist es auch ein Duplikat des zweiten Beispiels in Tgamblins Antwort von 2009.
Ghoti
10

In diesem Fall benutze ich nicht sed. Ich benutze tr.

cat Somefile |tr ',' '\012' 

Dies nimmt das Komma und ersetzt es durch den Wagenrücklauf.

user1612632
quelle
1
Ich fand, dass dies auch funktioniert: cat Somefile | tr ',' '\n'YMMV
LS
9

Sie können Perl-Einzeiler verwenden, ähnlich wie Sie es mit sed tun, mit dem Vorteil der vollständigen Unterstützung von Perl-regulären Ausdrücken (die viel leistungsfähiger ist als das, was Sie mit sed erhalten). Es gibt auch sehr geringe Unterschiede zwischen * nix-Plattformen - Perl ist im Allgemeinen Perl. Sie können also aufhören, sich Gedanken darüber zu machen, wie Sie die sed-Version Ihres bestimmten Systems dazu bringen können, das zu tun, was Sie wollen.

In diesem Fall können Sie tun

perl -pe 's/(regex)/\n$1/'

-pe Versetzt Perl in eine "Execute and Print" -Schleife, ähnlich wie bei der normalen Betriebsart von sed.

' zitiert alles andere, damit die Shell nicht stört

()Um den regulären Ausdruck herum befindet sich ein Gruppierungsoperator. $1Auf der rechten Seite der Substitution wird gedruckt, was in diesen Parens übereinstimmte.

Schließlich \nist eine neue Zeile.

Unabhängig davon, ob Sie Klammern als Gruppierungsoperator verwenden, müssen Sie alle Klammern, mit denen Sie übereinstimmen möchten, maskieren. Ein Regex, der dem oben aufgeführten Muster entspricht, wäre also so etwas wie

\(\d\d\d\)\d\d\d-\d\d\d\d

\(oder \)entspricht einem wörtlichen Paren und \deiner Ziffer.

Besser:

\(\d{3}\)\d{3}-\d{4}

Ich kann mir vorstellen, dass Sie herausfinden können, was die Zahlen in geschweiften Klammern bewirken.

Darüber hinaus können Sie andere Trennzeichen als / für Ihre Regex verwenden. Wenn Sie also übereinstimmen müssen, müssen Sie nicht entkommen. Beides entspricht dem regulären Ausdruck zu Beginn meiner Antwort. Theoretisch können Sie die Standards durch ein beliebiges Zeichen ersetzen .

perl -pe 's#(regex)#\n$1#'
perl -pe 's{(regex)}{\n$1}'

Ein paar letzte Gedanken.

Verwenden -neanstelle von -peverhält sich ähnlich, druckt aber am Ende nicht automatisch. Dies kann nützlich sein, wenn Sie selbst drucken möchten. Beispiel: Hier ist ein Grep-Alike ( m/foobar/ist ein Regex-Match):

perl -ne 'if (m/foobar/) {print}'

Wenn Sie Probleme beim Umgang mit Zeilenumbrüchen haben und möchten, dass diese auf magische Weise für Sie erledigt werden, fügen Sie hinzu -l. Nicht nützlich für das OP, das mit Zeilenumbrüchen arbeitete.

Bonus-Tipp - Wenn Sie das PCRE-Paket installiert haben, wird es mitgeliefert pcregrep, das vollständige Perl-kompatible Regexes verwendet.

Dan Pritts
quelle
4

Hmm, gerade entkommene Zeilenumbrüche scheinen in neueren Versionen von sed(ich habe GNU sed 4.2.1) zu funktionieren ,

dev:~/pg/services/places> echo 'foobar' | sed -r 's/(bar)/\n\1/;'
foo
bar
gatoatigrado
quelle
1
Wie bereits erwähnt, funktioniert dies mit verschiedenen Versionen von GNU sed, jedoch nicht mit dem in macOS enthaltenen sed.
LS
4
echo pattern | sed -E -e $'s/^(pattern)/\\\n\\1/'

hat mit ()Unterstützung gut an El Captitan funktioniert

Quanlong
quelle
Dies hat großartig funktioniert und Sie geben sogar einen vollständigen Befehl zum Testen und Extrapolieren, um sich auf den eigenen Zweck zu spezialisieren. Gut gemacht!
Jxramos
3

Um eine neue Zeile für den Ausgabestream unter Linux einzufügen, habe ich Folgendes verwendet:

sed -i "s/def/abc\\\ndef/" file1

Wo file1war:

def

Vor dem Sed-In-Place-Austausch und:

abc
def

Nach dem Sed-In-Place-Austausch. Bitte beachten Sie die Verwendung von \\\n. Wenn die Muster ein "Inneres haben, entkommen Sie mit \".

Karthik
quelle
Für mich funktioniert der obige Code nicht. sedEinfügungen \nanstelle von LF, da \\nder Parameter von der Shell abgerufen wird. --- Dieser Code funktioniert : sed -i "s/def/abc\ndef/" file1. --- GNU sed version 4.2.1, GNU bash, version 4.1.2(1) / 4.2.25(1)(CentOS Release 6.4 / Ubuntu 12.04.3).
Pabouk
2

In sed können Sie Gruppen in Ihrem Muster mit "\ 1", "\ 2" usw. referenzieren. Wenn das gesuchte Muster also "MUSTER" ist und Sie "VOR" davor einfügen möchten , können Sie verwenden, ohne zu entkommen

sed 's/(PATTERN)/BEFORE\1/g'

dh

  sed 's/\(PATTERN\)/BEFORE\1/g'
Steve B.
quelle
Gerade gemacht: Testdatei Inhalt = "ABC ABC ABC". Ran "sed 's / \ (ABC \) / \ n \ 1 / g' Testdatei, hat die Zeilenumbrüche erhalten. Experimentieren Sie mit den Escapezeichen und versuchen Sie, Ihrem Muster jeweils 1 Element hinzuzufügen, z. B. stellen Sie sicher, dass Sie mit dem übereinstimmen Muster, dann überprüfen Sie die Gruppenübereinstimmung, dann fügen Sie die Newline-Überprüfung hinzu.
Steve B.
Ich habe gerade genau das versucht und bekam "nABC nABC nABC". Verwenden Sie eine andere Version von sed?
Todd Gamblin
Das Entkommen der Muschel steht wahrscheinlich Tgamblins Versuchen im Wege. Wenn Sie die vollständigen Argumente wie Steve B in einfache Anführungszeichen setzen, sollte dies behoben sein. Möglicherweise verstehen verschiedene Versionen von sed das \ n für Newline nicht.
Dan Pritts
2

Sie können dies auch mit awk tun, indem Sie -vdas Muster bereitstellen:

awk -v patt="pattern" '$0 ~ patt {gsub(patt, "\n"patt)}1' file

Dies prüft, ob eine Zeile ein bestimmtes Muster enthält. In diesem Fall wird eine neue Zeile an den Anfang angehängt.

Sehen Sie sich ein einfaches Beispiel an:

$ cat file
hello
this is some pattern and we are going ahead
bye!
$ awk -v patt="pattern" '$0 ~ patt {gsub(patt, "\n"patt)}1' file
hello
this is some 
pattern and we are going ahead
bye!

Beachten Sie, dass dies Auswirkungen auf alle Muster in einer Zeile hat:

$ cat file
this pattern is some pattern and we are going ahead
$ awk -v patt="pattern" '$0 ~ patt {gsub(patt, "\n"patt)}1' d
this 
pattern is some 
pattern and we are going ahead
fedorqui 'SO hör auf zu schaden'
quelle
1
Was macht die 1 dabei?
Whatahitson
1
@whatahitson 1wird in Awk als Abkürzung für verwendet {print $0}. Der Grund dafür ist, dass jede Bedingung, die als True ausgewertet wird, die Standardaktion von Awk auslöst, die darin besteht, den aktuellen Datensatz zu drucken.
Fedorqui 'SO hör auf,'
1

Dies funktioniert in MAC für mich

sed -i.bak -e 's/regex/xregex/g' input.txt sed -i.bak -e 's/qregex/\'$'\nregex/g' input.txt

Dono ob es perfekt ist ...

Sam
quelle
1

Nachdem ich alle Antworten auf diese Frage gelesen hatte, brauchte ich noch viele Versuche, um die richtige Syntax für das folgende Beispielskript zu erhalten:

#!/bin/bash
# script: add_domain
# using fixed values instead of command line parameters $1, $2
# to show typical variable values in this example
ipaddr="127.0.0.1"
domain="example.com"
# no need to escape $ipaddr and $domain values if we use separate quotes.
sudo sed -i '$a \\n'"$ipaddr www.$domain $domain" /etc/hosts

Das Skript fügt \nmit einem einzigen sedBefehl eine neue Zeile gefolgt von einer weiteren Textzeile an das Ende einer Datei an .

Xavier
quelle
1
sed -e 's/regexp/\0\n/g'

\ 0 ist die Null, also wird Ihr Ausdruck durch Null (nichts) ersetzt und dann ...
\ n ist die neue Zeile

Bei einigen Unix-Versionen funktioniert das nicht, aber ich denke, es ist die Lösung für Ihr Problem.

echo "Hello" | sed -e 's/Hello/\0\ntmow/g'
Hello
tmow
tmow
quelle
0

In vi auf Red Hat konnte ich Wagenrückläufe nur mit dem Zeichen \ r einfügen. Ich glaube, dass dies intern 'ex' anstelle von 'sed' ausführt, aber es ist ähnlich, und vi kann eine andere Möglichkeit sein, Massenbearbeitungen wie Code-Patches durchzuführen. Beispielsweise. Ich umgebe einen Suchbegriff mit einer if-Anweisung, die darauf besteht, dass der Wagen nach den Klammern zurückkehrt:

:.,$s/\(my_function(.*)\)/if(!skip_option){\r\t\1\r\t}/

Beachten Sie, dass ich auch einige Registerkarten einfügen musste, um die Ausrichtung zu verbessern.

Robert Casey
quelle