Wie sed verwendet nur das erste Vorkommen in einer Datei zu ersetzen?
-
02-07-2019 - |
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.
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
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, diere
auf nachfolgende Linien übereinstimmt; mit anderen Worten: der nicht das erste Auftreten einerre
Ü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 ims
Anruf; in beiden Fällen wird regexfoo
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 mitt
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
arbeitensed '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 alss/text[^\n]*//
neu geschrieben werden.[^\n]
paßt alles außer die Newline-Zeichen.[^\n]*
werden alle Symbole nachtext
entsprechen, bis ein Newline erreicht ist. -
s/^text//
kann alss/(^|\n)text//
neu geschrieben werden. -
s/text$//
kann alss/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.