Wie kann man mit einem regulären Ausdruck mehrere Zeilen aus einer Datei entfernen?
Ich möchte oft mehrere Zeilen erhalten / mehrere Zeilen durch einen regulären Ausdruck ändern. Ein Beispielfall:
Ich versuche, einen Teil einer XML / SGML-Datei zu lesen (sie sind nicht unbedingt gut geformt oder haben eine vorhersehbare Syntax, daher wäre ein regulärer Ausdruck sicherer als ein richtiger Parser. Außerdem möchte ich dies auch vollständig tun können unstrukturierte Dateien, in denen nur einige Schlüsselwörter bekannt sind.) in einem Shell-Skript (läuft unter Solaris und Linux).
Beispiel XML:
<tag1>
<tag2>bar</tag2>
</tag1>
<tag1>
<tag2>foo</tag2>
</tag1>
Daraus möchte ich das lesen, <tag1>
wenn es foo
irgendwo drin ist.
Ein Regex wie (<tag1>.*?foo.*?</tag1>)
sollte den richtigen Teil geben, aber Werkzeuge mögen grep
und sed
funktionieren nur für mich mit einzelnen Zeilen. Wie bekomme ich
<tag1>
<tag2>foo</tag2>
</tag1>
in diesem Beispiel?
Antworten:
Wenn Sie GNU grep installiert haben können Sie mehrzeilige , indem in der Suche starten
-P
(Perl reguläre Ausdrücke) Flagge und AktivierungPCRE_DOTALL
mit(?s)
Wenn das oben Genannte auf Ihrer Plattform nicht funktioniert, versuchen Sie
-z
zusätzlich, das Flag zu übergeben. Dadurch wird grep gezwungen, NUL als Zeilentrennzeichen zu behandeln, sodass die gesamte Datei wie eine einzelne Zeile aussieht.quelle
(?s)
Tipp(GNU grep) 2.14
auf Debian. Ich habe das OPs-Beispiel so wie es ist kopiert (nur die letzte neue Zeile hinzugefügt) und Ihrgrep
darauf ausgeführt, aber keine Ergebnisse erhalten.grep -ozP
stattgrep -oP
auf Ihren Plattformen zu versuchen ?Wenn Sie angesichts der angezeigten Daten vor der letzten Bereinigungszeile die oben genannten Schritte ausführen, sollten Sie mit einem
sed
Musterbereich arbeiten, der wie folgt aussieht:Sie können Ihren Musterbereich jederzeit mit
l
ook ausdrucken . Sie können dann\n
Zeichen adressieren .Zeigt Ihnen, dass jede Zeile
sed
sie in der Phase verarbeitet, in der siel
aufgerufen wird.Also habe ich es gerade getestet und es brauchte noch eines
\backslash
nach dem,comma
in der ersten Zeile, aber ansonsten funktioniert es so wie es ist. Hier habe ich es in ein_sed_function
Feld eingefügt, damit ich es in dieser Antwort leicht zu Demonstrationszwecken aufrufen kann: (funktioniert mit eingeschlossenen Kommentaren, wird hier jedoch der Kürze halber entfernt)Jetzt wechseln wir das
p
für ein,l
damit wir sehen können, womit wir arbeiten, während wir unser Skript entwickeln, und entfernen die Nicht-Op-Demo,s?
so dass die letzte Zeile von unssed 3<<\SCRIPT
einfach so aussieht:Dann werde ich es wieder ausführen:
In Ordnung! Ich hatte also Recht - das ist ein gutes Gefühl. Mischen wir jetzt unseren
l
Blick, um die Linien zu sehen, die er zieht, aber löscht. Wir werden unseren Strom entfernenl
und einen hinzufügen,!{block}
damit es so aussieht:So sieht es aus, kurz bevor wir es auslöschen.
Eine letzte Sache, die ich Ihnen zeigen möchte, ist der
H
alte Raum, während wir ihn aufbauen. Es gibt einige Schlüsselkonzepte, die ich hoffentlich demonstrieren kann. Also entferne ich den letztenl
Blick wieder und ändere die erste Zeile, umH
am Ende einen Blick in den alten Raum zu werfen :H
Der alte Raum überlebt Linienzyklen - daher der Name. Was die Leute oft stolpern - ok, worauf ich oft stolpere - ist, dass es gelöscht werden muss, nachdem Sie es benutzt haben. In diesem Fallx
ändere ich mich nur einmal, so dass der Haltebereich zum Musterraum wird und umgekehrt, und diese Änderung überlebt auch Linienzyklen.Der Effekt ist, dass ich meinen Haltebereich löschen muss, der früher mein Musterbereich war. Ich mache das, indem ich zuerst den aktuellen Musterraum lösche mit:
Das wählt einfach jedes Zeichen aus und entfernt es. Ich kann nicht verwenden,
d
da dies meinen aktuellen Zeilenzyklus beenden würde und der nächste Befehl nicht abgeschlossen würde, was mein Skript so ziemlich in den Papierkorb werfen würde.Dies funktioniert ähnlich wie,
H
aber es überschreibt den Haltebereich. Daher habe ich meinen leeren Musterbereich über den oberen Bereich meines Haltebereichs kopiert und ihn effektiv gelöscht. Jetzt kann ich einfach:aus.
Und so schreibe ich
sed
Skripte.quelle
Die Antwort von @ jamespfinn funktioniert einwandfrei, wenn Ihre Datei so einfach ist wie Ihr Beispiel. Wenn Sie eine komplexere Situation haben, in der
<tag1>
sich mehr als 2 Zeilen erstrecken könnten, benötigen Sie einen etwas komplexeren Trick. Beispielsweise:Das Perl-Skript verarbeitet jede Zeile Ihrer Eingabedatei und
if(/<tag1>/){$a=1;}
: Die Variable$a
wird auf gesetzt,1
wenn ein öffnendes Tag (<tag1>
) gefunden wird.if($a==1){push @l,$_}
: Wenn dies der Fall$a
ist1
, fügen Sie diese Zeile dem Array hinzu@l
.if(/<\/tag1>/)
: Wenn die aktuelle Zeile mit dem schließenden Tag übereinstimmt:if(grep {/foo/} @l){print "@l"}
: Wenn eine der im Array gespeicherten Zeilen@l
(dies sind die Zeilen zwischen<tag1>
und</tag1>
) mit der Zeichenfolge übereinstimmtfoo
, drucken Sie den Inhalt von@l
.$a=0; @l=()
: Leere die Liste (@l=()
) und setze$a
sie auf 0 zurück.quelle
<tag1>
mit enthält,foo
und es funktioniert gut. Wann scheitert es für Sie?Hier ist eine
sed
Alternative:Erläuterung
-n
bedeutet, keine Zeilen ohne Anweisung zu drucken./<tag1/
stimmt zuerst mit dem Eröffnungs-Tag überein:x
ist eine Bezeichnung, um später zu diesem Punkt springen zu könnenN
Fügt die nächste Zeile zum Musterbereich hinzu (aktiver Puffer)./<\/tag1/!b x
Wenn der aktuelle Musterbereich kein schließendes Tag enthält, verzweigen Sie zu demx
zuvor erstellten Label. Wir fügen dem Musterbereich daher so lange Linien hinzu, bis wir unser schließendes Tag gefunden haben./foo/p
bedeutet, wenn der aktuelle Musterbereich übereinstimmtfoo
, sollte er gedruckt werden.quelle
Sie könnten es mit GNU awk tun, indem Sie das End-Tag als Datensatztrennzeichen behandeln, z. B. für ein bekanntes End-Tag
</tag1>
:oder allgemeiner (mit einem regulären Ausdruck für das End-Tag)
Testen auf @ terdon's
foo.xml
:quelle
Wenn Ihre Datei genau so aufgebaut ist, wie Sie es oben gezeigt haben, können Sie die Flags -A (Zeilen nach) und -B (Zeilen vor) für grep verwenden ... zum Beispiel:
Wenn Ihre Version dies
grep
unterstützt, können Sie auch die einfachere-C
Option (für den Kontext) verwenden, mit der die umgebenden N Zeilen gedruckt werden:quelle
tail -3 input_file.xml
. Ja, es funktioniert für dieses spezielle Beispiel, aber es ist keine hilfreiche Antwort auf die Frage.