Shell scripting

Alguna vez cuando creamos un script tenemos la curiosidad (o a veces necesidad) de hacer que nuestro programa actue al presionar una tecla (alfanumerica, teclas de funcion, teclas especiales) sin que el usuario tenga que presionar despues ENTER.

En este articulo se describira una tecnica que hara que cuando el usuario presione una tecla, nuestro script ejecutara la siguiente accion dentro del script. Y cuando se menciona "una tecla", aparte de las teclas alfanumericas, tambien actuara con las teclas de funcion (F1, F2, ...).

El codigo de ejemplo que se utilizara fue probado en korn shell. Puede ser que funcione en algun otro shell sin modificaciones.

Basicamente, el truco esta en utilizar los comandos stty y dd.

  • stty. Con el cambiaremos el numero minimo de caracteres que se utilizaran en un read. Todas las opciones del comando aqui.
  • dd. Este comando generalmente se utiliza para copiar o convertir archivos. Tambien para copiar imagenes enteras a algun drive. Aqui lo utilizaremos para leer un caracter de la entrada estandar. Mas informacion del comando aqui.

Esta es la parte principal del script. Lo unico que hace es entrar en un ciclo hasta que el usuario presione la letra q (minuscula).

# programa principal
typeset tecla=""
while [ "$tecla" != "q" ]
do
tecla=`leerTecla`
echo "tecla = '$tecla'"
done

A la variable tecla se le asigna el valor que devuelve la funcion leerTecla. Esa es la encargada de leer las teclas que presiona el usuario.

function leerTecla {
  typeset final
  typeset t2
  typeset t3
  typeset sttyAnt=`stty -g`
  stty -icanon -echo min 1 time 0 -isig
  final=`dd bs=1 count=1 2>/dev/null`
  final=`teclaEspecial "$final"`
  if [ "$final" = "ESC" ]; then
    t2=`dd bs=1 count=1 2>/dev/null`
    if [ "$t2" = "[" ]; then
      t3=`dd bs=1 count=1 2>/dev/null`
      final="$final[$t3" # ESC[ + algo
    else
      final="$final$t2" # ESC + algo
    fi
  fi
  stty $sttyAnt
  echo "$final"
}

Como vamos a cambiar la configuracion de stty, dentro de esta funcion se guarda primero la configuracion actual de stty en la variable sttyAnt. Despues, con el comando dd leemos un caracter de la entrada estandar y lo guardamos en la variable final. Despues enviamos esta variable como parametro a la funcion teclaEspecial que se encarga de regresar una cadena con el nombre de la tecla que se presiono (ENTER, ESC, CTRL_E, etc.).

En algunos casos, cuando presionamos alguna tecla (como las teclas de funcion), al teclado se envian varios caracteres. Comunmente estas teclas "compuestas" envian primero el caracter ESC seguido de otro caracter. Por esto, dentro de la funcion se verifica si la tecla que se leyo fue ESC. Si fue asi entonces leemos otra tecla. Si la siguiente tecla que se leyo no es corchete, entonces simplemente concatenamos ESC mas la tecla que se leyo. Si fuera un corchete, entonces tendriamos que leer otro caracter y lo concatenamos a ESC[.

Al final de la funcion se vuelven a los valores originales que habiamos guardado con el comando stty $sttyAnt. Y por ultimo regresamos la cadena que indica la(s) tecla(s) que leimos.

La funcion teclaEspecial se encarga de regresar el "nombre" de la tecla que se presiono.

function teclaEspecial {
  if [[ -z "$1" ]]; then
    echo "ENTER"
  elif [[ -z $(echo "$1"|tr -d '\004') ]]; then
    echo "CTRL_D"
  elif [[ -z $(echo "$1"|tr -d '\005') ]]; then
    echo "CTRL_E"
  elif [[ -z $(echo "$1"|tr -d '\011') ]]; then
    echo "TAB"
  elif [[ -z $(echo "$1"|tr -d '\017') ]]; then
    echo "CTRL_O"
  elif [[ -z $(echo "$1"|tr -d '\033') ]]; then
    echo "ESC"
  elif [[ -z $(echo "$1"|tr -d '\177') ]]; then
    echo "RETROCESO"
  else
    echo "$1"
  fi
}

El parametro -z dentro del IF devuelve verdadero si la cadena esta vacia. Asi, en el primer IF, verificamos si el parametro esta vacio. De ser asi, suponemos que el usuario presiono ENTER.

En los demas IFs, lo que se hace es un echo del parametro que recibio la funcion y eliminamos (con el comando tr -d) ciertos caracteres. Por ejemplo, para saber si se presiono ESC, hacemos

elif [[ -z $(echo "$1"|tr -d '\033') ]]; then

El codigo de la tecla ESC es 27 (o \033 en el sistema octal). Asi, despues de eliminar el caracter \033, si la cadena esta vacia, significa que se presiono ESC.

En el ejemplo solo se muestran algunas teclas especiales. Agregando mas condiciones podremos hacer que se procesen las que podamos necesitar.