Determinar si el archivo está en proceso de ser escrito?

25

Necesito implementar un proceso automatizado (a través de un script cron de 1 min) que busca archivos tar en un directorio específico. Si se encuentra un archivo tar, no está marcado en la ubicación adecuada y luego se elimina el archivo tar.

Los archivos tar se copian automáticamente a este servidor a través de SSH desde otro servidor. En algunos casos, los archivos tar son extremadamente grandes, con muchos archivos.

El problema con el que espero encontrarme: si el archivo tar tarda más de 1 minuto en copiarse en el servidor y el script cron se ejecuta una vez cada minuto, verá el archivo .tar.gz e intentará hacerlo descomprimirlo, a pesar de que el archivo tar todavía está en proceso de ser escrito.

¿Hay alguna forma (a través de comandos bash) de probar si un archivo se está escribiendo actualmente, o si es solo un archivo parcial, etc.?

Una alternativa en la que estaba pensando era copiar el archivo como una extensión de archivo diferente (como .tar.gz.part) y luego cambiarle el nombre una .tar.gzvez completada la transferencia. Pero pensé que intentaría averiguar si simplemente hay una manera de determinar si el archivo está completo en la línea de comando primero ... ¿Alguna pista?

Jake Wilson
fuente
2
¿Cómo se transfiere exactamente el archivo? Por ejemplo, rsyncusa un nombre de archivo temporal durante la transferencia (por defecto), y solo después de que el archivo se haya transferido por completo, cambia el nombre al nombre de archivo real.
Piskvor

Respuestas:

12

Está en el camino correcto, renombrar el archivo es una operación atómica, por lo que realizar el cambio de nombre después de la carga es simple, elegante y no propenso a errores. Otro enfoque que se me ocurre es utilizar lsof | grep filename.tar.gzpara verificar si otro proceso está accediendo al archivo.

Alex
fuente
77
( lsof filename.tar.gzes más eficiente y más preciso que lsof | grep filename.tar.gz)
Rico
Por cierto, debería ser una ruta absoluta de nombre de archivo
DennisLi
14

Su mejor opción es usar lsofpara determinar si un archivo ha sido abierto por algún proceso:

#  lsof -f -- /var/log/syslog
COMMAND   PID   USER   FD   TYPE DEVICE SIZE/OFF  NODE NAME
rsyslogd 1520 syslog    1w   REG  252,2    72692 16719 /var/log/syslog

No se puede saber fácilmente si se está escribiendo, pero si se está escribiendo, DEBE estar abierto.


Editar: ¡resolvamos el problema real aquí en lugar de intentar implementar la solución propuesta!

Use rsync para transferir el archivo:

  rsync -e ssh remote:big.tar.gz .

De esta manera, el archivo no se copiará sobre el archivo existente, sino que se copiará en un archivo temporal ( .big.tar.gz.XXXXXX) hasta que se complete la transferencia, luego se moverá a su lugar.

MikeyB
fuente
6

Un poco viejo, pero la mayoría de las respuestas pierde por completo el punto de la pregunta:

Pero pensé que intentaría averiguar si simplemente hay una manera de determinar si el archivo está completo en la línea de comando primero ...

En general, no lo hay. Simplemente no tiene suficiente información para determinar eso.

Porque determinar que el archivo está cerrado no es lo mismo que determinar si el archivo está completo . Por ejemplo, un archivo se "cerrará" si la conexión se pierde durante la transferencia.

Solo la respuesta de @ Alex acertó. E incluso él se enamoró de usar lsofalgo.

Para determinar si el archivo se ha realizado completamente, la transferencia exitosa requiere más datos. Como:

Una alternativa en la que estaba pensando era copiar el archivo como una extensión de archivo diferente (como .tar.gz.part) y luego cambiarle el nombre una .tar.gzvez completada la transferencia.

Esa es una forma perfecta de comunicar que el archivo se ha transferido completa y exitosamente. También puede mover archivos de un directorio a otro siempre que permanezca dentro del mismo sistema de archivos. O haga que el remitente envíe un filename.donearchivo vacío para indicar la finalización.

Pero todos los métodos tienen que depender de que el remitente indique de alguna manera que la transferencia se ha completado con éxito. Porque solo el remitente tiene esa información.

Algunos formatos de archivo (como PDF) tienen datos que le permiten determinar si el archivo está completo. Pero tienes que abrir y leer casi todo el archivo para descubrirlo.

lsofsolo le dirá que el archivo ya no está abierto; no le dirá por qué ya no está abierto. Tampoco le dirá qué tan grande se supone que debe ser el archivo.

Andrew Henle
fuente
1
No puedo votar esto lo suficiente. Buen trabajo resolviendo el problema XY aquí.
Beefster
5

La mejor manera de hacer esto es usar incron ("sistema cron inotify"). Le permite configurar un reloj inotify en un directorio que luego le notificará de las operaciones de archivo. En este caso, debe mirar el directorio para un close_write. Eso le permitirá ejecutar su comando una vez que el archivo se cerró después de una escritura.

Kyle
fuente
2

Parece que lsof puede detectar en qué modo está abierto un archivo:

lsof -f -- a_file
COMMAND   PID  USER   FD   TYPE DEVICE SIZE/OFF     NODE NAME
cat     52391 bob    1w   REG    1,2       15 19545007 a_file

¿Ves donde dice 1w? Eso significa que el número de descriptor de archivo es 1 y el modo es w o write.

Kevin Baragona
fuente
El FDcampo se muestra 3rpara mí cuando el archivo está abierto para lectura.
Sopalajo de Arrierez
0

El uso inotifywaitpuede lograr lo que busca: tiene la capacidad de esperar hasta que finalice la escritura de un archivo antes de ejecutar un comando.

Lo siguiente observará continuamente una carpeta en busca de nuevos archivos y ejecutará el comando en el bucle cuando haya finalizado la escritura en el archivo.

WATCH_DIR=/directory/to/monitor
DEST_DIR=/x/y/z

/usr/bin/inotifywait --recursive --monitor --quiet -e moved_to -e close_write --format '%w%f' "$WATCH_DIR" | while read -r INPUT_FILE; do

mv "$0" "$DEST_DIR"

done

Para obtener más opciones de configuración, consulte https://linux.die.net/man/1/inotifywatch

teeedubb
fuente