Remplacer la deuxième occurrence en ligne

12

J'ai une liste de fichiers:

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

Et je veux supprimer le temp.sur chaque ligne, mais seulement la deuxième occurrence , ainsi, le fichier devrait ressembler à:

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

Comment dois-je procéder?

nobe4
la source
Est-il possible qu'il y ait une troisième ou quatrième occurrence sur une ligne? Doivent-ils rester intacts?
James
Mon cas était de faire correspondre l'ancien nom de fichier au nouveau nom de fichier, donc seulement 2 occurrences seront sur chaque ligne. Et seul le second devrait changer.
nobe4

Réponses:

10

En général, vous pouvez faire correspondre la Nième occurrence de quelque chose en utilisant \zset \{N}. Il y a un exemple donné à :help \zs.

Dans votre cas, la commande serait:

:%s/\(.\{-}\zstemp\.\)\{2}//
Antony
la source
avec le drapeau très magique, vous pouvez réduire l'expression %s/\v(.{-}\zstemp.){2}//
régulière
8

C'est beaucoup plus facile à faire avec sed:

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

Avec Vim, vous avez besoin d'une correspondance non gourmande, et il n'est pas facile d'étendre la méthode pour remplacer la 3ème, 4ème, etc. occurrence de temp. Mais cela peut être fait si vous insistez:

:%s/\.temp\..\{-}\.\zstemp\.//
Sato Katsura
la source
Merci d'avoir donné la solution sed, pensez-vous qu'il est possible d'appliquer cela sur chaque ligne?
nobe4
@ nobe4 Je ne sais pas ce que vous entendez par là. sedapplique successivement des scripts à chaque ligne d'entrée.
Sato Katsura du
oui, mais vous pouvez passer des lignes dans un programme externe et récupérer le résultat.
nobe4
2
Tu veux dire de Vim? Bien sûr, vous pouvez diriger des lignes vers sedtout comme vous pouvez les diriger vers n'importe quel autre programme. Fi: :%!sed 's/\.temp\././2'. sedsous UNIX, il lit les entrées stdinet écrit les résultats dans stdout.
Sato Katsura
8

Vous pouvez également le faire avec un aperçu.

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

Si vous souhaitez supprimer TOUTES les occurrences après la première, vous pouvez ajouter le gdrapeau à la fin.

\(\)\@<=Recherche tout modèle entre \(et \)n'ajoutera pas le texte trouvé à la correspondance.

Voir :h \@<=pour plus d'informations.

Tumbler41
la source
6

Vous pouvez utiliser une macro comme @Meshpi l'a déjà suggéré. Mais, c'est une autre façon d'accomplir la même chose.

02/temp^Mdwx+

0 - Aller au début d'une ligne

2 / temp - Recherchez temp et passez à la deuxième occurrence

^ M - C'est juste en appuyant sur la touche Entrée

dw - supprimer le mot (temp)

x - supprimez le '.'

+ - Aller au début de la ligne suivante

Et puis, vous pouvez simplement le répéter jusqu'à la fin. Et, il y aura beaucoup plus de façons de faire de même.

Durga Swaroop
la source
6

Cette solution est similaire à celle de TessellatingHeckler mais s'adapte plus facilement à n'importe quel motif à supprimer.

:g/temp\./normal 2ngnd

Voici comment ça fonctionne:

  1. :g/temp\./ pour chaque ligne correspondant à "temp."
  2. normal exécuter ce qui suit en mode normal
    1. 2n trouver la deuxième occurrence du motif
    2. gn sélectionnez-le
    3. d et supprimez-le.

Cela fonctionnera pour n'importe quel modèle donné :g. normalpeut être abrégé en norm. gndest équivalent à dgn. Edit: En fait, 2ngndc'est équivalent à d2gn.

Voir le Wiki Vim Tips pour plus d'exemples de la commande globale.

frangio
la source
4

Dans Vim, vous pouvez définir la colonne avant / à / après laquelle vous souhaitez que votre correspondance se produise avec :help \%c:

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

Cela supprimera tous les éléments .temptrouvés après la colonne 17 de chaque ligne du tampon actuel.


Remarque 1: le /gn'est pas nécessaire avec votre échantillon.

Note 2: J'utilise très souvent cette fonctionnalité pratique avec qmv . Conseillé!

romainl
la source
Neat, je pensais faire un plugin, mais c'est sympa!
nobe4
Vim fait déjà tellement de choses…
romainl
Bien sûr, mais je n'étais pas au courant de cet outil. Et un peu de script est toujours amusant!
nobe4
6
@ nobe4 Si vous vous souciez de ce genre de choses, voir aussi vidiret vipedu paquet moreutils .
Sato Katsura
4

C'est en fait assez simple. Pour faire correspondre le deuxième temp, autorisez tout ce qui est suivi de "temp" suivi de quoi que ce soit, puis recherchez à nouveau temp.

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

Le \zsmoyen "Commencer la sélection ici" pour que la première partie du match (tout avant le deuxième temp) ne soit pas supprimée. C'est en fait une fonctionnalité extrêmement utile que je viens d'apprendre! Sans cela, l'expression régulière devrait ressembler à ceci:

:%s/\(.*temp.*\)temp\./\1
James
la source
S'il y en a plus de deux tempsur une ligne, votre recette supprime la dernière, plutôt que la seconde. Blâmez la cupidité. :)
lcd047
2
Oui, c'est gourmand, et donc il supprime le dernier. Cependant, OP a déclaré 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

Au lieu de regex, j'irais avec une commande qui fait:

  • Aller à la fin de la ligne
  • Recherche en arrière pour temp
  • Supprimer 5 caractères

et exécutez-le sur chaque ligne:

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

NB. ^[est spécial et représente une pression sur la touche Échap; vous le tapez avec:Ctrl-q <Esc>


Mais une option regex, qui, je pense, n'a pas encore été suggérée, consiste à faire correspondre temp.suivi de quoi que ce soit, sauf un ., ancré à la fin de la ligne.

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

Qui correspondra temp.txtà la fin d'une ligne et la remplacera par juste le txtbit.

TessellatingHeckler
la source
1

J'écrirais une macro avec le curseur commençant en haut à gauche. qq4f.dfpq

Ensuite, je sélectionnerais le reste des lignes en utilisant Vet exécuter la macro sur ces lignes avec:'<,'>norm!@q

Cela ne fonctionnera que si le format de texte reste le même par rapport au nombre de points avant le deuxième temp.


la source
5
Pour rendre votre macro plus robuste, vous devez utiliser une recherche et nau lieu de 4f.parce que si un nom de fichier a plus de points, votre macro pourrait échouer ou supprimer des éléments qui ne devraient pas être supprimés.
statox
1

Rien ne vous empêche d'utiliser deux expressions régulières au lieu d'une seule, c'est-à-dire changer la première occurrence puis échanger les colonnes:

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

Ce n'est peut-être pas très intelligent, mais je le trouve très lisible.

grochmal
la source
0

Ma tentative

:%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
SergioAraujo
la source