viernes, 4 de octubre de 2019

Definición y ejemplos de: Entrada estándar (stdin), Salida estándar (stdout), Error estándar (stderr), redirección y tuberías en Linux


Hola amigos del software libre.

En este artículo intentaré explicaros con ejemplos el concepto de: Salida estándar (stdin) o descriptor 0, Entrada estándar (stdout) o descriptor 1, Error estándar (stderr) o descriptor 2, en los sistemas tipo Unix y por ende en GNU/Linux.


En los sistemas operativos de tipo Unix, como es GNU/Linux, cuando un programa o comando escribe un resultado de algo en la pantalla, está usando algo llamado salida estándar, en inglés standard output o stdout, es la manera que tiene el programa de escribirle cosas al usuario. A este algo se le llama stream, flujo de datos o canal de datos.

Los datos que se le dan o entran a un programa o comando son recogidos en la entrada estándar, en inglés standard input o stdin.

Cuando un programa o comando escribe un mensaje de error en la pantalla, está usando el canal llamado error estándar, en inglés standard error o stderr, es la manera que tiene el programa de escribirle los mensajes de error al usuario en un canal diferente del canal de salida estándar stdout.

De manera que un programa siempre tomará los datos de entrada por el descriptor cero, enviará los resultados por el descriptor uno y mostrará los errores por el descriptor dos.


Por tanto y para abreviar, hablaremos de tres streams, flujos o canales estándar:

- La entrada estándar (stdin) es el canal o entrada de datos al programa por parte del usuario. Normalmente estos datos son ingresados mediante el teclado, aunque la entrada de datos también puede provenir de un archivo o del resultado de un programa o comando anterior. El descriptor de archivo asociado a stdin es el 0.

- La salida estándar (stdout) es el canal o salida de datos por un programa o comando después de su ejecución, mostrados en el terminal normalmente, para que puedan ser visualizados por el usuario. Aunque también es posible redireccionar la salida de estos resultados a un archivo o a otro programa o comando. El descriptor de archivo correspondiente a stdout es el 1.

- El error estándar (stderr) es el canal o salida de un mensaje de error por el fallo en la ejecución de un programa o comando, mostrado en el terminal de forma predeterminada, para que pueda ser visualizado por el usuario. Y como en los canales anteriores también es posible redireccionar esta salida de errores a un archivo o a otro programa o comando. El descriptor de archivo para stderr es el 2.

VAMOS A LOS EJEMPLOS:

Cuando queremos listar los archivos de un directorio con el comando "ls", a dicho comando le tenemos que dar una entrada de datos. En este ejemplo escribiremos la ruta "/home/$USER" como entrada de datos al comando "ls", por tanto estamos realizando una entrada estándar (stdin) por teclado de los caracteres "/home/$USER".
Abrimos un emulador de terminal de comandos y escribiremos:

$ ls /home/$USER


Una vez pulsamos "Enter", este flujo de datos de entrada es recogido por el comando "ls" en el descriptor 0. El intérprete de comandos (bash en GNU/Linux) traduce el comando y el flujo de datos y se lo entrega al kernel de Linux para que lo pueda entender y realizar las operaciones pertinentes.

El mismo kernel devuelve los resultados al intérprete (bash), el cual los traduce y muestra el flujo de datos en el terminal por el descriptor 1, para que los pueda entender el usuario. Por tanto realiza una salida estándar (stdout) y por defecto la realiza en el terminal por pantalla.


El resultado es la impresión del listado de los archivos del directorio especificado por pantalla en el terminal.

Acabamos de ver como funciona la entrada de datos estándar (stdin) y la salida de datos estándar (stdout).

Un programa también puede escribir en el canal de error estándar por el descriptor 2. El error estándar (stderr) está casi siempre conectado al terminal para que el usuario pueda leer el mensaje.

En el siguiente ejemplo os muestro una entrada estándar (stdin) que serán estos caracteres "/home/$USER" con una opción del comando errónea a propósito:

$ ls -2 /home/$USER


