Mover archivo pero solo si está cerrado

10

Quiero mover un archivo grande creado por un proceso externo tan pronto como se cierre.

¿Es correcto este comando de prueba?

if lsof "/file/name"
then
        # file is open, don't touch it!
else
        if [ 1 -eq $? ]
        then
                # file is closed
                mv /file/name /other/file/name
        else
                # lsof failed for some other reason
        fi
fi

EDITAR: el archivo representa un conjunto de datos y tengo que esperar hasta que esté completo para moverlo para que otro programa pueda actuar sobre él. Es por eso que necesito saber si el proceso externo se realiza con el archivo.

Peter Kovac
fuente
3
Nota al margen: una vez que se abre un archivo, los procesos usan descriptores de archivo y datos de inodo para manipularlo. Cambiar la ruta (es decir, mover el archivo) no causará demasiados problemas al proceso.
John WH Smith
2
¿Tienes algún control sobre el proceso externo? ¿Sería posible para el proceso externo crear un archivo temporal y cambiarle el nombre una vez que haya terminado de escribirle?
Jenny D
@ JennyD Investigué un poco y resulta ser cierto. No necesito lsofnada, solo necesito verificar si la extensión del archivo no lo es .tmp. Eso lo hace trivial. Sin embargo, me alegro de haber hecho mi pregunta desde que aprendí un poco acerca de lsofy inotifyy esas cosas.
Peter Kovac
@PeterKovac También aprendí más sobre ellos, al leer las respuestas, así que estoy muy contento de que lo hayas preguntado.
Jenny D
@JohnWHSmith: eso normalmente es cierto si mueve el archivo dentro del mismo sistema de archivos, si mueve el archivo a un nuevo sistema de archivos antes de que el escritor haya terminado de escribir en él, perderá algunos datos.
Johnny

Respuestas:

11

Desde la lsofpágina del manual

Lsof devuelve uno (1) si se detectó algún error, incluida la imposibilidad de localizar nombres de comandos, nombres de archivos, direcciones de Internet o archivos, nombres de inicio de sesión, archivos NFS, PID, PGID o UID que se le pidió que enumerara. Si se especifica la opción -V, lsof indicará los elementos de búsqueda que no pudo enumerar.

Eso sugeriría que su lsof failed for some other reasoncláusula nunca se ejecutaría.

¿Has intentado mover el archivo mientras tu proceso externo todavía lo tiene abierto? Si el directorio de destino está en el mismo sistema de archivos, entonces no debería haber problemas para hacerlo a menos que necesite acceder a él bajo la ruta original desde un tercer proceso ya que el inodo subyacente seguirá siendo el mismo. De lo contrario, creo mvque fallará de todos modos.

Si realmente necesita esperar hasta que su proceso externo termine con el archivo, es mejor usar un comando que bloquee en lugar de sondear repetidamente. En Linux, puede usar inotifywaitpara esto. P.ej:

 inotifywait -e close_write /path/to/file

Si debe usar lsof(tal vez para portabilidad), podría intentar algo como:

until err_str=$(lsof /path/to/file 2>&1 >/dev/null); do
  if [ -n "$err_str" ]; then
    # lsof printed an error string, file may or may not be open
    echo "lsof: $err_str" >&2

    # tricky to decide what to do here, you may want to retry a number of times,
    # but for this example just break
    break
  fi

  # lsof returned 1 but didn't print an error string, assume the file is open
  sleep 1
done

if [ -z "$err_str" ]; then
  # file has been closed, move it
  mv /path/to/file /destination/path
fi

Actualizar

Como señaló @JohnWHSmith a continuación, el diseño más seguro siempre usaría un lsofbucle como el anterior, ya que es posible que más de un proceso tenga el archivo abierto para escribir (un caso de ejemplo puede ser un demonio de indexación mal escrito que abre archivos con la lectura / escribir bandera cuando realmente debería ser de solo lectura). inotifywaitsin embargo, todavía se puede usar en lugar de dormir, solo reemplace la línea de sueño con inotifywait -e close /path/to/file.

Graeme
fuente
Gracias, no estaba al tanto inotify. Desafortunadamente, no está instalado en mi caja, pero estoy seguro de que encontraré un paquete en alguna parte. Vea mi edición por una razón por la que necesito que se cierre el archivo: es un conjunto de datos y debe completarse antes de seguir procesándolo.
Peter Kovac
1
Otra nota al margen: si bien inotifywaitevitará que el script "sondee" dos veces, el OP aún debe verificar lsofen un bucle: si el archivo se abre dos veces, cerrar una vez podría desencadenar el inotifyevento, aunque el archivo no esté listo para ser manipulado (por ejemplo, en su último fragmento de código, su sleepllamada podría ser reemplazada por inotifywait).
John WH Smith
@John a close_writedebería estar bien, ya que solo un proceso puede tener el archivo abierto para escribir a la vez. Asume que otro no lo abrirá inmediatamente después de que se cierre, pero existe el mismo problema con el lsofsondeo.
Graeme
1
@Graeme Si bien esto podría ser cierto por diseño en el caso del OP, el kernel permite que un archivo se abra dos veces para escribir (en cuyo caso, CLOSE_WRITEse activa dos veces).
John WH Smith
@John, actualizado.
Graeme
4

Como enfoque alternativo, este es el caso perfecto para una tubería : el segundo proceso procesará la salida del primer proceso tan pronto como esté disponible, en lugar de esperar a que termine el proceso completo:

process1 input_file.dat | process2 > output_file.dat

Ventajas:

  • Mucho más rápido en general:
    • No tiene que escribir y leer desde el disco (esto se puede evitar si usa un disco RAM).
    • Debería usar los recursos de la máquina más completamente.
  • No hay archivo intermedio para eliminar después de terminar.
  • No es necesario un bloqueo complejo, como en OP.

Si no tiene forma de crear una tubería directamente pero tiene coreutils de GNU , puede usar esto:

tail -F -n +0 input_file.dat | process2 > output_file.dat

Esto comenzará a leer el archivo de entrada desde el principio, sin importar qué tan lejos esté el primer proceso a través de la escritura del archivo (incluso si aún no se ha iniciado o ya está terminado).

l0b0
fuente
Sí, esa sería la solución "obvia". Desafortunadamente, el proceso de generación de datos está fuera de mi control (ejecutado por otro usuario).
Peter Kovac
@PeterKovac Eso es irrelevante: cat input_file.dat | process2 output_file.dat
MariusMatutiae
@MariusMatutiae pero caty process2podrían terminar antes de que process1se haya terminado. No bloquearían.
cpugeniusmv