Notxor tiene un blog

Defenestrando la vida

Las conchas de tu madre

2020-08-08

Pues hoy vengo a hablar de conchas ─o shells, en inglés─ dentro del entorno de Emacs. No pude resistirme a la tentación de escribir el título así, ya me perdonará quien entienda otra cosa o piense que va leer algún tipo de artículo soez o socarrón, quizá campechano, sobre temas reproductivos humanos. Pero lamentablemente la cosa va de herramientas que vienen con nuestro editor favorito.

Esto parte de una conversación en el canal de Telegram del blog. Patricio nos preguntó por qué no utilizábamos más eshell o por qué no hablábamos de él. Nos hizo una pequeña demostración con un par de ejemplos para demostrarnos su potencia y la cosa quedó ahí... bueno, no del todo, porque ahora os vengo a cansinar yo con las conchas.

Efectivamente, son las conchas, porque hay más de una opción y cada una tiene su clít... cosa. Pero para resumir las enumero y luego hacemos hincapié más despacio en ellas, con algunos trucos y todo.

Así pues, tenemos tres conchas que toquetear ¿cuál elegir? Pues depende fundamentalmente de lo que queramos hacer. La forma más rápida de ejecutar un comando de shell desde Emacs es utilizar el minibuffer con M-!, esto llama al comando shell-command y nos pregunta por un comando. La salida del mismo nos la muestra en un buffer llamado *Shell Command Output*. También es posible que la salida se vuelque al buffer actual si le damos un argumento numérico... por ejemplo si queremos que nos añada al buffer una lista de los ficheros en el directorio, podemos teclear M-1 M-! ls -lh *.txt y nos añadirá una lista de todos los ficheros con extensión .txt que encuentre en el directorio de trabajo.

También en ocasiones lo que queremos es que se ejecute el comando pero en segundo plano. Lo mismo que hacemos en el terminal cuando ejecutamos cualquier comando finalizándolo con el carácter &. En este caso Emacs nos proporciona el comando async-shell-command y lo podemos invocar con la combinación M-& en lugar de M-!. Si ese comando produce cualquier salida, la mostrará en un buffer con el nombre *Async Shell Command*.

Pero bueno, en realidad todo esto no es una concha, sólo nos permite acceder a los comandos del sistema sin necesidad de cerrar el entorno de edición. Lo que queremos hacer es tener una terminal interactiva.

Shell

El terminal más a mano que tenemos en Emacs es el comando shell. Como he dicho antes ejecuta la mayoría de los comandos pero tiene limitaciones puesto que no constituye un terminal completo. Además Emacs tiene la costumbre de abrir la concha en un buffer con nombre *shell*, si ya tenemos uno abierto, lo que hace es llevarnos a él, pero no abre uno nuevo. ¿Se pueden tener varios buffers con distintas sesiones de shell abiertas? Pues sí. Además, para hacerlo, tenemos varias alternativas. La primera consiste en renombrar el buffer donde tenemos abierta la sesión con M-x rename-uniquely y veremos que nos pondrá el nombre a algo como *shell<2>*, ahora podemos llamar de nuevo a M-x shell y tendremos dos buffers independientes, uno con nombre *shell* y otro con nombre *shell<2>*. La otra opción es utilizar el comando C-u M-x shell. Este comando nos pregunta por un nombre para el buffer, si le proporcionamos uno distinto de shell nos lo abrirá en un buffer con ese nombre.

No voy a entrar en más profundidades. Podría poner una lista de combinaciones de teclas que funcionan en este modo, pero no quiero alargar innecesariamente el artículo, porque el objetivo son las dos conchas más potentes. Pero siempre tenéis a mano la ayuda para conocer los atajos.

Term

Básicamente term funciona como el anterior shell sin embargo, tiene características que lo hacen más potente. Pero antes de seguir tienes que recordar que la tecla que le sirve como escape es C-c.

¿Qué es capaz de hacer éste terminal? Pues cualquier cosa que pueda hacer un terminal... el resumen es que un buffer de nuestro Emacs se ha convertido en un terminal completo, como puede ser xterm o KiTTY o Terminator o cualquier otro al uso.

Captura-pantalla-meta-emacs.png

