Wie kann sed verwendet werden, um nur das erste Vorkommen in einer Datei zu ersetzen?

217

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 sednur 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.

David Dibben
quelle

Antworten:

135
 # sed script to change "foo" to "bar" only on the first occurrence
 1{x;s/^/first/;x;}
 1,/foo/{x;/first/s///;x;s/foo/bar/;}
 #---end of script---

oder, wenn Sie es vorziehen: Anmerkung des Herausgebers: Funktioniert nur mit GNU sed .

sed '0,/foo/s//bar/' file 

Quelle

Ben Hoffstein
quelle
86
Ich glaube, ich bevorzuge die Lösung "oder wenn Sie es vorziehen". Es wäre auch gut, die Antworten zu erklären - und die Antwort direkt auf die Frage zu richten und dann zu verallgemeinern, anstatt nur zu verallgemeinern. Aber gute Antwort.
Jonathan Leffler
7
Zu Ihrer Information, für Mac-Benutzer müssen Sie die 0 durch eine 1 ersetzen, also: sed '1, / RE / s // to_that /' file
mhost
1
@mhost Nein, es wird dann ersetzt, wenn das Muster in Zeile 1 gefunden wird und nicht, wenn sich das Muster in einer anderen Zeile befindet, aber immer noch das erste gefundene Muster ist. PS Es sollte erwähnt werden, dass dies 0,nur mitgnu sed
Jotne
11
Könnte jemand bitte die "oder wenn Sie bevorzugen" -Lösung erklären? Ich weiß nicht, wo ich das "Von" -Muster ablegen soll.
Jean-Luc Nacif Coelho
3
@ Jean-LucNacifCoelho: Die Verwendung s//eines leeren regulären Ausdrucks bedeutet, dass der zuletzt angewendete reguläre Ausdruck implizit wiederverwendet wird. in diesem Fall RE. Diese praktische Verknüpfung bedeutet, dass Sie den regulären Ausdruck für das Ende des Bereichs in Ihrem sAnruf nicht duplizieren müssen .
mklement0
289

Ein sedSkript, das nur das erste Auftreten von "Apple" durch "Banana" ersetzt.

Beispiel

     Input:      Output:

     Apple       Banana
     Apple       Apple
     Orange      Orange
     Apple       Apple

Dies ist das einfache Skript: Anmerkung des Herausgebers: Funktioniert nur mit GNU sed .

sed '0,/Apple/{s/Apple/Banana/}' input_filename

Die ersten beiden Parameter 0und /Apple/sind der Bereichsspezifizierer. Das s/Apple/Banana/ist, was in diesem Bereich ausgeführt wird. In diesem Fall also "im Bereich von begin ( 0) bis zur ersten Instanz von Appleersetzen Appledurch Banana. Nur die erste Applewird ersetzt.

Hintergrund: Traditionell ist sedder 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 ). GNUsed fügt seine eigene Erweiterung hinzu, die es erlaubt, start als "Pseudo" anzugeben, line 0so dass das Ende des Bereichs sein kann, line 1und 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):

sed '0,/Apple/{s//Banana/}' input_filename

Und die geschweiften Klammern sind für den Befehl optionals , daher ist dies auch gleichbedeutend:

sed '0,/Apple/s//Banana/' input_filename

Alle diese funktionieren nur auf GNU sed.

Sie können GNU sed auch mit Homebrew unter OS X installieren brew install gnu-sed.

tim
quelle
166
übersetzt in die menschliche Sprache: Beginnen Sie in Zeile 0, fahren Sie fort, bis Sie mit 'Apple' übereinstimmen, und führen Sie die Ersetzung in geschweiften Klammern aus. cfr: grymoire.com/Unix/Sed.html#uh-29
mariotomo
8
Unter OS X bekomme ichsed: 1: "…": bad flag in substitute command: '}'
ELLIOTTCABLE
5
@ELLIOTTCABLE unter OS X verwenden sed -e '1s/Apple/Banana/;t' -e '1,/Apple/s//Banana/'. Aus der Antwort von @ MikhailVS (derzeit) ganz unten.
DJB
8
Funktioniert auch ohne die Klammern:sed '0,/foo/s/foo/bar/'
Innokenty
5
Ich bekomme sed: -e expression #1, char 3: unexpected '' damit
Jonathan
56
sed '0,/pattern/s/pattern/replacement/' filename

das hat bei mir funktioniert.

Beispiel

sed '0,/<Menu>/s/<Menu>/<Menu><Menu>Sub menu<\/Menu>/' try.txt > abc.txt

Anmerkung des Herausgebers: Beide funktionieren nur mit GNU sed .

