Vaciar un archivo sin interrumpir la escritura de la tubería.

12

Tengo un programa cuya salida redirijo a un archivo de registro:

./my_app > log

Me gustaría borrar (es decir, vaciar) el registro de vez en cuando (a pedido) y probé varias cosas como

cat "" > log

Sin embargo, siempre parece que la tubería original se interrumpe y el programa ya no redirige su salida al archivo de registro.

¿Hay alguna forma de hacer eso?

Actualizar

Tenga en cuenta que no puedo modificar la aplicación que produce la salida. Simplemente lo escupe en stdout y quiero guardarlo en un registro para poder inspeccionarlo cuando lo necesite y borrarlo cuando lo desee. Sin embargo, no debería necesitar reiniciar la aplicación.

bangnab
fuente
es por eso que usualmente usas un demonio de registro para registrar cosas ...
Kiwy 03 de
@Kiwy, ¿puede explicar cómo resolvería el problema?
bangnab
bueno, usualmente usas un demonio de registro o dejas que tu aplicación maneje el registro, porque escribir cosas en la salida y redirigirlo no es confiable. deberías echar un vistazo a syslogdologrotate
Kiwy
2
¿Funcionan las cosas si lo haces ./my_app >> log(para forzar la adición) y cp /dev/null logpara truncarlo?
Mark Plotnick
1
¿Qué mensaje de error recibes? ¿Qué comportamiento ves? "Ya no redirige su salida al archivo de registro" no es muy específico. Además, cat "" > logno es un catcomando válido ya que no se llama ningún archivo "".
Mikel

Respuestas:

13

Otra forma de este problema ocurre con aplicaciones de larga ejecución cuyos registros se rotan periódicamente. Incluso si mueve el registro original (por ejemplo, mv log.txt log.1) y lo reemplaza inmediatamente con un archivo con el mismo nombre antes de que ocurra un registro real, si el proceso mantiene el archivo abierto, terminará escribiendo en log.1(porque aún puede ser el inodo abierto) o para nada.

Una forma común de lidiar con esto (el registrador del sistema funciona de esta manera) es implementar un controlador de señal en el proceso que cerrará y volverá a abrir sus registros. Luego, cuando quiera mover o borrar (borrando) el registro, envíe esa señal al proceso inmediatamente después.

Aquí hay una demostración simple de bash: perdona mis habilidades de shell (pero si vas a editar esto para las mejores prácticas, etc., asegúrate de comprender primero la funcionalidad y prueba tu revisión antes de editar):

#!/bin/bash

trap sighandler INT

function sighandler () {
    touch log.txt
    exec &> log.txt
}

echo $BASHPID
exec &> log.txt

count=0;
while [ $count -lt 60 ]; do
    echo "$BASHPID Count is now $count"
    sleep 2
    ((count++))
done          

Comience bifurcando en el fondo:

> ./test.sh &
12356

Observe que informa su PID al terminal y luego comienza a iniciar sesión log.txt. Ahora tienes 2 minutos para jugar. Espere unos segundos e intente:

> mv log.txt log.1 && kill -s 2 12356

Simplemente kill -2 12356puede funcionar para usted aquí también. La señal 2 es SIGINT (también es lo que hace Ctrl-C, por lo que puede probar esto en primer plano y mover o eliminar el archivo de registro de otro terminal), que trapdebería atrapar. Verificar;

> cat log.1
12356 Count is now 0
12356 Count is now 1
12356 Count is now 2
12356 Count is now 3
12356 Count is now 4
12356 Count is now 5
12356 Count is now 6
12356 Count is now 7
12356 Count is now 8
12356 Count is now 9
12356 Count is now 10
12356 Count is now 11
12356 Count is now 12
12356 Count is now 13
12356 Count is now 14

Ahora veamos si todavía está escribiendo en un log.txtarchivo aunque lo hayamos movido:

> cat log.txt
12356 Count is now 15
12356 Count is now 16
12356 Count is now 17
12356 Count is now 18
12356 Count is now 19
12356 Count is now 20
12356 Count is now 21

Observe que siguió yendo justo donde lo dejó. Si no desea mantener el registro, simplemente borre el registro eliminándolo

> rm -f log.txt && kill -s 2 12356

Cheque:

> cat log.txt
12356 Count is now 29
12356 Count is now 30
12356 Count is now 31
12356 Count is now 32
12356 Count is now 33
12356 Count is now 34
12356 Count is now 35
12356 Count is now 36

Sigue yendo

Desafortunadamente, no puede hacer esto en un script de shell para un subproceso ejecutado, porque si está en primer plano, los manejadores de señal de bash trapestán suspendidos, y si lo bifurca en segundo plano, no puede reasignar su salida. Es decir, esto es algo que debes implementar en tu aplicación.

Sin embargo...

Si no puede modificar la aplicación (por ejemplo, porque no la escribió), tengo una utilidad CLI que puede usar como intermediario. También podría implementar una versión simple de esto en un script que sirva como canalización al registro:

#!/bin/bash

trap sighandler INT

function sighandler () {
    touch log.txt
    exec 1> log.txt
}

echo "$0 $BASHPID"
exec 1> log.txt

count=0;
while read; do
    echo $REPLY
done  

Llamemos esto pipetrap.sh. Ahora necesitamos un programa separado para probar, imitando la aplicación que desea iniciar sesión:

#!/bin/bash

count=0
while [ $count -lt 60 ]; do
    echo "$BASHPID Count is now $count"
    sleep 2
    ((count++))
done           

Eso será test.sh:

> (./test.sh | ./pipetrap.sh) &
./pipetrap.sh 15859

Estos son dos procesos separados con PID separados. Para borrar test.shla salida, que se está canalizando a través de pipetrap.sh:

> rm -f log.txt && kill -s 2 15859

Cheque:

>cat log.txt
15858 Count is now 6
15858 Count is now 7
15858 Count is now 8

15858, test.shtodavía se está ejecutando y su salida se está registrando. En este caso, no se necesitan modificaciones en la aplicación.

encerrada dorada
fuente
Gracias por las buenas explicaciones. Sin embargo, en mi caso no puedo modificar la aplicación para implementar su solución.
bangnab 03 de
2
Si no puede implementar un controlador de señal en su aplicación (porque no puede modificar el período), puede usar esta técnica para canalizar el registro a través de una trampa de señal - vea las cosas después de "Sin embargo ..."
Ricitos de oro
Ok, lo intentaré y te diré cómo te fue.
bangnab
Finalmente tengo una aplicación CLI escrita en C para esto (lo siento, tardó un poco más de lo previsto originalmente): cognitivedissonance.ca/cogware/pipelog
goldilocks
6

TL; DR

Abra su archivo de registro en modo agregar :

cmd >> log

Luego, puede truncarlo con seguridad con:

: > log

Detalles

Con un shell tipo Bourne, hay 3 formas principales en que un archivo se puede abrir para escribir. En modo de solo escritura ( >), lectura + escritura ( <>) o anexar (y solo de escritura >>).

En los primeros dos, el kernel recuerda la posición actual en la que usted (quiero decir, la descripción del archivo abierto , compartida por todos los descriptores de archivo que lo han duplicado o heredado al bifurcar desde el que abrió el archivo) está en archivo.

Cuando tu lo hagas:

cmd > log

logestá abierto en modo de solo escritura por el shell para el stdout de cmd.

cmd(su proceso inicial generado por el shell y todos los hijos posibles) al escribir en su stdout, escriba en la posición actual del cursor que mantiene la descripción del archivo abierto que comparten en ese archivo.

Por ejemplo, si cmdinicialmente escribe zzz, la posición estará en el byte offset 4 en el archivo, y la próxima vez que cmdsus hijos escriban en el archivo, allí es donde se escribirán los datos independientemente de si el archivo ha crecido o disminuido en el intervalo .

Si el archivo se ha reducido, por ejemplo, si se ha truncado con un

: > log

y cmdescribe xx, esos xxse escribirán en offset 4, y los primeros 3 caracteres serán reemplazados por caracteres NUL.

$ exec 3> log # open file on fd 3.
$ printf zzz >&3
$ od -c log
0000000   z   z   z
0000003
$ printf aaaa >> log # other open file description -> different cursor
$ od -c log
0000000   z   z   z   a   a   a   a
0000007
$ printf bb >&3 # still write at the original position
$ od -c log
0000000   z   z   z   b   b   a   a
0000007
$ : > log
$ wc log
0 0 0 log
$ printf x >&3
$ od -c log
0000000  \0  \0  \0  \0  \0   x
0000006

Eso significa que no puede truncar un archivo que se ha abierto en modo de solo escritura (y eso es lo mismo para leer + escribir ) como si lo hiciera, los procesos que tenían descriptores de archivo abiertos en el archivo, dejarán caracteres NUL al comienzo del archivo (aquellos, excepto en OS / X, generalmente no ocupan espacio en el disco, se convierten en archivos dispersos).

En cambio (y notará que la mayoría de las aplicaciones lo hacen cuando escriben en archivos de registro), debe abrir el archivo en modo de adición :

cmd >> log

o

: > log && cmd >> log

si quieres comenzar en un archivo vacío.

En el modo de agregar, todas las escrituras se realizan al final del archivo, independientemente de dónde fue la última escritura:

$ exec 4>> log
$ printf aa >&4
$ printf x >> log
$ printf bb >&4
$ od -c log
0000000   a   a   x   b   b
0000005
$ : > log
$ printf cc >&4
$ od -c log
0000000   c   c
0000002

Eso también es más seguro, ya que si dos procesos han abierto (de esa manera) el archivo por error (como, por ejemplo, si ha iniciado dos instancias del mismo demonio), su salida no se sobrescribirá entre sí.

En las versiones recientes de Linux, puede verificar la posición actual y si se ha abierto un descriptor de archivo en modo de agregado mirando /proc/<pid>/fdinfo/<fd>:

$ cat /proc/self/fdinfo/4
pos:        2
flags:      0102001

O con:

$ lsof +f G -p "$$" -ad 4
COMMAND  PID USER   FD   TYPE  FILE-FLAG DEVICE SIZE/OFF     NODE NAME
zsh     4870 root    4w   REG 0x8401;0x0 252,18        2 59431479 /home/chazelas/log
~# lsof +f g -p "$$" -ad 4
COMMAND  PID USER   FD   TYPE FILE-FLAG DEVICE SIZE/OFF     NODE NAME
zsh     4870 root    4w   REG   W,AP,LG 252,18        2 59431479 /home/chazelas/log

Esos indicadores corresponden a los indicadores O ..._ pasados ​​a la openllamada al sistema.

$ gcc -E - <<< $'#include <fcntl.h>\nO_APPEND O_WRONLY' | tail -n1
02000 01

( O_APPENDes 0x400 u octal 02000)

Entonces, el shell >>abre el archivo con O_WRONLY|O_APPEND(y 0100000 aquí es O_LARGEFILE que no es relevante para esta pregunta) mientras >es O_WRONLYsolo (y <>es O_RDWRsolo).

Si haces un:

sudo lsof -nP +f g | grep ,AP

para buscar archivos abiertos O_APPEND, encontrará la mayoría de los archivos de registro actualmente abiertos para escribir en su sistema.

Stéphane Chazelas
fuente
¿Por qué usas el :(colon) en : > ?
mvorisek
1
@Mvorisek, eso es para redirigir la salida del comando que no produce ninguna salida: :. Sin un comando, el comportamiento varía entre conchas.
Stéphane Chazelas
1

Si lo entiendo correctamente, teeparece un enfoque razonable:

$ ./myapp-that-echoes-the-date-every-second | tee log > /dev/null &
[1] 20519
$ head log
Thu Apr  3 11:29:34 EDT 2014
Thu Apr  3 11:29:35 EDT 2014
Thu Apr  3 11:29:36 EDT 2014
$ > log
$ head log
Thu Apr  3 11:29:40 EDT 2014
Thu Apr  3 11:29:41 EDT 2014
Thu Apr  3 11:29:42 EDT 2014
obispo
fuente
1

Como solución rápida, puede usar un registro con rotación (rotación diaria, por ejemplo):

date=`date +%Y%m%d`
LOGFILE=/home/log$date.log

y redirigir el inicio de sesión ./my_app >> log$date.log

Charles nakhel
fuente
Me gustaría poder rotar a pedido. Esto es en realidad un registro que se produce durante una prueba automatizada y me gustaría borrarlo antes de ejecutar la prueba.
bangnab
0

Este es un problema que se ha resuelto durante mucho tiempo con syslog (en todas sus variantes), pero hay dos herramientas que resolverían su problema particular con un mínimo de esfuerzo.

La primera solución más portátil pero menos versátil es el registrador (imprescindible para cualquier caja de herramientas de administrador). Es una utilidad simple que copia la entrada estándar a syslog. (pasando el dinero y haciendo que la rotación de archivos sea el problema de logrotate y syslog)

La segunda solución, más elegante pero menos portátil, es syslog-ng, que además de aceptar mensajes de registro de los sockets estándar de syslog puede ejecutar programas cuya salida se filtra a través del registrador. (Todavía no he usado esta función, pero se ve perfecta para lo que quieres hacer).

hildred
fuente