vi en Linux — ejercicios con resolv.conf, líneas repetidas y una tarjeta clonada (y el NBOOT=yes que nadie había escrito)

El día del examen práctico de redes, un compañero de mi fila levantó la mano: «Profe, vi me ha escrito una línea que pone NBOOT=yes y yo no he sido». Sí había sido él: tecleó ONBOOT=yes en modo normal y la O se convirtió en una orden. Toda la clase se rió… y la mitad comprobamos a escondidas que nuestro archivo estaba bien. La lección: en vi, antes de escribir, mira abajo; si no pone -- INSERT --, tus letras son órdenes.

Estos ejercicios cierran el tema de vi después de la teoría y la práctica. Ahora tú decides las teclas.

Cómo usar este artículo

  1. Lee el enunciado y la salida esperada (lo que tiene que dar cat -n al final).
  2. Haz una copia del archivo y resuélvelo en vi sin mirar la solución. Si te atascas, abre la pista.
  3. Antes de mirar las soluciones, haz los tres fragmentos de «¿Qué falla aquí?»: predice qué pasará.
  4. Compara tus teclas con las mías. Hay muchas secuencias válidas: lo que cuenta es el resultado y que sepas por qué funciona.

Nivel 1: crea el resolv.conf

El archivo /etc/resolv.conf dice a qué servidores DNS pregunta la máquina para traducir nombres (como ulpgc.es) a direcciones IP. Crea desde cero un archivo resolv.conf con los dos primeros servidores DNS de la universidad, uno por línea, con el formato nameserver IP: 193.145.147.22 y 193.145.138.100.

Partes de que el archivo no existe:

$ cat -n resolv.conf
cat: resolv.conf: No such file or directory

Salida esperada:

$ cat -n resolv.conf
     1	nameserver 193.145.147.22
     2	nameserver 193.145.138.100

vi resolv.conf abre un archivo vacío (vi lo crea al guardar). Entra en inserción, escribe la primera línea, Enter, la segunda, Esc y guarda.

Nivel 2: fuera los repetidos

Este resolv.conf tiene el primer servidor repetido tres veces y dos servidores públicos que el laboratorio no quiere usar (8.8.8.8 y 8.8.4.4). Deja solo los tres de la universidad, sin borrar línea a línea: usa órdenes con número.

$ cat -n resolv.conf
     1	nameserver 193.145.147.22
     2	nameserver 193.145.147.22
     3	nameserver 193.145.147.22
     4	nameserver 193.145.138.100
     5	nameserver 8.8.8.8
     6	nameserver 8.8.4.4
     7	nameserver 193.145.138.200

Salida esperada:

$ cat -n resolv.conf
     1	nameserver 193.145.147.22
     2	nameserver 193.145.138.100
     3	nameserver 193.145.138.200

Son dos borrados de dos líneas cada uno. Fíjate en dónde queda el cursor después del primero.

Nivel 3: clona la tarjeta

La estación va a tener una segunda tarjeta, enp3s0, con la misma configuración que enp2s0 pero la IP siguiente (.28 en lugar de .27). Partiendo de ifcfg-enp2s0, crea ifcfg-enp3s0 sin modificar el original y sin escribir la IP a mano.

$ cat -n ifcfg-enp2s0
     1	DEVICE=enp2s0
     2	BOOTPROTO=none
     3	ONBOOT=yes
     4	IPADDR=10.0.1.27
     5	NETMASK=255.255.255.0
     6	GATEWAY=10.0.1.1
     7	NETWORK=10.0.1.0
$ cat -n ifcfg-enp3s0
cat: ifcfg-enp3s0: No such file or directory

Salida esperada:

$ cat -n ifcfg-enp2s0
     1	DEVICE=enp2s0
     2	BOOTPROTO=none
     3	ONBOOT=yes
     4	IPADDR=10.0.1.27
     5	NETMASK=255.255.255.0
     6	GATEWAY=10.0.1.1
     7	NETWORK=10.0.1.0
$ cat -n ifcfg-enp3s0
     1	DEVICE=enp3s0
     2	BOOTPROTO=none
     3	ONBOOT=yes
     4	IPADDR=10.0.1.28
     5	NETMASK=255.255.255.0
     6	GATEWAY=10.0.1.1
     7	NETWORK=10.0.1.0

Necesitas tres cosas de vim que no son de vi clásico ni de la práctica: :w nombre guarda con otro nombre, Ctrl-a suma 1 al número que hay en el cursor o a su derecha, y :q! sale sin tocar el archivo que abriste.

¿Qué falla aquí?

Tres fragmentos sobre el ifcfg-enp2s0 de la teoría (BOOTPROTO=dhcp, ONBOOT=no, red 192.168.2.x). Antes de leer la explicación, predice qué saldrá en la pantalla.

Fragmento 1: escribir sin entrar en inserción

Abres el archivo y, sin pulsar nada más, tecleas:

ONBOOT=yes     # tecleado en modo normal, como si estuvieras en inserción
<Esc>:q!<CR>

Predice: ¿aparece ONBOOT=yes en algún sitio?

La pantalla real tras teclear ONBOOT=yes:

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

Hay una línea nueva arriba del todo con NBOOT=yes y vim está en inserción. Cada letra se interpretó según el modo en que estaba vi en ese momento:

Qué hace vi con ONBOOT=yes tecleado en modo normalLa O es una orden que abre una línea encima del cursor y entra en inserción; las letras siguientes, NBOOT=yes, se escriben como texto en esa línea nueva.ONBOOT=yes  orden: abre una línea ENCIMA y pasa a inserciónya en inserción: se escriben como textoLo que queda en la línea 1:NBOOT=yesDEVICE=enp2s0BOOTPROTO=dhcp— INSERT —
La primera letra es una orden y se 'come': por eso la línea nueva empieza por NBOOT.

En modo normal, O es la orden «abre una línea encima y entra en inserción». A partir de ahí ya estás en inserción y el resto (NBOOT=yes) se escribe tal cual. La posición 1,10 es el final de esa línea nueva.

Confiar en que vi «ya estaba escribiendo». Con otras palabras el destrozo cambia, porque cada letra es una orden distinta (d borra, x borra, u deshace, p pega…). Mira siempre la última línea.

El arreglo: Esc, u para deshacer, ir a la línea correcta y cambiarla entera con cc: 3GccONBOOT=yes<Esc>. Es justo la primera orden del fragmento 2, y su resultado lo ves allí.

Fragmento 2: salir sin guardar… o no

3GccONBOOT=yes<Esc>   # cambia la línea 3 entera
:q<CR>         # intenta salir
<CR>
:wq<CR>        # guarda y sale

Predice: ¿qué hace el :q?

BOOTPROTO=dhcp
ONBOOT=yes
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: hay un cambio sin guardar (la línea 3) y :q se niega a salir. La pantalla ha subido una línea para hacer sitio al mensaje y vim espera un Enter. Tras ese Enter, :wq sí guarda y sale:

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

Lee el mensaje entero: add ! to override te dice que :q! saldría tirando el cambio. vi nunca te deja atrapado; te obliga a decidir.

Fragmento 3: la sustitución que no encuentra nada

El cursor está en la línea 1 (recién abierto) y quieres pasar el archivo de la red 192.168.2 a la 10.0.1:

:s/192.168.2/10.0.1/g<CR>    # sin % : solo la línea del cursor (la 1)
:%s/192.168.2/10.0.1/g<CR>   # con % : todo el archivo
:wq<CR>

Predice: ¿cuántas líneas cambia la primera orden?

DEVICE=enp2s0
BOOTPROTO=dhcp
ONBOOT=no
IPADDR=192.168.2.27
NETMASK=255.255.255.0
GATEWAY=192.168.2.1
~
~
~
E486: Pattern not found: 192.168.2                    1,1           All

E486: Pattern not found: ninguna. Sin %, :s solo mira la línea del cursor, y la línea 1 (DEVICE=enp2s0) no contiene 192.168.2. vim no busca en el resto del archivo. Con % sí:

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

Y 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

Si :s te da E486 y estás seguro de que el texto está en el archivo, casi siempre falta el % (o un rango como :4,6s).

Soluciones

Solución del Nivel 1

inameserver 193.145.147.22<CR>nameserver 193.145.138.100<Esc>
:wq<CR>

La pantalla antes de guardar:

nameserver 193.145.147.22
nameserver 193.145.138.100
~
~
~
~
~
~
~
                                                      2,26          All

Por qué está hecho así:

  • i entra en inserción en un archivo vacío (también valdría a u o).
  • <CR> dentro de inserción es un salto de línea normal: no ejecuta nada.
  • Esc antes de :wq, porque los dos puntos solo abren el modo de órdenes desde el modo normal. Si no pulsas Esc, escribirías :wq dentro del archivo.
  • Comprobación: la regla dice 2,26 porque nameserver 193.145.138.100 tiene 26 caracteres (10 + 1 espacio + 15). Ya no pone -- INSERT --: Esc nos devolvió al modo normal.

Solución del Nivel 2

j2dd           # en la línea 2, borra las dos copias
j2dd           # baja a 8.8.8.8 y borra esa línea y 8.8.4.4
:wq<CR>

Tras el primer j2dd:

nameserver 193.145.147.22
nameserver 193.145.138.100
nameserver 8.8.8.8
nameserver 8.8.4.4
nameserver 193.145.138.200
~
~
~
~
                                                      2,1           All

Por qué está hecho así:

  • j baja a la línea 2 y 2dd borra la 2 y la 3: las dos copias. La primera aparición se queda. Quedan 7 − 2 = 5 líneas.
  • Después del borrado el cursor está en la línea 2 (193.145.138.100): lo que había debajo subió. Un j más y estamos en 8.8.8.8.

Tras el segundo j2dd:

nameserver 193.145.147.22
nameserver 193.145.138.100
nameserver 193.145.138.200
~
~
~
~
~
~
                                                      3,1           All
  • 2dd se lleva 8.8.8.8 y 8.8.4.4: 5 − 2 = 3 líneas. El cursor queda en la 3 (193.145.138.200), la que subió.
  • La alternativa obvia, dd cinco veces moviéndote entre medias, funciona, pero son más teclas y más ocasiones de borrar la que no es.

Solución del Nivel 3

:%s/enp2s0/enp3s0/g<CR>   # el nombre de la tarjeta
/IPADDR<CR>    # la línea de la IP
$<C-a>         # al final de la línea y suma 1 al número: 27 -> 28
:w ifcfg-enp3s0<CR>   # guarda con OTRO nombre
:q!<CR>        # el original no se toca

Tras la sustitución, la búsqueda y el Ctrl-a:

DEVICE=enp3s0
BOOTPROTO=none
ONBOOT=yes
IPADDR=10.0.1.28
NETMASK=255.255.255.0
GATEWAY=10.0.1.1
NETWORK=10.0.1.0
~
~
/IPADDR                                               4,16          All

IPADDR=10.0.1.28: Ctrl-a buscó el número desde el cursor y le sumó 1 (27 + 1 = 28). La regla dice 4,16, el último dígito de la línea. Después, :w ifcfg-enp3s0:

DEVICE=enp3s0
BOOTPROTO=none
ONBOOT=yes
IPADDR=10.0.1.28
NETMASK=255.255.255.0
GATEWAY=10.0.1.1
NETWORK=10.0.1.0
~
~
"ifcfg-enp3s0" [New] 7L, 113B written                 4,16          All

[New] 7L, 113B written: el archivo nuevo tiene 7 líneas y 113 bytes, los mismos que el original (cambiamos 2 por 3 y 7 por 8, mismo tamaño). El mensaje no cabe entero en la esquina y vim lo recorta por la izquierda con <.

Por qué está hecho así:

  • :%s/enp2s0/enp3s0/g cambia el nombre en cualquier línea donde aparezca (aquí solo DEVICE, pero en otros ifcfg también va en NAME).
  • $ antes de Ctrl-a deja el cursor sobre el 7: así suma 1 a 27. Desde el principio de la línea, Ctrl-a también habría encontrado el primer número de la línea, que es el 10 de 10.0.1.27, y habrías creado 11.0.1.27.
  • :w ifcfg-enp3s0 escribe el búfer en otro archivo, pero vi sigue editando ifcfg-enp2s0.
  • :q! es clave: el búfer tiene cambios respecto a ifcfg-enp2s0 y no queremos guardarlos ahí. El original sale intacto, como pedía el enunciado.
El resultado del nivel 3: dos archivosEl original ifcfg-enp2s0 no cambia porque se sale con :q!; el nuevo ifcfg-enp3s0 tiene DEVICE=enp3s0 e IPADDR=10.0.1.28.ifcfg-enp2s0 (intacto, :q!)ifcfg-enp3s0 (nuevo, :w nombre)DEVICE=enp2s0DEVICE=enp3s0:%s/enp2s0/enp3s0/gBOOTPROTO=noneBOOTPROTO=noneONBOOT=yesONBOOT=yesIPADDR=10.0.1.27IPADDR=10.0.1.28$ Ctrl-a (27 → 28)NETMASK=255.255.255.0NETMASK=255.255.255.0GATEWAY=10.0.1.1GATEWAY=10.0.1.1NETWORK=10.0.1.0NETWORK=10.0.1.0
La sustitución y el Ctrl-a solo viven en el búfer: :w nombre los guarda en otro archivo y :q! deja el original como estaba.

El dibujo enseña los dos archivos al final: el original sin tocar y el clon con sus dos diferencias.

Pruébalo en tu terminal

El fragmento 3 tiene una versión aún más traicionera: dentro de un script, con vim -es. Esta sesión es real, sobre el ifcfg-enp2s0 con la red 192.168.2:

# El fragmento 3 en modo silencioso: sin % la orden solo mira la línea 1
vim -u NONE -es -c 's/192.168.2/10.0.1/g' -c 'wq' ifcfg-enp2s0
echo $?                                                         # distinto de 0: algo falló
grep -c 192.168.2 ifcfg-enp2s0                                  # cuántas líneas siguen con la red vieja
vim -u NONE -es -c '%s/192.168.2/10.0.1/g' -c 'wq' ifcfg-enp2s0
echo $?
grep -c 192.168.2 ifcfg-enp2s0

Lo que salió:

$ vim -u NONE -es -c 's/192.168.2/10.0.1/g' -c 'wq' ifcfg-enp2s0
$ echo $?
0
$ grep -c 192.168.2 ifcfg-enp2s0
1
$ vim -u NONE -es -c '%s/192.168.2/10.0.1/g' -c 'wq' ifcfg-enp2s0
$ echo $?
0
$ grep -c 192.168.2 ifcfg-enp2s0
0

Qué observar:

  1. La primera orden, sin %, no da error: echo $? vale 0. En modo ex, la línea actual al empezar es la última del archivo (GATEWAY=192.168.2.1), y ahí sí había coincidencia.
  2. Por eso grep -c cuenta 1: de las dos líneas con 192.168.2, solo cambió una. Queda IPADDR con la red vieja.
  3. El mismo descuido da un resultado distinto según dónde esté el cursor: en pantalla (fragmento 3) falló con E486; aquí «funciona» a medias y en silencio.
  4. Con %, la segunda orden arregla lo que quedaba y el recuento baja a 0.
  5. Moraleja para tus scripts: comprueba el resultado (grep -c), no solo $?.

Chuletario: vi

Lo esencial en 30 segundos

vi tiene tres modos: normal (órdenes), inserción (texto) y órdenes (:). Esc siempre vuelve al normal. Las órdenes se combinan: número + operador (d, c, y) + movimiento. Nada llega al disco hasta :w, y :q! sale sin guardar.

Moverse

Quiero…TeclasResultado
bajar / subir una líneaj / kmisma columna (si existe)
ir al principio / final de línea0 / $
ir a la línea 55G
primera / última líneagg / G
siguiente palabra / anteriorw / b= cuenta como palabra
buscar ONBOOT/ONBOOT Entercursor en la O; n siguiente, N anterior
saltar al siguiente = de la líneaf=

Editar

Quiero…TeclasResultado
escribir antes del cursor / al final de líneai / A-- INSERT --
línea nueva debajo / encimao / O
cambiar una palabracwborra y entra en inserción
cambiar la línea enteracc
borrar carácter / línea / 3 líneasx / dd / 3dd3 fewer lines
copiar línea y pegarla debajoyy p
intercambiar con la de abajoddp
deshacer / rehacer / repetiru / Ctrl-r / .
sumar 1 al númeroCtrl-a27 → 28

Órdenes de :

Quiero…OrdenResultado
guardar / guardar y salir:w / :wq"archivo" 7L, 113B written
salir sin guardar:q!el disco no cambia
guardar con otro nombre:w ifcfg-enp3s0[New]
sustituir en todo el archivo:%s/viejo/nuevo/gN substitutions on M lines
sustituir en las líneas 4 a 6:4,6s/viejo/nuevo/g
borrar las líneas que empiezan por #:g/^#/d
sin abrir pantalla (scripts)vim -es -c '%s/a/b/g' -c wq archivo

Plantillas de los ejercicios

EjercicioTeclasResultado
Nivel 1i texto <CR> texto <Esc> :wqarchivo nuevo de 2 líneas
Nivel 2j2dd j2dd :wqde 7 a 3 líneas
Nivel 3:%s/enp2s0/enp3s0/g /IPADDR $ Ctrl-a :w ifcfg-enp3s0 :q!original intacto + clon con .28

Síntoma / causa probable

SíntomaCausa probable
aparecen letras sueltas o líneas rarasescribiste en modo normal; Esc y u
E37: No write since last change:q con cambios: :wq o :q!
E486: Pattern not found con :sfalta % o el patrón no coincide (¿un punto sin escapar?)
:wq aparece escrito dentro del archivoestabas en inserción: Esc antes de :
E45: 'readonly' option is setel archivo es de otro usuario (root): sudo vi
vim dice Press ENTERel mensaje no cabía: pulsa Enter y sigue

Antes de entregar, pregúntate

  1. ¿He comprobado el archivo con cat -n después de salir de vi, no solo en la pantalla del editor?
  2. ¿El número de sustituciones o de líneas borradas es el que esperaba?
  3. ¿He salido con :wq cuando quería guardar y con :q! cuando no?
  4. ¿He trabajado sobre una copia hasta estar seguro?

Con vi dominado, el siguiente tema abre por fin lo que hay dentro del ifcfg: la configuración de red en Linux.

Sigue aprendiendoTodos los artículos de Linux

¿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 *