La instalación silenciosa y despliegue automático de librerías o componentes es un aspecto fundamental a tener en cuenta a la hora de montar un entorno fiable y productivo.

Es habitual que en proyectos de desarrollo software o en gestión de sistemas se tengan que integrar procesos estandarizados. Si estos procesos requieren la interacción del usuario, el tiempo que se tarda en completar la tarea aumenta considerablemente, al igual que el riesgo debido al factor humano. Por eso, herramientas como Expect facilitan el trabajo al hacer posible la automatización de algunas tareas como los scripts interactivos.

En este post veremos los aspectos básicos de esta librería para Linux y un par de ejemplos que nos ayuden a crear nuestros propios ficheros ejecutables.

¿Qué es Expect?

Expectes un programa que habla a otros programas a través de un script. Siguiendo este script, Expect sabe qué salida esperar del programa que ejecuta y responder en consecuencia y, si procede, es posible devolver el control al usuario o revocarlo.

Resulta muy útil para automatizar tareas repetitivas en sistemas, tanto de forma local como remota, que requieren introducir información manualmente, más aún cuando trabajamos con instrucciones y protocolos como SSH, SCP, SFTP, TELNET o RLOGIN.

De igual modo, los despliegues de aplicaciones o componentes en máquinas remotas pueden ser gestionados con scripts de usuarios (init.d), que controlan los permisos y el acceso externo. Con Expect, la secuencia de comandos a introducir es fácilmente automatizable y los scripts simples también son fáciles de integrar en nuestro workflow.

Para equipos que deban administrar sistemas, instalando siempre los mismos programas o modificando configuraciones a través de la terminal, también resulta una opción a tener en cuenta, ya que se puede desarrollar un script que lance todos esos procesos de una sola vez.

Instalando Expect

En el caso de las sistemas Linux esta herramienta puede venir instalada por defecto. Si no es nuestro caso y nuestra distribución es Debian o Ubuntu, podemos usar ‘apt-get’:

$ sudo apt-get install expect

Para entornos como Fedora o CentOs, la instalación se realiza a través de ‘yum’. En el caso de los sistemas Mac, se puede instalar del mismo modo a través de HomeBrew:

$ sudo yum install expect

En Windows la instalación es un poco más laboriosa. Debemos descargarnos las librerías Expect desde sourceforge y los binarios tcl desde ActiveState. Tras instalar las librerías se deberá compilar el código y referenciar en el path los archivos generados para poder utilizar Expect con normalidad. En los archivos descargados se puede encontrar información detallada acerca de cómo instalar la herramienta.

En este punto ya tenemos instalada la nueva herramienta y lista para funcionar.

Ejecutando Expect

En el apartado anterior hemos comentado las formas de instalar Expect en función del sistema operativo. En este caso vamos a utilizar Ubuntu, ya que es de los más sencillos tanto para instalarlo como para crear nuestros scripts.

Podemos arrancar abriendo la consola de Expect con este comando:

$ expect

Desde aquí podemos probar los comandos más comunes y hacer algunas pruebas base:

Para comprender mejor cómo se procesan las respuestas de la consola, debemos ver el comportamiento de las variables expect_out (0, string) y expect_out (buffer).

Si tenemos este script:

 expect "Hola\n"
 send "Has escrito <$expect_out (buffer)>"
 send "Pero solo esperaba <$expect_out(O,string)>"

Al lanzarlo se nos permitirá introducir el texto que queramos. Supongamos que introducimos esto:

 Buenas!
 Hola
 Has escrito <Buenas!
 Hola>
 Pero solo esperaba <Hola>

Uso de Expect a través de scripts Ficheros .exp

Por convención, Expect utiliza la extensión .exp en sus scripts, pero la llamada que ejecuta los scripts le es indiferente la extensión. Se podría indicar .sh o directamente ninguna. Únicamente se debe indicar al inicio del fichero *#!/usr/bin/expect *para que se interprete correctamente.

