Búsqueda y reemplazo avanzado
El dialecto de patrones de Vim y cómo domarlo con very magic, los átomos que hay que conocer, \zs y \ze para exigir contexto sin incluirlo, el flujo cgn que sustituye al 90% de los :s, :substitute con capturas y expresiones calculadas, la previsualización en vivo con inccommand, y la ventana de línea de comandos.
Buscar y reemplazar es donde el editor deja de parecer un editor. Con un patrón bien escrito transformas en una línea lo que a mano son diez minutos —y con uno mal escrito destrozas un fichero en el mismo tiempo—. Por eso esta lección va en dos direcciones a la vez: enseñarte el dialecto de expresiones regulares de Vim, que no es el que conoces, y enseñarte las tres formas que tiene el editor de dejarte comprobar el patrón antes de aplicarlo. La segunda parte importa más que la primera.
- Escribir patrones legibles con very magic, y reconocer los átomos propios de Vim.
- Usar
\zsy\zepara exigir contexto sin incluirlo en la sustitución. - Sustituir revisando una por una con el flujo
cgn+., sin escribir un solo comando. - Dominar
:substitute: rangos, banderas, capturas,\=y el patrón vacío. - Ver el resultado antes de confirmarlo con
'inccommand', la banderanyq:. - Repetir la última sustitución con
&,:&&yg&.
El dialecto de Vim, y cómo domarlo
La sintaxis de patrones de Vim es anterior a que existiera nada parecido a un estándar. En la práctica significa que +, ?, (, ), {, } y | hay que escaparlos para que signifiquen lo que significan en cualquier otro sitio:
sin \v: /\(foo\|bar\)\+con \v: /\v(foo|bar)+\v al principio —very magic— hace que casi todo se comporte como esperas.
Empieza todo patrón con \v y olvídate
No hay ningún caso en que \v estorbe si tu patrón es mínimamente complejo. Cuesta dos caracteres y elimina la mitad de los errores. A cambio, ahora casi cualquier símbolo que no sea una letra, un número o un guion bajo tiene significado especial, así que un punto literal se escribe \. —que es lo que ya hacías en cualquier otro lenguaje—.
Los cuatro niveles existen y se documentan en :h /magic: \v very magic, \m magic (el defecto), \M nomagic y \V very nomagic. Este último es útil para lo contrario: \Vhttp://ejemplo.com busca esa cadena literal, sin que ningún punto ni barra signifique nada.
Los átomos que hay que conocer
Con \v puesto, esto es lo que vas a usar:
| Átomo | Casa |
|---|---|
. |
cualquier carácter |
\d \D |
un dígito / lo que no sea un dígito |
\w \W |
carácter de palabra / lo contrario |
\s \S |
espacio en blanco / lo contrario |
\a \l \u |
letra / minúscula / mayúscula |
* + ? |
cero o más / una o más / opcional |
{2,5} {-} |
entre 2 y 5 / no ávido, lo mínimo posible |
(...) |
captura, recuperable con \1 |
%(...) |
agrupa sin capturar |
a|b |
alternativa |
< > |
límites de palabra |
^ $ |
principio / final de línea |
[abc] [^abc] |
conjunto / conjunto negado |
\zs \ze |
dónde empieza / acaba de verdad la coincidencia |
\n |
un salto de línea (para patrones multilínea) |
\_. \_s |
como . y \s, pero cruzando líneas |
%V |
solo dentro de la selección visual |
%>20l |
solo por debajo de la línea 20 |
El {-} merece atención porque es donde más gente se estrella. \v".{-}" casa la cadena entrecomillada más corta; \v".*" se come desde la primera comilla hasta la última de la línea. En un lenguaje con PCRE eso sería .*?; aquí es {-}.
Y dos que casi nadie conoce y resuelven casos difíciles: \_. y \_s cruzan saltos de línea, con lo que puedes buscar patrones que abarcan varias líneas —\v\{\_.{-}\} casa un bloque de llaves entero—.
Mayúsculas: \c y \C ganan a todo
Ya lo viste en la lección 0.3: por defecto la búsqueda distingue mayúsculas, y hay dos opciones —'ignorecase' y 'smartcase'— que se ponen en la configuración.
Dentro de un patrón, \c fuerza ignorarlas y \C fuerza respetarlas, estén como estén las opciones, y da igual en qué punto del patrón los escribas. Cuando compartas un patrón con alguien o lo pongas en un script, poner el \c o el \C explícito es la diferencia entre que funcione siempre y que funcione en tu máquina.
\zs y \ze: contexto que no entra
\zs marca dónde empieza de verdad la coincidencia y \ze dónde termina, aunque el patrón exija más alrededor:
/\vfoo\zsbar casa 'bar', pero solo si va detrás de 'foo'/\vitem\ze: casa 'item', pero solo si va seguido de ':'/\v^\s+\zs\S casa el primer carácter con contenido de la líneaEs «busca X en tal sitio, pero actúa solo sobre X». Con :s eso significa que sustituyes únicamente la parte marcada y el contexto queda intacto:
:%s/\vversion: \zs[0-9.]+/2.0.0/Cambia el número de versión sin tocar la etiqueta y sin tener que reescribirla en el reemplazo. En otros lenguajes esto se hace con lookbehind y lookahead; aquí es más corto de escribir y más fácil de leer.
cgn: sustituir revisando, sin comandos
gn selecciona la siguiente coincidencia de tu última búsqueda. Y como se comporta como un objeto de texto, cualquier operador lo acepta.
* busca la palabra bajo el cursorcgn cambia la siguiente coincidenciasuma<Esc> escribes el reemplazo. siguiente: repítelo. siguiente: repítelon esta no: sáltala. la de después síPor qué esto sustituye al 90% de los :s
:%s/viejo/nuevo/g te obliga a decidirlo todo de golpe, antes de ver nada. :%s/viejo/nuevo/gc te pregunta una por una, pero con un flujo distinto al de editar: estás respondiendo a un formulario, con sus teclas propias —y, n, a, q, l—, y si te equivocas no hay marcha atrás fina.
Con cgn sigues dentro del editor. Ves cada aparición en su contexto, con su código alrededor, y decides con una tecla que ya conoces: . la cambio, n la salto. Y como cada cambio es una edición normal, u deshace una sola, no las cuarenta. Además funciona con cualquier operador: dgn borra la siguiente, ygn la copia, gUgn la pasa a mayúsculas.
Es el patrón que más usan los veteranos y casi nunca aparece en los tutoriales, porque exige haber entendido antes que gn es un complemento y no un comando.
:substitute a fondo
:[rango]s/patrón/reemplazo/[banderas] [cuenta]Sin rango, actúa solo en la línea actual. Esa es la fuente número uno de «no me hizo nada»: falta el %.
| Rango | Alcance |
|---|---|
| (nada) | la línea actual |
% |
todo el fichero |
1,50 |
de la línea 1 a la 50 |
.,+10 |
de la actual, diez más abajo |
.,$ |
de la actual al final |
'<,'> |
la última selección visual |
'a,'b |
entre dos marcas |
/inicio/,/fin/ |
entre dos patrones |
El separador no tiene que ser una barra
:s acepta casi cualquier carácter como separador, y eso salva la legibilidad cuando el patrón lleva barras:
:%s/\/usr\/local\/bin/\/opt\/bin/g ilegible:%s#/usr/local/bin#/opt/bin#g lo mismo, legibleCon rutas, URLs o expresiones con barras, cambia el separador y te ahorras la mitad de los escapes.
Las banderas
| Bandera | Qué hace |
|---|---|
g |
todas las del renglón, no solo la primera |
c |
pide confirmación en cada una |
i / I |
ignora / respeta mayúsculas |
n |
no sustituye: solo cuenta cuántas habría |
e |
no da error si no encuentra nada |
& |
reutiliza las banderas de la sustitución anterior |
La bandera n es la más infravalorada:
:%s/\vTODO//gnNo cambia nada y responde «17 matches on 12 lines». Es la forma de contar apariciones antes de decidir si merece la pena el cambio, y sobre todo de comprobar que tu patrón casa lo que crees antes de dejarlo suelto por el fichero.
La e es la que hace que un :s dentro de una macro o de un :g no aborte todo cuando una línea no casa. La vas a necesitar en las dos lecciones siguientes.
Capturas y transformaciones
Los paréntesis capturan y en el reemplazo se recuperan con \1, \2…
" intercambiar dos columnas separadas por coma:%s/\v(\w+), (\w+)/\2 \1/" convertir let en const:%s/\vlet (\w+) \= /const \1 = /| En el reemplazo | Significa |
|---|---|
& o \0 |
toda la coincidencia |
~ |
el reemplazo anterior que usaste |
\u / \U |
la siguiente letra / todo lo que sigue, en MAYÚSCULAS |
\l / \L |
igual, en minúsculas |
\e / \E |
fin de un \U o \L |
\r |
un salto de línea de verdad |
\n |
un byte nulo — no es un salto de línea |
Ese último par es una trampa clásica: en el patrón, \n es un salto de línea; en el reemplazo, el salto de línea es \r. Si escribes \n en el reemplazo insertas un carácter nulo y el resultado parece corrupto.
" poner en mayúscula la primera letra de cada palabra:%s/\v<(\w)/\u\1/g" envolver en comillas lo que ya casaba:%s/\v<\d+>/"&"/g" partir una línea por cada coma:%s/, /\r/gEl patrón vacío
Si dejas el patrón en blanco, :s reutiliza la última búsqueda:
/\v<usuario> busca y comprueba con n que casa lo que quieres:%s//cliente/g sustituye sin volver a escribir el patrónEsto es más importante de lo que parece: te deja verificar el patrón antes de usarlo. Buscas, pulsas n unas cuantas veces para ver qué está casando de verdad, y solo entonces sustituyes. Nunca vuelves a escribir un patrón dos veces ni a equivocarte al copiarlo.
\=: reemplazo calculado
Con \= el reemplazo es una expresión que se evalúa en cada coincidencia:
" numerar líneas:%s/^/\=line('.') . '. '/" duplicar cada número que encuentre:%s/\v\d+/\=submatch(0) * 2/g" y con Lua, para lo que se ponga difícil:%s/\v\w+/\=luaeval("_A:upper()", submatch(0))/gsubmatch(0) es la coincidencia completa, submatch(1) la primera captura. Es la puerta a sustituciones que dependen del contenido, no solo del patrón: numerar, calcular, formatear, consultar una tabla.
Repetir la última sustitución
Tres formas, y la diferencia entre ellas es exactamente qué conservan:
| Comando | Repite el último :s… |
|---|---|
& |
en la línea actual, sin las banderas |
:&& |
en la línea actual, con las banderas |
g& |
en todo el fichero, con las banderas y el último patrón buscado |
g& es la que salva el día: acabas de hacer un :s en un rango, ves que hacía falta en todo el fichero, y en vez de reescribirlo pulsas dos teclas. Equivale a :%s//~/&.
Ver antes de aplicar
Aquí está la parte que separa un :s seguro de una ruleta.
'inccommand': previsualización en vivo
Es una de las mejores cosas que Neovim añadió a Vim, y funciona sola: mientras escribes un :s, el editor te resalta lo que va a cambiar y te enseña el resultado, actualizándose con cada tecla.
:set inccommand? " mira qué tienes:set inccommand=split " y prueba esteCon nosplit, los cambios se previsualizan en el propio buffer. Con split, además se abre una ventana con todas las líneas afectadas, incluidas las que no caben en pantalla. Ese segundo modo es el que convierte un :%s a ciegas en algo que puedes revisar: escribes el patrón, miras la lista de abajo, ajustas, y solo pulsas Enter cuando la lista dice lo que querías.
La ventana de línea de comandos: editar el comando con Vim
Un :s complicado es difícil de escribir en la línea de comandos, donde solo tienes Ctrl-w y las flechas. Pulsa q: en modo Normal —o Ctrl-f si ya estás escribiendo— y se abre un buffer normal con tu historial de comandos.
Ahí dentro te mueves con j y k, editas el patrón con ciw, copias uno viejo con yy y lo modificas, buscas con / un comando de la semana pasada. Enter ejecuta la línea del cursor; :q sale sin ejecutar nada.
q/ hace lo mismo con el historial de búsquedas. Es de lo más útil que tiene el editor y de lo menos conocido, y es exactamente donde se construye un patrón largo sin sufrir.
Todo lo peligroso debería tener un ensayo, y aquí hay tres
Fíjate en que esta lección te ha dado, sin anunciarlo, tres formas distintas de no aplicar todavía un cambio destructivo: la bandera n, que cuenta sin tocar; 'inccommand', que enseña el resultado mientras escribes; y buscar primero con / para comprobar el patrón antes de reutilizarlo con :%s//.
No es casualidad ni exceso de celo. Un :%s mal escrito sobre un fichero de tres mil líneas es una de las poquísimas operaciones del editor cuyo daño no se ve: u lo deshace, sí, pero solo si te enteras de que salió mal, y el modo de fallo típico no es que rompa el fichero, es que cambie de más en un rincón que no estabas mirando. La diferencia entre una herramienta afilada y una peligrosa nunca es cuánto puede cambiar: es cuánto te deja ver antes de cambiarlo.
El patrón tiene nombres distintos según el gremio y siempre es el mismo. --dry-run en cualquier utilidad de línea de comandos. terraform plan antes del apply. git diff --staged antes del commit, y git add -p para decidir trozo a trozo. Una transacción de base de datos que se puede revertir. Un EXPLAIN antes de la consulta pesada. El -n de sed. En todos, alguien se dio cuenta de que la operación destructiva y la operación de enterarse de qué va a pasar son dos cosas distintas, y que la segunda tiene que ser barata, rápida y estar a mano —porque si cuesta más que ejecutar y rezar, nadie la usa—.
Y de ahí sale una prueba práctica que puedes aplicar a lo que construyas: si tu herramienta puede destruir algo, ¿cuánto cuesta averiguar qué va a destruir? Si la respuesta es «ejecútala y mira», no has terminado. La versión buena de :s no es la que sustituye bien; es la que te deja mirar la lista antes de pulsar Enter.
Dentro de una selección
Con una selección visual activa, : pone '<,'> solo. Pero eso actúa sobre líneas enteras, no sobre el bloque que marcaste.
Para limitar de verdad a lo seleccionado —útil sobre todo con selecciones en bloque— está \%V:
:'<,'>s/\v%Vfoo/bar/gSolo sustituye dentro de la región marcada, no en el resto de esas líneas.
Ordenar y deduplicar
:sort es un comando aparte y es el complemento natural de todo esto:
:sort " ordena todo el fichero:'<,'>sort " solo la selección:sort u " ordena y quita duplicados:sort n " orden numérico de verdad (2 antes que 10):sort! " al revés:sort /\v^\S+\s/ " ordena ignorando lo que casa el patrónEse último es el que casi nadie conoce: ordena por lo que viene después de la parte que casa el patrón. Con un fichero de import o de definiciones alineadas, te deja ordenar por la segunda columna sin tocar nada.
Y cuando el criterio es «qué líneas», no «qué texto»
Si lo que quieres es actuar sobre las líneas que cumplen algo —borrarlas, moverlas, comentarlas—, la herramienta no es :s: es :global, que es la lección siguiente. Los dos se combinan, y ahí está la mitad de su potencia: :g/^function/s/foo/bar/g sustituye solo dentro de las líneas que empiezan por function.
En todo el proyecto
El camino nativo pasa por la quickfix, que es la lección 1.6:
:grep! \vviejo src/:copen " revisar la lista, y filtrarla si hace falta:cdo s/\vviejo/nuevo/ge | updateBuscar, mirar, aplicar. La bandera e evita que se pare en la primera entrada que ya no case, y update guarda solo los ficheros modificados.
Hay plugins que envuelven esto en una interfaz con vista previa en vivo del proyecto entero, y los verás en el Nivel 4. Por debajo hacen esto mismo.
* busca, cgn cambia la siguiente, . repite y n salta. Sin comandos, y con u deshaciendo de una en una.
La bandera n cuenta y no toca nada.
\zs deja fuera de la coincidencia todo lo que va antes.
{-} es el cuantificador no ávido. Con .* te comes la línea entera.
El separador de :s puede ser cualquier carácter.
Repite el último :s en todo el buffer, conservando las banderas.
La ventana de línea de comandos es un buffer normal con tu historial.
Deja de editar a mano
- Pon
:set inccommand=splity escribe un:%slargo sin pulsarEnter. Observa la ventana de abajo mientras ajustas el patrón. - Busca algo con
\vy comprueba connque casa lo que crees. - Cuenta las apariciones con
:%s///gnantes de tocar nada. - Sustituye reutilizando el patrón con
:%s//nuevo/g. - Renombra un identificador con
*ycgn, saltándote a propósito dos apariciones conn. Comprueba queudeshace una sola. - Usa
\zspara cambiar solo una parte de una coincidencia más larga. - Numera las líneas de un bloque con
:'<,'>s/^/\=line('.') . '. '/. - Parte una línea de comas en varias con
:s/, /\r/g, y comprueba qué pasa si escribes\nen su lugar. - Ordena un bloque de imports y quita duplicados con
:'<,'>sort u. - Abre
q:, busca dentro de tu historial el:sdel punto 7, edítalo conciwy ejecútalo desde ahí.