vi en Linux — teoría con los tres modos, órdenes que se combinan como palabras y :%s (y el día que no sabía salir del editor)

Primera sesión del laboratorio de redes. El enunciado decía algo tan simple como «pon ONBOOT=yes en el archivo de la tarjeta y guarda». Abrí el archivo con vi, empecé a teclear ONBOOT=yes… y arriba del todo apareció una línea nueva que decía NBOOT=yes. Intenté salir con :q y la pantalla me contestó algo así (lo he reproducido después, en limpio):

ONBOOT=no
IPADDR=192.168.2.27
NETMASK=255.255.255.0
GATEWAY=192.168.2.1
~
~
~
~
E37: No write since last change (add ! to override)
Press ENTER or type command to continue

Ni guardaba, ni salía, ni entendía por qué. Acabé cerrando la terminal entera. La lección de aquel día: vi no es un editor raro, es un editor con modos, y en cuanto entiendes en qué modo estás, todo encaja.

En el artículo anterior escribimos scripts que automatizan la terminal. Hoy toca el editor que vas a encontrar en cualquier máquina Linux, aunque no tenga ni ratón ni ventanas: vi. Vamos a usar un archivo de verdad del laboratorio, ifcfg-enp2s0, la configuración de una tarjeta de red. Cuando acabes, tienes la práctica de vi para soltarte los dedos.

Por qué vi (y por qué vim)

En el laboratorio de IC2 trabajas en máquinas con CentOS por terminal: no hay gedit ni bloc de notas. Pero vi siempre está: forma parte del estándar POSIX, así que cualquier Unix lo trae. Lo que hoy abres al escribir vi suele ser vim («vi mejorado»), que añade deshacer ilimitado, resaltado y mensajes más claros, pero respeta todas las órdenes de vi.

Todo lo que ves en este artículo es real: he tecleado cada secuencia en vim 9.1 dentro de una terminal de 72 columnas, con los ajustes por defecto de vim, y he copiado la pantalla tal y como quedó. Este es el archivo con el que vamos a trabajar:

$ cat -n ifcfg-enp2s0
     1	DEVICE=enp2s0
     2	BOOTPROTO=dhcp
     3	ONBOOT=no
     4	IPADDR=192.168.2.27
     5	NETMASK=255.255.255.0
     6	GATEWAY=192.168.2.1

Seis líneas con formato CLAVE=valor. Tiene dos cosas que habrá que arreglar (dhcp y ONBOOT=no), pero de momento solo vamos a aprender a movernos por él.

cat -n enseña el archivo con el número de cada línea. Lo uso antes y después de cada prueba para que veas exactamente qué cambió en el disco, no solo en la pantalla de vi.

Los tres modos de vi

Este es el concepto que lo cambia todo. vi tiene tres modos y cada tecla significa una cosa distinta según el modo en el que estés:

Los tres modos de vivi arranca en modo normal. Con i, a, o, A u O pasas a inserción y con Esc vuelves; con dos puntos pasas al modo de órdenes, que se ejecuta con Enter o se cancela con Esc.MODO NORMALmoverse, borrar, copiar  vi abre aquíINSERCIÓNescribir textoÓRDENESguardar, salir, sustituiri a o A OEsc:Enter / EscAbajo ves:— INSERT —(nada, o el nombre del archivo):wq
Todo pasa por el modo normal: Esc te devuelve siempre a él, y desde él se entra a los otros dos.

Fíjate en el dibujo: el modo normal está en el centro y Esc siempre te devuelve a él. Si alguna vez te pierdes, pulsa Esc (dos veces no hace daño) y ya sabes dónde estás.

ModoPara qué sirveCómo entrasQué ves abajo
Normalmoverte, borrar, copiar, pegaral abrir vi, o con Escnada, o el último mensaje
Inserciónescribir textoi, a, o, A, O…-- INSERT --
Órdenesguardar, salir, sustituir: desde el modo normallo que escribes tras los dos puntos

Vamos a recorrer los tres con este archivo de teclas. La notación es la de la documentación de vim: <Esc> es la tecla Escape y <CR> es Enter.

# Recién abierto: modo normal
jjA            # baja dos líneas y entra en inserción al final de la línea
<Esc>:wq       # vuelve a normal y empieza una orden (sin pulsar Enter)
<Esc>:q<CR>    # cancela la orden y sale: no habíamos cambiado nada

Nada más abrir, vi está en modo normal y en la última línea te dice qué archivo ha cargado:

DEVICE=enp2s0
BOOTPROTO=dhcp
ONBOOT=no
IPADDR=192.168.2.27
NETMASK=255.255.255.0
GATEWAY=192.168.2.1
~
~
~
"ifcfg-enp2s0" 6L, 101B                               1,1           All

