Frage

Ich mag eine große Anzahl von C ++ Quelldateien mit einer extra aktualisieren Richtlinie vor irgendwelchen vorhandenen #includes umfassen. Für diese Art von Aufgabe, verwende ich normalerweise einen kleinen Bash-Skript mit sed die Datei neu zu schreiben.

Wie kann ich sed nur das erste Vorkommen einer Zeichenkette in einer Datei zu ersetzen, erhalten anstatt jedes Vorkommen zu ersetzen?

Wenn ich

sed s/#include/#include "newfile.h"\n#include/

es ersetzt alle #includes.

Alternative Vorschläge, das Gleiche zu erreichen sind auch willkommen.

War es hilfreich?

Lösung

 # 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 der Redaktion:. Arbeitet mit GNU sed nur

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

Quelle

Andere Tipps

Schreiben Sie einen Sed-Skript, das nur das erste Vorkommen von „Apple“ ersetzt wird durch „Banana“

Beispiel Eingang: Ausgang:

     Apple       Banana
     Orange      Orange
     Apple       Apple

Dies ist die einfache Skript: Anmerkung der Redaktion:. Arbeitet mit GNU sed nur

sed '0,/Apple/{s/Apple/Banana/}' filename
sed '0,/pattern/s/pattern/replacement/' filename

Dies funktioniert für mich.

Beispiel

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

Anmerkung der Redaktion:. Beide arbeiten mit GNU sed nur

Ein Übersicht der vielen hilfreichen bestehende Antworten , ergänzt mit Erklärungen :

Die Beispiele hier verwenden, um einen vereinfachte Anwendungsfall. Ersetzen Sie das Wort ‚foo‘ mit ‚bar‘ in der ersten Anpassungsleitung nur
Durch Verwendung von ANSI C-Strings in Anführungszeichen ($'...') bereitzustellen, wird die Probeneingangsleitungen, bash, ksh oder zsh als Shell angenommen.


GNU sed nur:

Ben Hoffstein des anwswer zeigt uns, dass GNU bietet eine Erweiterung auf die POSIX Spezifikation für sed dass ermöglicht die folgende 2-Adresse: 0,/re/ (re stellt ein beliebige reguläre Ausdrücke hier).

0,/re/ ermöglicht die Regex Spiel auf der ersten Zeile auch . Mit anderen Worten:. Eine solche Adresse wird einen Bereich von der ersten Zeile bis erstellen zu und einschließlich der Zeile, die re passt - ob re auf der 1. Zeile auftritt oder bei einer nachfolgenden Zeile

  • Vergleichen Sie dies mit der POSIX-kompatibeler Form 1,/re/ , die eine Reihe erstellt, die bis zu der ersten Linie übereinstimmt und mit der Zeile, die re auf nachfolgende Linien übereinstimmt; mit anderen Worten: der nicht das erste Auftreten einer re Übereinstimmung festzustellen, ob es auf dem 1. auftreten geschieht Zeile und auch verhindert die Verwendung der Kurz // für die Wiederverwendung des zuletzt verwendeten regex (siehe nächsten Punkt). [1]

Wenn Sie kombinieren eine 0,/re/ Adresse mit einer s/.../.../ (Substitution) Aufruf, der die verwendet gleichen regulärer Ausdruck, wird Ihr Befehl effektiv nur die Substitution am führen Sie zuerst Linie, Spiele re.
sed bietet eine bequeme Verknüpfung für die Wiederverwendung des zuletzt angelegten regulären Ausdrucks : ein leer Begrenzer Paar, // .

$ 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

Ein POSIX-Funktionen nur für sed wie BSD (macOS) sed (wird auch mit Arbeit GNU sed):

Da 0,/re/ nicht verwendet werden können und die Form 1,/re/ nicht re wenn sie sich auf der ersten Zeile auftreten geschieht (siehe oben), Sonderbehandlung für die 1. Zeile ist erforderlich .

MikhailVS Antwort die Technik erwähnt, ein konkretes Beispiel setzte in hier:

$ 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 zweimal hier verwendet: einmal für den Endpunkt des Bereichs, und einmal im s Anruf; in beiden Fällen wird regex foo implizit wiederverwendet, so dass wir es nicht duplizieren zu müssen, was sowohl für kürzere und wartbaren Code macht.

  • POSIX sed braucht tatsächliche Zeilenumbrüche nach bestimmten Funktionen, wie zum Beispiel nach dem Namen eines Labels oder sogar seine Unterlassung, wie es der Fall mit t ist hier; strategisch das Skripts in mehr -e Optionen Aufspaltung ist eine Alternative zu einer tatsächlichen Zeilenumbruch zu verwenden: jeden -e Skript chunk enden, wo ein Newline normalerweise benötigen würde gehen

  • .

