Ersetzen Sie das zweite Vorkommen online


12

Ich habe eine Liste von Dateien:

./a.temp.txt     ./a.temp.txt
./a/b.temp.txt   ./a/b.temp.txt
./a/b/c.temp.txt ./a/b/c.temp.txt

Und ich möchte das temp.in jeder Zeile entfernen , aber nur das zweite Vorkommen , daher sollte die Datei so aussehen:

./a.temp.txt     ./a.txt
./a/b.temp.txt   ./a/b.txt
./a/b/c.temp.txt ./a/b/c.txt

Wie soll ich das machen?


Ist es möglich, dass es in einer Zeile ein drittes oder viertes Vorkommen gibt? Müssen diese intakt bleiben?
James

Mein Fall war, den alten Dateinamen mit dem neuen Dateinamen abzugleichen, sodass nur 2 Vorkommen in jeder Zeile vorhanden sind. Und nur der zweite sollte sich ändern.
nobe4

Antworten:


10

Im Allgemeinen können Sie das N-te Vorkommen von etwas mit \zsund abgleichen \{N}. Es gibt ein Beispiel bei :help \zs.

In Ihrem Fall wäre der Befehl:

:%s/\(.\{-}\zstemp\.\)\{2}//

Mit der sehr magischen Flagge können Sie den regulären Ausdruck reduzieren:%s/\v(.{-}\zstemp.){2}//
nobe4

8

Dies ist viel einfacher gemacht mit sed:

sed 's/\.temp\././2'

Mit Vim benötigen Sie einen nicht gierigen Abgleich, und es ist nicht einfach, die Methode auf das Ersetzen des 3., 4. usw. Vorkommens von zu erweitern temp. Aber es kann getan werden, wenn Sie darauf bestehen:

:%s/\.temp\..\{-}\.\zstemp\.//

Denken Sie, dass Sie die sed-Lösung für jede Zeile anwenden können?
nobe4

@ nobe4 Ich bin mir nicht sicher, was du damit meinst. sedWendet nacheinander Skripte auf jede Eingabezeile an.
Sato Katsura

Ja, aber Sie können Zeilen an ein externes Programm übergeben und das Ergebnis zurückerhalten.
nobe4

2
Du meinst von Vim? Sicher, Sie können Linien an sedgenau wie an jedes andere Programm weiterleiten. Fi : :%!sed 's/\.temp\././2'. sedIn UNIX-freundlich liest es Eingaben von stdinund schreibt Ergebnisse in stdout.
Sato Katsura

8

Sie können dies auch mit einem Lookbehind tun.

:%s/\(temp\..*\)\@<=temp\.//

Wenn Sie ALLE Vorkommen nach dem ersten entfernen möchten , können Sie das gFlag am Ende anhängen .

\(\)\@<=Sucht nach einem Muster zwischen \(und \)fügt den gefundenen Text jedoch nicht zur Übereinstimmung hinzu.

Siehe :h \@<=für weitere Informationen.


6

Sie können ein Makro verwenden, wie @Meshpi bereits vorgeschlagen hat. Dies ist jedoch eine andere Möglichkeit, dasselbe zu erreichen.

02/temp^Mdwx+

0 - Zum Zeilenanfang gehen

2 / temp - Suchen Sie nach temp und fahren Sie mit dem zweiten Ereignis fort

^ M - Dies ist nur das Drücken der Eingabetaste

dw - lösche das Wort (temp)

x - lösche das '.'

+ - Zum Anfang der nächsten Zeile gehen

Und dann können Sie es einfach bis zum Ende wiederholen. Und es wird noch viel mehr Möglichkeiten geben, dasselbe zu tun.


6

Diese Lösung ähnelt der von TessellatingHeckler, lässt sich jedoch leichter an das zu löschende Muster anpassen.

:g/temp\./normal 2ngnd

So funktioniert das:

  1. :g/temp\./ für jede Zeile, die mit "temp" übereinstimmt.
  2. normal Führen Sie im normalen Modus Folgendes aus
    1. 2n Finden Sie das zweite Auftreten des Musters
    2. gn wähle es aus
    3. d und löschen Sie es.

Dies funktioniert für jedes Muster :g. normalkann abgekürzt werden mit norm. gndist äquivalent zu dgn. Bearbeiten: In der Tat 2ngndist gleichbedeutend mit d2gn.