6L, 101B significa 6 líneas y 101 bytes. Los ~ del final no son parte del archivo: marcan líneas «vacías» de la pantalla, más allá del final. Abajo a la derecha, 1,1 es la posición del cursor (línea 1, columna 1) y All dice que el archivo entero cabe en pantalla.

Tras jjA (dos líneas abajo y A = añadir al final de la línea) estamos en inserción:

DEVICE=enp2s0
BOOTPROTO=dhcp
ONBOOT=no
IPADDR=192.168.2.27
NETMASK=255.255.255.0
GATEWAY=192.168.2.1
~
~
~
-- INSERT --                                          3,10          All

El -- INSERT -- es tu mejor amigo: si lo ves, lo que teclees se escribe en el archivo. La posición 3,10 es justo detrás de ONBOOT=no (9 caracteres, así que la columna 10 es la siguiente).

Por último, Esc nos devuelve al modo normal y : abre el modo de órdenes. Lo que escribes aparece en la última línea, todavía sin ejecutar:

DEVICE=enp2s0
BOOTPROTO=dhcp
ONBOOT=no
IPADDR=192.168.2.27
NETMASK=255.255.255.0
GATEWAY=192.168.2.1
~
~
~
:wq

Si pulsáramos Enter se guardaría y saldría. En vez de eso, Esc cancela la orden y :q sale sin tocar nada (no habíamos cambiado ni un carácter, así que vi no protesta). El archivo sigue igual:

$ cat -n ifcfg-enp2s0
     1	DEVICE=enp2s0
     2	BOOTPROTO=dhcp
     3	ONBOOT=no
     4	IPADDR=192.168.2.27
     5	NETMASK=255.255.255.0
     6	GATEWAY=192.168.2.1

Empezar a escribir nada más abrir vi. Estás en modo normal y cada letra es una orden: d, x, o, u… hacen cosas. Antes de escribir texto, pulsa i y busca el -- INSERT --.

Moverse sin ratón

En modo normal te mueves con el teclado. Las cuatro básicas son h (izquierda), j (abajo), k (arriba) y l (derecha): están en la fila central para no mover la mano. Las flechas también funcionan en vim, pero los saltos grandes son los que te ahorran tiempo:

TeclaAdónde va
h j k lun carácter a la izquierda, una línea abajo, una arriba, un carácter a la derecha
w / bal principio de la palabra siguiente / anterior
0 / $al principio / al final de la línea
f=al siguiente = de la línea (vale con cualquier carácter: f., fy…)
gg / Ga la primera / a la última línea
5Ga la línea 5
3jtres líneas abajo (un número delante repite el movimiento)
/texto + Entera la siguiente aparición de texto
n / Na la siguiente / anterior aparición de la última búsqueda

He tecleado este recorrido y he apuntado, tras cada tecla, la posición que marcaba vim en la esquina (la «regla», línea,columna):

j              # una línea abajo
$              # al final de la línea
0              # al principio de la línea
f=             # salta al siguiente = de la línea
w              # a la siguiente palabra
G              # a la última línea
gg             # a la primera línea
3j             # tres líneas abajo
/255<CR>       # busca 255 hacia delante
n              # siguiente coincidencia
:q<CR>

Esta es la traza real, con el carácter que quedaba bajo el cursor:

PasoTeclaPosiciónCarácterPor qué
1j2,1Buna línea abajo, misma columna
2$2,14pBOOTPROTO=dhcp tiene 14 caracteres
302,1Bvuelta al principio
4f=2,10=BOOTPROTO tiene 9 letras: el = es el 10
5w2,11dpara vi, = y dhcp son «palabras» distintas
6G6,1Gúltima línea (la G de GATEWAY)
7gg1,1Dprimera línea
83j4,1I1 + 3 = 4
9/255 Enter5,92NETMASK= ocupa 8 caracteres
10n5,132el segundo 255: 9 + 4 (255.) = 13

Y aquí lo tienes dibujado sobre el archivo:

Adónde salta el cursor con cada movimientoEl archivo ifcfg-enp2s0 con el recorrido real del cursor: j, $, 0, f=, w, G, gg, 3j, /255 y n, numerados en orden.1DEVICE=enp2s02BOOTPROTO=dhcp3ONBOOT=no4IPADDR=192.168.2.275NETMASK=255.255.255.06GATEWAY=192.168.2.112345678910paso · tecla · línea,col1j2,12$2,14302,14f=2,105w2,116G6,17gg1,183j4,19/255 Enter5,910n5,13
Cada círculo es la posición real del cursor tras esa tecla (sacada de la ruta de vim, línea,columna).

