¿Cómo redirijo la salida de un script de shell completo dentro del script mismo?

205

¿Es posible redirigir toda la salida de un script de shell Bourne a algún lugar, pero con comandos de shell dentro del propio script?

Redirigir la salida de un solo comando es fácil, pero quiero algo más como esto:

#!/bin/sh
if [ ! -t 0 ]; then
    # redirect all of my output to a file here
fi

# rest of script...

Significado: si el script se ejecuta de forma no interactiva (por ejemplo, cron), guarde la salida de todo en un archivo. Si se ejecuta de forma interactiva desde un shell, deje que la salida vaya a stdout como de costumbre.

Quiero hacer esto para un script normalmente ejecutado por la utilidad periódica de FreeBSD. Es parte de la ejecución diaria, que normalmente no me gusta ver todos los días en el correo electrónico, por lo que no me la envían. Sin embargo, si algo dentro de este script en particular falla, eso es importante para mí y me gustaría poder capturar y enviar por correo electrónico la salida de esta parte de los trabajos diarios.

Actualización: la respuesta de Joshua es acertada, pero también quería guardar y restaurar stdout y stderr en todo el script, lo que se hace así:

# save stdout and stderr to file descriptors 3 and 4, then redirect them to "foo"
exec 3>&1 4>&2 >foo 2>&1

# ...

# restore stdout and stderr
exec 1>&3 2>&4
Steve Madsen
fuente
2
La prueba de $ TERM no es la mejor manera de probar el modo interactivo. En su lugar, pruebe si stdin es un tty (prueba -t 0).
Chris Jester-Young
2
En otras palabras: si [! -t 0]; entonces exec> somefile 2> & 1; fi
Chris Jester-Young
1
Vea aquí todas las bondades: http://tldp.org/LDP/abs/html/io-redirection.html Básicamente lo que dijo Joshua. El archivo exec> redirige stdout a un archivo específico, el archivo exec <reemplaza stdin por archivo, etc. Es lo mismo que de costumbre pero usa exec (vea man exec para más detalles).
Loki
En su sección de actualización, también debe cerrar los FD 3 y 4, así: exec 1>&3 2>&4 3>&- 4>&-
Gurjeet Singh

Respuestas:

173

Abordando la pregunta como actualizada.

#...part of script without redirection...

{
    #...part of script with redirection...
} > file1 2>file2 # ...and others as appropriate...

#...residue of script without redirection...

Las llaves '{...}' proporcionan una unidad de redirección de E / S. Las llaves deben aparecer donde pueda aparecer un comando, de manera simplista, al comienzo de una línea o después de un punto y coma. ( Sí, eso se puede hacer más preciso; si quieres objetar, házmelo saber ) .

Tiene razón en que puede preservar el stdout y stderr original con las redirecciones que mostró, pero por lo general es más simple para las personas que tienen que mantener el script más tarde para comprender lo que está sucediendo si alcanza el código redirigido como se muestra arriba.

Las secciones relevantes del manual de Bash son Comandos de agrupación y redirección de E / S . Las secciones correspondientes de la especificación POSIX shell son comandos compuestos y I / O redirección . Bash tiene algunas anotaciones adicionales, pero por lo demás es similar a la especificación de shell POSIX.

Jonathan Leffler
fuente
3
Esto es mucho más claro que guardar los descriptores originales y restaurarlos más tarde.
Steve Madsen
23
Tuve que buscar en Google para entender lo que realmente está haciendo, así que quería compartir. Las llaves se convierten en un "bloque de código" que, en efecto, crea una función anónima . La salida de todo en el bloque de código se puede redirigir (consulte el ejemplo 3-2 desde ese enlace). También tenga en cuenta que las llaves no inician una subshell , pero se pueden hacer redirecciones de E / S similares con subshell utilizando paréntesis.
Chris
3
Me gusta esta solución mejor que las otras. Incluso una persona con la comprensión más básica de la redirección de E / S puede comprender lo que está sucediendo. Además, es más detallado. Y, como Pythoner, me encanta verbose.
John Red
Mejor hacer >>. Algunas personas tienen la costumbre de >. Anexar siempre es más seguro y más recomendable que sobresalir. Alguien escribió una aplicación que usa el comando de copia estándar para exportar algunos datos al mismo destino.
neverMind9
Esa aplicación es ccc.bmw71 (.pro) (3C Battery Monitor Widget, anteriormente "Battery Monitor Widget") siempre exporta el historial de la batería a /sdcard/bmw_history.txt. Si ya existe, adivina qué, ¡SOBRESCRIBIR! Esto me hizo perder accidentalmente algo de historial de batería. Establecí el límite en días de 30 a un número muy alto, lo que lo invalidó y lo puse nuevamente en 30. Quería exportar el historial actual de la batería antes de importar la copia de seguridad existente. Entonces eso sucedió.
neverMind9
175

Por lo general, colocaríamos uno de estos en o cerca de la parte superior del script. Las secuencias de comandos que analizan sus líneas de comando realizarían la redirección después del análisis.

Enviar stdout a un archivo

exec > file

con stderr

exec > file                                                                      
exec 2>&1

anexar stdout y stderr al archivo

exec >> file
exec 2>&1

Como Jonathan Leffler mencionó en su comentario :

execTiene dos trabajos separados. El primero es reemplazar el shell (script) que se está ejecutando actualmente con un nuevo programa. El otro está cambiando las redirecciones de E / S en el shell actual. Esto se distingue por no tener argumentos para exec.

Joshua
fuente
77
Digo también agregue 2> y 1 al final de eso, solo para que stderr quede atrapado también. :-)
Chris Jester-Young
77
¿Dónde pones estos? En la parte superior del guión?
colan
44
Con esta solución, también se debe restablecer la redirección a la que sale el script. La siguiente respuesta de Jonathan Leffler es más "a prueba de fallas" en este sentido.
Chuim
66
@JohnRed: exectiene dos trabajos separados. Una es reemplazar el script actual con otro comando, usando el mismo proceso: especifica el otro comando como argumento para exec(y puede ajustar las redirecciones de E / S a medida que lo hace). El otro trabajo está cambiando las redirecciones de E / S en el script de shell actual sin reemplazarlo. Esta notación se distingue por no tener un comando como argumento para exec. La notación en esta respuesta es de la variante "solo E / S": solo cambia la redirección y no reemplaza el script que se está ejecutando. (El setcomando es igualmente multipropósito.)
Jonathan Leffler
18
exec > >(tee -a "logs/logdata.log") 2>&1imprime los registros en la pantalla y los escribe en un archivo
shriyog
32

Puede hacer que todo el script sea una función como esta:

main_function() {
  do_things_here
}

entonces al final del guión ten esto:

if [ -z $TERM ]; then
  # if not run via terminal, log everything into a log file
  main_function 2>&1 >> /var/log/my_uber_script.log
else
  # run via terminal, only output to screen
  main_function
fi

Alternativamente, puede registrar todo en el archivo de registro en cada ejecución y aún enviarlo a stdout simplemente haciendo:

# log everything, but also output to stdout
main_function 2>&1 | tee -a /var/log/my_uber_script.log
dbguy
fuente
Quiso decir main_function >> /var/log/my_uber_script.log 2> & 1
Felipe Alvarez
Me gusta usar main_function en esa tubería. Pero en este caso su script no devuelve el valor de retorno original. En bash case, debe salir y luego usar 'exit $ {PIPESTATUS [0]}'.
rudimeier
7

Para guardar el stdout y stderr original, puede usar:

exec [fd number]<&1 
exec [fd number]<&2

Por ejemplo, el siguiente código imprimirá "walla1" y "walla2" en el archivo de registro ( a.txt), "walla3" en stdout, "walla4" en stderr.

#!/bin/bash

exec 5<&1
exec 6<&2

exec 1> ~/a.txt 2>&1

echo "walla1"
echo "walla2" >&2
echo "walla3" >&5
echo "walla4" >&6
Eyal leshem
fuente
1
Normalmente, sería mejor usar exec 5>&1y exec 6>&2, usando la notación de redirección de salida en lugar de la notación de redirección de entrada para las salidas. Se saldrá con la suya porque cuando el script se ejecuta desde un terminal, la entrada estándar también se puede escribir y tanto la salida estándar como el error estándar son legibles en virtud (¿o es un 'vicio'?) De una peculiaridad histórica: el terminal está abierto para lectura y escritura y la misma descripción de archivo abierto se utiliza para los tres descriptores de archivo de E / S estándar.
Jonathan Leffler
3
[ -t <&0 ] || exec >> test.log
Dimitar
fuente