Scripts Bash – Parte 4 – Entrada y salida

Mikhail Raevskiy
Mikhail Raevskiy
Sigue
28 de julio, 2020 – 11 min read

La última vez, en la Parte 3 de esta serie de scripts en bash, hablamos de los parámetros de la línea de comandos y los switches. Nuestro tema de hoy es la entrada, la salida y todo lo relacionado con esto.

Foto de Ben Kolde en Unsplash

Ya estás familiarizado con dos métodos para trabajar con lo que los comandos dede línea de comandos:

  • Mostrar los datos de salida en la pantalla.
  • Redirigir la salida a un archivo.
  • A veces es necesario mostrar algo en la pantalla, y escribir algo en un archivo, por lo que es necesario entender cómo se maneja la entrada y la salida en Linux, lo que significa que hay que aprender a enviar los resultados de los scripts a donde se necesita. Empecemos hablando de los descriptores de archivo estándar.

    Todo en Linux son archivos, incluyendo la entrada y la salida. El sistema operativo identifica los archivos mediante descriptores.

    Cada proceso puede tener hasta nueve descriptores de archivo abiertos. El shell bash reserva los tres primeros descriptores con los ID 0, 1 y 2. Esto es lo que significan.

    • 0STDIN– flujo de entrada estándar.
    • 1STDOUT– flujo de salida estándar.
    • 2STDERR– flujo de error estándar.
      • Estos tres descriptores especiales manejan la entrada y la salida del script.
        Es necesario entender bien los flujos estándar. Pueden compararse con la base sobre la que se construye la interacción de los scripts con el mundo exterior. Consideremos los detalles sobre ellos.

        STDIN

        STDIN – esta es la entrada estándar del shell. Para un terminal, la entrada estándar es el teclado. Cuando los scripts utilizan el carácter de redirección de entrada – <, Linux sustituye el descriptor de archivo de entrada estándar por el especificado en el comando. El sistema lee el archivo y procesa los datos como si fueran introducidos desde el teclado.

        Muchos comandos bash toman la entrada de STDINa menos que se especifique un archivo en la línea de comandos del que recuperar los datos. Por ejemplo, esto es cierto para un equipo cat.

        Cuando se introduce un comando caten la línea de comandos sin especificar parámetros, acepta la entrada de STDIN. Después de introducir la siguiente línea, se catimprime en la pantalla.

        STDOUT

        STDOUT – la salida estándar del shell. Por defecto, esta es la pantalla. La mayoría de los comandos bash dan salida a los datos STDOUT a la consola, lo que hace que aparezcan en la consola. Los datos pueden ser redirigidos a un archivo adjuntando su contenido mediante el comando >>.

        Así, tenemos un archivo de datos determinado, al que podemos añadir otros datos usando este comando:

pwd >> myfile

Lo que arroje pwdse añadirá al archivo myfile, mientras que los datos que ya están en él no irán a ninguna parte.

raevskym@DESKTOP-JNF3L6H:~/bash_course$ pwd >> myfile
raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./myfile
this is old file content
/home/raevskym/Desktop

Hasta aquí todo bien, pero qué pasa si se intenta hacer algo como lo que se muestra a continuación, accediendo a un fichero inexistentexfile, pensando todo esto para quemyfileel mensaje de error llegue al fichero.

ls –l xfile > myfile

Después de ejecutar este comando, veremos los mensajes de error en la pantalla.

raevskym@DESKTOP-JNF3L6H:~/bash_course$ ls -l xfile > myfile
ls: cannot access '-l': No sudh file or directory
ls: cannot access 'xfile': No such file or directory

Un intento de acceder a un fichero inexistente genera un error, pero el shell no redirigió los mensajes de error al fichero, mostrándolos. Pero queríamos que los mensajes de error fueran al archivo. ¿Qué hacer? La respuesta es sencilla: utilizar el tercer descriptor estándar.

STDERR

STDERR es el flujo de error estándar del shell. Por defecto, este manejador apunta a lo mismo que apunta STDOUT, por eso cuando se produce un error, vemos un mensaje en la pantalla.

Pues bien, supongamos que queremos redirigir los mensajes de error a, por ejemplo, un archivo de registro, o a algún otro lugar, en lugar de imprimirlos por pantalla.