Sushil
quelle
2
@Landys dies ersetzt immer noch Instanzen in den anderen Zeilen; nicht nur die erste Instanz
Sarat
1
@sarat Ja, du hast recht. sed '1,/pattern/s/pattern/replacement/' filenameFunktioniert 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.
Landys
45

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, oder zshwird als Shell angenommen.


sedNur GNU :

Die Antwort von Ben Hoffstein zeigt uns, dass GNU eine Erweiterung der POSIX-Spezifikationsed bereitstellt , die die folgende 2-Adressen-Form zulässt : 0,/re/( restellt 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 Zeile re- unabhängig davon, ob sie rein der ersten Zeile oder in einer nachfolgenden Zeile auftritt.

  • Vergleichen Sie dies mit dem POSIX-kompatiblen Formular 1,/re/, das einen Bereich erstellt, der von der ersten Zeile bis einschließlich der Zeile rein den nachfolgenden Zeilen übereinstimmt . Mit anderen Worten: Dies erkennt nicht das erste Auftreten einer reÜ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). 1

Wenn Sie eine 0,/re/Adresse mit einem s/.../.../(Ersetzungs-) Aufruf kombinieren , der denselben regulären Ausdruck verwendet, führt Ihr Befehl die Ersetzung effektiv nur in der ersten Zeile durch, die übereinstimmt re.
sedbietet eine praktische Verknüpfung für die Wiederverwendung des zuletzt angewendeten regulären Ausdrucks : ein leeres Trennzeichenpaar// .

$ sed '0,/foo/ s//bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo' 
1st bar         # only 1st match of 'foo' replaced
Unrelated
2nd foo
3rd foo

Nur POSIX-Funktionen sedwie BSD (macOS)sed (funktionieren auch mit GNU sed ):

Da 0,/re/es nicht verwendet werden kann und das Formular 1,/re/nicht erkennt , reob 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:

$ sed -e '1 s/foo/bar/; t' -e '1,// s//bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar         # only 1st match of 'foo' replaced
Unrelated
2nd foo
3rd foo

Hinweis:

  • Die leere Regex- //Verknüpfung wird hier zweimal verwendet: einmal für den Endpunkt des Bereichs und einmal im sAufruf; In beiden Fällen wird Regex fooimplizit wiederverwendet, sodass wir ihn nicht duplizieren müssen, was sowohl zu kürzerem als auch zu wartbarem Code führt.

  • POSIX sedbenötigt nach bestimmten Funktionen tatsächliche Zeilenumbrüche, z. B. nach dem Namen eines Etiketts oder sogar nach dessen Auslassung, wie dies thier der Fall ist. Die strategische Aufteilung des Skripts in mehrere -eOptionen ist eine Alternative zur Verwendung eines tatsächlichen Zeilenumbruchs: Beenden Sie jeden -eSkriptabschnitt dort, wo normalerweise ein Zeilenumbruch erforderlich ist.

1 s/foo/bar/wird nur fooin der 1. Zeile ersetzt, wenn dort gefunden. Wenn ja, tverzweigt sich zum Ende des Skripts (überspringt verbleibende Befehle in der Zeile). (Die tFunktion verzweigt nur dann zu einem Label, wenn der letzte sAufruf 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 ist 2.

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‚s 0,/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 :

$ sed -e '/foo/ {s//bar/; ' -e ':a' -e '$!{n;ba' -e '};}' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar
Unrelated
2nd foo
3rd foo

Schleifentechnik 2, nur für kleinere Dateien : Lesen Sie die gesamte Eingabe in den Speicher und führen Sie eine einzelne Ersetzung durch .

$ sed -e ':a' -e '$!{N;ba' -e '}; s/foo/bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar
Unrelated
2nd foo
3rd foo

1 1.61803 enthält Beispiele dafür, was mit 1,/re/, mit und ohne nachfolgende Ereignisse geschieht s//:
- sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo'Erträge $'1bar\n2bar'; Das heißt, beide Zeilen wurden aktualisiert, da die Zeilennummer 1mit 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 die s/foo/bar/Substitution wird für beide durchgeführt.
- sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' schlägt fehl : mit sed: first RE may not be empty(BSD / macOS) und sed: -e expression #1, char 0: no previous regular expression(GNU), da zum Zeitpunkt der Verarbeitung der ersten Zeile (aufgrund der Zeilennummer, 1die den Bereich beginnt) noch kein regulärer Ausdruck angewendet wurde//bezieht sich auf nichts.
Mit Ausnahme der sedspeziellen 0,/re/Syntax von GNU schließt jeder Bereich, der mit einer Zeilennummer beginnt, die Verwendung von effektiv aus //.

mklement0
quelle
25

Sie könnten awk verwenden, um etwas Ähnliches zu tun.

awk '/#include/ && !done { print "#include \"newfile.h\""; done=1;}; 1;' file.c

Erläuterung:

/#include/ && !done

Führt die Aktionsanweisung zwischen {} aus, wenn die Zeile mit "#include" übereinstimmt und wir sie noch nicht verarbeitet haben.

{print "#include \"newfile.h\""; done=1;}

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.

1;

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 :-)