Mira el paso 5: w no salta a la línea siguiente, sino de = a dhcp, porque los signos de puntuación cuentan como palabras propias. Y en el 10, n repite la búsqueda sin volver a escribirla.

/ busca hacia delante y ? hacia atrás. Si la búsqueda llega al final, vim da la vuelta y sigue desde el principio (te avisa con search hit BOTTOM, continuing at TOP).

Cambiar texto: i, a, o, x y cw

Para escribir hay varias puertas al modo inserción. Todas hacen lo mismo (te dejan escribir), lo que cambia es dónde empiezas:

TeclaEmpiezas a escribir…
i / aantes / después del cursor
I / Aal principio / al final de la línea
o / Oen una línea nueva debajo / encima
cwen lugar de la palabra (la borra y entra en inserción)
ccen lugar de la línea entera

Y para pequeños arreglos sin entrar en inserción: x borra el carácter del cursor y r seguido de una letra lo reemplaza (rY cambia lo que haya por Y).

Con esto ya podemos arreglar la primera cosa del archivo: dhcp debe ser none (la IP va escrita a mano, no la da un servidor DHCP).

/dhcp<CR>      # el cursor salta a la d de dhcp
cwnone<Esc>    # cambia la palabra entera por none
u              # deshace el cambio
<C-r>          # lo rehace
:wq<CR>        # guarda y sale

/dhcp deja el cursor sobre la d y cw sustituye la palabra entera. Tras Esc:

DEVICE=enp2s0
BOOTPROTO=none
ONBOOT=no
IPADDR=192.168.2.27
NETMASK=255.255.255.0
GATEWAY=192.168.2.1
~
~
~
                                                      2,14          All

El cursor quedó en 2,14, sobre la última letra de lo escrito: al salir de inserción, vi retrocede una posición. Ahora u (undo) deshace el último cambio:

DEVICE=enp2s0
BOOTPROTO=dhcp
ONBOOT=no
IPADDR=192.168.2.27
NETMASK=255.255.255.0
GATEWAY=192.168.2.1
~
~
~
1 change; before #1  2 seconds ago                    2,11          All

Vuelve dhcp y vim te cuenta qué ha deshecho: 1 change; before #1 (deshizo 1 cambio, el número 1 de la historia) y hace cuánto se hizo (en tu ordenador saldrá otro tiempo). Ctrl-r (redo) lo rehace, y :wq guarda. Resultado en el disco:

$ cat -n ifcfg-enp2s0
     1	DEVICE=enp2s0
     2	BOOTPROTO=none
     3	ONBOOT=no
     4	IPADDR=192.168.2.27
     5	NETMASK=255.255.255.0
     6	GATEWAY=192.168.2.1

cw es c (cambiar) + w (hasta la siguiente palabra). No es una orden suelta que haya que memorizar: es una combinación. Eso es lo que vemos ahora.

Borrar, copiar y pegar: la gramática de vi

Aquí está la idea que hace que vi deje de parecer una lista infinita de atajos. Las órdenes se construyen como frases:

La gramática de las órdenes de viUna orden de vi se forma con un número opcional, un operador (d borrar, c cambiar, y copiar) y un movimiento. Ejemplos: 3dd, dw, cw, 2yy y d$.númerocuántas veces3+operadorqué hacerd c y+movimientohasta dóndew $ j G3dd3 · d · dborra 3 líneas (dd = la línea entera)dwd · wborra hasta la siguiente palabracwc · wcambia la palabra: borra y entra en inserción2yy2 · y · ycopia 2 líneasd$d · $borra hasta el final de la línea
Si sabes cinco operadores y diez movimientos, ya sabes cincuenta órdenes: se combinan como palabras.
  • Operador: d (delete, borrar), c (change, cambiar), y (yank, copiar).
  • Movimiento: cualquiera de la sección anterior (w, $, j, G…).
  • Número delante: cuántas veces.

Si repites el operador (dd, cc, yy), actúa sobre la línea entera. Lo que borras o copias va a un «portapapeles» de vi (el registro sin nombre) y lo pegas con p (debajo o detrás del cursor) o P (encima o delante).

Quiero…OrdenSe lee
borrar la líneaddborrar línea
borrar 3 líneas3dd3 veces borrar línea
borrar hasta el final de línead$borrar hasta $
copiar la líneayycopiar línea
pegar debajo / encimap / P
deshacer / rehaceru / Ctrl-r
repetir el último cambio.

Probémoslo:

