Foto de Ben Kolde en UnsplashYa 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.
-
0
STDIN
– flujo de entrada estándar.
-
1
STDOUT
– flujo de salida estándar.
-
2
STDERR
– 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 STDIN
a 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 cat
en la línea de comandos sin especificar parámetros, acepta la entrada de STDIN
. Después de introducir la siguiente línea, se cat
imprime 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 pwd
se 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 quemyfile
el 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 comandols
normalmente envía aSTDOUT
un ficherocorrectcontent
por su construcción1>
. Los mensajes de error que hubieran terminadoSTDERR
en el archivo sonerrorcontent
debido al comando de redirección2>
.
Si es necesario, ySTDERR
, ySTDOUT
pueden 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 pretendeSTDERR
ySTDOUT
está 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 echo
inconveniente 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 comandoexec
puede 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 comandoexec
establece la redirección de la salida deSTDERR
a un archivomyerror
. Luego la salida de varios comandos esecho
enviada aSTDOUT
y mostrada en la pantalla. Después, el comandoexec
se establece enviando lo que vaya aSTDOUT
un archivomyfile
, y finalmente, utilizamos el comando de redirecciónSTDERR
en 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 exec
te permite hacer una fuente de datos para STDIN
un 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 comandoread
para 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 STDIN
en otro descriptor antes de redirigir la entrada.
Después de terminar de leer el archivo, puedes restaurar STDIN
y 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 elSTDIN
archivo se convirtió. Después, la entrada para el comandoread
procedía de la redirecciónSTDIN
, es decir, del fichero.
Después de leer el fichero, revertimosSTDIN
a 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 lsof
en /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.
-
-p
Permite especificar un ID
proceso.
-
-d
Permite 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 -a
utiliza para realizar una operación booleana AND
sobre 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 aSTDIN
STDOUT
ySTDERR —
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 comandolsof
desde 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 (3
y6
) 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.