Cómo mantener las últimas 50 líneas en el archivo de registro

22

Intento mantener las últimas 50 líneas en mi archivo donde guardo la temperatura cada minuto. Usé este comando:

tail -n 50 /home/pi/Documents/test > /home/pi/Documents/test

Pero el resultado es un archivo de prueba vacío. Pensé, enumerará las últimas 50 líneas del archivo de prueba y lo insertará en el archivo de prueba. Cuando uso este comando:

tail -n 50 /home/pi/Documents/test > /home/pi/Documents/test2

Está funcionando bien. Hay 50 líneas en el archivo test2.

¿Alguien puede explicarme dónde está el problema?

dorinand
fuente
2
Algo como rrdtool puede ser más apropiado para mantener N registros (entre otras estadísticas) a lo largo del tiempo.
thrig
Ver también unix.stackexchange.com/a/147620/117549
Jeff Schaller
2
problema clásico de truncamiento
haylem
Si está utilizando Python para generar sus registros, debe buscar en el loggingmódulo
Wayne Werner

Respuestas:

30

El problema es que su shell está configurando la canalización de comandos antes de ejecutar los comandos. No es una cuestión de "entrada y salida", es que el contenido del archivo ya se ha ido antes de que se ejecute la cola. Va algo como:

  1. El shell abre el >archivo de salida para escribir, truncándolo
  2. El shell se configura para que el descriptor de archivo 1 (para stdout) se use para esa salida
  3. El shell se ejecuta tail.
  4. tailcorre, abre /home/pi/Documents/testy no encuentra nada allí

Hay varias soluciones, pero la clave es comprender el problema, qué está realmente mal y por qué.

Esto producirá lo que estás buscando,

echo "$(tail -n 50 /home/pi/Documents/test)" > /home/pi/Documents/test

Explicacion:

  • $() se llama sustitución de comando que ejecuta tail -n 50 /home/pi/Documents/test
  • las comillas conservan los saltos de línea en la salida.
  • > /home/pi/Documents/testredirige la salida echo "$(tail -n 50 /home/pi/Documents/test)"al mismo archivo.
Rahul
fuente
¡Gracias, funciona bien! Tengo una pregunta más. ¿Podría explicar cómo funciona su procedimiento paso a paso?
dorinand
1
Pero, ¿por qué en su caso bash no realiza> primero? No entiendo cómo bash procesa el comando. ¿Alguien puede explicar?
dorinand
1
El> está en el comando echo, por lo que se ejecuta cuando el comando echo comienza a ejecutarse. No puede comenzar la ejecución antes de que se escriba. La sustitución de variables es lo que escribe el comando. Ejecuta el comando anidado y crea el comando echo sustituyendo el valor.
jobermark
Cuando intenté usar lo mismo para el archivo de registro de 44 gb con 5000 líneas en lugar de 50, recibí un errorbash: xrealloc: cannot allocate 18446744071562067968 bytes
Carmageddon el
8

Otra solución para la redirección de archivos que borra el archivo primero es usarla spongedesde el moreutilspaquete de esta manera:

tail -n 50 /home/pi/Documents/test | sponge /home/pi/Documents/test
yoder
fuente
6

Esto se debe a que bash procesa la redirección con la >primera, eliminando el contenido del archivo. Luego ejecuta el comando. Si usara >>, las últimas 50 líneas se agregarían al final de lo que está actualmente en el archivo. En este caso, tendría las mismas 50 líneas repetidas dos veces.

El comando funciona como se espera al redirigir a un archivo diferente. Aquí hay una forma de escribir las últimas 50 líneas de un archivo en un archivo con el mismo nombre:

tail -50 /home/pi/Documents/test > /home/pi/Documents/test2 && mv /home/pi/Documents/test2 /home/pi/Documents/test

Esto primero escribe las últimas 50 líneas en un archivo temporal, que luego se mueve mvpara reemplazar el archivo original.

Como se señaló en los comentarios, esto no funcionará si el archivo aún está abierto. Mover el archivo también crea un nuevo inodo y puede cambiar la propiedad y los permisos. Una mejor manera de hacer esto usando un archivo temporal sería:

tail -50 /home/pi/Documents/test > /home/pi/Documents/test2 ; cat /home/pi/Documents/test2 > /home/pi/Documents/test

El archivo temporal también se puede eliminar, aunque cada vez que esto ocurra se sobrescribirá su contenido.

clk
fuente
gracias. ¿Podría explicarme paso a paso qué ejecución de bash? No puedo imaginar cómo funciona.
dorinand
tail -50 /home/pi/Documents/test >/tmp/foo && cat /tmp/foo >/home/pi/Documents/test
steve
1
tenga en cuenta que esto no funcionará si el archivo de registro aún está abierto por el proceso de registro (que continuará iniciando sesión en el archivo original eliminado). tempfile + move da como resultado un nuevo inodo (rompiendo cualquier enlace duro) y posiblemente diferentes propietarios o permisos. tail ... > temp ; cat temp > orig ; rm -f temptrabajos.
cas
4

Como ha visto el problema principal con la redirección de shell, aquí hay una forma alternativa de podar un archivo a sus últimas 50 líneas:

file=/path/to/the/file
n=$(( $(wc -l < "$file") - 50 ))
[[ $n -gt 0 ]] && sed -i 1,${n}d "$file"

El trabajo duro lo realiza (GNU) sed con la -ifunción de "edición en el lugar", que funciona bajo las cubiertas al crear la salida en un archivo temporal. El resto de las líneas configuran las matemáticas para la operación de sed, a saber:

  1. cuente las líneas en el archivo ( wc), luego reste 50; asignar eso a n.
  2. si n es positivo, ejecute el comando sed para eliminar las líneas 1 a n.
Jeff Schaller
fuente
4
printf '%s\n' '1,$-50d'   w | ed -s /home/pi/Documents/tes

printfse utiliza para canalizar comandos (uno por línea) en ed. Los edcomandos son:

  • 1,$-50d - Eliminar todas las últimas 50 líneas
  • w - escribir el archivo modificado de nuevo en el disco

No hay redirecciones involucradas, por lo que el shell no puede sobrescribir el archivo de salida antes de que se haya leído.

Además, a diferencia de la mayoría de las formas de edición "in situ" (que generalmente solo simulan la edición "in situ" creando un archivo temporal y luego renombrándolo sobre el original), en edrealidad edita el archivo original, por lo que mantiene el mismo inodo ( y propietario, grupo y permisos: tempfile + mv siempre cambiará el inodo y puede cambiar los demás según las circunstancias).

cas
fuente
4

En una pista ligeramente diferente, puede usar logrotate(8)para hacer copias de seguridad de los archivos de registro regularmente en archivos con nombres incrementales y luego eliminar los antiguos.

Así es como se gestionan los archivos de registro del sistema principal para evitar que crezcan demasiado.

CSM
fuente