Ich möchte nur die ersten kInstanzen eines Wortes ersetzen .
Wie kann ich das machen?
Z.B. Angenommen, die Datei foo.txtenthält 100 Instanzen des Wortes "Linux".
Ich muss nur die ersten 50 Vorkommen ersetzen.
Ich möchte nur die ersten kInstanzen eines Wortes ersetzen .
Wie kann ich das machen?
Z.B. Angenommen, die Datei foo.txtenthält 100 Instanzen des Wortes "Linux".
Ich muss nur die ersten 50 Vorkommen ersetzen.
Antworten:
Der erste Abschnitt beschreibt, sedwie 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 k3 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 sedbietet 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 \nist hier nützlich, weil wir sicher sein können, dass es nie in einer Zeile auftritt.
Wir verwenden drei sedSubstitutionsbefehle:
s/\<old\>/\n/g4
Dies ist die GNU-Erweiterung, um das vierte und alle nachfolgenden Vorkommen von oldmit 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 -EOption zu sed.
s/\<old\>/new/g
Es oldbleiben 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 oldwurden \nim 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 oldto ändern möchten new, verwenden Sie drei sBefehle:
$ 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 kes 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 -eOption eingeführt. Es kann auch erforderlich sein, zu überprüfen, ob Ihr seddie 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 oldeiner 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;xlesen 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 sedsollte in Ordnung sein. Wer eine Nicht-GNU-Version von verwendet, sedsollte seine Fähigkeit testen, lange Leitungen zu handhaben.
Mit einem GNU-Sed können wir den goben beschriebenen Trick weiter verwenden , aber durch \nersetzt \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 kwird groß. Dies setzt jedoch voraus, dass dies \x00nicht in Ihrer ursprünglichen Zeichenfolge enthalten ist. Da es unmöglich ist, das Zeichen \x00in 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 trZeichen 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 27Vorkommen von olddurchnew
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 vonoldnachnew.
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 1einer 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 hemit heyihm wird immer noch funktionieren.
Ich mache das wie:
s/1/\
&/g
Zweitens zähle ich die Ersetzungen, indem ich hbei 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 -eZeug 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 sedzeilenorientiert 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. sedist 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:
newfü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 $ninsgesamt 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 olddie 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 awkSie das Datensatztrennzeichen RSauf 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 kDatensä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