Cuando el intérprete de comandos percibe un error, el flujo de datos de salida se realiza en el canal de error estándar (stderr) por el descriptor 2, donde nos muestra el mensaje de error de forma predefinida por pantalla en el terminal.


Con un pequeño esquema de los tres canales de datos lo acabaréis de ver mas claro:


Supongo que os ha quedado claro cada uno de los tres canales estándar: stdin, stdout y stderr.

Vamos a seguir hablando ahora de los redireccionamientos de estos tres canales. Básicamente se trata de que cada uno de los tres canales anteriores pueden redirigirse hacia o desde archivos y hacia o desde otros comandos en lugar de ser escritos en el terminal o mostrados por pantalla.

El redireccionamiento de cada uno de estos canales pueden representarse con los siguientes símbolos:

Tipo de canal
Símbolo
standard input (stdin)
0<
standard output (stdout)
1>
standard error (stderr)
2>

Con ejemplos lo veréis muy claro:

REDIRECCIONAMIENTO DE STDIN

He preparado un archivo de texto llano con el nombre "ruta.txt" y en su interior contiene estos caracteres "/home/belinux". Os lo muestro a continuación con el comando "cat":

$ cat ruta.txt


Vamos a utilizar de ejemplo el mismo comando "ls", pero en lugar de dar los datos a la entrada estándar (stdin) por teclado, vamos a redireccionar el contenido del archivo "ruta.txt" a la entrada estándar (stdin) en el descriptor 0. Escribiremos en el emulador de terminal:

$ xargs ls -F --color=yes 0< ruta.txt


Como podéis observar me he ayudado del comando "xargs" el cual lo que hace es convertir los datos de entrada en el stdin (descriptor 0) en argumentos válidos para ser utilizados por el comando "ls". Si probáis sin el comando "xargs" veréis que el resultado no es el esperado, ya que los datos recogidos en el stdin por el comando "ls" no los trata como argumentos válidos. Esto no significa que debáis usar siempre el comando "xargs" conjuntamente con otros comandos cuando redireccionéis los datos de los archivos a la entrada estándar stdin. En el caso del comando "ls" y de algún otro comando mas si es necesario, pero no en todos.

Como véis utilizo el símbolo "0<" (descriptor 0), para redireccionar archivos a la entrada estándar stdin, también puede usarse el signo "<" a secas como escribo a continuación:

$ xargs ls -F --color=yes < ruta.txt



REDIRECCIONAMIENTO DE STDOUT

Continuemos con el mismo comando "ls", esta vez redireccionaremos el flujo de datos de la salida estándar stdout a un archivo. Escribimos en el emulador de terminal:

$ ls -l 1> listado.txt


Acabo de listar el contenido del directorio actual y no quiero que me entregue la salida estándar por pantalla, sino que me la guarde en un archivo con el nombre "listado.txt" que será creado. Por tanto, acabo de redireccionar el stdout a un archivo mediante el símbolo "1>" (descriptor 1). También puede usarse simplemente ">", como muestro a continuación:

$ ls -l > listado.txt


Podemos configurar la característica "noclobber" para evitar la sobrescritura de un archivo existente con la operación de redireccionamiento. En este caso, el redireccionamiento a un archivo existente fallará.
Os lo muestro con un ejemplo donde confirmaremos la característica "noclobber":


$ set -o noclobber

$ cat listado.txt > salidaValida.txt


Como podéis observar nos aparece una advertencia en la cual nos apercibe de la imposibilidad de sobrescribir el archivo existente.

Ahora bien, podemos pasar por alto la característica noclobber , colocando el signo vertical tras el operador de redirección. También se puede colocar el comando "noclobber" en un archivo de configuración shell (bash script), para generar una operación automática predeterminada.

En el siguiente ejemplo se configura la característica "noclobber" para la shell Bash y después se obliga a sobrescribir el archivo "salidaValida.txt":

$ set –o noclobber

$ cat listado.txt >| salidaValida.txt



