Ich möchte eine große Anzahl von C++ - Quelldateien mit einer zusätzlichen include-Direktive aktualisieren, bevor ein vorhandenes # enthält. Für diese Art von Aufgabe verwende ich normalerweise ein kleines Bash-Skript mit sed, um die Datei neu zu schreiben.
Wie erhalte ich, dass sed
nur das erste Vorkommen einer Zeichenfolge in einer Datei ersetzt, anstatt jedes Vorkommen zu ersetzen?
Wenn ich benutze
sed s/#include/#include "newfile.h"\n#include/
es ersetzt alle #inclusse.
Alternative Vorschläge, um dasselbe zu erreichen, sind ebenfalls willkommen.
# 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 bevorzugen: Anmerkung des Herausgebers: Funktioniert nur mit GNUsed
.
sed '0,/RE/s//to_that/' file
Schreiben Sie ein Sed-Skript, das nur das erste Vorkommen von "Apple" durch "Banana" ersetzt.
Beispieleingabe: Ausgabe:
Apple Banana
Orange Orange
Apple Apple
Dies ist das einfache Skript: Anmerkung der Redaktion: Funktioniert nur mitGNUsed
.
sed '0,/Apple/{s/Apple/Banana/}' filename
sed '0,/pattern/s/pattern/replacement/' filename
das hat für mich funktioniert.
beispiel
sed '0,/<Menu>/s/<Menu>/<Menu><Menu>Sub menu<\/Menu>/' try.txt > abc.txt
Anmerkung des Herausgebers: Beide arbeiten nur mitGNUsed
.
Eine Übersicht der vielen hilfreichen vorhandenen Antworten , ergänzt mit Erläuterungen :
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-quoted strings ($'...'
) zur Bereitstellung der Beispieleingabezeilen wird als Shell bash
, ksh
oder zsh
angenommen.
GNU sed
only:
Ben Hoffsteins Antwort zeigt uns, dass GNU eine -Erweiterung zur POSIX-Spezifikation für sed
bereitstellt, die die folgenden 2- Adressform: 0,/re/
(re
steht hier für einen beliebigen regulären Ausdruck).
0,/re/
erlaubt dem Regex, mit in der allerersten Zeile abzugleichen, auch. Mit anderen Worten: Eine solche Adresse erstellt einen Bereich von der ersten Zeile bis einschließlich der Zeile, die mit re
übereinstimmt - unabhängig davon, ob re
in der ersten Zeile oder in einer nachfolgenden Zeile vorkommt.
1,/re/
, mit dem ein Bereich erstellt wird, der von der ersten Zeile bis einschließlich der Zeile übereinstimmt, die in nachfolgenden Zeilen mit re
übereinstimmt. Mit anderen Worten: Dies erkennt nicht das erste Auftreten einer re
-Übereinstimmung, wenn es zufällig in der 1st -Zeile auftritt und verhindert auch die Verwendung der Kurzform //
für die Wiederverwendung der letzten Regex verwendet (siehe nächster Punkt).[1]Wenn Sie eine 0,/re/
-Adresse mit einem s/.../.../
-Aufruf (Substitution) kombinieren, der den regulären Ausdruck same verwendet, führt Ihr Befehl die Substitution effektiv nur in der Zeile first durch, die mit re
übereinstimmt.sed
bietet eine praktische Verknüpfung zur Wiederverwendung des zuletzt angewendeten regulären Ausdrucks : einleeres 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 sed
wie BSD (macOS) sed
(funktioniert auch mit GNU sed
):
Da 0,/re/
nicht verwendet werden kann und das Formular 1,/re/
re
nicht erkennt, wenn es in der allerersten Zeile auftritt (siehe oben), ist eine spezielle Behandlung für die erste Zeile erforderlich .
MikhailVS's answer erwähnt die Technik, die hier in einem konkreten Beispiel aufgeführt ist:
$ 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 Aufruf s
; In beiden Fällen wird regex foo
implizit wiederverwendet, sodass wir es nicht duplizieren müssen. Dies führt zu kürzerem und besser wartbarem Code.
POSIX sed
benötigt nach bestimmten Funktionen aktuelle Zeilenumbrüche, z. B. nach dem Namen eines Etiketts oder nach dessen Auslassung, wie dies hier bei t
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/
ersetzt foo
nur in der ersten Zeile, falls dort vorhanden. In diesem Fall verzweigt t
zum Ende des Skripts (überspringt die verbleibenden Befehle in der Zeile). (Die Funktion t
verzweigt nur zu einer Bezeichnung, wenn der letzte Aufruf von s
eine tatsächliche Ersetzung durchgeführt hat. Wenn keine Bezeichnung vorhanden ist, wird wie hier das Ende des Skripts verzweigt.).
In diesem Fall wird die Bereichsadresse 1,//
, die normalerweise das erste Vorkommen ab Zeile 2 findet, nicht übereinstimmen und der Bereich nicht verarbeitet, da die Adresse bei der Auswertung von ausgewertet wird Die aktuelle Zeile ist bereits 2
.
Umgekehrt, wenn in der ersten Zeile keine Übereinstimmung vorhanden ist, wird 1,//
eingegeben und findet die wahre erste Übereinstimmung.
Der Nettoeffekt ist derselbe wie bei GNU= sed
's 0,/re/
: Es wird nur das erste Vorkommen ersetzt, egal ob es in der 1. Zeile oder einem anderen auftritt.
NON-Range-Ansätze
Potongs Antwort demonstriertloop Techniken die die Notwendigkeit eines Bereichs umgehen ; Da er die Syntax GNU sed
verwendet, sind hier die POSIX-kompatiblen Entsprechungen :
Schleifentechnik 1: Führen Sie beim ersten Match die Ersetzung durch und geben Sie dann eine Schleife ein, die einfach die verbleibenden Zeilen druckt, 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
Loop-Technik 2, nur für kleinere Dateien : liest die gesamte Eingabe in den Speicher und setzt sie dann einzeln um .
$ 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.618 liefert Beispiele für die Vorgänge mit 1,/re/
mit und ohne nachfolgenden s//
:
- sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo'
ergibt $'1bar\n2bar'
; d.h. beide Zeilen wurden aktualisiert, da die Zeilennummer 1
mit der ersten Zeile übereinstimmt und der Ausdruck /foo/
- das Ende des Bereichs - nur ab der Zeile next gesucht wird. Daher werden in diesem Fall both -Zeilen ausgewählt, und die s/foo/bar/
-Ersetzung wird für beide Zeilen ausgeführt.
- sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo'
failed: 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 1. Zeile (aufgrund der Zeilennummer 1
, die den Bereich beginnt) keine Regex vorhanden ist wurde bereits angewendet, daher bezieht sich //
auf nichts.
Mit Ausnahme der speziellen 0,/re/
-Syntax von GNU= sed
) schließt any, die mit line number beginnt, die Verwendung von //
effektiv aus.
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 gibt #include "newfile.h" aus, wir müssen den Anführungszeichen entgehen. Dann setzen wir die done-Variable auf 1, so dass wir keine weiteren Includes hinzufügen.
1;
Dies bedeutet "die Zeile ausdrucken" - eine leere Aktion gibt standardmäßig $ 0 aus und druckt die gesamte Zeile aus. Ein Liner und einfacher zu verstehen als sed IMO :-)
Eine umfassende Sammlung von Antworten zu linuxtopia sed FAQ . Es wird auch hervorgehoben, dass einige Antworten, die von Benutzern bereitgestellt wurden, nicht mit der Nicht-GNU-Version 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 zusammenarbeitet:
-e '/RE/{s//to_that/;:a' -e '$!N;$!ba' -e '}'
ex:
sed -e '/Apple/{s//Banana/;:a' -e '$!N;$!ba' -e '}' filename
#!/bin/sed -f
1,/^#include/ {
/^#include/i\
#include "newfile.h"
}
Funktionsweise dieses Skripts: Für Zeilen zwischen 1 und dem ersten #include
(nach Zeile 1), wenn die Zeile mit #include
beginnt, wird die angegebene Zeile vorangestellt.
Befindet sich der erste #include
jedoch in Zeile 1, wird die Zeile in Zeile 1 und im nächsten #include
vorangestellt. Wenn Sie GNU sed
verwenden, hat es eine Erweiterung, bei der 0,/^#include/
(anstelle von 1,
) das Richtige tut.
Fügen Sie einfach die Anzahl des Vorkommens am Ende hinzu:
sed s/#include/#include "newfile.h"\n#include/1
Eine mögliche Lösung:
/#include/!{p;d;}
i\
#include "newfile.h"
:
n
b
Erläuterung:
Ich weiß, dass dies ein alter Beitrag ist, aber ich hatte eine Lösung, die ich 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 finden und dort anzuhalten. Drucken Sie auch die Zeilennummer, dh 5: Zeile. Pipe das in sed und entferne das: und alles danach, so dass du nur noch eine Zeilennummer hinterlässt. Pipe das in sed, das s /.*/ als Ersetzung hinzufügt, um das Ende zu erreichen, das ein einzeiliges Skript ergibt, das in den letzten sed geleitet wird, um als Skript für Datei auszuführen.
wenn also regex = #include und replace = blah ist und das erste Vorkommen, das grep in Zeile 5 findet, dann die zum letzten sed geleiteten Daten 5s /.*/ blah/lautet.
Wenn jemand hierher gekommen ist, um ein Zeichen für das erste Vorkommen in allen Zeilen (wie ich) zu ersetzen, 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
Indem Sie beispielsweise 1 zu 2 ändern, können Sie stattdessen alle zweiten a ersetzen.
Als alternativen Vorschlag können Sie den Befehl ed
betrachten.
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
Verwenden Sie FreeBSD ed
und vermeiden Sie den Fehler "keine Übereinstimmung" von ed
, falls keine include
-Anweisung in einer zu verarbeitenden Datei vorhanden ist:
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
Ich habe es endlich verstanden, in einem Bash-Skript zu arbeiten, mit dem ein eindeutiger Zeitstempel in jedes Element eines RSS-Feeds eingefügt wird:
sed "1,/====RSSpermalink====/s/====RSSpermalink====/${nowms}/" \
production-feed2.xml.tmp2 > production-feed2.xml.tmp.$counter
Es ändert sich 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 im Skript verwendet wird. \
ermöglicht, dass der Befehl in der nächsten Zeile fortgesetzt wird.
Die Datei wird eingelesen und stdout wird in eine Arbeitsdatei umgeleitet.
So wie ich es verstehe, sagt 1,/====RSSpermalink====/
sed, wann er aufhören soll, indem er eine Bereichsbegrenzung einstellt, und dann ist s/====RSSpermalink====/${nowms}/
der bekannte sed-Befehl, der den ersten String durch den zweiten ersetzt.
In meinem Fall füge ich den Befehl in doppelte Anführungszeichen ein, da ich ihn in einem Bash-Skript mit Variablen verwende.
Das 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...
ich würde das mit einem awk-Skript machen:
BEGIN {i=0}
(i==0) && /#include/ {print "#include \"newfile.h\""; i=1}
{print $0}
END {}
dann mit awk laufen lassen:
awk -f awkscript headerfile.h > headerfilenew.h
könnte schlampig sein, ich bin neu in diesem Bereich.
Nichts Neues, aber vielleicht etwas konkretere Antwort: sed -rn '0,/foo(bar).*/ s%%\1%p'
Beispiel: xwininfo -name unity-launcher
erzeugt eine Ausgabe wie folgt:
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
POSIXly (auch gültig in sed), Nur one regex wird verwendet, Speicher wird nur für eine Zeile benötigt (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.
Mit der Option -z
von GNU sed können Sie die gesamte Datei so verarbeiten, als wäre sie nur eine Zeile. Auf diese Weise ersetzt ein s/…/…/
nur die erste Übereinstimmung in der gesamten Datei. Denken Sie daran: s/…/…/
ersetzt nur die erste Übereinstimmung in jeder Zeile, aber mit der -z
-Option sed
wird die gesamte Datei als eine einzige Zeile behandelt.
sed -z 's/#include/#include "newfile.h"\n#include'
Im Allgemeinen müssen Sie Ihren sed-Ausdruck neu schreiben, da der Musterbereich jetzt die gesamte Datei enthält und nicht nur eine Zeile. Einige Beispiele:
s/text.*//
kann als s/text[^\n]*//
umgeschrieben werden. [^\n]
stimmt mit allem überein außer dem Zeilenvorschubzeichen. [^\n]*
stimmt mit allen Symbolen nach text
überein, bis eine neue Zeile erreicht wird.s/^text//
kann als s/(^|\n)text//
umgeschrieben werden.s/text$//
kann als s/text(\n|$)//
umgeschrieben werden.Der folgende Befehl entfernt das erste Vorkommen einer Zeichenfolge in einer Datei. Es entfernt auch die leere Zeile. 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 Vorkommen 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: Unter Solaris SunOS 5.10 (ziemlich alt) funktionierte es nicht für mich, aber unter Linux 2.6, Sed-Version 4.1.5