Wie ersetze ich nur das N-te Vorkommen eines Musters in einer Datei?

10

So ersetzen Sie das dritte Vorkommen der Zeichenfolge in der Datei mit dem sedBefehl.

Beispiel:

Ändern Sie nur das dritte Vorkommen von isto usin der Datei.

Meine Eingabedatei enthält:

hai this is linux.
hai this is unix.
hai this is mac.
hai this is unchanged.

Ich erwarte Ausgabe ist:

hai this is linux.
hai thus is unix.
hai this is mac.
hai this is unchanged.
Suresh Kumar
quelle
3
Eingabe und Ausgabe sind gleich.
Hauke ​​Laging
4
sedist nicht das richtige Werkzeug für den Job.
Choroba
@don_crissti Ich habe es behoben. Das OP hatte die Formatierungswerkzeuge nicht verwendet (übrigens Sureshkumar, hier finden Sie Hilfe zum Bearbeiten Ihrer Fragen), und aufeinanderfolgende Redakteure hatten falsch verstanden, was gewünscht wurde.
Terdon

Antworten:

11

Es ist viel einfacher gemacht perl.

Um die 3 zu ändern rd Vorkommen:

perl -pe 's{is}{++$n == 3 ? "us" : $&}ge'

So ändern Sie jedes dritte Vorkommen:

perl -pe 's{is}{++$n % 3 ? $& : "us"}ge'
Stéphane Chazelas
quelle
3

Wenn die Ersetzungszeichenfolge nur einmal pro Zeile vorkommt, können Sie verschiedene Dienstprogramme kombinieren.
Wenn sich die Eingabe in der Datei "Eingabe" befindet und Sie "ist" durch "uns" ersetzen, können Sie verwenden

LINENR=$(cat input | grep -n " is " | head -3 | tail -1 | cut -d: -f1)
cat input | sed ${LINENR}' s/ is / us /'
Walter A.
quelle
In dem Beispiel in der Frage gibt es mehr als eine ispro Zeile.
Terdon
Ich dachte du suchst "ist" mit Leerzeichen. Ich könnte meine Antwort mit dem Befehl tr bearbeiten, wie es @jimmij verwendet hat, aber meine Lösung würde seiner weit unterlegen sein.
Walter A
Ich bin nicht der Fragesteller :). Ich dachte , die gleiche Sache, weshalb ich Ihre Antwort upvoted hatte, aber wenn man sich die Originalversion der Frage suchen (klicken Sie auf den „Edited X Minuten“ Link) Sie werden sehen , dass die OP erwartet das ist in dieser geändert werden , so . Übrigens ist dort keine Katze nötig .
Terdon
2

Das folgende Skript (unter Verwendung der GNU- sed Syntax) kann für die Inplace-Bearbeitung und nicht für die Ausgabe verwendet werden, da die Druckzeilen nach der gewünschten Ersetzung gestoppt werden:

sed -i '/is/{: 1 ; /\(.*is\)\{3\}/!{N;b1} ; s/is/us/3 ; q}' text.file

Wenn Sie eine Choroba- Entscheidung mögen , können Sie diese oben ändern

sed '/is/{:1 ; /\(.*is\)\{3\}/!{N;b1} ; s/is/us/3 ; :2 ; n ; $!b2}' text.file

welches alle Zeilen ausgibt

Oder Sie müssen alle Zeilen in den Musterbereich einfügen (im Speicher, seien Sie also vorsichtig mit der Größenbeschränkung) und ersetzen

sed ': 1 ; N ; $!b1 ; s/is/us/3 ' text.file
Costas
quelle
2

Sie können dies verwenden sed, wenn zuvor Zeilenumbrüche durch andere Zeichen ersetzt wurden, z.

tr '\n' '\000' | sed 's/is/us/3' | tr '\000' '\n'

Und das gleiche mit pure (GNU) sed:

sed ':a;N;$!ba;s/\n/\x0/g;s/is/us/3;s/\x0/\n/g'

