lunes, 12 de noviembre de 2007

Capitulo 6: Entrada y Salida

Conceptos de Entrada y Salida
Comandos asociados con la salida
Pipes o tuberías
Otras utilidades de E/S en el Shell
More y less
Comandos útiles del less
stderr y otras redirecciones


Conceptos de Entrada y Salida

Es posible que ya hayas visto en algún sitio los terminos entrada y salida aplicados al mundo de la informática (quizás como I/O -input/output- o bien E/S -entrada/salida-).

En realidad es algo más simple de lo que pueda parecer, como vamos a ver mediante ejemplos sencillos. Cuando estamos dentro del sistema usando la terminal bash, generalmente tecleamos comandos para que sean ejecutados. El hecho de que estemos simplemente presionando el teclado implica que estamos entrando datos al sistema mediante el teclado. Cuando en ejemplos anteriores hemos usado el comando ls , ha aparecido en pantalla un listado de archivos y directorios, claramente el sistema ha producido salida de datos mediante el monitor.

Hemos puesto dos ejemplos, el teclado como vía para entrar datos al sistema (o bien a un programa, como ya vimos con vim), y el monitor como vía de salida de los datos. Pero no son, ni mucho menos, las únicas posibilidades. Los distintos puertos físicos del ordenador pueden ser vía de entrada o de salida de datos, igualmente con la tarjeta de sonido, módem, tarjeta de red...

Aunque lo que nos interesa en esta sección, es manejar la entrada y la salida de datos de y entre ciertos programas. Preguntas como... ¿y qué puedo hacer con el listado que me da el comando ls además de mostrarlo por pantalla? o ¿qué otras formas de pasar datos y opciones a los programas existen además del teclado? A todo esto y a algunos detalles más daremos respuesta en este capítulo.

Los sistemas UNIX (y por ello también los Linux), son especialmente conocidos por su facilidad y eficiencia para pasar datos entre programas y programas o entre programas y dispositivos, siendo esta una de sus múltiples ventajas.

Otros conceptos importantes dentro de estos son los de entrada estándar y salida estándar (stdin y stdout respectivamente). Por defecto, stdin apunta al teclado (los datos, se toman generalmente por defecto del teclado), y stdout apunta a la pantalla (por defecto los programas suelen enviar la salida por pantalla).


Comandos asociados con la Salida

Son, principalmente echo, cat.

En primer lugar, echemos un vistazo al comando echo. Este comando nos permite "imprimir" cosas en la pantalla.

Veamos algunos ejemplos:

usuario@maquina ~ $ echo "Hola, usuario de Linux"
Hola, usuario de Linux

Como ves, por defecto el comando echo recibe las cadenas de texto entre comillas (simples o dobles), y saca por defecto a la pantalla lo que le pasamos como argumento; aunque puede enviar la salida a otros sitios, como podrás comprobar más adelante.

Adelantándonos un poco, el shell puede hacer operaciones aritméticas básicas, y podemos aprovecharlas con el comando echo:

usuario@maquina ~ $ echo $[5*3]
15

Cambiemos de comando, vamos a aprender ahora el comando cat. Es un comando útil que será nuestro amigo a partir de ahora.

cat tiene asociada la entrada al teclado por defecto, y la salida a la pantalla. Asi que si lo llamamos sin argumentos, simplemente tecleando cat, tecleamos y pulsamos INTRO, volverá a imprimir en pantalla cada línea que nosotros tecleemos:

usuario@maquina ~ $ cat
Escribo esto
Escribo esto
Y escribo esto otro
Y escribo esto otro

Continuará hasta que le mandemos detenerse manteniendo pulsada la tecla CONTROL y presionando a la vez la tecla C. Puede parecernos poco útil el funcionamiento por defecto del comando cat. Pero observemos ahora el funcionamiento más común de este comando, que es mostrar el contenido de un fichero:

