Forzar el vaciado de la salida a un archivo mientras el script bash aún se está ejecutando

89

Tengo un pequeño script, que crontab llama a diario usando el siguiente comando:

/homedir/MyScript &> some_log.log

El problema con este método es que some_log.log solo se crea después de que MyScript finaliza. Me gustaría vaciar la salida del programa en el archivo mientras se está ejecutando para poder hacer cosas como

tail -f some_log.log

y realizar un seguimiento del progreso, etc.

olamundo
fuente
Necesitaremos tener una descripción, o si es posible un código, de lo que su pequeño script hace exactamente ...
ChristopheD
7
Para eliminar el búfer de los scripts de Python, puede usar "python -u". Para anular el búfer de los scripts de Perl, consulte la respuesta de Greg Hewgill a continuación. Y así sucesivamente ...
Eloici
Si puede editar el script, normalmente puede vaciar el búfer de salida explícitamente dentro de un script, por ejemplo en python con sys.stdout.flush().
drevicko

Respuestas:

28

bash en sí mismo nunca escribirá ninguna salida en su archivo de registro. En cambio, los comandos que invoca como parte de la secuencia de comandos escribirán individualmente la salida y se eliminarán cuando lo deseen. Entonces, su pregunta es realmente cómo forzar que los comandos dentro del script bash se descarguen, y eso depende de lo que sean.

Chris Dodd
fuente
23
Realmente no entiendo esta respuesta.
Alfonso Santiago
2
Para tener una mejor idea de por qué la salida estándar se comporta así, consulte stackoverflow.com/a/13933741/282728 . Una versión corta: de forma predeterminada, si se redirige a un archivo, stdout se almacena completamente en búfer; se escribe en un archivo solo después de un vaciado. Stderr no lo es, está escrito después de cada '\ n'. Una solución es usar el comando 'script' recomendado por user3258569 a continuación, para que stdout se vacíe después de cada final de línea.
Alex
Decir lo obvio, y diez años después, pero esto es un comentario, no una respuesta, y no debería ser la respuesta aceptada.
RealHandy hace
84

Encontré una solución a esto aquí . Usando el ejemplo de OP, básicamente ejecutas

stdbuf -oL /homedir/MyScript &> some_log.log

y luego el búfer se vacía después de cada línea de salida. A menudo combino esto con nohuppara ejecutar trabajos largos en una máquina remota.

stdbuf -oL nohup /homedir/MyScript &> some_log.log

De esta manera, su proceso no se cancelará cuando cierre la sesión.

Martin Wiebusch
fuente
1
¿Podría agregar un enlace a alguna documentación para stdbuf? Según este comentario , parece que no está disponible en algunas distribuciones. ¿Podrías aclarar?
Financia la demanda de Monica el
1
stdbuf -o ajusta el almacenamiento en búfer de stdout. Las otras opciones son -i y -e para stdin y stderr. L establece el búfer de línea. También se podría especificar el tamaño del búfer, o 0 si no hay búfer.
Seppo Enarvi
5
ese enlace ya no está disponible.
Fzs2
2
@NicHartley: stdbufes parte de GNU coreutils, la documentación se puede encontrar en gnu.org
Thor
En caso de que ayude a alguien, use export -f my_function y luego, stdbuf -oL bash -c "my_function -args"si necesita ejecutar una función en lugar de un script
anónimo
28
script -c <PROGRAM> -f OUTPUT.txt

La clave es -f. Cita del guión del hombre:

-f, --flush
     Flush output after each write.  This is nice for telecooperation: one person
     does 'mkfifo foo; script -f foo', and another can supervise real-time what is
     being done using 'cat foo'.

Ejecutar en segundo plano:

nohup script -c <PROGRAM> -f OUTPUT.txt
usuario3258569
fuente
¡Guauu! ¡Una solución que funciona busybox! (mi caparazón se congela después, pero lo que sea)
Victor Sergienko
9

Puede utilizar teepara escribir en el archivo sin necesidad de vaciarlo.

/homedir/MyScript 2>&1 | tee some_log.log > /dev/null
crenate
fuente
2
Esto aún almacena la salida, al menos en mi entorno Ubuntu 18.04. El contenido eventualmente se escribe en el archivo de cualquier manera, pero creo que el OP está pidiendo un método en el que puedan monitorear el progreso con mayor precisión antes de que el archivo termine de escribir, y este método no lo permite más que la redirección de salida hace.
mltsy
3

Esto no es una función de bash, ya que todo lo que hace el shell es abrir el archivo en cuestión y luego pasar el descriptor del archivo como salida estándar del script. Lo que debe hacer es asegurarse de que la salida se elimine de su secuencia de comandos con más frecuencia de la que tiene actualmente.

En Perl, por ejemplo, esto podría lograrse configurando:

$| = 1;

Consulte perlvar para obtener más información al respecto.

Greg Hewgill
fuente
2

El almacenamiento en búfer de la salida depende de cómo /homedir/MyScriptse implemente su programa . Si encuentra que la salida se está almacenando en búfer, debe forzarla en su implementación. Por ejemplo, use sys.stdout.flush () si es un programa de Python o use fflush (stdout) si es un programa de C.

Midas
fuente
2

¿Ayudaría esto?

tail -f access.log | stdbuf -oL cut -d ' ' -f1 | uniq 

Esto mostrará inmediatamente entradas únicas de access.log utilizando la utilidad stdbuf .

Ondra Žižka
fuente
El único problema es que stdbuf parece ser una utilidad antigua que no está disponible en las nuevas distribuciones.
Ondra Žižka
..ni en mi busybox :(
Campa
De hecho, tengo stdbufdisponible en Ubuntu en este momento, no estoy seguro de dónde lo conseguí.
Ondra Žižka
Tengo stdbuf en Centos 7.5
Max
1
En Ubuntu 18.04, stdbufes parte de coreutilus(se encuentra con apt-file search /usr/bin/stdbuf).
Rmano
1

El problema que se acaba de ver aquí es que tienes que esperar a que los programas que ejecutas desde tu script terminen su trabajo.
Si en su secuencia de comandos ejecuta el programa en segundo plano , puede intentar algo más.

En general, una llamada a syncantes de salir permite vaciar los búferes del sistema de archivos y puede ayudar un poco.

Si en el script inicia algunos programas en segundo plano ( &), puede esperar a que terminen antes de salir del script. Para tener una idea de cómo puede funcionar, puede ver a continuación.

#!/bin/bash
#... some stuffs ...
program_1 &          # here you start a program 1 in background
PID_PROGRAM_1=${!}   # here you remember its PID
#... some other stuffs ... 
program_2 &          # here you start a program 2 in background
wait ${!}            # You wait it finish not really useful here
#... some other stuffs ... 
daemon_1 &           # We will not wait it will finish
program_3 &          # here you start a program 1 in background
PID_PROGRAM_3=${!}   # here you remember its PID
#... last other stuffs ... 
sync
wait $PID_PROGRAM_1
wait $PID_PROGRAM_3  # program 2 is just ended
# ...

Dado que waitfunciona tanto con trabajos como con PIDnúmeros, una solución perezosa debería ser poner al final del script

for job in `jobs -p`
do
   wait $job 
done

Más difícil es la situación si ejecuta algo que ejecuta otra cosa en segundo plano porque tiene que buscar y esperar (si es el caso) el final de todo el proceso hijo : por ejemplo, si ejecuta un demonio, probablemente no sea el caso esperar que termine :-).

Nota:

  • esperar $ {!} significa "esperar hasta que se complete el último proceso en segundo plano" donde $!está el PID del último proceso en segundo plano. Así que ponerwait ${!} justo después program_2 &es equivalente a ejecutar directamente program_2sin enviarlo en segundo plano con&

  • Con la ayuda de wait:

    Syntax    
        wait [n ...]
    Key  
        n A process ID or a job specification
    
Hastur
fuente
1

Gracias @user3258569, ¡el script es quizás lo único que funciona busybox!

Sin embargo, el caparazón se me estaba congelando después de eso. Buscando la causa, encontré estas grandes advertencias rojas "no usar en shells no interactivos" en la página del manual del script :

scriptestá diseñado principalmente para sesiones de terminal interactivas. Cuando stdin no es una terminal (por ejemplo:) echo foo | script, la sesión puede bloquearse, porque el shell interactivo dentro de la sesión del script pierde EOF y scriptno tiene idea de cuándo cerrar la sesión. Consulte la sección NOTAS para obtener más información.

Cierto. script -c "make_hay" -f /dev/null | grep "needle"estaba congelando el caparazón para mí.

Contrariamente a la advertencia, pensé que echo "make_hay" | scriptPASARÁ un EOF, así que intenté

echo "make_hay; exit" | script -f /dev/null | grep 'needle'

¡Y funcionó!

Tenga en cuenta las advertencias en la página de manual. Puede que esto no funcione para usted.

Victor Sergienko
fuente
0

La alternativa a stdbuf es awk '{print} END {fflush()}' que desearía que hubiera un bash incorporado para hacer esto. Normalmente no debería ser necesario, pero con versiones anteriores puede haber errores de sincronización de bash en los descriptores de archivos.

Brian Chrisman
fuente
-2

No sé si funcionaría, pero ¿qué hay de llamar sync?

esperar
fuente
1
synces una operación del sistema de archivos de bajo nivel y no está relacionada con la salida almacenada en búfer a nivel de la aplicación.
Greg Hewgill
2
syncescribe cualquier búfer sucio del sistema de archivos en el almacenamiento físico, si es necesario. Esto es interno del sistema operativo; las aplicaciones que se ejecutan en la parte superior del sistema operativo siempre ven una vista coherente del sistema de archivos, ya sea que los bloques de disco se hayan escrito o no en el almacenamiento físico. Para la pregunta original, la aplicación (script) probablemente está almacenando la salida en un búfer interno de la aplicación, y el sistema operativo ni siquiera sabrá (todavía) que la salida está destinada a escribirse en stdout. Por lo tanto, una operación hipotética de tipo "sincronización" no podría "alcanzar" el script y extraer los datos.
Greg Hewgill
-2

Tuve este problema con un proceso en segundo plano en Mac OS X usando el StartupItems. Así es como lo resuelvo:

Si lo hago sudo ps auxpuedo ver que mytoolse lanza.

Descubrí que (debido al almacenamiento en búfer) cuando Mac OS X se apaga, mytoolnunca transfiere la salida al sedcomando. Sin embargo, si ejecuto sudo killall mytool, mytooltransfiero la salida al sedcomando. Por lo tanto, agregué un stopcaso al StartupItemsque se ejecuta cuando Mac OS X se apaga:

start)
    if [ -x /sw/sbin/mytool ]; then
      # run the daemon
      ConsoleMessage "Starting mytool"
      (mytool | sed .... >> myfile.txt) & 
    fi
    ;;
stop)
    ConsoleMessage "Killing mytool"
    killall mytool
    ;;
Hombre libre
fuente
Esto realmente no es una buena respuesta, Freeman, ya que es muy específico para su entorno. El OP quiere monitorear la salida, no matarla.
Gray
-3

te guste o no, así es como funciona la redirección.

En su caso, la salida (lo que significa que su secuencia de comandos ha finalizado) de su secuencia de comandos se redirigió a ese archivo.

Lo que quiere hacer es agregar esas redirecciones en su secuencia de comandos.


fuente