( sedNewline-Ersatz, der schamlos von /programming//a/1252191/4488514 gestohlen wurde )

jimmij
quelle
Wenn Sie eine GNU- sedspezifische Syntax verwenden möchten, können Sie diese auch verwenden sed -z 's/is/us/3'.
Stéphane Chazelas
@ StéphaneChazelas -zmuss eine brandneue Funktion sein, ich GNU sed version 4.2.1weiß nichts über diese Option.
Jimmy
1
Hinzugefügt in 4.2.2 (2012). In Ihrer zweiten Lösung benötigen Sie die Konvertierung nicht in \x0Schritt.
Stéphane Chazelas
Entschuldigung für die Bearbeitung. Ich hatte die Originalversion der Frage nicht gesehen und jemand hatte sie missverstanden und die falsche Zeile bearbeitet. Ich bin zur vorherigen Version zurückgekehrt.
Terdon
1
p='[:punct:]' s='[:space:]'
sed -Ee'1!{/\n/!b' -e\}            \
     -e's/(\n*)(.*)/ \2 \1/'       \
     -e"s/is[$p]?[$s]/\n&/g"       \
     -e"s/([^$s])\n/\1/g;1G"       \
-e:c -e"s/\ni(.* )\n{3}/u\1/"      \
     -e"/\n$/!s/\n//g;/\ni/G"      \
     -e's//i/;//tc'                \
     -e's/^ (.*) /\1/;P;$d;N;D'

Dieses Bit von sedträgt nur eine Liste von isEreignissen von einer Zeile zur nächsten. Es sollte zuverlässig so viele ises pro Zeile verarbeiten, wie Sie darauf werfen, und es muss dabei keine alten Zeilen puffern - es behält nur ein einzelnes Zeilenumbruchzeichen für jedes is, auf das es trifft, das nicht Teil eines anderen Wortes ist.

Das Ergebnis ist, dass nur das dritte Vorkommen in einer Datei geändert wird - und die Anzahl pro Zeile übertragen wird. Wenn also eine Datei so aussieht:

1. is is isis
2. is does

... es wird gedruckt ...

1. is is isis
2. us does

Zunächst werden Randfälle behandelt, indem am Kopf und am Ende jeder Zeile ein Leerzeichen eingefügt wird. Dies erleichtert die Ermittlung von Wortgrenzen.

Als nächstes wird nach gültigen ises \ngesucht, indem eine Ewline eingefügt wird, bevor alle Vorkommen isunmittelbar vor null oder einem Satzzeichen stehen, gefolgt von einem Leerzeichen. Es führt einen weiteren Durchgang durch und entfernt alle \nEwlines, denen unmittelbar ein Nicht-Leerzeichen vorangestellt ist. Diese zurückgelassenen Markierungen stimmen überein is.und isaber nicht thisoder ?is.

Als nächstes wird jeder Marker am Ende der Zeichenfolge \nigesammelt. Bei jeder Übereinstimmung in einer Zeile wird eine \nneue Zeile an das Ende der Zeichenfolge angehängt und durch entweder ioder ersetzt u. Wenn \nsich am Ende des Strings 3 Ewlines in einer Reihe befinden, wird das u - sonst das i verwendet. Das erste Mal, wenn au verwendet wird, ist auch das letzte Mal - der Ersatz löst eine Endlosschleife aus, die auf get line, print line, get line, print line,usw. hinausläuft.

Am Ende jedes Try-Loop-Zyklus werden die eingefügten Leerzeichen bereinigt, nur bis zur ersten auftretenden neuen Zeile im Musterbereich gedruckt und erneut ausgeführt.

Ich werde einen look-Befehl am Kopf der Schleife hinzufügen wie:

l; s/\ni(.* )\n{9}/u\1/...

... und schauen Sie sich an, was es mit dieser Eingabe macht:

hai this is linux.
hai this is unix.


hai this is mac.
hai this is unchanged is.

... also hier ist was es tut:

 hai this \nis linux. \n$        #behind the scenes
hai this is linux.               #actually printed
 hai this \nis unix. \n\n$       #it builds the marker string
hai this is unix.
  \n\n\n$                        #only for lines matching the

  \n\n\n$                        #pattern - and not otherwise.

 hai this \nis mac. \n\n\n$      #here's the match - 3 ises so far in file.
hai this us mac.                 #printed
hai this is unchanged is.        #no look here - this line is never evaled

Es macht vielleicht mehr Sinn mit mehr ises pro Zeile:

nthword()(  p='[:punct:]' s='[:space:]'         
    sed -e '1!{/\n/!b' -e\}             \
        -e 's/\(\n*\)\(.*\)/ \2 \1/'    \
        -e "s/$1[$p]\{0,1\}[$s]/\n&/g"  \
        -e "s/\([^$s]\)\n/\1/g;1G;:c"   \
        -e "${dbg+l;}s/\n$1\(.* \)\n\{$3\}/$2\1/" \
        -e '/\n$/!s/\n//g;/\n'"$1/G"    \
        -e "s//$1/;//tc" -e 's/^ \(.*\) /\1/'     \
        -e 'P;$d;N;D'
)        

Das ist praktisch das Gleiche, aber mit POSIX BRE und rudimentärer Argumentation geschrieben.

 printf 'is is. is? this is%.0s\n' {1..4}  | nthword is us 12

... bekommt ...

is is. is? this is
is is. is? this is
is is. is? this us
is is. is? this is

... und wenn ich aktiviere ${dbg}:

printf 'is is. is? this is%.0s\n' {1..4}  | 
dbg=1 nthword is us 12

... wir können es iterieren sehen ...

 \nis \nis. \nis? this \nis \n$
 is \nis. \nis? this \nis \n\n$
 is is. \nis? this \nis \n\n\n$
 is is. is? this \nis \n\n\n\n$
is is. is? this is
 \nis \nis. \nis? this \nis \n\n\n\n\n$
 is \nis. \nis? this \nis \n\n\n\n\n\n$
 is is. \nis? this \nis \n\n\n\n\n\n\n$
 is is. is? this \nis \n\n\n\n\n\n\n\n$
is is. is? this is
 \nis \nis. \nis? this \nis \n\n\n\n\n\n\n\n\n$
 is \nis. \nis? this \nis \n\n\n\n\n\n\n\n\n\n$
 is is. \nis? this \nis \n\n\n\n\n\n\n\n\n\n\n$
 is is. is? this \nis \n\n\n\n\n\n\n\n\n\n\n\n$
is is. is? this us
is is. is? this is
mikeserv
quelle
Wussten Sie, dass in Ihrem Beispiel "isis" steht?
Flarn2006
@ flarn2006 - ich bin mir ziemlich sicher, dass es heißt.
Mikeserv
0

Hier ist eine logische Lösung, die ein Skript verwendet sedund trin dieses geschrieben werden muss, damit es funktioniert. Der folgende Code ersetzt jedes dritte Vorkommen des im sedBefehl angegebenen Wortes . Ersetzen Sie i=3durch i=n, damit dies für alle funktioniert n.

Code:

# replace new lines with '^' character to get everything onto a single line
tr '\n' '^' < input.txt > output.txt

# count number of occurrences of the word to be replaced
num=`grep -o "apple" "output.txt" | wc -l`

# in successive iterations, replace the i + (n-1)th occurrence
n=3
i=3
while [ $i -le $num ]
do
    sed -i '' "s/apple/lemon/${i}" 'output.txt'
    i=$(( i + (n-1) ))
done

# replace the '^' back to new line character
tr '^' '\n' < output.txt > tmp && mv tmp output.txt


Warum das funktioniert:

Angenommen, die Textdatei ist a b b b b a c a d a b b b a b e b z b s b a b.

  • Wenn n = 2: Wir wollen jedes zweite Vorkommen von ersetzen b.

    • a b b b b a c a d a b b b a b e b z b s b a b
      . . ^ . ^ . . . . . . ^ . . ^ . . . ^ . ^ . ^
    • Zuerst ersetzen wir das 2. Vorkommen, dann das 3. Vorkommen, dann das 4., 5. und so weiter. Zählen Sie in der oben gezeigten Reihenfolge, um dies selbst zu sehen.
  • Wenn n = 3: Wir wollen jedes dritte Vorkommen von ersetzen b.

    • a b b b b a c a d a b b b a b e b z b s b a b
      . . . ^ . . . . . . . ^ . . . . ^ . . . . . ^
    • Zuerst ersetzen wir das 3. Vorkommen, dann das 5., dann das 7., 9., 11. und so weiter.
  • Wenn n = 4: Wir wollen jedes dritte Vorkommen von ersetzen b.

    • Zuerst ersetzen wir das 4. Vorkommen, dann das 7., dann das 10., das 13. und so weiter.
agdhruv
quelle