usuario@maquina ~ $ cat directorio/mifichero
Esto es el contenido del fichero
mifichero que se encuentra en ~/directorio.

Este es el uso más frecuente para el comando cat, tomar como entrada un fichero y sacar su contenido por la salida. Por defecto es por la pantalla, pero descubriremos después que puede enviar el contenido de un fichero por otras vías.

Como ya habrás imaginado, los ficheros de disco pueden ser objeto de entrada (leer su contenido) o de salida (escribir la salida de un determinado comando a un fichero). El comando siguiente, touch nos da la posibilidad de crear un fichero vacío con el nombre que nosotros le especifiquemos:

usuario@maquina ~ $ touch minuevofichero

Así crearemos un fichero nuevo vacío al que podremos posteriormente enviar la salida de nuestros comandos. Si listas el fichero con ls -l minuevofichero verás como efectivamente su tamaño es cero. No obstante, touch puede hacer alguna otra cosa, como explicaremos en secciones posteriores.

Otro comando útil es el comando grep. Este comando por defecto lee de stdin (la entrada estándar), y envía los datos a la salida estándar (stdout). El objetivo primordial del comando grep es, "devolver" las líneas que contienen la cadena de texto que le pasamos al principio. Por ejemplo:

$ grep hola
Esta frase no contiene la palabra, por lo tanto no la repetirá
Esta frase contiene la palabra hola, por lo que la repetirá
Esta frase contiene la palabra hola, por lo que la repetirá

Como ves, el comando grep ha identificado las líneas que contenian la palabra que le pasamos como primer argumento.


Pipes o tuberías

Hasta ahora hemos estado practicando de dónde toman los datos algunas de las utilidades más comunes, y hacia dónde envían su salida. Lo siguiente que tenemos que plantearnos sería... ¿no podríamos "conectar" de alguna forma la salida de un programa y hacer que otro programa lea de ella?

Efectivamente, podemos conseguir esto. El caracter | nos permite crear estas conexiones entre programas en el shell. Ejemplo:

$ cat mifichero | grep gato

Lo que este comando hará será lo siguiente: La primera parte antes de la barra vertical, conseguirá el contenido del fichero mifichero, y con la barra vertical, en lugar de sacarlo por la pantalla, lo enviará a la entrada estándar (stdin) de tal forma que el programa grep leerá desde stdin el contenido del fichero mifichero. El concepto del efecto que esto produce es fácil de entender: el comando que va detrás de la barra lee de la salida que dió el comando anterior. Es obvio que lo que obtendremos serán las líneas del fichero mifichero que contienen la palabra "gato" por la pantalla (stdout).

Como ves, no estamos pasando ninguna opción ni al comando cat ni al comando grep. Esto es porque el primero (cat) envía la salida por defecto a stdout, y grep, si recuerdas, lee por defecto de stdin (que al usar la barra vertical, hemos hecho que stdin sea la salida del primer comando y no las líneas que tecleemos a continuación como hicimos anteriormente). Pero los pipes con la barra vertical podremos emplearlos con otros programas que no tengan este comportamiento por defecto, esto es, que su salida no apunte por defecto a stdout (podría apuntar, por ejemplo, a un fichero) o que no lean por defecto de stdin. Podremos hacer que tengan este comportamiento pasándoles determinadas opciones si disponen de ellas. Es muy común la opción - (un solo guión) para hacer que un programa lea de stdin o escriba a stdout si tiene esta funcionalidad; seguro que te encuentras algun comando que tenga esta posibilidad más adelante.

Otros caracteres que nos permiten crear "pipes", son < y >. Generalmente, este par de caracter trabajan con un fichero a un lado y un comando a otro. Me explico; veamos primero el caracter <:

$ grep gato <>

Esta línea es equivalente a la del ejemplo anterior. El contenido del fichero que ponemos a la derecha del signo se va a la entrada estándar de la que lee el comando que ponemos a su izquierda, por lo tanto esta línea conseguiría exactamente el mismo efecto que la del ejemplo anterior.