Como podéis observar se salta la prohibición de sobrescritura impuesta con "noclobber" y sobrescribe el archivo existente.

Para poder desactivar "noclobber" otra vez y así poder sobrescribir todos los archivos existentes, tendremos que escribir en el emulador de terminal:

$ set +o noclobber


Ahora ya podremos volver a sobrescribir archivos existentes desde el canal de datos stdout.


REDIRECCIONAMIENTO DE STDOUT CON ADICIÓN

En el siguiente caso os mostraré que en el redireccionado podemos añadir datos a los archivos ya creados sin tener que ser sobrescritos. Esto es posible con el símbolo">>". Se añadirán los nuevos datos a continuación de los que ya existen en el interior del mismo archivo. En cambio con el símbolo ">" sobrescribe los datos almacenados en el archivo redireccionado.

Para añadir mas datos al archivo "listado.txt" sin eliminar el contenido ya existente escribiremos:

$ ls -l >> listado.txt



REDIRECCIONAMIENTO DE STDERR

Exactamente lo mismo para la salida de error estándar. Utilizaré el comando "ls", escribiré mal una opción y redirigiré la salida stderr a un archivo. El archivo se creará y se guardarán los mensajes de error en su interior.

$ ls -2 2> errores.txt


Aquí NO podemos ignorar el símbolo "2>" (receptor 2), de lo contrario sería confundido con la salida estándar stdout.

En el siguiente caso produciremos un error en el comando "ls" y redireccionaremos el stdout al archivo errores. ¿Que ocurrirá?:

$ ls -2 > errores.txt


Como podéis ver se nos muestra la salida de error estándar stderr por pantalla y nos crea el archivo "errores.txt", pero no nos guarda nada en su interior, porque el comando no da ninguna solución por tanto no da ningún resultado en la salida estándar stdout, la cual hemos redireccionado al archivo "errores.txt".


REDIRECCIONAMIENTO DE STDERR A /dev/null

Podemos redireccionar los datos del canal de salida stderr hacia "/dev/null". Pero, ¿que es "/dev/null"?

En los sistemas operativos tipo Unix, "/dev/null" o null device (‘periférico nulo’) es un archivo especial que descarta toda la información que se escribe en o se redirige hacia él. A su vez, no proporciona ningún dato a cualquier proceso que intente leer de él, devolviendo simplemente un EOF (End Of File) o fin de fichero.

Generalmente se usa en shell scripts para deshacerse de la salida de un flujo de datos de un proceso o como un fichero vacío que actúa como entrada de un flujo de datos de un proceso.

La forma más comúnmente utilizada es mediante la redirección, ya que /dev/null es un archivo especial y no un directorio; por lo tanto, no se pueden mover (mv) ni copiar (cp) ficheros en su interior.

También se lo conoce como un agujero negro, ya que todos los datos dirigidos a ese archivo desaparecen.

Por tanto redireccionar un flujo de datos a "/dev/null" es querer eliminarlos, y no recuperarlos ni visualizarlos. Hay veces que no queremos saber nada de los mensajes de error que se pueden generar y lo mas sensato es enviarlos a "/dev/null".

Un ejemplo sería el siguiente:

$ find -name "*" -print 2> /dev/null



REDIRECCIONAMIENTO DE STDERR CON ADICIÓN

En stderr también podemos redirigir su salida a un archivo ya existente sin perder la información que contenía dentro, por tanto podemos añadir información de la misma forma que lo hemos hecho en la salida estándar stdout:

$ ls -7 2>> errores.txt




También podemos realizar combinaciones de las dos salidas estándar. Por ejemplo, si queremos enviar los resultados de la ejecución del comando a stdout y si suceden errores durante la ejecución del comando, enviarlos de la salida stderr a un archivo de errores:

$ ls -l > listado.txt 2> errores.txt


En el archivo "listado.txt" se vuelcan los datos del stdout y el archivo "errores.txt" se crea, pero está vacío porque no ha sucedido ningún error durante la ejecución del comando.

