El comando global: :g y :v
El comando que más trabajo ahorra de todo el editor y el que menos gente entiende. Su algoritmo de dos pasadas y por qué :g/^/m0 invierte un fichero, las combinaciones con :d, :m, :t, :s, :normal y :y, la diferencia entre :normal y :normal!, y cómo construir órdenes con :execute cuando hacen falta teclas especiales.
:g es el comando que más trabajo ahorra de todo el editor y el que menos gente entiende. No es un buscar-y-reemplazar con esteroides: es «aplica esta orden a todas las líneas que cumplan esto», y su segunda mitad puede ser cualquier orden del editor —borrar, mover, copiar, sustituir, ejecutar teclas de modo Normal o incluso una macro entera—. La lección anterior te enseñó a decidir qué texto cambia; esta te enseña a decidir qué líneas. Juntas son la mitad del trabajo repetitivo que hacías a mano.
- Leer y escribir la forma completa
:[rango]g/patrón/comando. - Explicar el algoritmo de dos pasadas y por qué hace posible lo que parece imposible.
- Filtrar con
:vquedándote solo con lo que importa. - Combinar
:gcon:d,:m,:t,:s,:yy:normal. - Usar
:normal!en vez de:normal, y saber por qué la exclamación no es opcional. - Construir órdenes con teclas especiales usando
:execute.
La forma
:[rango]g/patrón/comandoSin rango, el rango es el fichero entero. El comando es cualquier comando de dos puntos, y si no pones ninguno, :g imprime las líneas que casan —de ahí viene su nombre completo, :global—.
:g/TODO/d " borra todas las líneas con TODO:g/^$/d " borra todas las líneas vacías:g/error/ " lista las líneas con 'error' sin tocar nada:g/error/# " igual, pero con el número de línea delante:10,50g/foo/d " solo entre las líneas 10 y 50Léelo siempre en voz alta con la misma frase: «en las líneas que casen con X, haz Y». Todo lo demás de esta lección es cambiar la Y.
El algoritmo de dos pasadas
Aquí está lo que hay que entender, y de aquí sale todo lo que parece brujería.
:g no recorre el fichero ejecutando el comando línea a línea. Hace dos pasadas:
Marca
Recorre el rango entero y marca internamente todas las líneas que casan con el patrón. No ejecuta nada todavía.
Ejecuta
Recorre las líneas marcadas y ejecuta el comando en cada una. Aunque el comando borre líneas, mueva líneas o cambie la numeración entera, las marcas siguen apuntando a las líneas correctas.
Y de ahí sale el ejemplo que parece un truco de magia:
:g/^/m0«En todas las líneas —^ casa con todas—, muévela a después de la línea 0.» Se marcan todas. Se coge la primera y se manda arriba. Se coge la segunda y se manda arriba, quedando por encima de la anterior. Y así hasta el final.
El fichero queda invertido. No hay comando de «invertir fichero» en Vim: hay :g y :m.
Decidir el conjunto antes de tocarlo es un patrón que vas a reconocer en todas partes
Si :g funcionara en una sola pasada, :g/^/d borraría la primera línea, la segunda pasaría a ser la primera, el cursor avanzaría a la que ahora es la segunda… y borrarías la mitad del fichero, saltándote una de cada dos. Cualquiera que haya escrito un bucle que modifica la lista que está recorriendo ha vivido exactamente ese fallo, normalmente a las once de la noche.
Las dos pasadas lo eliminan de raíz, y la formulación general del arreglo es la que merece la pena llevarse: separar la decisión de qué tocar de la acción de tocarlo. Primero se materializa el conjunto; después se opera sobre él. Mientras se opera, el conjunto ya no depende del estado que estás modificando.
En cuanto lo ves con ese nombre, aparece por todas partes. Es por lo que en casi cualquier lenguaje se itera sobre una copia de la colección que vas a modificar, o se acumulan los índices y se borran al final —y en orden inverso, para que los índices no se desplacen—. Es lo que hace una base de datos con el aislamiento por instantánea: tu transacción ve una foto coherente aunque el mundo cambie debajo. Es la razón de que git rebase -i te enseñe primero el plan —una lista de commits con una orden al lado— y solo después lo ejecute, en vez de ir preguntando sobre la marcha mientras los hashes cambian bajo sus pies. Y es lo que hace un recolector de basura que marca y luego barre, que es literalmente el mismo nombre: mark and sweep.
El patrón contrario —decidir mientras modificas— no siempre falla, y ese es justo el problema: funciona en el caso pequeño, funciona en tu prueba, y falla cuando el conjunto es grande o cuando la acción cambia la estructura. Cuando te encuentres escribiendo un bucle que borra elementos de aquello mismo que recorre, acuérdate de que :g resolvió eso en 1976 haciendo dos pasadas, y que la solución nunca fue más lista: fue más ordenada.
:v — lo que no casa
:v —o :g!— aplica el comando a las líneas que no casan.
:v/importante/d " deja SOLO las líneas con 'importante':v/^\s*#/d " deja solo los comentarios de shellEs la forma de filtrar un fichero hasta quedarte con lo que buscas. Un log de cuatro mil líneas se convierte en las doce que importan con un :v.
Y se encadenan: un :v para quedarte con lo relevante, y otro :g sobre el resultado para afinar. Cada uno reduce, y como todo pasa por el árbol de deshacer, siempre puedes retroceder un paso.
Las combinaciones que se usan de verdad
:d — borrar
:g/^$/d " líneas vacías:g/console\.log/d " los rastros de depuración:g/^\s*$/d " líneas con solo espacios:g/^$/d _ " igual, pero SIN machacar tus registrosEsa última merece una nota. :d acepta el nombre de un registro detrás, así que d _ manda lo borrado al agujero negro. Sin eso, un :g/^$/d sobre doscientas líneas vacías te llena el anillo de borrados de basura y pierdes lo que tenías copiado.
:m y :t — mover y copiar
:g/^import/m0 " todos los imports al principio, en orden inverso:g/^import/m$ " al final, CONSERVANDO el orden entre ellos:g/TODO/t$ " COPIA cada TODO al final, dejando el original:g/^$/,/./-1j " junta bloques separados por líneas vacías:m mueve, :t copia (de to, y también existe como :copy). $ es la última línea, 0 es antes de la primera.
Invertir o conservar el orden es elegir entre m0 y m$
m$ mueve cada coincidencia al final, y como se procesan de arriba abajo, cada una acaba después de la anterior: el orden relativo se conserva. m0 las manda al principio, y cada una queda encima de la anterior: el orden se invierte.
Es la misma diferencia que entre añadir a una lista por la cola o por la cabeza, y saberla te evita el u que todo el mundo pulsa la primera vez.
:s — sustituir, pero solo donde toca
Esta es la que más se infrautiliza. :%s/viejo/nuevo/g cambia en todo el fichero; con :g delante, eliges en qué líneas:
:g/^function/s/foo/bar/g " solo dentro de las líneas que empiezan por function:v/^#/s/\s\+$//g " limpia espacios finales excepto en comentarios:g/^\s*\/\//s/TODO/PENDIENTE/g " solo dentro de los comentariosDos patrones distintos: uno elige las líneas, otro elige qué se sustituye dentro. Es una precisión que un :%s solo no te da, y es la razón de que estas dos lecciones vayan seguidas.
:y — recolectar en un registro
Poco conocida y muy útil. :y A copia la línea añadiéndola al registro a, así que un :g recolecta todas las coincidencias en un sitio:
qaq " vacía el registro a primero:g/^ERROR/y A " acumula todas las líneas de error en élY después las pegas donde quieras con "ap, o las mandas a otro fichero. Es la forma nativa de extraer las líneas que cumplen algo sin destruir el original, que es lo que :v/…/d sí hace.
:normal — todo el modo Normal dentro de :g
:g/^const/normal! A; " punto y coma al final de cada const:g/^import/normal! >> " indenta cada import:g/^-/normal! dw " borra la primera palabra de cada línea de guion:normal ejecuta teclas de modo Normal empezando al principio de cada línea marcada. Con esto :g deja de estar limitado a los comandos de dos puntos y accede a todo el lenguaje de edición de la lección 0.4.
Y su forma definitiva, :g/patrón/normal! @a, ejecuta una macro en cada línea que casa. Eso lo verás en la lección 1.5; la mitad del trabajo ya la tienes hecha aquí.
normal! con exclamación, siempre
Sin exclamación, :normal ejecuta las teclas respetando tus mapeos. Eso suena inofensivo y es una bomba: el día que remapees A o dd en tu configuración, todos los :g/…/normal que tuvieras escritos harán otra cosa. Peor aún, harán otra cosa solo en tu máquina, así que el comando que compartes con alguien no se comporta igual.
:normal! ignora los mapeos y ejecuta las teclas nativas. Es la misma exclamación de nnoremap frente a nmap, por la misma razón. Ponla siempre; los comandos de esta lección la llevan todos.
Las teclas especiales necesitan :execute
Aquí hay una trampa que se lleva a mucha gente. Si escribes esto:
:g/TODO/normal! 0i// <Esc>no funciona: <Esc> se inserta como cinco caracteres literales, y acabas con // <Esc> en cada línea. La línea de comandos no interpreta esa notación; solo la interpretan los mapeos.
Hay dos salidas. La primera es no ponerlo: :normal cierra el modo Insertar por su cuenta al terminar, así que casi siempre sobra.
:g/TODO/normal! I//La segunda, para cuando la tecla especial va en medio o necesitas un espacio final que la línea de comandos se comería, es construir la orden con :execute, que sí interpreta las secuencias de escape:
:g/TODO/execute "normal! I// ":g/^fn /execute "normal! f(a\<Esc>"Entre comillas dobles, \<Esc>, \<CR> y \<Tab> se convierten en la tecla de verdad. Es el idioma estándar y el que verás en cualquier configuración seria.
Direcciones relativas: actuar sobre la línea de al lado
El comando de :g acepta una dirección delante, y esa dirección es relativa a la línea que casó:
:g/^Capítulo/+1d " borra la línea SIGUIENTE a cada 'Capítulo':g/^}/-1d " borra la línea ANTERIOR a cada llave de cierre:g/^BEGIN/,/^END/d " borra desde cada BEGIN hasta su END:g/^class/+1,+3> " indenta las tres líneas siguientes a cada classEl tercero merece una lectura despacio: , separa un rango, así que /^BEGIN/,/^END/ es «desde esta línea hasta la siguiente que diga END». Con :g delante, eso se aplica a cada bloque BEGIN…END del fichero. Borrar todos los bloques de un tipo es una línea.
:g anidado
:g puede llevar otro :g como comando:
:g/^class/.,/^}/g/TODO/d«Para cada línea que empiece por class, en el rango de esa línea hasta la siguiente }, borra las líneas con TODO.» Es raro de necesitar y espectacular cuando toca. Si te encuentras escribiéndolo, vas por buen camino.
Deshacer, y cómo no necesitarlo
Un :g entero es un solo cambio. Da igual que haya tocado trescientas líneas: un u lo revierte todo de golpe. Eso quita la mayor parte del miedo.
Aun así, el reflejo de la lección anterior vale igual aquí, y es más barato que deshacer:
Pruébalo sin comando antes de ejecutarlo
Escribe el :g sin la parte del comando:
:g/tu-patrón/Sin comando, :g imprime las líneas que casan; con # en su lugar, las imprime con su número. Ves exactamente sobre qué va a actuar antes de decidir qué les haces. Cuando el patrón sea el correcto, recupéralo con q: —la ventana de línea de comandos de la lección anterior— y añádele la orden ahí, con toda la gramática del editor a mano.
Y si el resultado es una lista larga que quieres conservar, :g/patrón/# seguido de copiar la salida no es la mejor vía: para eso están :y A, que acabas de ver, y la quickfix de la lección 1.6.
:d acepta un registro detrás. _ es el agujero negro.
:v aplica el comando a las líneas que no casan: borra el ruido y queda la señal. :g/ERROR/d habría hecho exactamente lo contrario.
Cada línea se manda al principio, en orden. El algoritmo de dos pasadas es lo que hace que no se pierda.
qaq vacía el registro; y A va acumulando en él. Después, "ap donde quieras.
:execute interpreta la cadena, así que el espacio final sobrevive y las teclas especiales funcionan.
La dirección +1 es relativa a la línea que casó.
El comando que sustituye a media hora
Coge un fichero de log o un volcado de datos de verdad y:
- Lista, sin borrar nada, las líneas que te interesan con
:g/patrón/#. - Fíltralo a solo esas con
:v/patrón/d, y deshaz conupara comprobar que fue un solo cambio. - Recolecta esas mismas líneas en un registro con
qaqy:g/patrón/y A, y pégalas al final con"ap. Compara este método con el del punto 2: uno destruye, el otro extrae. - En un fichero de código, manda todos los
importal principio con:g/^import/m0y fíjate en que quedan al revés. Deshaz y repite conm$. - Comenta todas las líneas con TODO usando
:g/TODO/execute "normal! I// ". Prueba antes la versión con<Esc>literal para ver el fallo con tus propios ojos. - Sustituye solo dentro de un tipo de línea con
:g/^function/s/foo/bar/g. - Borra los bloques enteros entre dos marcadores con
:g/^INICIO/,/^FIN/d.
Cuando :g te salga sin pensar, habrás dejado de editar ficheros a mano para siempre.