Podemos ver en la imagen anterior, cómo incluso podemos ejecutar una sesión de Emacs dentro de un terminal dentro de Emacs, sólo tenemos que recordarle que funcione en modo texto con el comando emacs -nw y funciona sin que se produzca una anomalía dentro del espacio-tiempo, ni nada. Eso sí, para enviarle combinaciones de teclas tenemos que escaparlas y el comando para salir, será por tanto C-c C-x C-c C-c. O por poner otro ejemplo, que no necesitará escapar los comandos, podemos ejecutar vim dentro de Emacs:

Captura-pantalla-emacs-vim.png

En este caso, como digo, puesto que los comandos de vim no coinciden con las combinaciones de Emacs no nos hará falta escaparlos para que se ejecuten.

Hablando de estas cosas de escapar comandos, en el terminal tenemos que tener en cuenta el modo de trabajo que tengamos establecido. Podemos estar en line mode o en char mode. Si queréis apreciar la diferencia sólo tenéis que hacer pruebas en el mismo terminal. Con C-c C-j se entra en term-line-mode y con C-c C-k entramos en term-char-mode. Algunos de los comandos sólo funcionan cuando estamos en modo char, como C-c C-c que envía al programa que esté ejecutando el terminal un C-c, o C-c CHAR que sería el equivalente de enviar en el Emacs normal un comando C-x CHAR.

Además, el terminal también nos permite realizar conexiones remotas a través de ssh o telnet. Y ya para rematar, también podemos utilizar el terminal a través de un puerto serie con M-x serial-term y comunicarnos con cualquier dispositivo que tengamos conectado a un puerto serie. Nos preguntará por el nombre del puerto y por la velocidad. Además podemos configurar el puerto serie pinchando en 8N1 en la línea de modo, lo que significa que cada byte consiste en 8 bits, sin paridad (No parity) y un stopbit. Si la velocidad no está bien configurada, seguramente sólo obtendrás basura del puerto, así que primero mirad bien el manual de instrucciones de lo que quiera que conectéis... en mi caso probé en su día con un par de lectoras de marcas y me permitió cargar configuración e incluso leer formularios. Un lujo al alcance de todos.

Eshell

Pues aquí viene la joya de la corona de los terminales para Emacs. eshell está escrito enteramente en elisp. ¿Qué nos proporciona? pues toda la potencia de Emacs en el terminal. En el caso anterior, debíamos ejecutar term indicando qué concha llamar (bash, zsh, fish...), el buffer se convertía en un intermediario entre ese comando y nosotros, por lo que estamos limitados a hacer las cosas que hagamos en un terminal normal. O dicho de otro modo, hemos cambiado una ventana de xterm por un buffer de Emacs ─que ya está bien para muchas cosas─.

Pero para apreciar la diferencia, abre eshell con el comando M-x eshell y teclea cualquier comando elisp o de Emacs y mira a ver qué hace... por ejemplo: prueba a escribir (+ 2 3) y te devolverá 5. Pero mucho más interesante que eso: estás navegando por los directorios y te gustaría abrir un fichero... tecleas find-file nombre-fichero.ext y ¡eh! lo abre en un buffer... dired, magit, cualquier comando responderá como se espera.

Funciona con todos los comandos que se invocan desde M-x. Incluso puede llamar, si lo necesitas a un terminal ansi con el comando ansi-term bash si fuera el caso. Con los comandos más gráficos ocurre como con shell no funcionan algunos comandos más visuales, no podemos tener vim corriendo en él, por ejemplo, pero tampoco es tan grave, dado que ampliamos el terminal con la potencia de los comandos de Emacs, y si bien git log no terminará de verse bien y funcionar correctamente, puedes utilizar magit-log para abrir un buffer de magit.

También, por supuesto, podemos ejecutar varios comandos separándolos con ; o en segundo plano terminándolos con &, como en otros terminales.

Comandos internos

Eshell define algunos comandos internos. En algunos casos para sustituir a comandos externos del sistema operativo. Por ejemplo, si estás ejecutando eshell, prueba el siguiente comando:

~ $ which ls
eshell/ls is a compiled Lisp function.

y sin embargo, podemos seguir accediendo al comando del sistema:

~ $ which *ls
/usr/bin/ls