Otra solución que se puede adoptar es el enviar stdout y stderr en el mismo archivo:

$ find ~/Tips -name "*" -print 2> mismoarchivo.txt 1>&2


Utilizamos el comando "find" para que nos busque en la ruta especificada todos los nombres de archivos contenidos en ella, los resultados stderr (descriptor 2) los enviamos al archivo "mismoarchivo.txt" y también enviamos los resultados del canal stdout (descriptor 1) al archivo donde se han enviado los resultados del canal stderr. Se puede escribir con esta sintaxis "1>&2", para no tener que volver a escribir el nombre del archivo.

La misma operación puede escribirse con el orden inverso de redirección y no afecta al resultado:

$ find ~/Tips -name "*" -print 1> mismoarchivo.txt 2>&1


Enviamos primero los resultados del canal stdout (descriptor 1) a un archivo y luego enviamos los resultados del canal sterr (descriptor 2) al mismo archivo.


También podemos combinar el redireccionamiento de la entrada de datos del canal stdin con la salida de datos del canal stdout, como ejemplo utilizaré el comando    :

$ cat < file1.txt > file5.txt


En este caso direccionamos el contenido del archivo "file1.txt" al canal stdin y el resultado del comando cat es dirigido del canal stdout al archivo "file5.txt".

Este ejemplo es un poco absurdo y solo sirve como ejemplo valga la redundancia, ya que el comando "cat" ya se encarga de recoger el contenido de un fichero, por tanto nosotros los escribiríamos así:

$ cat file.txt > file5.txt


Un ejemplo claro si sería el siguiente:

$ xargs ls -F --color=yes < ruta.txt > listado.txt



El comando "ls" ayudado por el comando "xargs" recoge en el canal stdin el contenido del archivo "ruta.txt" y el resultado del comando "ls" en el canal stdout es direccionado al archivo "listado.txt".


TUBERÍAS o PIPES

Hemos visto hasta ahora el direccionamiento de los tres canales stdin, stdout y stderr desde o hacia archivos, pero no hacia o desde comandos o programas.

Para que el direccionamiento del canal stdout de un comando lo recoja el canal stdin del siguiente comando escribiremos los dos comandos separados por este signo "|" (la barra vertical, se encuentra en la tecla del número 1 de nuestro teclado español).

Podemos ir enlazando comandos y comandos hasta conseguir una cadena verdaderamente larga, de ahí el nombre de tubería.

Con estos dos esquemas de como funciona una tubería lo entenderéis:


Y otro esquema con todas las posibilidades de los tres canales:



Vamos a los ejemplos:

Un ejemplo de tubería sería el siguiente:

$ ls -l /bin | grep a | less



El resultado del comando "ls" en el canal stdout lo recoge el siguiente comando "grep" mediante el canal stdin y una vez ejecutado el resultado en su canal stdout es direccionado al canal stdin del comando "less" el cual muestra los resultados por pantalla en el terminal por el canal stdout.

La tubería puede ser tan larga como deseéis. Y además podéis combinar los direccionamientos de los canales stdin, stdout y stderr antes citados. Un ejemplo de ello os lo muestro a continuación:

$ xargs ls -F --color=yes < ruta.txt | grep file > listado.txt


Se direcciona al canal stdin del comando "ls" el contenido del archivo "ruta,txt", el resultado de la ejecución de dicho comando en el canal stdout es recogido por el canal stdin del comando "grep", el resultado del mismo en el canal stdout es dirigido al archivo "listado.txt".

Gracias a la tuberías podéis realizar tareas muy productivas e interesantes para la gestión y mantenimiento del sistema, para la obtención de datos o para lo que necesitéis realizar.

Y aquí termina este artículo. Espero os haya quedado claro que es stdin, stdio y stderr, su direccionamiento y las tuberías. Si tenéis alguna duda podéis dejar algún comentario.

Saludos y hasta el próximo artículo.

No hay comentarios:

Publicar un comentario

Gracias por participar en este blog.