Leer y escribir en la terminal.

En el capítulo 3, aprendió que cuando se llama a un programa desde el incitador de comandos, la shell solicita la conexión de los flujos de entrada y de salida estándar con su programa. Debería ser capaz de interactuar con el usuario usando simplemente las rutinas getchar y printf para leer y escribir dichos flujos predeterminados.

En la siguiente sección de ejemplo, intentará reescribir las rutinas del menú en C, usando únicamente las dos rutinas, en un programa denominado menu1.c.

Comenzamos con las rutinas de menú en C

  1. Comenzamos con las siguientes líneas, que definen la matriz que se ha de usar como un menú, y realiza un prototipo de la función getchoice:menu1_parte1
  2. La función principal llama a getchoice con el menu de ejemplo, menu:menu1_parte2
  3. Ahora, para el código importante, la función que muestra el menú y que además lee la entrada del usuario:menu1_parte3

Cómo funciona

getchoice muestra la presentación del programa greet y el menú choices y le pide al usuario que escoja el primer carácter.

como_parte1

Después, el programa hace un bucle hasta que getchar envía un carácter que se corresponde con la primera letra de una de las entradas de la matriz option.

Cuando compilemos el programa, descubriremos que no se comporta según lo esperado. A continuación presentamos parte del diálogo de la terminal para demostrar el problema:

menu1_salidaAquí, el usuario debe presionar A-Intro-Q-Intro y así continuamente para realizar las elecciones. Parece que hay, como mínimo, dos problemas: El más serio es que estamos obteniendo Incorrect choice después de cada elección correcta. Además, es necesario presionar Intro (o la tecla Retorno) antes de que el programa lea la entrada.

Modos canónicos versus modos no canónicos

Los dos problemas están estrechamente relacionados. Por defecto, un programa no puede disponer de la entrada de terminal hasta que el usuario presione Intro o Return. En la mayoría de los casos,  es una ventaja porque le permite al usuario corregir los errores de escritura usando las opciones del teclado. Cuando el usuario esté contento con lo que ve en pantalla presionará Intro para que el programa pueda disponer así de la entrada.

Este comportamiento se denomina modo canónico o estándar. Toda las entrada se procesa en base a las líneas. Hasta que se completa la línea de entrada (normalmente cuando el usuario pulsa Intro), el terminal gestiona todas las opciones tecleadas, incluyendo la tecla de retroceso, y la aplicación no leerá ningún carácter.

Lo contrario es el modo no canónico, con la cual la aplicación tiene más control sobre el procesamiento de caracteres para la entrada.

El manipulador de terminal de Linux convierte los caracteres de interrupción en señales (deteniendo, por ejemplo, su programa cuando pulsamos CTRL-C) y puede realizar el proceso de retroceso o eliminación en su nombre y no hará falte implementarlo cada vez que escriba un programa.

En el programa anterior, Linux está guardando las entradas hasta que el usuario presiona Intro, transmitiendo a la vez el carácter elegido como el Intro posterior al programa. Por lo que cada vez que introducimos una elección del menú, el programa llama a getchar, procesa el carácter, y después llama de nuevo a getchar, que devuelve inmediatamente el carácter Intro.

nota_cap5_1Podemos corregir esta deficiencia de la rutina del menú ignorando el carácter de nueva línea mediante un código del tipo:

modo_canonico_1Así queda resuelto el problema viendo la salida resultante:

canonico_salidaEl segundo problema que es tener que pulsar la tecla Intro. Más tarde veremos una solución más elegante para la nueva línea.

Gestión de las salidas redireccionadas

Es muy común en los programas Linux, incluso en los que son interactivos, que se redireccione su salida o su entrada, hacia archivos o programas. Observemos como reacciona nuestro programa cuando redireccionamos la salida hacia un archivo:

menu1_salida2Parece todo correcto porque la salida se ha enviado a un archivo en vez de a la terminal. Sin embargo, no siempre querremos que sea así , posiblemente queramos separar los mensajes para el ordenador que sí que pueden ver los usuarios, de aquellas salidas que desea redireccionar sin peligro.

Si un descriptor de archivo de bajo nivel está asociado con una terminal significa que la salida estándar ha sido redireccionada. La llamada al sistema isatty lo hace. Sólo tiene que transmitir un descriptor de archivos válido y comprobar si está conectada con un terminal.

salidas_1gestion_2Estamos usando en este programa los flujos de archivo, pero isatty opera únicamente con los descriptores de archivos. Para proporcionar la conversión necesaria, tiene que combinar la llamada isatty con la rutina fileno (que estudiamos en el capítulo 3).

¿Que pasa si redireccionamos a stdout? Salirse no es una buena elección, porque el usuario no tiene modo alguno de saber por que falló el programa. Mostrando un mensaje en stdout no funcionaría ya que debería haberse redireccionado antes fuera de la terminal. Una opción es escribir hacia el flujo de error estándar, stderr, que no ha sido redireccionado por el comando < file de la shell.

Comprobar el redireccionamiento de las salidas

Vamos a utilizar el programa menu1.c que fue creado en la sección de ejemplo anterior, para realizar los siguientes cambios en las inclusiones del archivo de cabecera y en la función main. Le pondremos al siguiente archivo el nombre menu2.c.

menu2Observe ahora la siguiente salida:

Cómo funciona

La nueva sección de código usa la función isatty para comprobar si la salida estándar está conectada con una terminal y si no lo está detiene la ejecución. Es la misma comprobación que usa la shell para decidir si ofrecer mensajes al operador. Es posible, y común, redireccionar tanto stdout como stderr fuera de la terminal. Puede dirigir el flujo de error a otro archivo diferente:

menu2_parte2O también combinar los flujos de salida en un único archivo:

menu2_parte3comofunciona_notaHablar con el terminal

Para evitar que las partes de su programa que interactuan con el usuario sean redireccionada, pero, permitiendolo a otras entradas y salidas, tenemos que separar la interacción stdout y stderr. Esto lo podemos conseguir escribiendo directamente en el terminal. Como Linux es de base un sistema multiusuario, a menudo con muchas terminales conectadas ya sea directamente o a través de una red, ¿como podemos escoger la terminal correcta?

Afortunadamente, Linux y UNIX facilitan las cosas proporcionando un dispositivo especial, /dev/tty, que muestra siempre el terminal actual, o la sesión de registro. Como para Linux todos son archivos, puede usar las operaciones de archivo normales para leer y escribir a /dev/tty.

El siguiente ejemplo vamos a modificar el programa de elección de manera que pueda transmitir parámetros a la rutina getchoice, para proporcionar un mejor control sobre la salida. Se trata de menu3.c.

Uso de /dev/tty

Abra menu2.c, modifique y añade el código por el siguiente que hemos remarcado, de manera que la entrada y la salida provenga de y se envíen a /dev/tty:

uso_dev_ttyAhora, al ejecutar el programa con la salida redireccionada, podrá seguir viendo los mensajes y la salida normal del programa (indicando las opciones elegidas)  será redireccionada a un archivo que podrá observar más tarde.

menu3_salidaatras

Deja un comentario

Este sitio utiliza Akismet para reducir el spam. Conoce cómo se procesan los datos de tus comentarios.