Expect utiliza TCL (Tool Command Languaje). Esto significa que utiliza las instrucciones más comunes en scripting, como son if, for o break, evalúa expresiones y otras características como recursividad o declaración de procedimientos y subrutinas. Los comandos más importantes, sobre los que basamos los siguientes ejemplos, son spawn, expect y send.

spawn ftp ftp.host.net
spawn ssh  usuario@host
spawn sh install_all.sh
spawn /bin/sh
expect "\\$ "
send "ls -la\r"

Para ver la estructura básica de un script .exp, aquí tenemos un ejemplo de automatización de un comando scp:

#/bin/sh
scp /ruta/origen usuario@host:/ruta/destino/

Si ejecutamos el script anterior como sh convencional, el comando scp nos irá solicitando las contraseñas de la máquina host.

Con Expect hacemos la llamada lanzando scp de forma independiente y procesando la entrada estándar de consola. Cuando se detecte la cadena ‘password:’, se introducirá lo que le indiquemos de forma automática.

#!/usr/bin/expect -f
 set filename [lindex $argv 0]
 set timeout -1
 spawn scp $filename user@host:/home/user/
 set pass "password" 
 expect {
      password: {send "$pass\r" ; exp_continue}
      eof exit
 }

En la primera línea #!/usr/bin/expect -f indicamos que el script debe ejecutarse con Expect y que los comandos vendrán introducidos por fichero .exp_continue e indica al script que no evalúe más expresiones y continúe con la siguiente instrucción.

Cada vez que se llama a esta acción, el timeout de espera (por defecto cada 10 segundos) para cada respuesta de consola se reinicia. Esto se puede evitar indicando exp_continue –continue_timer.

Los ficheros de Expect (.exp) pueden ejecutarse de este modo siempre que tengan el permiso adecuado de ejecución:

$ chmod +x script01.exp
$ expect script01.exp

La salida correspondiente al script anterior (sin intervención alguna del usuario) es la siguiente:

Login remoto con Expect

En el próximo ejemplo vamos a escribir y comentar un script más completo que actúe en una máquina remota. Levantaremos una sesión ssh incluyendo logs y control de errores.

#!/usr/bin/expect -f 
 # Uso sshlogin.exp <ssh user> <ssh host> <ssh password>
 #Seteamos timeout personalizado y procesamos los argumentos.
 set timeout 20
 set ip [lindex $argv 0]
 set user [lindex $argv 1]
 set password [lindex $argv 2]
 #regex con los caracteres mas comunes en las consolas (habitualmente '

Si queremos evitar indicar StrictHostKeyChecking no podemos controlar la comprobación de las keys:

spawn ssh "$user@$ip";
expect "yes/no" {
           send "yes\r"
           expect "*assword" { send "$password\r" }                 }
"*assword" { send "$password" }
}

Se habrá notado un agujero de seguridad bastante evidente en estos scripts. Como son ejemplos introductorios, no hemos tenido en cuenta el hecho de escribir en plano o por parámetro una contraseña para una conexión remota, pero cuando usemos Expect en otros entornos la forma correcta de proceder es mediante encriptaciones (por ejemplo, con openssl) en ficheros protegidos o SSH keys.

Autoexpect

Hemos visto varios scripts que muestran los aspectos básicos de Expect y las funciones de sus comandos principales.

Para llevar esta librería (o vaguear) al siguiente nivel, debemos comentar la herramienta Autoexpect que, al ejecutarse, observa las instrucciones que realiza el usuario y genera un script de Expect de forma autónoma.

Autoexpect viene unido a Expect, por lo que no sería necesario realizar ninguna instalación adicional. El código generado es algo tosco, pero puede afinarse fácilmente:

$ autoexpect

A partir de este punto, Autoexpect irá incluyendo en el fichero script.exp los comandos que se introduzcan por consola en lenguaje expect. De este modo, cuando se complete el trabajo y se cierre la terminal (o el comando exit), el fichero resultante será completamente viable para ejecutarlo como un .exp normal.

Se debe tener en cuenta que también se guarda la salida completa de la consola, por lo que si esa salida contiene datos variables (como fechas en ssh o date), el script no va a funcionar dado que no coincide exactamente la salida de la consola.

Para evitar esto, Autoexpect puede ejecutar en modo ‘promt’, mediante el cual únicamente guarda la última línea de la salida de consola.

En la siguiente imagen se aprecia cómo crear un script sencillo con Autoexpect:

Una vez se ha creado, se ejecuta como .exp:

Conclusión: ¿en qué puntos de mi ciclo de desarrollo puede utilizarse Expect?

Expect es una herramienta poco conocida, pero potente. Hemos visto que es capaz de simplificar procesos que requieren la intervención del usuario para pasar a ejecutarlos de forma autónoma con un lenguaje muy intuitivo y aplicable, como suite de apoyo para administración de sistemas o como ayuda para mejorar el tiempo y la calidad del ciclo de software.

Recalcar que a la hora de crear nuestros propios scripts, debemos tener muy en cuenta lo relacionado con la seguridad y conocer muy bien el proceso que queremos automatizar para controlar los posibles fallos y crear un código robusto.

No debemos olvidar que Expect se ciñe exclusivamente a lo que aparezca en su fichero, por lo que es muy importante el control de errores, timeouts o salidas alternativas y procesar dichas salidas de cada comando de forma minuciosa e independiente para evitar sustos innecesarios. Nadie quiere que se ejecute un rm si el anterior cd ha fallado.

Es habitual que en la fase de despliegue de servicios o aplicaciones haya que instalar o ejecutar comandos sobre consola en sistemas externos. Expect podría ser la herramienta adecuada para ello si estas instrucciones deben ejecutarse de manera interactiva.

El hecho de que en nuestros entornos locales o de cliente los despliegues de la aplicación estén controlados por scripts estandarizados internos, no debe impedir que se complete el ciclo de desarrollo continuo, ya que utilizando Expect con herramientas de integración continua (como Jenkins o goCD…) se pueden automatizar estos procesos desde máquinas fijas, virtuales o dockers.

En mi experiencia en el uso de Expect, los resultados tras aplicar esta herramienta sobre despliegues en entornos externos han sido tremendamente positivos. Eliminando el factor humano y con una correcta configuración, el tiempo de despliegue de una aplicación web de reinicio del servidor y el envío de ficheros de configuración se ha visto reducido de forma muy significativa, al igual que el riesgo de fallo.

Para finalizar, decir que Expect también está disponible en otros lenguajes como Java, c#, Perl, Scala o Python, entre otros.

Si queremos evitar indicar StrictHostKeyChecking no podemos controlar la comprobación de las keys:

--md-var-new-line---md-var-lower-than-br --md-var-slash---md-var-greater-than---md-var-new-line-spawn ssh &quot;--md-var-dollar-sign-user--md-var-backslash-@--md-var-dollar-sign-ip&quot;;--md-var-lower-than-br --md-var-slash---md-var-greater-than---md-var-new-line-expect &quot;yes--md-var-slash-no&quot; --md-var-opening-curly-brace---md-var-lower-than-br --md-var-slash---md-var-greater-than---md-var-new-line-           send &quot;yes--md-var-backslash-r&quot;--md-var-lower-than-br --md-var-slash---md-var-greater-than---md-var-new-line-           expect &quot;--md-var-asterisk-assword&quot; --md-var-opening-curly-brace- send &quot;--md-var-dollar-sign-password--md-var-backslash-r&quot; --md-var-closing-curly-brace-                 --md-var-closing-curly-brace---md-var-lower-than-br --md-var-slash---md-var-greater-than---md-var-new-line-&quot;--md-var-asterisk-assword&quot; --md-var-opening-curly-brace- send &quot;--md-var-dollar-sign-password&quot; --md-var-closing-curly-brace---md-var-lower-than-br --md-var-slash---md-var-greater-than---md-var-new-line---md-var-closing-curly-brace---md-var-lower-than-br --md-var-slash---md-var-greater-than---md-var-new-line-

Para finalizar, decir que Expect también está disponible en otros lenguajes como Java, c#, Perl, Scala o Python, entre otros.

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.

Suscríbete