Ahora el signo contrario. A la izquierda del signo > ponemos un comando, y a la derecha el nombre del fichero al que queremos que se escriba la salida del comando. Por ejemplo:

$ ls -la > listado

La lista del contenido del directorio actual que el comando ls sacaría normalmente por pantalla, en vez de eso se ha escrito en el fichero listado. Si el fichero no existía, se ha creado, y si existía y tenía contenido, ha sido sobreescrito, asi que tengamos cuidado.

El uso de los signos >> juntos, tiene una funcionalidad similar al uso de un signo solamente, y puede sernos útil muchas veces:

$ echo "Esta es otra línea" >> texto

Vemos en qué es similar a lo anterior; la línea "Esta es otra línea se ha escrito al fichero texto. Si el fichero no existía, ha sido creado, y la diferencia, si existía y tenía contenido, la línea se ha escrito al final del fichero, debajo del contenido que ya tenía. Este comportamiento nos puede ser muy útil de cara a ir añadiendo líneas a un archivo sin necesidad de tener que abrirlo con un editor.


Otras utilidades de E/S en el Shell

Aunque es adelantar un poquito, vamos a ver cómo la entrada estándar, la salida estándar y alguna cosa más "están" en el sistema de archivos.

La entrada estándar (stdin) está enlazada mediante el dispositivo (sobre lo que se hablará más adelante) /dev/stdin:

$ echo "hola" | cat /dev/stdin
hola

Con el comando echo, escribimos la palabra "hola" a la salida estándar, que con la barra vertical, se pasa a la entrada estándar, pero como le estamos pasando un argumento a cat, lee de /dev/stdin que... ¡sorpresa! ha resultado ser la entrada estándar. Si hubiesemos escrito el mismo comando quitando /dev/stdin, el resultado habría sido el mismo, puesto que si a cat no le pasamos ningún argumento, lee desde stdin. No te preocupes si no puedes comprender del todo lo que hace este ejemplo, simplemente tú asocia /dev/stdin con la entrada estándar que es accesible desde el sistema de ficheros.

La salida estándar está asociada a /dev/stdout:

$ cat fichero > /dev/stdout
--contenido de fichero--

Aquí, estamos enviando el contenido de fichero a /dev/stdout que, curiosamente, igual que en el caso anterior, ha resultado "conectar" con la salida estándar, esto es, la pantalla.

Por último, os presentaremos a nuestro basurero particular :-) , que es /dev/null. Nos puede ser útil para cuando queramos despreciar la salida de un comando, que ni salga por pantalla ni que se quede escrito en un fichero, simplemente que se pierda:

$ cat mimayorfichero > /dev/null

Una ventaja de /dev/null con respecto a los basureros físicos es que nunca se llena ;-) , podemos "arrojarle" toda salida que queramos despreciar.


more y less

more y less son dos comandos útiles a la hora de paginar. Paginar es ver un archivo o una salida larga por partes (o páginas), puesto que de otra forma sólo podríamos ver el final de esta salida larga. Pongamos por ejemplo que quisiésemos leer un archivo largo. Como ya sabes, esto podrías hacerlo con el editor vim, pero también podrías hacerlo con cat y la ayuda de estos dos comandos:

$ cat archivolargo | more
$ more <>
$ more archivolargo

Como ya sabemos, los dos primeros comandos son exactamente equivalentes. Pero el tercero tiene una pequeña diferencia con respecto a los anteriores. Si introducimos uno de los dos primeros con un archivo largo, entonces veremos que el programa more nos indica en la parte inferior "Más" o "More", y nada más; mientras que si introducimos el tercer comando, en la parte inferior de la terminal nos aparecerá "More (15%)", esto es, el porcentaje del fichero que estamos mostrando. La diferencia pues entre estos comandos es que en los dos primeros, more no conoce la longitud de los datos que vienen por la stdin, simplemente los procesa conforme el usuario se lo pide; mientras que en el tercer caso, el funcionamiento de more es conocer la longitud del fichero que se le pasa como primer argumento, y después paginarlo igual que hacía con los datos de stdin. Esto es importante; en UNIX y en Linux podemos trabajar con la stdin y stdout conforme vayamos necesitando sus datos, sin necesidad de saber cuál es su longitud o sin miedo de que estos vayan a perderse.

En lo que respecta al comando more, podemos seguir paginando hacia abajo con la barra espaciadora, hasta llegar al final del fichero o de los datos de entrada; o bien presionar la tecla q en cualquier momento para abandonar el programa.

El comando less es un "more" de GNU mejorado. Si recuerdas, con more sólo podíamos paginar hacia abajo, y bajar página por página. Con less podemos hacer algunas cosas más, como volver arriba o abajo, o bajar o subir línea por línea. Se puede usar igual que los ejemplos anteriores del comando more, con la diferencia de que una vez que estemos viendo la entrada paginada, además de poder bajar con la barra espaciadora, podemos subir y bajar con las flechas de dirección, o bien con las teclas de "avanzar página" y "retroceder página" de nuestro teclado. En el momento de salir, lo haremos igualmente presionando la tecla q.


Comandos útiles de less

Frente a more, less nos ofrece algunos comandos que nos ayudan a buscar cadenas y patrones dentro del buffer que less esté visualizando. Como ya es costumbre en este manual os proporcionamos una tabla con los comandos más utilizados en less, por supuesto hay muchos más, pero la idea no es reproducir la página del manual.

Para invocar a less podemos hacerlo con un pipe o directamente así:

$ less fichero1 fichero2 fichero3

Tabla 6.1. Órdenes más comunes de less

ComandoDescripciónEjemplo de uso si aplica
:n Examina el siguiente fichero en la lista de argumentos de less --
:p Examina el fichero anterior en la lista de argumentos de less --
:e fichero Examina el fichero pasado como argumento :e /etc/xml/docbook/catalog
:x N Examina el primer fichero de la lista si N no ha sido especificado, en caso contrario examina el fichero que esté en la posición N de la lista. :x 6
:d Elimina el fichero que se está examinando de la lista --
= o ^G o ^F Da información adicional sobre el fichero incluyendo el nombre, el número de línea el %... =
!comando Nos permite ejecutar el comando como si estuvieramos en una shell. !! ejecutará el último comando ejecutado. Si se incluye en el comando el símbolo % será reemplazado por el fichero que se está examinando en ese momento, y # por el ultimo fichero examinado. Si no se especifica un comando devolverá una shell que se tomará de la variable de entorno SHELL. !xmllint --valid --noout %
/patrón Se busca en el fichero la cadena especificada por patrón. Si no se especifica ningun patrón se utilizará el último patrón buscado. Si el primer caracter de patrón es un * se buscará el patrón en todos los ficheros de la lista. La búsqueda es siempre hacia posiciones posteriores al cursor, es decir, hacia adelante. /*gfdl
?patrón Idéntico en todo a / pero la búsqueda se hace hacia atrás. ?fdisk
s fichero Si el buffer que se está visualizando no es un fichero sino información leida de un pipe podemos guardar el buffer en fichero s ~/temp/log


Nota: Una letra precedida de ^ significa que hay que pulsar primero la tecla Ctrl de forma que ^G es lo mismo que decir: Ctrl-G

Nota: Es muy util ajustar la variable de entorno PAGER a less (export PAGER=/usr/bin/less) dado que así las páginas del manual se mostrarán con less y podrás utilizar sus funciones de búsqueda de patrones.


Recuerda que puedes utilizar more y less con otros comandos distintos que produzcan mucha salida, y usarlos para ver la salida detalladamente.

Otro pequeño truco, es que si estás en una terminal, puedes ver las líneas anteriores (que ya no se muestran en pantalla) manteniendo pulsada la tecla de las mayúsculas (no el bloqueo de mayúsculas) y a continuación presionando las teclas "avanzar página" y "retrocer página" de tu teclado para moverte por líneas anteriores de tu sesión en una terminal.


stderr y otras redirecciones

Hemos visto stdin y stdout, entrada y salida estándar, respectivamente. Sabemos que el siguiente ejemplo listará los contenidos del directorio actual y despreciará ese listado, enviándolo a /dev/null:

$ ls ./ > /dev/null
$ ls directorio_inexistente/ > /dev/null
ls: directorio_inexistente: No existe el fichero o el directorio.

En el primer caso, todo fue como esperábamos, la salida del listado fue simplemente despreciada. Pero... ¿qué ha pasado en el segundo caso? Con el signo > le decíamos que enviase la salida estándar, en este caso, a /dev/null. Y, como no te hemos mentido, la salida estándar fue a /dev/null. Lo que ha pasado es que el mensaje de error no ha ido a stdout; porque de haber sido así habría sido despreciado. ¿Dónde ha ido entonces el mensaje de error? Es sencillo, a stderr. La mayoría de los programas imprimen stderr por pantalla; aunque podrían (como hacen algunos), escribir los mensajes de error a un fichero, o por qué no, despreciarlos.

Algunos programas hacen estas cosas con stderr; cosas que ya hemos aprendido nosotros a hacer con stdout. Una solución posible sería que stderr fuese a stdout, y a partir de ahí, nosotros ya pudiésemos redirigir stdout como sabemos. Conseguir esto no es difícil, se hace con el operador >& :

$ ls directorio_inexistente/ > /dev/null 2>&1

Ahora sí será despreciada stderr; pero analicemos este comando por partes, y entendámoslo bien. Debemos saber que en él, "2" representa a stderr, y que "1" representa a stdout. Más cosas; este comando tiene dos partes. La primera es ls directorio_inexistente/ > /dev/null y la segunda es 2>&1. En las redirecciones de salida complejas, como es esta, el shell las interpreta "al revés", esto es, de derecha a izquierda. Primero interpreta lo que hemos dicho que es el segundo comando, y luego interpreta el primero. Lo que ocurre entonces es lo siguiente: primero, stderr va a stdout por el segundo comando que se procesa en primer lugar, y luego stdout (que ya "contiene" a stderr) se va a /dev/null. Entonces conseguimos el efecto deseado, despreciar tanto stdout como stderr; aunque podríamos haber hecho otras cosas con ellas, lo importante es que ambas "salidas" habrían ido al mismo sitio.

Pero puede ocurrirte (y de hecho te ocurrirá), que desees tratar por separado stdout y stderr. Si se entendió el ejemplo anterior esto no debería causar problemas. Vamos a poner, como caso práctico, que vamos a listar una serie de archivos o directorios; los que existan se escribirán sus detalles en un fichero, y los que no obtendremos el error en otro fichero distinto:

$ ls -l fichero1 fichero2 fichero3 > existentes 2>inexistentes
$ ls -l fichero1 fichero2 fichero3 1>existentes 2>inexistentes

Estos dos comandos son absolutamente equivalentes. Recordemos que este tipo de comandos son interpretados de derecha a izquierda. En el primero de ellos, al principio stderr se escribirá al fichero inexistentes, y después el resto (o sea, stdout) se escribirá al fichero existentes. En el segundo, primero 2 (stderr) se escribirá a inexistentes, y después 1 (stdout) se escribirá a existentes. Usando cualquiera de ellos, al final tendremos en el fichero existentes los detalles de los ficheros existentes y en el fichero inexistentes el error producido por los ficheros (o directorios) que no existen. Este ejemplo es bastante ilustrativo de cómo podemos tratar separadamente stderr y stdout.

No hay comentarios: