Sustituir la segunda ocurrencia en línea

12

Tengo una lista de archivos:

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

Y quiero eliminar el temp.en cada línea, pero solo la segunda ocurrencia , por lo tanto, el archivo debería verse así:

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

¿Cómo debería hacer esto?

nobe4
fuente
¿Es posible que haya una tercera o cuarta aparición en cualquier línea? ¿Tienen que permanecer intactos?
James
Mi caso fue hacer coincidir el nombre del archivo antiguo con el nombre del archivo nuevo, por lo que solo habrá 2 ocurrencias en cada línea. Y solo el segundo debería cambiar.
nobe4

Respuestas:

10

En general, puede hacer coincidir la enésima aparición de algo con \zsy \{N}. Hay un ejemplo dado en :help \zs.

En su caso, el comando sería:

:%s/\(.\{-}\zstemp\.\)\{2}//
Antonio
fuente
con la bandera verymagic puedes reducir la expresión regular:%s/\v(.{-}\zstemp.){2}//
nobe4
8

Esto es mucho más fácil de hacer con sed:

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

Con Vim necesita juego no expansivo, y no es fácil de extender el método a la sustitución de la tercera, cuarta, etc. ocurrencia de temp. Pero se puede hacer si insiste:

:%s/\.temp\..\{-}\.\zstemp\.//
Sato Katsura
fuente
Gracias por dar la solución sed, ¿crees que es posible aplicar esto en cada línea?
nobe4
@ nobe4 No estoy seguro de lo que quieres decir con eso. sedaplica secuencias de comandos a cada línea de entrada a su vez.
Sato Katsura
Sí, pero puede pasar líneas al programa externo y recuperar el resultado.
nobe4
2
¿Te refieres a Vim? Claro, puede canalizar las líneas al sedigual que puede canalizarlas a cualquier otro programa. FI: :%!sed 's/\.temp\././2'. seden UNIX-friendly, lee entradas stdiny escribe resultados en stdout.
Sato Katsura
8

También puede hacer esto con un vistazo atrás.

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

Si desea eliminar TODAS las ocurrencias después de la primera, puede agregar la gbandera al final.

\(\)\@<=Buscará cualquier patrón entre \(y \)pero no agregará el texto encontrado a la coincidencia.

Ver :h \@<=para más información.

Vaso41
fuente
6

Puede usar una macro como @Meshpi ya sugirió. Pero, esta es otra forma de lograr lo mismo.

02/temp^Mdwx+

0 - Ir al inicio de una línea

2 / temp - Busca temp y ve a la segunda ocurrencia

^ M - Esto solo está presionando la tecla Enter

dw - borra la palabra (temp)

x - borra el '.'

+ - Ir al comienzo de la siguiente línea

Y luego puedes repetirlo hasta el final. Y habrá muchas más formas de hacer lo mismo.

Durga Swaroop
fuente
6

Esta solución es similar a la de TessellatingHeckler, pero se adapta más fácilmente a cualquier patrón que deba eliminarse.

:g/temp\./normal 2ngnd

Así es como funciona:

  1. :g/temp\./ por cada línea que coincida con "temp".
  2. normal ejecute lo siguiente en modo normal
    1. 2n encontrar la segunda aparición del patrón
    2. gn selecciónalo
    3. d y eliminarlo

Esto funcionará para cualquier patrón que se le dé :g. normalpuede ser abreviado a norm. gndes equivalente a dgn. Editar: de hecho 2ngndes equivalente a d2gn.

Consulte el Wiki de Vim Tips para obtener más ejemplos del comando global.

frangio
fuente
4

En Vim, puede definir la columna antes / en / después de la cual desea que su coincidencia ocurra :help \%c:

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

Esto eliminará todos los .tempencontrados después de la columna 17 de cada línea en el búfer actual.


Nota 1: /gno es necesario con su muestra.

Nota 2: uso esa característica práctica muy a menudo con qmv . ¡Recomendado!

romainl
fuente
Genial, estaba pensando en hacer un complemento, ¡pero esto es bueno!
nobe4
Vim ya hace tantas cosas ...
romainl
Claro, pero no estaba al tanto de esta herramienta. ¡Y un poco de secuencias de comandos siempre es divertido!
nobe4
66
@ nobe4 Si le interesan este tipo de cosas, consulte también vidiry vipedesde el paquete moreutils .
Sato Katsura
4

Esto es realmente bastante simple. Para hacer coincidir la segunda temperatura, permita cualquier cosa seguida de "temp" seguida de cualquier cosa, luego busque temp nuevamente

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

Los \zsmedios "iniciar la selección aquí", de modo que la primera parte del partido (todo antes de la segunda temp) no se quita. En realidad, es una característica extremadamente útil que acabo de aprender. Sin ella, la expresión regular debería verse así:

:%s/\(.*temp.*\)temp\./\1
James
fuente
Si hay más de dos tempen una línea, su receta elimina la última, en lugar de la segunda. Culpa a la avaricia. :)
lcd047
2
Sí, esto es codicioso, por lo que elimina el último. Sin embargo, OP dijo 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

En lugar de expresiones regulares, iría con un comando que hace:

  • Saltar al final de la línea.
  • Busque al revés temp
  • Eliminar 5 caracteres

y ejecutarlo en cada línea:

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

NÓTESE BIEN. ^[es especial y representa presionar la tecla Escape; lo escribes con:Ctrl-q <Esc>


Pero una opción de expresiones regulares, que no creo que se haya sugerido todavía, es hacer coincidir temp.seguido de cualquier cosa, excepto una ., anclada al final de la línea.

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

Que coincidirá temp.txtal final de una línea y lo reemplazará solo con el txtbit.

TessellatingHeckler
fuente
1

Escribiría una macro con el cursor comenzando en la esquina superior izquierda. qq4f.dfpq

Luego seleccionaría el resto de las líneas usando Vy ejecutaría la macro en esas líneas con:'<,'>norm!@q

Esto solo funcionará si el formato de texto permanece igual en relación con el número de puntos antes de la segunda temperatura.


fuente
55
Para hacer que su macro sea más robusta, debe usar una búsqueda y, en nlugar de hacerlo, 4f.porque si un nombre de archivo tiene más puntos, su macro podría fallar o eliminar cosas que no deberían eliminarse.
statox
1

No hay nada que le impida utilizar dos expresiones regulares en lugar de una sola, es decir, cambiar la primera aparición y luego intercambiar las columnas:

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

Puede que no sea muy inteligente, pero me parece muy legible.

grochmal
fuente
0

Mi intento

:%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
fuente