Es decir, si queremos utilizar el comando correspondiente del sistema, debemos ponerle un * delante. No voy a hacer una lista exhaustiva de todos los comando, pero por nombrar algunos tenemos: alias, clear, date, diff, grep, info, kill, make ...

Para nuestros scripts también cuenta con algunas variables internas que podemos utilizar:

$+
Directorio de trabajo actual.
$-
Directorio de trabajo anterior.
$_
El último argumento del último comando.
$$
El resultado del último comando interno, si el comando es externo contendrá t o nil.
$?
Código de salida del último comando que puede ser 0 o 1.

Además de las habituales variables $* para pasar todos los argumentos o $1, $2... para pasar el argumento correspondiente.

Aliases

Eshell nos permite tener también alias para nuestros comandos. Es un poco distinto de cómo funcionan los alias en bash, pero no demasiado. Para eshell, los argumentos se deben definir explícitamente, es decir, en bash nos podemos encontrar algún alias definido del estilo de:

alias ls = "ls --color"
alias ll = "ls -l"

En eshell las definiciones serían más del estilo: alias ls 'ls --color $*', o alias ll 'ls -l $*'. Como se puede apreciar, hay que especificar el uso de argumentos.

Manejar explícitamente los argumentos nos permite crear alias más complejos como por ejemplo alias mcd 'mkdir $1 && cd $1', que lo que hará si escribimos mcd lo-que-sea es crear un directorio con nombre lo-que-sea y movernos a él.

Cuando utilizamos un alias en la línea de comandos, eshell lo guarda para siguientes sesiones en un fichero y, por supuesto, también podemos modificar nuestro fichero de aliases directamente. Dicho fichero de alias será aquél que apunte la variable eshell-aliases-file, que por defecto es ~/.emacs.d/eshell/alias, pero que, como todo en Emacs podemos configurarlo y cambiarlo.

Redirección

Los operadores de redirección son los habituales de un shell, es decir, funcionan los habituales > o >>. Esos operadores sirven para guardar la salida de un comando en un fichero (>) o para añadir la salida de un comando a un fichero (>>). ¿Y si queremos llevar la salida de un comando a un buffer? Pues para eso, eshell nos proporciona el operador >>>.

Por ejemplo: echo "Hola Mundo!" >>> #<buffer *scratch*> insertará la cadena «Hola Mundo!» en el buffer *scratch*. También servirá la forma más corta del nombre del buffer #<*scratch*>. Y también, atentos a las comillas, porque si no, es posible que la salida que nos encontremos sea ("Hola" "Mundo!"), que no era lo deseado ─o a lo mejor sí, que nunca se sabe lo que quiere un usuario de /Emacs/─.

Conclusión

Tienes a tu alcance toda la potencia del shell sin salir de Emacs. En ningún momento necesitas abandonar el editor para ejecutar lo que quiera que necesites hacer en el Sistema Operativo. Lo tienes a un golpe de tecla... y además con varias opciones para elegir la que mejor se adapte a eso que quieres, o necesitas, hacer.

El eshell es un lujo cuando te acostumbras a él, pero cuidado: luego intentarás abrir ficheros con find-file en tu terminal favorito, fuera de Emacs y tendrás que obligarte a repensar lo que quieres hacer sin los comandos de Emacs a mano. No puedes teclear dired en un xterm y navegar por los directorios y los ficheros... no, no hace falta que abras Emacs ─si no lo tienes abierto─ para lo que sea, basta recordar que ahí fuera las cosas son distintas...

Cuando me preguntan por qué me gusta tanto Emacs es fácil: por cosas como ésta. Flexibilidad, potencia, el poder adaptar la herramienta a mí en lugar de adaptarme yo a la herramienta. Porque puedo ejecutar, por ejemplo vim, en un buffer y trabajar como lo haría desde un terminal normal. Y además hacerlo tanto en modo gráfico como en modo texto, de la misma manera y con la misma efectividad.

Categoría: emacs shell

Comentarios

Debido a algunos ataques mailintencionados a través de la herramienta de comentarios, he decidido no proporcionar dicha opción en el Blog. Si alguien quiere comentar algo, me puede encontrar en Mastodon y en Diaspora con el nick de Notxor.

También se ha abierto un grupo en Telegram, para usuarios de esa red.

Disculpen las molestias.