richq
quelle
4
Diese Antwort ist portabler als die sed-Lösungen, die auf gnu sed usw. beruhen (z. B. sed in OS-X saugt!)
Jay Taylor
1
Das ist wirklich verständlicher, aber für mich fügt es eine Zeile hinzu, anstatt sie zu ersetzen. Befehl verwendet: awk '/version/ && !done {print " \"version\": \"'${NEWVERSION}'\""; done=1;}; 1;' package.json
Cereal Killer
Das gleiche gilt für den verständlichsten Befehl, fügt jedoch eine Zeile über der gefundenen Zeichenfolge hinzu, anstatt sie zu ersetzen
Flion,
1
Die Antwort ist gut lesbar. Hier ist meine Version, die Zeichenfolge ersetzt, anstatt eine neue Zeile hinzuzufügen. awk '/#include/ && !done { gsub(/#include/, "include \"newfile.h\""); done=1}; 1' file.c
Orsiris de Jong
18

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

sed '0,/RE/s//to_that/' file

in Nicht-GNU-Version muss sein

sed -e '1s/RE/to_that/;t' -e '1,/RE/s//to_that/'

Diese Version funktioniert jedoch nicht mit gnu sed.

Hier ist eine Version, die mit beiden funktioniert:

-e '/RE/{s//to_that/;:a' -e '$!N;$!ba' -e '}'

Ex:

sed -e '/Apple/{s//Banana/;:a' -e '$!N;$!ba' -e '}' filename
MikhailVS
quelle
In der Tat getestet unter Ubuntu Linux v16 und FreeBSD v10.2. Danke.
Sopalajo de Arrierez
12
#!/bin/sed -f
1,/^#include/ {
    /^#include/i\
#include "newfile.h"
}

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 #includeZeile in Zeile 1, wird sowohl in Zeile 1 als auch in der nächsten #includeZeile die Zeile vorangestellt. Wenn Sie GNU verwenden sed, hat es eine Erweiterung, in der 0,/^#include/(anstelle von 1,) das Richtige getan wird.

Chris Jester-Young
quelle
11

Fügen Sie einfach die Anzahl der Vorkommen am Ende hinzu:

sed s/#include/#include "newfile.h"\n#include/1
nicht vorhanden
quelle
7
Dies funktioniert leider nicht. Es ersetzt das gerade erste Vorkommen in jeder Zeile der Datei und nicht das erste Vorkommen in der Datei.
David Dibben
1
Darüber hinaus handelt es sich um eine GNU-Sed-Erweiterung, nicht um eine Standard-Sed-Funktion.
Jonathan Leffler
10
Hmmm ... die Zeit vergeht. POSIX 2008/2013 für sedspezifiziert den Ersatzbefehl mit: [2addr]s/BRE/replacement/flagsund 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 Trailing 1daher keine GNU- sedErweiterung. Selbst im SUS / POSIX 1997- Standard wurde dies unterstützt, so dass ich 2008 stark aus der Reihe geraten war.
Jonathan Leffler
7

Eine mögliche Lösung:

    /#include/!{p;d;}
    i\
    #include "newfile.h"
    :a
    n
    ba

Erläuterung:

  • Lesen Sie die Zeilen, bis wir das #include finden, drucken Sie diese Zeilen und starten Sie den neuen Zyklus
  • Fügen Sie die neue Include-Zeile ein
  • Geben Sie eine Schleife ein, die nur Zeilen liest (standardmäßig druckt sed auch diese Zeilen). Von hier aus kehren wir nicht zum ersten Teil des Skripts zurück
mitchnull
quelle
sed: file me4.sed line 4: ":" lacks a label
Rogerdpack
anscheinend hat sich in den letzten sed-version etwas geändert und leere etiketten sind nicht mehr erlaubt. aktualisierte die Antwort
mitchnull
4

Ich weiß, dass dies ein alter Beitrag ist, aber ich hatte eine Lösung, die ich früher verwendet habe:

grep -E -m 1 -n 'old' file | sed 's/:.*$//' - | sed 's/$/s\/old\/new\//' - | sed -f - file

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 = #includeund ersetzen = blahund den ersten Auftreten grep Funde ist in Zeile 5 dann die Daten an den letzten geleitet werden sed würde 5s/.*/blah/.

Funktioniert auch, wenn das erste Vorkommen in der ersten Zeile steht.

Michael Edwards
quelle
Ich hasse mehrzeilige sed-Skripte oder sed-Befehle mit etwas anderem als s und einer Leinenzahl, also bin ich mit diesem Ansatz an Bord. Folgendes habe ich für meinen Anwendungsfall verwendet (funktioniert mit bash): filepath = / etc / hosts; patt = '^ \ (127 \ .0 \ .0 \ .1. * \)'; repl = '\ 1 newhostalias'; sed $ (IFS =: linearray = ($ (grep -E -m 1 -n "$ patt" "$ filepath")) && echo $ {linearray [0]}) s / "$ patt" / "$ repl" / "$ filepath"
Parität3
Es klappt. Obwohl nur mit Sed's, die klug genug sind, um zu akzeptieren, sed -f -was einige nicht sind, aber Sie können es
umgehen
3

Wenn jemand hierher gekommen ist, um ein Zeichen für das erste Vorkommen in allen Zeilen zu ersetzen (wie ich), verwenden Sie Folgendes:

sed '/old/s/old/new/1' file

-bash-4.2$ cat file
123a456a789a
12a34a56
a12
-bash-4.2$ sed '/a/s/a/b/1' file
123b456a789a
12b34a56
b12

Wenn Sie beispielsweise 1 in 2 ändern, können Sie stattdessen nur alle zweiten As ersetzen.

FatihSarigol
quelle
2
Sie müssen nicht alles tun, 's/a/b/'bedeutet match a, unddo just first match for every matching line
Samveen
Ich bin bei Samveen. Auch dies beantwortet nicht die hier gestellte Frage . Ich würde empfehlen, diese Antwort zu löschen.
Socowi
Die Frage war "erstes Vorkommen in einer Datei", nicht "erstes Vorkommen in einer Zeile"
Manuel Romeiro
3

Mit der -zOption von GNU sed können Sie die gesamte Datei so verarbeiten, als wäre es nur eine Zeile. Auf diese Weise s/…/…/würde a nur die erste Übereinstimmung in der gesamten Datei ersetzen. Denken Sie daran: Ersetzt s/…/…/nur die erste Übereinstimmung in jeder Zeile, behandelt jedoch mit der -zOption seddie gesamte Datei als einzelne Zeile.

sed -z 's/#include/#include "newfile.h"\n#include'

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 als s/text[^\n]*//. [^\n]passt zu allem außer dem Zeilenumbruchzeichen. stimmt [^\n]*mit allen Symbolen textüberein, bis eine neue Zeile erreicht ist.
  • s/^text//kann umgeschrieben werden als s/(^|\n)text//.
  • s/text$//kann umgeschrieben werden als s/text(\n|$)//.
Socowi
quelle
2

Ich würde dies mit einem awk-Skript tun:

BEGIN {i=0}
(i==0) && /#include/ {print "#include \"newfile.h\""; i=1}
{print $0}    
END {}

dann starte es mit awk:

awk -f awkscript headerfile.h > headerfilenew.h

könnte schlampig sein, ich bin neu in diesem.

Wakingrufus
quelle
2

Als alternativen Vorschlag können Sie sich den edBefehl ansehen .

man 1 ed

teststr='
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
'

# for in-place file editing use "ed -s file" and replace ",p" with "w"
# cf. http://wiki.bash-hackers.org/howto/edit-ed
cat <<-'EOF' | sed -e 's/^ *//' -e 's/ *$//' | ed -s <(echo "$teststr")
   H
   /# *include/i
   #include "newfile.h"
   .
   ,p
   q
EOF
timo
quelle
2

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:

        sed "1,/====RSSpermalink====/s/====RSSpermalink====/${nowms}/" \
            production-feed2.xml.tmp2 > production-feed2.xml.tmp.$counter

Es ändert nur das erste Vorkommen.

${nowms}ist die Zeit in Millisekunden, die von einem Perl-Skript festgelegt wird, $counterist 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 dann s/====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.

Michael Cook
quelle
2

Verwenden Sie FreeBSD ed und vermeiden Sie edden Fehler "Keine Übereinstimmung", falls includeeine zu verarbeitende Datei keine Anweisung enthält:

teststr='
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
'

# using FreeBSD ed
# to avoid ed's "no match" error, see
# *emphasized text*http://codesnippets.joyent.com/posts/show/11917 
cat <<-'EOF' | sed -e 's/^ *//' -e 's/ *$//' | ed -s <(echo "$teststr")
   H
   ,g/# *include/u\
   u\
   i\
   #include "newfile.h"\
   .
   ,p
   q
EOF
nazq
quelle
Dies ist bemerkenswert ähnlich timo ‚s Antwort wurde aber ein Jahr später zugegeben.
Jonathan Leffler
2

Dies könnte für Sie funktionieren (GNU sed):

sed -si '/#include/{s//& "newfile.h\n&/;:a;$!{n;ba}}' file1 file2 file....

oder wenn der Speicher kein Problem ist:

sed -si ':a;$!{N;ba};s/#include/& "newfile.h\n&/' file1 file2 file...
Potong
quelle
0

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:

sed -e 0,/'<isTag>false<\/isTag>'/{s/'<isTag>false<\/isTag>'//}  -e 's/ *$//' -e  '/^$/d'  source.txt > output.txt

Quelldatei (source.txt)

<xml>
    <testdata>
        <canUseUpdate>true</canUseUpdate>
        <isTag>false</isTag>
        <moduleLocations>
            <module>esa_jee6</module>
            <isTag>false</isTag>
        </moduleLocations>
        <node>
            <isTag>false</isTag>
        </node>
    </testdata>
</xml>

Ergebnisdatei (output.txt)

<xml>
    <testdata>
        <canUseUpdate>true</canUseUpdate>
        <moduleLocations>
            <module>esa_jee6</module>
            <isTag>false</isTag>
        </moduleLocations>
        <node>
            <isTag>false</isTag>
        </node>
    </testdata>
</xml>

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

Andreas Panagiotidis
quelle
Dies ähnelt bemerkenswerterweise der gleichen Grundidee wie eine Reihe früherer Antworten, mit der gleichen Einschränkung, dass es nur mit GNU funktioniert 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.
Jonathan Leffler
0

Nichts Neues, aber vielleicht eine etwas konkretere Antwort: sed -rn '0,/foo(bar).*/ s%%\1%p'

Beispiel: xwininfo -name unity-launcherErzeugt eine Ausgabe wie:

xwininfo: Window id: 0x2200003 "unity-launcher"

  Absolute upper-left X:  -2980
  Absolute upper-left Y:  -198
  Relative upper-left X:  0
  Relative upper-left Y:  0
  Width: 2880
  Height: 98
  Depth: 24
  Visual: 0x21
  Visual Class: TrueColor
  Border width: 0
  Class: InputOutput
  Colormap: 0x20 (installed)
  Bit Gravity State: ForgetGravity
  Window Gravity State: NorthWestGravity
  Backing Store State: NotUseful
  Save Under State: no
  Map State: IsViewable
  Override Redirect State: no
  Corners:  +-2980+-198  -2980+-198  -2980-1900  +-2980-1900
  -geometry 2880x98+-2980+-198

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:

0x2200003
Stephen Niedzielski
quelle
0

POSIXly (auch gültig in sed), nur ein regulärer Ausdruck verwendet, benötigt nur Speicher für eine Zeile (wie üblich):

sed '/\(#include\).*/!b;//{h;s//\1 "newfile.h"/;G};:1;n;b1'

Erklärt:

sed '
/\(#include\).*/!b          # Only one regex used. On lines not matching
                            # the text  `#include` **yet**,
                            # branch to end, cause the default print. Re-start.
//{                         # On first line matching previous regex.
    h                       # hold the line.
    s//\1 "newfile.h"/      # append ` "newfile.h"` to the `#include` matched.
    G                       # append a newline.
  }                         # end of replacement.
:1                          # Once **one** replacement got done (the first match)
n                           # Loop continually reading a line each time
b1                          # and printing it by default.
'                           # end of sed script.
Isaac
quelle
0

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.

sed '1,10s/#include/#include "newfile.h"\n#include/'
sastorsl
quelle
0

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:

   -include file
       Process file as if "#include "file"" appeared as the first line of
       the primary source file.  However, the first directory searched for
       file is the preprocessor's working directory instead of the
       directory containing the main source file.  If not found there, it
       is searched for in the remainder of the "#include "..."" search
       chain as normal.

       If multiple -include options are given, the files are included in
       the order they appear on the command line.

   -imacros file
       Exactly like -include, except that any output produced by scanning
       file is thrown away.  Macros it defines remain defined.  This
       allows you to acquire all the macros from a header without also
       processing its declarations.

       All files specified by -imacros are processed before all files
       specified by -include.

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 -includedies.

Kaz
quelle
-1
sed -e 's/pattern/REPLACEMENT/1' <INPUTFILE
Warhansen
quelle