Redirigir el flujo de errores

Como ya sabes, un descriptor de archivo paraSTDERR es el 2. Podemos redirigir los errores colocando este descriptor antes del comando de redirección:

ls -l xfile 2>myfile
cat ./myfile

El mensaje de error irá ahora al archivo myfile.

raevskym@DESKTOP-JNF3L6H:~/bash_course$ ls -l xfile 2> myfile
raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./myfile
ls: cannot access 'xfile': No such flie or directory

Redirigiendo los flujos de error y salida

Cuando se escriben scripts de línea de comandos, puede surgir una situación en la que se necesita organizar tanto la redirección de los mensajes de error como la redirección de la salida estándar. Para ello, hay que utilizar los comandos de redirección de los descriptores correspondientes, indicando los ficheros donde deben ir los errores y la salida estándar:

ls –l myfile xfile anotherfile 2> errorcontent 1> correctcontent

Veamos más de cerca:

raevskym@DESKTOP-JNF3L6H:~/bash_course$ ls -l myfile xfile anotherfile 2> errorcontent 1> correctcontent
raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./correctcontent
myfile
raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./errorcontent
ls: cannot access '-l': No sudh file or directory
ls: cannot access 'xfile': No such file or directory
ls: cannot access 'anotherfile': No such file or directory

La shell redirigirá lo que el comandolsnormalmente envía aSTDOUTun ficherocorrectcontentpor su construcción1>. Los mensajes de error que hubieran terminadoSTDERRen el archivo sonerrorcontentdebido al comando de redirección2>.

Si es necesario, ySTDERR, ySTDOUTpueden redirigirse al mismo archivo mediante el comando&>:

raevskym@DESKTOP-JNF3L6H:~/bash_course$ ls -l myfile xfile anotherfile &> content
raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./content
ls: cannot access '-l': No sudh file or directory
ls: cannot access 'xfile': No such file or directory
ls: cannot access 'anotherfile': No such file or directory
myfile

Después de ejecutar el comando, lo que se pretendeSTDERRySTDOUTestá en el archivocontent.

Redirección de la salida en los scripts

Hay dos métodos para redirigir la salida en los scripts de línea de comandos:

  • Redirección temporal, o redirección de la salida de una línea.
  • Redirigir permanentemente, o redirigir toda o parte de la salida en el script.
    • Redirigir temporalmente la salida

      En un script, se puede redirigir la salida de una sola línea a STDERR. Para ello, basta con utilizar el comando de redirección, especificando un descriptor STDERR, y delante del número del descriptor, hay que poner un símbolo de ampersand ( &) :

#!/bin/bash
echo "This is an error" >&2
echo "This is normal output"

Si ejecutas el script, ambas líneas irán a la pantalla, ya que, como ya sabes, por defecto, los errores se muestran en el mismo lugar que los datos normales.

raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
This is an error
This is normal output

Ejecutamos el script para que la salidaSTDERR vaya a un fichero.

./myscript 2> myfile

Como puedes ver, ahora la salida habitual se hace a la consola, y los mensajes de error se envían al fichero.

raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript 2> content
This is normal output
raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./content
This is an error

Redirección permanente de la salida

Si un script necesita redirigir muchos datos mostrados, es echoinconveniente añadir el comando apropiado a cada llamada. En su lugar, puedes configurar la salida para que sea redirigida a un descriptor específico mientras dure el script utilizando el comando exec:

#!/bin/bash
exec 1>outfile
echo "This is a test of redirecting all output"
echo "from a shell script to another file."
echo "without having to redirect every line"

Ejecutamos el script:

raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat outfile
This is a test of redirecting all output
from a shell script to another file.
without having to redirect every line

Si miramos el archivo especificado en el comando de redirección de salida, resulta que todo lo que la salida del comandoecho fue a ese archivo.

El comandoexecpuede usarse no sólo al principio del script sino también en otros lugares:

#!/bin/bash
exec 2>myerror
echo "This is the start of the script"
echo "now redirecting all output to another location"
exec 1>myfile
echo "This should go to the myfile file"
echo "and this should go to the myerror file" >&2

Esto es lo que ocurre después de ejecutar el script y ver los archivos a los que redirigimos la salida.

raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
This is the start of the script
now redirecting all output to another location
raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./myerror
and this should go to the myerror file
raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./myfile
This should go to the myfile file

Primero, el comandoexecestablece la redirección de la salida deSTDERRa un archivomyerror. Luego la salida de varios comandos esechoenviada aSTDOUTy mostrada en la pantalla. Después, el comandoexecse establece enviando lo que vaya aSTDOUTun archivomyfile, y finalmente, utilizamos el comando de redirecciónSTDERRen el comandoecho, que lleva a escribir la línea correspondiente en el archivo.myerror.

Habiendo dominado esto, puedes redirigir la salida hacia donde quieras. Ahora hablemos de la redirección de la entrada.

Redirigir la entrada en los scripts

Para redirigir la entrada, puedes usar la misma técnica que usamos para redirigir la salida. Por ejemplo, el comando execte permite hacer una fuente de datos para STDINun archivo:

exec 0< myfile

Este comando le dice a la shell que su fuente de entrada sea un archivo myfile, no uno normal STDIN. Veamos la redirección de la entrada en acción:

#!/bin/bash
exec 0< testfile
count=1
while read line
do
echo "Line #$count: $line"
count=$(( $count + 1 ))
done

Esto es lo que aparecerá en la pantalla después de ejecutar el script.

raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
Line #1: this is the first line
Line #2: this is the second line
Line #3: this is the third line

En uno de los artículos anteriores, aprendiste a usar un comandoreadpara leer la entrada del usuario desde el teclado. Si redirige la entrada haciendo que la fuente de datos sea un archivo, entonces el comandoread, al intentar leer los datos deSTDIN, los leerá del archivo, y no del teclado.

Algunos administradores de Linux utilizan este enfoque para leer y luego procesar los archivos de registro.

Creando su propia redirección de salida

Cuando se redirige la entrada y la salida en los scripts, no está limitado a los tres descriptores de archivo estándar. Como se ha mencionado, puede tener hasta nueve descriptores abiertos. Los otros seis, numerados del 3 al 8, pueden utilizarse para redirigir la entrada o la salida. Cualquiera de ellos puede ser asignado a un archivo y utilizado en el código del script.

Se puede asignar un descriptor para la salida de datos utilizando el comando exec:

#!/bin/bash
exec 3>myfile
echo "This should display on the screen"
echo "and this should be stored in the file" >&3
echo "And this should be back on the screen"

Después de ejecutar el script, parte de la salida irá a la pantalla, y otra parte irá al archivo con el descriptor 3.

raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
This should display on the screen
And this should be back on the screen
raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./myfile
and this should be stored in the file

Creando descriptores de archivo para la entrada de datos

Puedes redirigir la entrada en un script de la misma manera que la salida. Almacena STDINen otro descriptor antes de redirigir la entrada.

Después de terminar de leer el archivo, puedes restaurar STDINy usarlo como siempre:

#!/bin/bash
exec 6<&0
exec 0< myfile
count=1
while read line
do
echo "Line #$count: $line"
count=$(( $count + 1 ))
done
exec 0<&6
read -p "Are you done now? " answer
case $answer in
y) echo "Goodbye";;
n) echo "Sorry, this is the end.";;
esac

Probemos el script:

raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
Line #1: First line
Line #2: Second line
Line #3: Third line
Are you done now? y
Goodbye
raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
Line #1: First line
Line #2: Second line
Line #3: Third line
Are you done now? n
Sorry, this is the end.

En este ejemplo, se utilizó el descriptor de archivo 6 para almacenar una referencia aSTDIN. Luego se hizo la redirección de entrada, la fuente de datos para elSTDINarchivo se convirtió. Después, la entrada para el comandoreadprocedía de la redirecciónSTDIN, es decir, del fichero.

Después de leer el fichero, revertimosSTDINa su estado original redirigiéndolo a un descriptor6. Ahora, para comprobar que todo funciona correctamente, el script hace una pregunta al usuario, espera la entrada del teclado y procesa lo introducido.

Cierre de descriptores de archivo

El shell cierra automáticamente los descriptores de archivo después de que el script termine. Sin embargo, en algunos casos, es necesario cerrar los descriptores manualmente antes de que el script termine de funcionar. Para cerrar el manejador, es necesario redirigirlo a &-. El aspecto es el siguiente:

#!/bin/bash
exec 3> myfile
echo "This is a test line of data" >&3
exec 3>&-
echo "This won't work" >&3

Después de ejecutar el script, recibiremos un mensaje de error.

raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
./myscript: line 5: 3: Bad file descriptor

La cuestión es que hemos intentado acceder a un descriptor de archivo inexistente.

Tenga cuidado al cerrar los descriptores de archivo en los scripts. Si enviaste datos a un archivo, luego cerraste el manejador y luego lo volviste a abrir, el shell reemplazará el archivo existente por el nuevo. Es decir, se perderá todo lo que se haya escrito antes en ese archivo.

Recuperación de información sobre los manejadores abiertos

Para obtener una lista de todos los descriptores abiertos en Linux, puedes utilizar el comando lsof. Muchas distribuciones, como Fedora, tienen la utilidad lsofen /usr/sbin. Este comando es muy útil, ya que muestra información sobre cada gestor abierto en el sistema. Esto incluye lo que abren los procesos que se ejecutan en segundo plano y lo que abren los usuarios que han iniciado sesión.

Este comando tiene muchas claves, veamos las más importantes.

  • -pPermite especificar un IDproceso.
  • -dPermite especificar el número de un descriptor sobre el que se quiere obtener información.

Para conocer el PID del proceso actual, se puede utilizar una variable de entorno especial $$en la que el shell escribe el actual PID.

La clave se -autiliza para realizar una operación booleana ANDsobre los resultados devueltos al utilizar las otras dos claves:

raevskym@DESKTOP-JNF3L6H:~/bash_course$ lsof -a -p $$ -d 0,1,2
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
myscript 24 raevskym 0u CHR 4,1 25332 /dev/tty1
myscript 24 raevskym 1u CHR 4,1 25332 /dev/tty1
myscript 24 raevskym 2u CHR 4,1 25332 /dev/tty1

El tipo de archivos asociados aSTDINSTDOUTySTDERR — CHR (modo carácter). Como todos ellos apuntan a un terminal, el nombre del archivo coincide con el nombre del dispositivo asignado al terminal. Los tres archivos estándar están disponibles para leer y escribir.

Veamos la llamada a un comandolsofdesde un script en el que, además del estándar, hay otros descriptores abiertos:

#!/bin/bash
exec 3> myfile1
exec 6> myfile2
exec 7< myfile3
lsof -a -p $$ -d 0,1,2,3,6,7

Esto es lo que ocurre si se ejecuta este script.

raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
myscript 24 raevskym 0u CHR 4,1 25332 /dev/tty1
myscript 24 raevskym 1u CHR 4,1 25332 /dev/tty1
myscript 24 raevskym 2u CHR 4,1 25332 /dev/tty1
myscript 24 raevskym 3w REG 0,14 0 13510 ~/bash_course/myfile1
myscript 24 raevskym 6w REG 0,14 0 13229 ~/bash_course/myfile2

El script abrió dos descriptores de salida (3y6) y uno de entrada (7). También se muestran aquí las rutas de los archivos utilizados para configurar los descriptores.

Supresión de la salida

A veces es necesario asegurarse de que los comandos del script, que, por ejemplo, pueden ejecutarse como un proceso en segundo plano, no muestren nada en la pantalla. Para ello, puedes redirigir la salida a /dev/null. Esto es algo así como un «agujero negro».

Por ejemplo, así es como se suprimen los mensajes de error:

ls -al badfile anotherfile 2> /dev/null

El mismo enfoque se utiliza si, por ejemplo, necesitas limpiar un archivo sin borrarlo:

cat /dev/null > myfile

Salida

Hoy has aprendido cómo funcionan la entrada y la salida en los scripts de línea de comandos. Ahora sabes cómo manejar los descriptores de archivo, crearlos, verlos y cerrarlos, sabes sobre la redirección de flujos de entrada, salida y error. Todo esto es muy importante en el negocio del desarrollo de scripts bash.

La próxima vez, vamos a hablar de las señales de Linux, cómo manejarlas en los scripts, cómo ejecutar trabajos programados y tareas en segundo plano.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *