Ich möchte nur die ersten k
Instanzen eines Wortes ersetzen .
Wie kann ich das machen?
Z.B. Angenommen, die Datei foo.txt
enthält 100 Instanzen des Wortes "Linux".
Ich muss nur die ersten 50 Vorkommen ersetzen.
Ich möchte nur die ersten k
Instanzen eines Wortes ersetzen .
Wie kann ich das machen?
Z.B. Angenommen, die Datei foo.txt
enthält 100 Instanzen des Wortes "Linux".
Ich muss nur die ersten 50 Vorkommen ersetzen.
Antworten:
Der erste Abschnitt beschreibt, sed
wie Sie die ersten k-Vorkommen in einer Zeile ändern. Der zweite Abschnitt erweitert diesen Ansatz, um nur die ersten k-Vorkommen in einer Datei zu ändern, unabhängig davon, in welcher Zeile sie erscheinen.
Mit standard sed gibt es einen Befehl, um das k-te Vorkommen eines Wortes in einer Zeile zu ersetzen. Wenn k
3 ist, zum Beispiel:
sed 's/old/new/3'
Oder man kann alle Vorkommen ersetzen durch:
sed 's/old/new/g'
Beides ist nicht das, was Sie wollen.
GNU sed
bietet eine Erweiterung, die das k-te Vorkommen und alles danach ändert. Wenn k 3 ist, zum Beispiel:
sed 's/old/new/g3'
Diese können kombiniert werden, um das zu tun, was Sie wollen. So ändern Sie die ersten 3 Vorkommen:
$ echo old old old old old | sed -E 's/\<old\>/\n/g4; s/\<old\>/new/g; s/\n/old/g'
new new new old old
Wo \n
ist hier nützlich, weil wir sicher sein können, dass es nie in einer Zeile auftritt.
Wir verwenden drei sed
Substitutionsbefehle:
s/\<old\>/\n/g4
Dies ist die GNU-Erweiterung, um das vierte und alle nachfolgenden Vorkommen von old
mit zu ersetzen \n
.
Die erweiterte Regex-Funktion \<
wird verwendet, um den Wortanfang und \>
das Wortende abzugleichen. Dies stellt sicher, dass nur vollständige Wörter gefunden werden. Erweiterte reguläre Ausdrücke erfordern die -E
Option zu sed
.
s/\<old\>/new/g
Es old
bleiben nur die ersten drei Vorkommen von übrig, und dies ersetzt sie alle durch new
.
s/\n/old/g
Das vierte und alle übrigen Vorkommen von old
wurden \n
im ersten Schritt durch ersetzt. Dies bringt sie in ihren ursprünglichen Zustand zurück.
Wenn GNU sed nicht verfügbar ist und Sie die ersten drei Vorkommen von old
to ändern möchten new
, verwenden Sie drei s
Befehle:
$ echo old old old old old | sed -E -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/'
new new new old old
Dies funktioniert gut, wenn k
es sich um eine kleine Zahl handelt, die jedoch schlecht bis groß skaliert k
.
Da einige Nicht-GNU-Seds das Kombinieren von Befehlen mit Semikolons nicht unterstützen, wird hier jeder Befehl mit einer eigenen -e
Option eingeführt. Es kann auch erforderlich sein, zu überprüfen, ob Ihr sed
die Wortbegrenzungssymbole \<
und unterstützt \>
.
Wir können sed anweisen, die gesamte Datei einzulesen und dann die Ersetzungen vorzunehmen. Um zum Beispiel die ersten drei Vorkommen old
einer BSD-artigen sed zu ersetzen :
sed -E -e 'H;1h;$!d;x' -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/'
Die sed-Befehle H;1h;$!d;x
lesen die gesamte Datei ein.
Da die oben genannten keine GNU-Erweiterung verwenden, sollte es auf BSD (OSX) sed funktionieren. Beachten Sie, dass dieser Ansatz ein erfordert sed
, das lange Zeilen verarbeiten kann. GNU sed
sollte in Ordnung sein. Wer eine Nicht-GNU-Version von verwendet, sed
sollte seine Fähigkeit testen, lange Leitungen zu handhaben.
Mit einem GNU-Sed können wir den g
oben beschriebenen Trick weiter verwenden , aber durch \n
ersetzt \x00
, um die ersten drei Vorkommen zu ersetzen:
sed -E -e 'H;1h;$!d;x; s/\<old\>/\x00/g4; s/\<old\>/new/g; s/\x00/old/g'
Dieser Ansatz skaliert gut und k
wird groß. Dies setzt jedoch voraus, dass dies \x00
nicht in Ihrer ursprünglichen Zeichenfolge enthalten ist. Da es unmöglich ist, das Zeichen \x00
in eine Bash-Zeichenfolge einzufügen, ist dies normalerweise eine sichere Annahme.
tr '\n' '|' < input_file | sed …
. Aber das wandelt natürlich die gesamte Eingabe in eine Zeile um, und einige Nicht-GNU-Seds können nicht mit beliebig langen Zeilen umgehen. (2) Sie sagen: „… oben sollte die in Anführungszeichen stehende Zeichenfolge '|'
durch ein beliebiges Zeichen oder eine Zeichenfolge ersetzt werden.“ Sie können jedoch kein tr
Zeichen durch eine Zeichenfolge (mit einer Länge> 1) ersetzen. (3) In Ihrem letzten Beispiel sagen Sie -e 's/\<old\>/new/' -e 's/\<old\>/w/' | tr '\000' '\n'\>/new
. Dies scheint ein Tippfehler für zu sein -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/' | tr '\000' '\n'
.
Die awk-Befehle können verwendet werden, um die ersten N Vorkommen des Wortes durch die Ersetzung zu ersetzen.
Die Befehle werden nur ersetzt, wenn das Wort vollständig übereinstimmt.
In den folgenden Beispielen ersetze ich die ersten 27
Vorkommen von old
durchnew
Unter Verwendung
awk '{for(i=1;i<=NF;i++){if(x<27&&$i=="old"){x++;sub("old","new",$i)}}}1' file
Dieser Befehl durchläuft jedes Feld, bis es übereinstimmt
old
, überprüft, ob der Zähler unter 27 liegt, inkrementiert und ersetzt die erste Übereinstimmung in der Zeile. Geht dann auf das nächste Feld / die nächste Zeile und wiederholt.
Ersetzen Sie das Feld manuell
awk '{for(i=1;i<=NF;i++)if(x<27&&$i=="old"&&$i="new")x++}1' file
Ähnlich wie beim vorherigen Befehl, aber da es bereits einen Marker für das Feld gibt
($i)
, ändert es einfach den Wert des Felds vonold
nachnew
.
Führen Sie vorher eine Überprüfung durch
awk '/old/&&x<27{for(i=1;i<=NF;i++)if(x<27&&$i=="old"&&$i="new")x++}1' file
Wenn Sie überprüfen, ob die Zeile alt ist und der Zähler unter 27 liegt
SHOULD
, erhöhen Sie die Geschwindigkeit geringfügig, da keine Zeilen verarbeitet werden, wenn diese falsch sind.
ERGEBNISSE
Z.B
old bold old old old
old old nold old old
old old old gold old
old gold gold old old
old old old man old old
old old old old dog old
old old old old say old
old old old old blah old
zu
new bold new new new
new new nold new new
new new new gold new
new gold gold new new
new new new man new new
new new new new dog new
new new old old say old
old old old old blah old
Angenommen, Sie möchten nur die ersten drei Instanzen einer Zeichenfolge ersetzen ...
seq 11 100 311 |
sed -e 's/1/\
&/g' \ #s/match string/\nmatch string/globally
-e :t \ #define label t
-e '/\n/{ x' \ #newlines must match - exchange hold and pattern spaces
-e '/.\{3\}/!{' \ #if not 3 characters in hold space do
-e 's/$/./' \ #add a new char to hold space
-e x \ #exchange hold/pattern spaces again
-e 's/\n1/2/' \ #replace first occurring '\n1' string w/ '2' string
-e 'b t' \ #branch back to label t
-e '};x' \ #end match function; exchange hold/pattern spaces
-e '};s/\n//g' #end match function; remove all newline characters
Hinweis: Das oben Genannte funktioniert wahrscheinlich nicht mit eingebetteten Kommentaren
... oder in meinem Beispiel mit einer '1' ...
22
211
211
311
Dort verwende ich zwei bemerkenswerte Techniken. Zunächst wird jedes Vorkommen 1
einer Zeile durch ersetzt \n1
. Auf diese Weise kann ich beim nächsten rekursiven Ersetzen sicher sein, dass das Vorkommen nicht zweimal ersetzt wird, wenn meine Ersetzungszeichenfolge meine Ersetzungszeichenfolge enthält. Zum Beispiel, wenn ich ersetzen he
mit hey
ihm wird immer noch funktionieren.
Ich mache das wie:
s/1/\
&/g
Zweitens zähle ich die Ersetzungen, indem ich h
bei jedem Auftreten ein Zeichen in das alte Feld einfüge. Sobald ich drei bin, tritt nichts mehr auf. Wenn Sie dies auf Ihre Daten anwenden und \{3\}
die Anzahl der von Ihnen gewünschten Ersetzungen und die /\n1/
Adressen der zu ersetzenden Adressen ändern , sollten Sie nur so viele Ersetzungen vornehmen, wie Sie möchten.
Ich habe das ganze -e
Zeug nur zur besseren Lesbarkeit gemacht. POSIXly Es könnte so geschrieben werden:
nl='
'; sed "s/1/\\$nl&/g;:t${nl}/\n/{x;/.\{3\}/!{${nl}s/$/./;x;s/\n1/2/;bt$nl};x$nl};s/\n//g"
Und w / GNU sed
:
sed 's/1/\n&/g;:t;/\n/{x;/.\{3\}/!{s/$/./;x;s/\n1/2/;bt};x};s/\n//g'
Denken Sie auch daran, dass dies sed
zeilenorientiert ist - es liest nicht die gesamte Datei ein und versucht dann, eine Schleife darüber zu erstellen, wie dies in anderen Editoren häufig der Fall ist. sed
ist einfach und effizient. Trotzdem ist es oft praktisch, Folgendes zu tun:
Hier ist eine kleine Shell-Funktion, die sie zu einem einfach ausgeführten Befehl zusammenfasst:
firstn() { sed "s/$2/\
&/g;:t
/\n/{x
/.\{$(($1))"',\}/!{
s/$/./; x; s/\n'"$2/$3"'/
b t
};x
};s/\n//g'; }
Also damit kann ich machen:
seq 11 100 311 | firstn 7 1 5
...und bekomme...
55
555
255
311
...oder...
seq 10 1 25 | firstn 6 '\(.\)\([1-5]\)' '\15\2'
...bekommen...
10
151
152
153
154
155
16
17
18
19
20
251
22
23
24
25
... oder, um Ihrem Beispiel zu entsprechen (in einer kleineren Größenordnung) :
yes linux | head -n 10 | firstn 5 linux 'linux is an os kernel'
linux is an os kernel
linux is an os kernel
linux is an os kernel
linux is an os kernel
linux is an os kernel
linux
linux
linux
linux
linux
Eine kurze Alternative in Perl:
perl -pe 'BEGIN{$n=3} 1 while s/old/new/ && ++$i < $n' your_file
Ändern Sie den Wert von `$ n $ nach Ihren Wünschen.
Wie es funktioniert:
new
für old
( s/old/new/
) und wann immer sie kann, erhöht er die Variable $i
( ++$i
).1 while ...
so lange an der Zeile ( ), wie es $n
insgesamt weniger als Ersetzungen vorgenommen hat, und es kann in dieser Zeile mindestens eine Ersetzung vornehmen.Verwenden Sie eine Muschelschlaufe und ex
!
{ for i in {1..50}; do printf %s\\n '0/old/s//new/'; done; echo x;} | ex file.txt
Ja, es ist ein bisschen doof.
;)
Hinweis: Dies kann fehlschlagen, wenn old
die Datei weniger als 50 Instanzen von enthält. (Ich habe es nicht getestet.) In diesem Fall würde die Datei unverändert bleiben.
Besser noch, benutze Vim.
vim file.txt
qqgg/old<CR>:s/old/new/<CR>q49@q
:x
Erläuterung:
q # Start recording macro
q # Into register q
gg # Go to start of file
/old<CR> # Go to first instance of 'old'
:s/old/new/<CR> # Change it to 'new'
q # Stop recording
49@q # Replay macro 49 times
:x # Save and exit
Eine einfache, aber nicht sehr schnelle Lösung besteht darin, die in /programming/148451/how-to-use-sed-to-replace-only-the-irst-occurrence-in-a beschriebenen Befehle zu durchlaufen -Datei
for i in $(seq 50) ; do sed -i -e "0,/oldword/s//newword/" file.txt ; done
Dieser spezielle sed Befehl funktioniert wahrscheinlich nur für GNU sed und wenn newword nicht Teil von oldword ist . Für Nicht-GNU-Benutzer siehe hier, wie nur das erste Muster in einer Datei ersetzt wird.
Mit GNU können awk
Sie das Datensatztrennzeichen RS
auf das Wort setzen, das durch Wortgrenzen getrennt werden soll. In diesem Fall wird das Datensatztrennzeichen in der Ausgabe auf das Ersatzwort für die ersten k
Datensätze gesetzt, während das ursprüngliche Datensatztrennzeichen für den Rest beibehalten wird
awk -vRS='\\ylinux\\y' -vreplacement=unix -vlimit=50 \
'{printf "%s%s", $0, NR <= limit? replacement: RT}' file
ODER
awk -vRS='\\ylinux\\y' -vreplacement=unix -vlimit=50 \
'{printf "%s%s", $0, limit--? replacement: RT}' file