Ich möchte eine große Anzahl von C ++ - Quelldateien mit einer zusätzlichen Include-Direktive aktualisieren, bevor #includes vorhanden sind. Für diese Art von Aufgabe verwende ich normalerweise ein kleines Bash-Skript mit sed, um die Datei neu zu schreiben.
Wie kann ich sed
nur das erste Vorkommen einer Zeichenfolge in einer Datei ersetzen, anstatt jedes Vorkommen zu ersetzen?
Wenn ich benutze
sed s/#include/#include "newfile.h"\n#include/
es ersetzt alle #includes.
Alternative Vorschläge, um dasselbe zu erreichen, sind ebenfalls willkommen.
command-line
sed
text-processing
David Dibben
quelle
quelle
0,
nur mitgnu sed
s//
eines leeren regulären Ausdrucks bedeutet, dass der zuletzt angewendete reguläre Ausdruck implizit wiederverwendet wird. in diesem FallRE
. Diese praktische Verknüpfung bedeutet, dass Sie den regulären Ausdruck für das Ende des Bereichs in Ihrems
Anruf nicht duplizieren müssen .Ein
sed
Skript, das nur das erste Auftreten von "Apple" durch "Banana" ersetzt.Beispiel
Dies ist das einfache Skript: Anmerkung des Herausgebers: Funktioniert nur mit GNU
sed
.Die ersten beiden Parameter
0
und/Apple/
sind der Bereichsspezifizierer. Dass/Apple/Banana/
ist, was in diesem Bereich ausgeführt wird. In diesem Fall also "im Bereich von begin (0
) bis zur ersten Instanz vonApple
ersetzenApple
durchBanana
. Nur die ersteApple
wird ersetzt.Hintergrund: Traditionell ist
sed
der Bereichsspezifizierer auch "hier beginnen" und "hier enden" (einschließlich). Der niedrigste "Anfang" ist jedoch die erste Zeile (Zeile 1), und wenn das "Ende hier" eine Regex ist, wird nur versucht, in der nächsten Zeile nach "Beginn" eine Übereinstimmung zu finden, sodass das frühestmögliche Ende die Zeile ist 2. Da der Bereich inklusive ist, ist der kleinstmögliche Bereich "2 Zeilen" und der kleinste Startbereich sind sowohl die Zeilen 1 als auch 2 (dh wenn in Zeile 1 ein Vorkommen auftritt, werden auch Vorkommen in Zeile 2 geändert, was in diesem Fall nicht erwünscht ist ).GNU
sed fügt seine eigene Erweiterung hinzu, die es erlaubt, start als "Pseudo" anzugeben,line 0
so dass das Ende des Bereichs sein kann,line 1
und erlaubt ihm einen Bereich von "nur der ersten Zeile".Oder eine vereinfachte Version (ein leeres RE-ähnliches
//
Mittel bedeutet, die zuvor angegebene Version wiederzuverwenden, dies ist also gleichwertig):Und die geschweiften Klammern sind für den Befehl optional
s
, daher ist dies auch gleichbedeutend:Alle diese funktionieren nur auf GNU
sed
.Sie können GNU sed auch mit Homebrew unter OS X installieren
brew install gnu-sed
.quelle
sed: 1: "…": bad flag in substitute command: '}'
sed -e '1s/Apple/Banana/;t' -e '1,/Apple/s//Banana/'
. Aus der Antwort von @ MikhailVS (derzeit) ganz unten.sed '0,/foo/s/foo/bar/'
sed: -e expression #1, char 3: unexpected
'' damitdas hat bei mir funktioniert.
Beispiel
Anmerkung des Herausgebers: Beide funktionieren nur mit GNU
sed
.quelle
sed '1,/pattern/s/pattern/replacement/' filename
Funktioniert nur, wenn "das Muster nicht in der ersten Zeile erscheint" auf dem Mac. Ich werde meinen vorherigen Kommentar löschen, da er nicht korrekt ist. Das Detail finden Sie hier ( linuxtopia.org/online_books/linux_tool_guides/the_sed_faq/… ). Andys Antwort funktioniert nur für GNU sed, nicht jedoch für Mac.Ein Überblick über die vielen hilfreichen Antworten , ergänzt durch Erklärungen :
In den Beispielen wird ein vereinfachter Anwendungsfall verwendet: Ersetzen Sie das Wort "foo" nur in der ersten übereinstimmenden Zeile durch "bar".
Aufgrund der Verwendung von ANSI C-Strings in Anführungszeichen (
$'...'
) die Probeneingangsleitungen zu liefern,bash
,ksh
, oderzsh
wird als Shell angenommen.sed
Nur GNU :Die Antwort von Ben Hoffstein zeigt uns, dass GNU eine Erweiterung der POSIX-Spezifikation
sed
bereitstellt , die die folgende 2-Adressen-Form zulässt :0,/re/
(re
stellt hier einen beliebigen regulären Ausdruck dar).0,/re/
Ermöglicht, dass der reguläre Ausdruck auch in der ersten Zeile übereinstimmt . Mit anderen Worten: Eine solche Adresse erstellt einen Bereich von der ersten Zeile bis einschließlich der übereinstimmenden Zeilere
- unabhängig davon, ob siere
in der ersten Zeile oder in einer nachfolgenden Zeile auftritt.1,/re/
, das einen Bereich erstellt, der von der ersten Zeile bis einschließlich der Zeilere
in den nachfolgenden Zeilen übereinstimmt . Mit anderen Worten: Dies erkennt nicht das erste Auftreten einerre
Übereinstimmung, wenn es in der ersten Zeile auftritt , und verhindert auch die Verwendung einer Kurzform//
für die Wiederverwendung des zuletzt verwendeten regulären Ausdrucks (siehe nächster Punkt). 1Wenn Sie eine
0,/re/
Adresse mit einems/.../.../
(Ersetzungs-) Aufruf kombinieren , der denselben regulären Ausdruck verwendet, führt Ihr Befehl die Ersetzung effektiv nur in der ersten Zeile durch, die übereinstimmtre
.sed
bietet eine praktische Verknüpfung für die Wiederverwendung des zuletzt angewendeten regulären Ausdrucks : ein leeres Trennzeichenpaar//
.Nur POSIX-Funktionen
sed
wie BSD (macOS)sed
(funktionieren auch mit GNUsed
):Da
0,/re/
es nicht verwendet werden kann und das Formular1,/re/
nicht erkennt ,re
ob es in der ersten Zeile auftritt (siehe oben), ist eine spezielle Behandlung für die erste Zeile erforderlich .Die Antwort von MikhailVS erwähnt die Technik, die hier in ein konkretes Beispiel gebracht wird:
Hinweis:
Die leere Regex-
//
Verknüpfung wird hier zweimal verwendet: einmal für den Endpunkt des Bereichs und einmal ims
Aufruf; In beiden Fällen wird Regexfoo
implizit wiederverwendet, sodass wir ihn nicht duplizieren müssen, was sowohl zu kürzerem als auch zu wartbarem Code führt.POSIX
sed
benötigt nach bestimmten Funktionen tatsächliche Zeilenumbrüche, z. B. nach dem Namen eines Etiketts oder sogar nach dessen Auslassung, wie diest
hier der Fall ist. Die strategische Aufteilung des Skripts in mehrere-e
Optionen ist eine Alternative zur Verwendung eines tatsächlichen Zeilenumbruchs: Beenden Sie jeden-e
Skriptabschnitt dort, wo normalerweise ein Zeilenumbruch erforderlich ist.1 s/foo/bar/
wird nurfoo
in der 1. Zeile ersetzt, wenn dort gefunden. Wenn ja,t
verzweigt sich zum Ende des Skripts (überspringt verbleibende Befehle in der Zeile). (Diet
Funktion verzweigt nur dann zu einem Label, wenn der letztes
Aufruf eine tatsächliche Ersetzung durchgeführt hat. Wenn kein Label vorhanden ist, wie hier der Fall, wird das Ende des Skripts zu verzweigt.)In diesem Fall stimmt die Bereichsadresse
1,//
, die normalerweise das erste Vorkommen ab Zeile 2 findet, nicht überein, und der Bereich wird nicht verarbeitet, da die Adresse ausgewertet wird, wenn die aktuelle Zeile bereits vorhanden ist2
.Wenn umgekehrt in der ersten Zeile keine Übereinstimmung vorhanden ist,
1,//
wird diese eingegeben und die wahre erste Übereinstimmung gefunden.Der Nettoeffekt ist das gleiche wie bei GNU
sed
‚s0,/re/
: nur dem ersten Vorkommen ersetzt wird , ob es auf der 1. Zeile oder ein anderes auftritt.NON-Range-Ansätze
Potongs Antwort zeigt Schleifentechniken , die die Notwendigkeit einer Reichweite umgehen . Da er die GNU-
sed
Syntax verwendet, sind hier die POSIX-kompatiblen Entsprechungen :Schleifentechnik 1: Führen Sie beim ersten Spiel die Ersetzung durch und geben Sie dann eine Schleife ein, in der die verbleibenden Zeilen einfach so gedruckt werden, wie sie sind :
Schleifentechnik 2, nur für kleinere Dateien : Lesen Sie die gesamte Eingabe in den Speicher und führen Sie eine einzelne Ersetzung durch .
1 1.61803 enthält Beispiele dafür, was mit
1,/re/
, mit und ohne nachfolgende Ereignisse geschiehts//
:-
sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo'
Erträge$'1bar\n2bar'
; Das heißt, beide Zeilen wurden aktualisiert, da die Zeilennummer1
mit der ersten Zeile übereinstimmt und Regex/foo/
- das Ende des Bereichs - erst ab der nächsten Zeile gesucht wird. Daher werden in diesem Fall beide Zeilen ausgewählt und dies/foo/bar/
Substitution wird für beide durchgeführt.-
sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo'
schlägt fehl : mitsed: first RE may not be empty
(BSD / macOS) undsed: -e expression #1, char 0: no previous regular expression
(GNU), da zum Zeitpunkt der Verarbeitung der ersten Zeile (aufgrund der Zeilennummer,1
die den Bereich beginnt) noch kein regulärer Ausdruck angewendet wurde//
bezieht sich auf nichts.Mit Ausnahme der
sed
speziellen0,/re/
Syntax von GNU schließt jeder Bereich, der mit einer Zeilennummer beginnt, die Verwendung von effektiv aus//
.quelle
Sie könnten awk verwenden, um etwas Ähnliches zu tun.
Erläuterung:
Führt die Aktionsanweisung zwischen {} aus, wenn die Zeile mit "#include" übereinstimmt und wir sie noch nicht verarbeitet haben.
Dies druckt #include "newfile.h", wir müssen den Anführungszeichen entkommen. Dann setzen wir die Variable done auf 1, damit wir keine weiteren Includes hinzufügen.
Dies bedeutet "Zeile ausdrucken" - eine leere Aktion gibt standardmäßig $ 0 aus, wodurch die gesamte Zeile ausgedruckt wird. Ein Einzeiler und leichter zu verstehen als sed IMO :-)
quelle
awk '/version/ && !done {print " \"version\": \"'${NEWVERSION}'\""; done=1;}; 1;' package.json
awk '/#include/ && !done { gsub(/#include/, "include \"newfile.h\""); done=1}; 1' file.c
Eine ziemlich umfassende Sammlung von Antworten auf häufig gestellte Fragen zu Linuxtopia sed . Es wird auch hervorgehoben, dass einige Antworten, die von Personen bereitgestellt wurden, nicht mit Nicht-GNU-Versionen von sed funktionieren, z
in Nicht-GNU-Version muss sein
Diese Version funktioniert jedoch nicht mit gnu sed.
Hier ist eine Version, die mit beiden funktioniert:
Ex:
quelle
So funktioniert dieses Skript: Wenn für Zeilen zwischen 1 und der ersten
#include
(nach Zeile 1) die Zeile mit beginnt#include
, stellen Sie die angegebene Zeile voran.Befindet sich die erste
#include
Zeile in Zeile 1, wird sowohl in Zeile 1 als auch in der nächsten#include
Zeile die Zeile vorangestellt. Wenn Sie GNU verwendensed
, hat es eine Erweiterung, in der0,/^#include/
(anstelle von1,
) das Richtige getan wird.quelle
Fügen Sie einfach die Anzahl der Vorkommen am Ende hinzu:
quelle
sed
spezifiziert den Ersatzbefehl mit:[2addr]s/BRE/replacement/flags
und stellt fest, dass "der Wert von Flags null oder mehr sein soll von: n Ersetzt nur das n-te Vorkommen der im Musterraum gefundenen BRE." Zumindest in POSIX 2008 ist das Trailing1
daher keine GNU-sed
Erweiterung. Selbst im SUS / POSIX 1997- Standard wurde dies unterstützt, so dass ich 2008 stark aus der Reihe geraten war.Eine mögliche Lösung:
Erläuterung:
quelle
sed: file me4.sed line 4: ":" lacks a label
Ich weiß, dass dies ein alter Beitrag ist, aber ich hatte eine Lösung, die ich früher verwendet habe:
Verwenden Sie grundsätzlich grep, um das erste Vorkommen zu drucken und dort anzuhalten. Drucken Sie zusätzlich die Zeilennummer, dh
5:line
. Pipe das in sed und entferne das: und alles danach, so dass du nur noch eine Zeilennummer hast. Pipe das in sed, was s /.*/ replace zur Endnummer hinzufügt, was zu einem 1-zeiligen Skript führt, das in das letzte sed geleitet wird, um als Skript in der Datei ausgeführt zu werden.also , wenn regex =
#include
und ersetzen =blah
und den ersten Auftreten grep Funde ist in Zeile 5 dann die Daten an den letzten geleitet werden sed würde5s/.*/blah/
.Funktioniert auch, wenn das erste Vorkommen in der ersten Zeile steht.
quelle
sed -f -
was einige nicht sind, aber Sie können esWenn jemand hierher gekommen ist, um ein Zeichen für das erste Vorkommen in allen Zeilen zu ersetzen (wie ich), verwenden Sie Folgendes:
Wenn Sie beispielsweise 1 in 2 ändern, können Sie stattdessen nur alle zweiten As ersetzen.
quelle
's/a/b/'
bedeutetmatch a
, unddo just first match
for every matching line
Mit der
-z
Option von GNU sed können Sie die gesamte Datei so verarbeiten, als wäre es nur eine Zeile. Auf diese Weises/…/…/
würde a nur die erste Übereinstimmung in der gesamten Datei ersetzen. Denken Sie daran: Ersetzts/…/…/
nur die erste Übereinstimmung in jeder Zeile, behandelt jedoch mit der-z
Optionsed
die gesamte Datei als einzelne Zeile.Im allgemeinen Fall müssen Sie Ihren sed-Ausdruck neu schreiben, da der Musterbereich jetzt die gesamte Datei anstelle nur einer Zeile enthält. Einige Beispiele:
s/text.*//
kann umgeschrieben werden alss/text[^\n]*//
.[^\n]
passt zu allem außer dem Zeilenumbruchzeichen. stimmt[^\n]*
mit allen Symbolentext
überein, bis eine neue Zeile erreicht ist.s/^text//
kann umgeschrieben werden alss/(^|\n)text//
.s/text$//
kann umgeschrieben werden alss/text(\n|$)//
.quelle
Ich würde dies mit einem awk-Skript tun:
dann starte es mit awk:
könnte schlampig sein, ich bin neu in diesem.
quelle
Als alternativen Vorschlag können Sie sich den
ed
Befehl ansehen .quelle
Ich habe dies endlich in einem Bash-Skript zum Laufen gebracht, mit dem in jedem Element eines RSS-Feeds ein eindeutiger Zeitstempel eingefügt wird:
Es ändert nur das erste Vorkommen.
${nowms}
ist die Zeit in Millisekunden, die von einem Perl-Skript festgelegt wird,$counter
ist ein Zähler, der für die Schleifensteuerung innerhalb des Skripts verwendet wird, und\
ermöglicht die Fortsetzung des Befehls in der nächsten Zeile.Die Datei wird eingelesen und stdout wird in eine Arbeitsdatei umgeleitet.
So wie ich es verstehe,
1,/====RSSpermalink====/
sagt sed, wann es aufhören soll, indem es eine Bereichsbeschränkung festlegt, und ist danns/====RSSpermalink====/${nowms}/
der bekannte sed-Befehl, um die erste Zeichenfolge durch die zweite zu ersetzen.In meinem Fall setze ich den Befehl in doppelte Anführungszeichen, da ich ihn in einem Bash-Skript mit Variablen verwende.
quelle
Verwenden Sie FreeBSD
ed
und vermeiden Sieed
den Fehler "Keine Übereinstimmung", fallsinclude
eine zu verarbeitende Datei keine Anweisung enthält:quelle
Dies könnte für Sie funktionieren (GNU sed):
oder wenn der Speicher kein Problem ist:
quelle
Der folgende Befehl entfernt das erste Auftreten einer Zeichenfolge in einer Datei. Es wird auch die leere Zeile entfernt. Es wird in einer XML-Datei dargestellt, funktioniert aber mit jeder Datei.
Nützlich, wenn Sie mit XML-Dateien arbeiten und ein Tag entfernen möchten. In diesem Beispiel wird das erste Auftreten des Tags "isTag" entfernt.
Befehl:
Quelldatei (source.txt)
Ergebnisdatei (output.txt)
ps: es hat bei mir unter Solaris SunOS 5.10 (ziemlich alt) nicht funktioniert, aber es funktioniert unter Linux 2.6, sed Version 4.1.5
quelle
sed
(daher funktionierte es nicht mit Solaris). Sie sollten dies bitte löschen - es liefert wirklich keine eindeutigen neuen Informationen zu einer Frage, die bei Ihrer Beantwortung bereits 4½ Jahre alt war. Zugegeben, es gibt ein funktionierendes Beispiel, aber das ist von fraglichem Wert, wenn die Frage so viele Antworten hat wie diese.Nichts Neues, aber vielleicht eine etwas konkretere Antwort:
sed -rn '0,/foo(bar).*/ s%%\1%p'
Beispiel:
xwininfo -name unity-launcher
Erzeugt eine Ausgabe wie:Das Extrahieren der Fenster-ID mit
xwininfo -name unity-launcher|sed -rn '0,/^xwininfo: Window id: (0x[0-9a-fA-F]+).*/ s%%\1%p'
erzeugt:quelle
POSIXly (auch gültig in sed), nur ein regulärer Ausdruck verwendet, benötigt nur Speicher für eine Zeile (wie üblich):
Erklärt:
quelle
Der Anwendungsfall kann möglicherweise sein, dass Ihre Vorkommen in Ihrer Datei verteilt sind, aber Sie wissen, dass Ihre einzige Sorge in den ersten 10, 20 oder 100 Zeilen liegt.
Durch einfaches Ansprechen dieser Zeilen wird das Problem behoben - auch wenn der Wortlaut des OP nur den ersten betrifft.
quelle
Eine mögliche Lösung könnte darin bestehen, den Compiler anzuweisen, den Header einzuschließen, ohne dass er in den Quelldateien erwähnt wird. In GCC gibt es folgende Optionen:
Der Compiler von Microsoft verfügt über die Option / FI (Forced Include).
Diese Funktion kann für einige häufig verwendete Header wie die Plattformkonfiguration nützlich sein. Das Makefile des Linux-Kernels verwendet
-include
dies.quelle
quelle