Weitere Beispiele für den globalen Befehl finden Sie im Vim Tips Wiki .


4

In Vim können Sie die Spalte vor / um / nach definieren, mit der Ihr Abgleich erfolgen soll :help \%c:

:%s/\%>17c\.temp/g

Dadurch werden alle .tempnach Spalte 17 gefundenen Zeilen jeder Zeile im aktuellen Puffer entfernt.


Hinweis 1: /gDies ist bei Ihrer Probe nicht erforderlich.

Hinweis 2: Ich verwende diese praktische Funktion sehr oft mit qmv . Empfohlen!


Ordentlich, ich habe darüber nachgedacht, ein Plugin zu erstellen, aber das ist schön!
nobe4

Vim macht schon so viele Dinge…
Romainl

Sicher, aber mir war dieses Tool nicht bekannt. Und ein bisschen Scripting macht immer Spaß!
nobe4

6
@ nobe4 Wenn Sie sich für solche Dinge interessieren, lesen Sie auch vidirund vipeaus dem moreutils- Paket.
Sato Katsura

4

Das ist eigentlich ziemlich einfach. Um mit der zweiten Temperatur übereinzustimmen, lassen Sie alles zu, gefolgt von "temp" gefolgt von irgendetwas, und suchen Sie dann erneut nach temp.

%s/.*temp.*\zstemp\.

Das \zsbedeutet "Auswahl hier starten", damit der erste Teil des Spiels (alles vor dem zweiten Temp) nicht entfernt wird. Es ist tatsächlich eine äußerst nützliche Funktion, die ich gerade kennengelernt habe! Ohne sie müsste der Regex so aussehen:

:%s/\(.*temp.*\)temp\./\1

Wenn sich mehr als zwei tempin einer Zeile befinden, löscht Ihr Rezept das letzte und nicht das zweite. Beschuldige die Gier. :)
lcd047

2
Ja, das ist gierig und löscht den letzten. OP sagte jedoch My case was to match old filename to new filename, so only 2 occurrences will be on each line. And only the second one should change.
James

2

Anstelle von Regex würde ich einen Befehl verwenden, der Folgendes bewirkt:

  • Zum Ende der Zeile springen
  • Rückwärts suchen nach temp
  • Löschen Sie 5 Zeichen

und führen Sie es in jeder Zeile aus:

:g//normal $?temp^[5x

NB. ^[ist etwas Besonderes und repräsentiert das Drücken der Escape-Taste; Sie tippen es mit:Ctrl-q <Esc>


Aber eine Regex-Option, von der ich glaube, dass sie noch nicht vorgeschlagen wurde, ist die Übereinstimmung, temp.gefolgt von etwas anderem als einem ., das am Ende der Zeile verankert ist.

:%s/temp\.\([^.]\+\)$/\1/

Welches temp.txtam Ende einer Zeile übereinstimmt und es nur durch das txtBit ersetzt.


1

Ich würde ein Makro schreiben, wobei der Cursor oben links beginnt. qq4f.dfpq

Dann würde ich den Rest der Zeilen mit auswählen Vund das Makro auf diesen Zeilen mit ausführen:'<,'>norm!@q

Dies funktioniert nur, wenn das Textformat in Bezug auf die Anzahl der Punkte vor der zweiten Temperatur gleich bleibt.


5
Um Ihr Makro robuster zu machen, sollten Sie eine Suche verwenden. nStatt 4f.wenn ein Dateiname mehr Punkte enthält, kann Ihr Makro fehlschlagen oder Dinge löschen, die nicht gelöscht werden sollten.
Statox

1

Nichts hindert Sie daran, zwei reguläre Ausdrücke anstelle von nur einem zu verwenden, dh das erste Vorkommen zu ändern und dann die Spalten auszutauschen :

:%s/\.temp//|%s/^\(\S\+\)\(\s\+\)\(\S\+\)/\3\2\1/

Könnte nicht besonders klug sein, aber ich finde es sehr lesbar.


0

Mein Versuch

:%norm 4ftdwx

:%norm ......... execute in normal mode over the whole file  
4ft ............ the 4th t matches the start of the second temp
dw ............. deletes the current word
x .............. deletes the dot
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.