4G             # va a la línea 4 (IPADDR)
yyp            # copia la línea y la pega debajo
dd             # borra la copia
gg3dd          # vuelve arriba y borra tres líneas
u              # deshace el borrado de las tres
:q!<CR>        # sale sin guardar nada

4G nos lleva a la línea de IPADDR; yy la copia y p la pega debajo:

DEVICE=enp2s0
BOOTPROTO=dhcp
ONBOOT=no
IPADDR=192.168.2.27
IPADDR=192.168.2.27
NETMASK=255.255.255.0
GATEWAY=192.168.2.1
~
~
                                                      5,1           All

Ahora hay 7 líneas y el cursor está en la copia (5,1). dd la borra; gg sube a la primera y 3dd borra tres de golpe:

IPADDR=192.168.2.27
NETMASK=255.255.255.0
GATEWAY=192.168.2.1
~
~
~
~
~
~
3 fewer lines                                         1,1           All

3 fewer lines: las líneas 1, 2 y 3 han desaparecido y quedan 6 − 3 = 3. Un u lo deshace entero, porque 3dd es un solo cambio:

DEVICE=enp2s0
BOOTPROTO=dhcp
ONBOOT=no
IPADDR=192.168.2.27
NETMASK=255.255.255.0
GATEWAY=192.168.2.1
~
~
~
3 more lines; before #3  1 second ago                 1,1           All

Para terminar, :q! sale sin guardar: todo lo que hicimos se queda en la memoria de vi y el disco no se entera.

$ cat -n ifcfg-enp2s0
     1	DEVICE=enp2s0
     2	BOOTPROTO=dhcp
     3	ONBOOT=no
     4	IPADDR=192.168.2.27
     5	NETMASK=255.255.255.0
     6	GATEWAY=192.168.2.1

Vi trabaja sobre una copia del archivo en memoria (el búfer). Nada llega al disco hasta que escribes :w. Por eso :q! es tan útil: es el botón de «aquí no ha pasado nada».

Buscar y sustituir

Para buscar ya conoces /texto, n y N. Para sustituir, el modo de órdenes tiene :s:

OrdenQué cambia
:s/viejo/nuevo/la primera aparición en la línea del cursor
:s/viejo/nuevo/gtodas las de la línea del cursor
:%s/viejo/nuevo/gtodas las de todo el archivo (% = todas las líneas)
:2,4s/viejo/nuevo/gtodas las de las líneas 2 a 4

Imagina que el laboratorio cambia de red: la estación pasa de 192.168.2.x a 10.0.1.x. Hay dos líneas que lo llevan:

:%s/192.168.2/10.0.1/g<CR>
:wq<CR>

Así queda la pantalla justo después de pulsar Enter:

DEVICE=enp2s0
BOOTPROTO=dhcp
ONBOOT=no
IPADDR=10.0.1.27
NETMASK=255.255.255.0
GATEWAY=10.0.1.1
~
~
~
:%s/192.168.2/10.0.1/g                                6,1           All

La orden se queda escrita abajo y el archivo ya muestra 10.0.1.27 y 10.0.1.1. Tras :wq:

$ cat -n ifcfg-enp2s0
     1	DEVICE=enp2s0
     2	BOOTPROTO=dhcp
     3	ONBOOT=no
     4	IPADDR=10.0.1.27
     5	NETMASK=255.255.255.0
     6	GATEWAY=10.0.1.1

Olvidar que el patrón es una expresión regular. En 192.168.2 cada . significa «cualquier carácter», así que también encajaría con 192x168y2. Aquí no pasa nada, pero si quieres un punto de verdad, escríbelo como \.. Lo verás en la práctica.

Guardar y salir (sin quedarte atrapado)

El gran meme de vi. Estas son todas las salidas que necesitas:

OrdenQué hace
:wguarda y sigues en vi
:qsale, solo si no hay cambios sin guardar
:wq (o :x, o ZZ en modo normal)guarda y sale
:q!sale tirando los cambios
:w otro-nombreguarda una copia con otro nombre

¿Qué pasa si hay cambios y pruebas con :q? Esto:

dd             # un cambio cualquiera
:q<CR>         # intenta salir
<CR>           # Enter para seguir
:q!<CR>        # sale tirando el cambio

El dd borra la primera línea y el :q choca con esto:

ONBOOT=no
IPADDR=192.168.2.27
NETMASK=255.255.255.0
GATEWAY=192.168.2.1
~
~
~
~
E37: No write since last change (add ! to override)
Press ENTER or type command to continue

E37: No write since last change (add ! to override): «no has guardado desde el último cambio (añade ! para forzar)». vi te protege: no te deja perder el trabajo sin querer. El mensaje es largo y la pantalla sube una línea, por eso aparece Press ENTER: pulsas Enter y vuelves al modo normal. Ahí eliges: :wq si quieres guardar, :q! si no. Nosotros elegimos :q!, así que el dd nunca llegó al disco:

$ cat -n ifcfg-enp2s0
     1	DEVICE=enp2s0
     2	BOOTPROTO=dhcp
     3	ONBOOT=no
     4	IPADDR=192.168.2.27
     5	NETMASK=255.255.255.0
     6	GATEWAY=192.168.2.1

Si ves una pantalla rara y no sabes qué has hecho, la salida de emergencia es Esc, Esc, :q! y Enter. Sales sin guardar nada y vuelves a abrir con calma.

Errores típicos

Cada letra es una orden. ONBOOT=yes tecleado sin pulsar i antes abre una línea encima (la O) y escribe NBOOT=yes. Busca siempre el -- INSERT --.

Sale E37. No es un fallo: decide entre :wq y :q!.

:s/a/b/g solo toca la línea del cursor. Para todo el archivo es :%s/a/b/g.

:%s/0/1/ cambia solo el primer 0 de cada línea.

Los ifcfg de /etc son de root. Si abres uno como usuario normal, vim te avisa de que es de solo lectura y :w falla. Ábrelo con sudo vi (lo verás en el tema de red).

Pruébalo en tu terminal

Haz una copia para trastear sin miedo y repite esta sesión. Yo la llamé ifcfg-test, con el mismo contenido que el archivo de antes:

$ vi ifcfg-test

Dentro de vi, teclea esto (nada de inserción: todo en modo normal):

dd             # borra la primera línea
dd             # y la siguiente
u              # deshace un borrado
:q!<CR>        # sale sin guardar

Tras los dos dd deberías ver:

ONBOOT=no
IPADDR=192.168.2.27
NETMASK=255.255.255.0
GATEWAY=192.168.2.1
~
~
~
~
~
                                                      1,1           All

Y tras el u:

BOOTPROTO=dhcp
ONBOOT=no
IPADDR=192.168.2.27
NETMASK=255.255.255.0
GATEWAY=192.168.2.1
~
~
~
~
1 more line; before #2  1 second ago                  1,1           All

Al salir con :q!, comprueba que el archivo no ha cambiado:

$ cat -n ifcfg-test
     1	DEVICE=enp2s0
     2	BOOTPROTO=dhcp
     3	ONBOOT=no
     4	IPADDR=192.168.2.27
     5	NETMASK=255.255.255.0
     6	GATEWAY=192.168.2.1

Qué observar:

  1. Tras el primer dd, la línea 2 sube a ser la 1: vi no deja huecos, recoloca las líneas.
  2. El segundo dd borra la que ahora es la primera (BOOTPROTO=dhcp), no la línea 2 original.
  3. u deshace solo el último borrado: vuelve BOOTPROTO, pero DEVICE sigue fuera. El mensaje dice before #2 porque deshace el cambio número 2.
  4. :q! tira los dos cambios a la vez: en el disco no ha pasado nada, como demuestra cat -n.
  5. Repite la prueba con :wq en lugar de :q! y vuelve a hacer cat -n: ahora sí falta la línea.

Resumen

ConceptoLo esencial
Modosnormal (órdenes), inserción (texto), órdenes (:); Esc vuelve a normal
Moverseh j k l, w b, 0 $, gg G, 5G, f=, /texto, n
Escribiri a I A o O, cw, cc; x y r para arreglos rápidos
Gramáticanúmero + operador (d c y) + movimiento: 3dd, dw, cw, 2yy
Pegar y deshacerp P, u, Ctrl-r, .
Sustituir:s/a/b/ (línea), :%s/a/b/g (todo, todas las veces)
Salir:w, :wq, :q, :q!, ZZ

Tres ideas para llevarte:

  1. Antes de teclear nada, pregúntate en qué modo estás. Esc te devuelve siempre al normal.
  2. Las órdenes son frases: operador + movimiento, con un número delante si quieres repetir.
  3. vi edita una copia en memoria: :w la lleva al disco y :q! la tira.

Ahora te toca a ti: en la práctica de vi dejamos el ifcfg-enp2s0 listo para el laboratorio con tres ediciones completas. Y con vi dominado, el siguiente paso es lo que hay dentro de ese archivo: la configuración de red en Linux.

Siguiente paso: prácticavi en Linux — práctica con un ifcfg de verdad: activar la tarjeta, limpiar el archivo y mudarlo de red (sin tocar el ratón)

¿Te ha servido este artículo? Así puedes apoyar SergioLearns →

Sigue aprendiendo

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *