Designador de eventos en terminal de Linux y macOS

El designador de eventos es una utilidad muy práctica que viene en todos los sistemas basados en unix, la cual nos permite poder acceder al historial en nuestra terminal para poder utilizar de manera rápida y práctica algunos de los comandos que hemos utilizado en el pasado, sin importar si tenemos miles de registros en el historial o sólo unos pocos. En este post estaré hablando brevemente de cómo podemos sacar provecho de esta utilidad.

Historial de comandos

Todas los sistemas basados en Unix, como lo son macOS y Linux, mantienen siempre un historial de todos los comandos que se han ejecutado en su terminal, a medida que vamos usando cada vez más la terminal, este historial crece muy rápido y puede llegar a ocupar miles de comandos fácilmente.

El historial puede ser de mucha ayuda ya que con él podremos recordar aquel comando que tecleamos hace varios meses (o años) atrás y que nunca más utilizamos de nuevo. Para poder consultar este historial podemos simplemente teclear history en nuestra terminal y podremos verlo pero esto nos trae un pequeño problema ¿Qué pasaría si quiero buscar un comando en particular que utilicé hace mucho tiempo?

Una manera de solucionar esto es filtrando el resultado del comando history para que solamente nos muestre aquellos comandos que contengan una palabra en particular, para hacer esto deberemos utilizar un pipe y tendremos que filtrar la slida con grep. En el siguiente ejemplo verás cómo es que estoy buscando en todo el historial de mi computadora personal, las veces que se tecleo la palabra vim
history | grep vim. Esto podría darnos un resultado como el siguiente

   13  vim ~/.bashrc
   64  vim config
   93  vim /etc/ssh/sshd_config
  181  take ~/.vim
  184  xclip -sel clip < ~/.vim/plugins.vim
  185  vim plugins.vim
  186  ls ~/.vim/plugins.vim
  187  ls ~/.vim
  190  xclip -sel clip < .gvimrc
  191  vim ~/.gvimrc
  192  vim .zshrc
  193  xclip -sel clip < .vimrc
  194  vim ~/.vimrc

Noten como la salida no nos muestra exactamente el comando que buscamos, sino todos los resultados que contengan el texto vim, ya sea que empiece con el o no, esto puede resultar útil pero no tanto si llegamos a tener miles de registros, así que podrás preguntarte ¿Existe una manera más sencilla de poder reutilizar comandos previos?.

La solución a esto está en el designador de eventos (event designator).

Designador de eventos

Un designador de eventos, como lo dice el manual de bash, es una referencia hacia una entrada de línea de comandos en la lista del historial. Esto significa que nos permite revisar todo el historial en busca de comandos para poder utilizarlo completos o parcialmente. El designador de eventos se representa en la terminal con el símbolo !, también llamado bang. A continuación veremos las maneras más usuales de utilizar el designador de eventos.

Ejecutar el comando anterior como root

En algún momento es seguro que te ha pasado que ejecutaste un comando y recibiste un mensaje diciendo que no se cuentan con permisos para ejecutar esa acción, esto podemos corregirlo con el prefijo sudo antes del comando que queremos ejecutar, para ello podemos escoger una de las siguientes 2 opciones

sudo !!
su /c "!!"

En el siguiente ejemplo se va a registrar un usuario nuevo en un servidor Ubuntu

~$ adduser john
adduser: Only root may add a user or group to the system.
~$ sudo !!
sudo adduser john
~$ id john
uid=1002(john) gid=1002(john) groups=1002(john)
~$ userdel -r john
userdel: Permission denied.
userdel: cannot lock /etc/passwd; try again later.
~$ sudo !!
sudo userdel -r john
userdel: john mail spool (/var/mail/john) not found
~$ id john
id: ‘john’: no such user

El signo ! es llamado el designador de eventos, este hace referencia a algún comando en el historial del shell. Al utilizar el operador !! o mejor conocido como bang-bang estamos haciendo referencia al último comando ejecutado en nuestra terminal, es debido a esto que podemos hacer sudo !! para ejecutar el último comando con permisos de sudo o en otras palabras, se ejecutan como root.

Repetir el último comando que empiece con una cadena determinada

También podemos usar el designador de eventos para ejecutar el último comando que empiece con una cadena determinada. Veamos el siguiente ejemplo:

~$ whoami
john
~$ uptime
20:01:32 up  1:34,  1 user,  load average: 0.00, 0.00, 0.00
~$ df -hT /boot/
Filesystem     Type  Size  Used Avail Use% Mounted on
/dev/sda1      ext4  9.7G  971M  8.7G  10% /
~$ !u
uptime
20:01:46 up  1:34,  1 user,  load average: 0.00, 0.00, 0.00
~$ sudo !w
sudo whoami
root

Con este ejemplo podemos ver que podemos mandar llamar comandos ejecutados previamente, en este caso lo hacemos de dos maneras, en la primera hacemos referencia al último comando ejecutado que haya comenzado con la letra u y en la segunda estamos mandando llamar, con sudo al último comando ejecutado que haya comenzado con la letra w.
Nota: Esto no se limita a una sola letra, en su lugar pueden usarse cadenas más largas

Reutilizar la segunda palabra (primer argumento) del comando anterior

Si alguna vez necesitas utilizar la segunda palabra del comando anterior, puedes utilizar el designador de eventos !^. En la parte donde se coloque este operador será reemplazado por la segunda palabra del comando anterior, o en otras palabras, el primer argumento del comando anterior

~$ host www.google.com 8.8.8.8
Using domain server:
Name: 8.8.8.8
Address: 8.8.8.8#53
Aliases:

www.google.com has address 172.217.11.164
www.google.com has IPv6 address 2607:f8b0:4007:801::2004
~$ ping -c1 !^
ping -c1 www.google.com
PING www.google.com (108.177.9.104) 56(84) bytes of data.
64 bytes from ox-in-f104.1e100.net (108.177.9.104): icmp_seq=1 ttl=63 time=73.3 ms

--- www.google.com ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 73.394/73.394/73.394/0.000 ms

Reutilizar la última palabra (último argumento) del comando anterior

Justo como vimos en la sección anterior, también podemos mandar llamar la última palabra del comando anterior con el designador de eventos !$. Este en lo personal me resulta muy útil al momento de crear y editar archivos o directorios.

~$ mkdir ~/.ssh
~$ chmod 700 !$
chmod 700 ~/.ssh
~$ cd !$
cd ~/.ssh
~/.ssh$ pwd
/home/john/.ssh

Repetir cualquier palabra (argumento) del comando anterior

Para poder acceder a cualquier palabra del comando anterior utiliza el designador de eventos !!:N, donde N es el número de la palabra que deseas obtener, comenzando desde el número 0. Esto es, la primer palabra es el índice 0, la segunda el 1, etc. Podría decirse que el comando es el número 0 y todos los argumentos empiezan del 1 hasta n.

Puede utilizarse el designador de eventos con el designador de palabras. En el siguiente ejemplo, !! es el comando más reciente touch john.txt jane.txt jake.txt. El designador de eventos !t representa el mismo comando, ya que es el más reciente que empieza con la letra `t``

~$ touch john.txt jane.txt jake.txt // Crea archivos john.txt, jane.txt y jake.txt
~$ mv !!:2 ~/.ssh/                  // Mueve el archivo jane.txt al directorio ~/.ssh
mv jane.txt ~/.ssh/
~$ rm !t:3                          // Elimina el archivo jake.txt
rm jake.txt
~$ ls                               // Lista el contenido del directorio actual
john.txt

Repetir el comando más reciente sustituyendo una cadena

Este pequeño truco es genial para corregir errores al estar tecleando comandos. Podemos sustituir una cadena del comando anterior con algo completamente diferente con el designador de eventos ^<cadena1>^<cadena2>^. Si se omite la ^<cadena2>, la <cadena1> será removida del comando anterior. Por default esto solamente funciona con <cadena1> pero si se desea reemplazar todas las veces que la <cadena1> aparezca en el comando podemos adjuntar :& al final del comando.

~$ grpe john /etc/passwd

Command 'grpe' not found, did you mean:

  command 'grpn' from deb grpn
  command 'nrpe' from deb nagios-nrpe-server
  command 'grep' from deb grep
  command 'gpre' from deb firebird3.0-utils
  command 'gvpe' from deb gvpe

Try: apt install <deb name>

~$ ^pe^ep
grep john /etc/passwd
john:x:1002:1002:,,,:/home/john:/bin/bash
~$ grep rooty /etc/passwd
~$ ^y
grep root /etc/passwd
root:x:0:0:root:/root:/bin/bash
~$ grep jhon /etc/passwd ; ls -ld /home/jhon
ls: cannot access '/home/jhon': No such file or directory
~$ ^jhon^john^:&
grep john:john /etc/passwd ; ls -ld /home/john
drwxr-xr-x 3 john john 4096 Oct 27 20:56 /home/john

Referenciar una palabra en el comando actual y reutilizarla

El designador de eventos !#:N representa la línea de comandos actual, donde N es el número del índice donde se encuentra la palabra que queremos refernciar. Como vimos anteriormente, el primero índice comienza con el número 0.

~$ mv user-configuration.pdf Chapter-2-!#:1
mv user-configuration.pdf Chapter-2-user-configuration.pdf