1 s/foo/bar/ ersetzt auf der 1. Zeile foo nur, wenn es gefunden. Wenn ja, t verzweigt zum Endedes Skripts (Rest- springt Befehle auf der Linie). (Die t Funktion verzweigt auf ein Etikett nur dann, wenn der jüngste s Aufruf ausgeführt, um eine tatsächliche Substitution, in der Abwesenheit eines Etiketts, wie es hier der Fall ist, ist das Ende des Skripts verzweigt)

.

Wenn das passiert, Bereichsadresse 1,//, die in der Regel das erste Vorkommen findet ab Zeile 2 , wird nicht Spiel, und der Bereich wird nicht verarbeitet werden, da die Adresse ausgewertet wird, wenn die aktuelle Zeile ist bereits 2.

Im Gegensatz dazu, wenn es keine Übereinstimmung in der 1. Zeile, 1,// wird eingegeben wird, und das wahre erste Spiel finden.

Der Nettoeffekt ist das gleiche wie bei GNU sed den 0,/re/: nur das erste Vorkommen ersetzt wird, ob es auf der 1. Zeile auftritt oder andere

.

NON-Bereich nähert sich

Potong Antwort zeigt Schleife Techniken , die umgeht die Notwendigkeit für einen Bereich ; da er benutzt GNU sed Syntax, hier ist der POSIX-kompatible Äquivalente :

Schleife Technik 1: Ein erstes Spiel, die Substitution durchzuführen, dann eine Schleife ein, die einfach druckt die restlichen Zeilen, wie sie ist :

$ 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

Loop-Technik 2, für eher kleine Dateien nur . die gesamte Eingabe in dem Speicher gelesen, und führen Sie dann eine einzelne Substitution auf sie

$ 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 liefert Beispiele dafür, was geschieht mit 1,/re/, mit und ohne anschließende s// :
- sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo' Ausbeuten $'1bar\n2bar'; dh beide Zeilen aktualisiert wurden, weil die Zeilennummer 1 entspricht die erste Zeile, und regex /foo/ - das Ende des Bereichs - wird dann nur schaute auf der weiter zum Starten line . Daher beide Linien sind in diesem Fall ausgewählt und die s/foo/bar/ Substitution wird auf beide durchgeführt.
- sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' nicht : mit sed: first RE may not be empty (BSD / macOS) und sed: -e expression #1, char 0: no previous regular expression (GNU), weil zum Zeitpunkt der ersten Zeile verarbeitet wird (aufgrund der Zeilennummer 1 den Bereich beginnend), keine regex wurde noch angewandt, so // auf etwas bezieht sich nicht.
Mit Ausnahme von spezieller sed Syntax des GNU 0,/re/, jeder Bereich, der mit einer Zeilennummer beginnt schließt effektiv die Verwendung von //.

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

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

Erklärung:

/#include/ && !done

Führt die Aktion Aussage zwischen {}, wenn die Linie entspricht „# include“ und wir haben es nicht schon verarbeitet.

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

Diese Ausgabe # include „newfile.h“, müssen wir die Zitate entkommen. Dann setzen wir die Variable getan 1, so dass wir nicht hinzufügen mehr enthält.

1;

Das bedeutet, „die Zeile auszudrucken“ - eine leere Aktion defaults $ 0, zu drucken, die die ganze Zeile druckt. Ein Einzeiler und leichter zu verstehen als sed IMO: -)

Ganz eine umfassende Sammlung von Antworten auf linuxtopia sed FAQ . Es unterstreicht auch, dass einige Antworten gegeben Menschen nicht mit Nicht-GNU-Version von sed, zB

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

in nicht-GNU-Version werden müssen

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

Allerdings wird diese Version nicht mit Gnu sed arbeiten.

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

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

sed s/#include/#include "newfile.h"\n#include/1
#!/bin/sed -f
1,/^#include/ {
    /^#include/i\
#include "newfile.h"
}

Wie das Skript funktioniert. Für Strecken zwischen 1 und der ersten #include (nach Zeile 1), wenn die Leitung mit #include beginnt, dann schreibe die angegebene Zeile

Wenn jedoch der erste #include in Zeile 1 ist, dann beide Linie 1 und die nächste nachfolgenden #include wird sich die Linie vorangestellt. Wenn Sie GNU sed verwenden, hat es eine Erweiterung, wo 0,/^#include/ (statt 1,) das Richtige tun wird.

Eine mögliche Lösung:

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

Erklärung:

  • Zeilen lesen, bis wir die # include finden, drucken Sie diese Zeilen dann neuen Zyklus beginnen
  • Setzen Sie die neue Include-Zeile
  • eine Schleife eingeben, die nur Linien lesen (standardmäßig sed druckt auch diese Zeilen), werden wir nicht wieder auf den ersten Teil des Skripts von hier
  • erhalten

Ich weiß, dass dies eine alte Post, aber ich hatte eine Lösung, die ich verwenden zu verwenden:

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

Im Grunde grep verwenden, um die ersten Vorkommen zu finden und dort anhält. Auch Druckzeilennummer dh 5: Zeile. Rohr, das in sed und entfernen: und etwas nach, so dass Sie nur mit einer Zeilennummer links. Rohr, das in sed, die fügen s /.*/ ersetzen zu Ende, die das eine 1 Zeile Skript gibt, die in den letzten sed geleitet wird als Skript auf Datei auszuführen.

also wenn regex = # include und ersetzen = blah und das erste Auftreten grep finden, ist in Zeile 5 dann die zum letzten verrohrt Daten sed 5s wäre /.*/ blah /.

Wenn jemand hier kam ein Zeichen für das erste Auftreten in allen Linien zu ersetzen (wie ich), verwenden Sie diese:

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

Nach 1 bis 2 zum Beispiel ändern, können Sie alle ersetzen die zweite eine ist nur statt.

ich tun würde, dies mit einem awk-Skript:

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

dann führen Sie es mit awk:

awk -f awkscript headerfile.h > headerfilenew.h

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

Als Alternativvorschlag können Sie auf dem ed Befehl suchen.

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

Ich habe endlich die in einem Bash-Skript zu arbeiten, verwendet, um einen eindeutigen Zeitstempel in jedem Elemente in einem RSS-Feed einzufügen:

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

Es ändert sich das erste Vorkommen nur.

${nowms} die Zeit in Millisekunden von einem Perl-Skript festgelegt, $counter ein Zähler für die Regelung im Skript verwendet wird, \ ermöglicht der Befehl in der nächsten Zeile fortgesetzt werden.

Die Datei wird eingelesen und stdout wird auf eine Arbeitsdatei umgeleitet.

So wie ich es verstehe, 1,/====RSSpermalink====/ Tells sed, wenn durch Einstellen eine Bereichsbegrenzung zu stoppen, und dann s/====RSSpermalink====/${nowms}/ ist der bekannte sed Befehl, um die erste Saite mit dem zweiten zu ersetzen.

In meinem Fall habe ich den Befehl in doppelten Anführungszeichen becauase ich es in einem Bash-Skript mit Variablen verwenden.

Mit FreeBSD ed und vermeiden ed des "keine Übereinstimmung" Fehler im Fall gibt es keine include-Anweisung in einer Datei verarbeitet werden:

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

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

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

oder wenn der Speicher ist kein Problem:

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

Mit GNU -z Option des sed könnten Sie die ganze Datei verarbeiten, als ob es nur eine Zeile war. Auf diese Weise ein s/…/…/ würde nur das erste Spiel in der gesamten Datei ersetzen. Denken Sie daran:. s/…/…/ nur ersetzt das erste Spiel in jeder Zeile, aber mit der Option -z sed behandelt die gesamte Datei als eine einzige Zeile

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

Im allgemeinen Fall haben Sie Ihren sed Ausdruck zu umschreiben, da der Musterraum hält nun die gesamte Datei, statt nur eine Zeile. Einige Beispiele:

  • s/text.*// kann als s/text[^\n]*// neu geschrieben werden. [^\n] paßt alles außer die Newline-Zeichen. [^\n]* werden alle Symbole nach text entsprechen, bis ein Newline erreicht ist.
  • s/^text// kann als s/(^|\n)text// neu geschrieben werden.
  • s/text$// kann als s/text(\n|$)// neu geschrieben werden.

Der folgende Befehl entfernt das erste Vorkommen eines Strings innerhalb einer Datei. Es entfernt die zu Leerzeile. Es basiert auf einer XML-Datei dargestellt, aber es wäre mit einem beliebigen Datei arbeiten.

Nützlich, wenn Sie mit XML-Dateien arbeiten, und Sie wollen einen Tag entfernen. In diesem Beispiel wird es das erste Vorkommen des „ISTAG“ Tages.

Befehl:

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

Die 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 für mich nicht auf Solaris SunOS arbeitet 5,10 (ziemlich alt), aber es funktioniert auf Linux 2.6, sed Version 4.1.5

Nichts Neues, aber vielleicht ein wenig mehr konkrete Antwort: sed -rn '0,/foo(bar).*/ s%%\1%p'

Beispiel: xwininfo -name unity-launcher erzeugt 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

Extrahieren Fenster ID mit xwininfo -name unity-launcher|sed -rn '0,/^xwininfo: Window id: (0x[0-9a-fA-F]+).*/ s%%\1%p' erzeugt:

0x2200003

POSIXly (gilt auch in sed), Nur ein regex verwendet, müssen Speicher nur 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.
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top