¿Cómo enviar texto a la pantalla y al archivo dentro de un script de shell?

49

Actualmente tengo un script de shell que registra mensajes en un archivo de registro como este:

log_file="/some/dir/log_file.log"
echo "some text" >> $log_file
do_some_command
echo "more text" >> $log_file
do_other_command

Al ejecutar este script, no hay salida a la pantalla y, como me estoy conectando al servidor a través de masilla, tengo que abrir otra conexión y hacer "tail -f log_file_path.log", porque no puedo terminar la ejecución script y quiero ver la salida en tiempo real.

Obviamente, lo que quiero es que los mensajes de texto se impriman en la pantalla y en el archivo, pero me gustaría hacerlo en una línea, no en dos líneas, una de las cuales no tiene redirección al archivo.

¿Cómo lograr esto?

jurchiks
fuente

Respuestas:

71

Esto funciona:

command | tee -a "$log_file"

teeguarda la entrada en un archivo (se usa -apara agregar en lugar de sobrescribir) y también copia la entrada en la salida estándar.

l0b0
fuente
8

Puede usar un documento aquí y. búscalo para un modelo de colector general eficiente y amigable con POSIX.

. 8<<-\IOHERE /proc/self/fd/8

command
 
fn() { declaration ; } <<WHATEVER
# though a nested heredoc might be finicky
# about stdin depending on shell
WHATEVER
cat -u ./stdout | ./works.as >> expect.ed
IOHERE

Cuando abre el heredoc, le indica al shell con un token de entrada IOHERE que debe redirigir su entrada al descriptor de archivo que especifique hasta que encuentre el otro extremo de su token limitador. He mirado a mi alrededor, pero no he visto muchos ejemplos del uso del número fd de redirección como he mostrado anteriormente en combinación con el operador heredoc, aunque su uso está claramente especificado en las pautas básicas de comandos de shell POSIX. La mayoría de las personas solo apuntan a stdin y disparan, pero creo que el suministro de scriptlets de esta manera puede mantener el stdin libre y las aplicaciones constituyentes se quejan de las rutas de E / S bloqueadas.

El contenido del heredoc se transmite al descriptor de archivo que especifique, que a su vez es interpretado como código shell y ejecutado por. incorporado, pero no sin especificar una ruta específica para. . Si la ruta / proc / self le da problemas, intente / dev / fd / no / proc / $$. Este mismo método funciona en tuberías, por cierto:

cat ./*.sh | . /dev/stdin

Probablemente sea al menos tan imprudente como parece. Puede hacer lo mismo con sh, por supuesto, pero el propósito de. Es ejecutar en el entorno de shell actual, que es probablemente lo que desea y, dependiendo de su shell, es mucho más probable que funcione con un heredoc que con un tubo anónimo estándar

De todos modos, como probablemente habrás notado, todavía no he respondido tu pregunta. Pero si lo piensa, de la misma manera que el heredoc transmite todo su código a .'s in, también le proporciona un punto único y simple:

. 5<<EOIN /dev/fd/5 |\ 
    tee -a ./log.file | cat -u >$(tty)
script
 
more script
EOIN

Entonces, todo el terminal stdout de cualquiera de los códigos ejecutados en su heredoc se extrae de. por supuesto y se puede extraer fácilmente de una sola tubería. Incluí la llamada de gato sin búfer porque no estoy claro sobre la dirección estándar actual, pero probablemente sea redundante (casi seguro es como está escrito de todos modos) y la tubería probablemente pueda terminar justo en el tee.

También puede cuestionar la cita de barra invertida que falta en el segundo ejemplo. Es importante comprender esta parte antes de ingresar y puede darle algunas ideas sobre cómo se puede usar. Un limitador heredoc citado (hasta ahora hemos usado IOHERE y EOIN, y el primero citado con una barra diagonal inversa, aunque las comillas 'simples' o "dobles" servirían para el mismo propósito) impedirá que el shell realice cualquier expansión de parámetros en el contenido, pero un limitador sin comillas dejará su contenido abierto a la expansión. Las consecuencias de esto cuando su heredoc es. de origen son dramáticos:

. 3<<HD ${fdpath}/3
: \${vars=$(printf '${var%s=$((%s*2))},' `seq 1 100`)} 
HD
echo $vars
> 4,8,12 
echo $var1 $var51
> 4 104

Como no cité el limitador heredoc, el shell expandió el contenido a medida que lo leía y antes de mostrar el descriptor de archivo resultante. ejecutar. Esto esencialmente resultó en que los comandos se analizaran dos veces, los expandibles de todos modos. Debido a que la barra invertida citó la expansión del parámetro $ vars, el shell ignoró su declaración en la primera pasada y solo eliminó la barra invertida para que todo el contenido expandido de printf pudiera ser evaluado por nulo cuando. obtuvo el guión en el segundo paso.

Básicamente, esta funcionalidad es exactamente lo que puede proporcionar el peligroso shell de evaluación incorporado, incluso si la cita es mucho más fácil de manejar en un heredoc que con eval, y puede ser igualmente peligroso. A menos que lo planifique con cuidado, probablemente sea mejor citar el limitador "EOF" como una costumbre. Solo digo.

EDITAR: Eh, estoy mirando hacia atrás y pensando que es un poco exagerado. Si TODO lo que necesita hacer es concatenar varias salidas en una tubería, entonces el método más simple es usar un:

{ or ( command ) list ; } | tee -a ea.sy >>pea.sy

Los curlies intentarán ejecutar el contenido en el shell actual, mientras que los parens se eliminarán automáticamente. Aún así, cualquiera puede decirte eso y, al menos en mi opinión, el. La solución heredoc es información mucho más valiosa, especialmente si desea comprender cómo funciona realmente el shell.

¡Que te diviertas!

mikeserv
fuente
3

Encontré esta respuesta mientras buscaba modificar un script de instalación para escribir un registro de instalación.

Mi script ya está lleno de declaraciones de eco como:

echo "Found OLD run script $oldrunscriptname"
echo "Please run OLD tmunsetup script first!"

Y no quería que una declaración de tee lo ejecutara (u otro script para llamar al existente con un tee), así que escribí esto:

#!/bin/bash
# A Shell subroutine to echo to screen and a log file

LOGFILE="junklog"

echolog()
(
echo $1
echo $1 >> $LOGFILE
)


echo "Going"
echolog "tojunk"

# eof

Entonces, ahora en mi script original, puedo cambiar 'echo' a 'echolog' donde quiero la salida en un archivo de registro.

AndruWitta
fuente