Déplacer le fichier mais uniquement s'il est fermé

10

Je souhaite déplacer un gros fichier créé par un processus externe dès qu'il est fermé.

Cette commande de test est-elle correcte?

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

EDIT: le fichier représente un ensemble de données et je dois attendre qu'il soit terminé pour le déplacer afin qu'un autre programme puisse agir dessus. C'est pourquoi j'ai besoin de savoir si le processus externe se fait avec le fichier.

Peter Kovac
la source
3
Remarque: une fois qu'un fichier est ouvert, les processus utilisent des descripteurs de fichier et des données d'inode pour le manipuler. Changer le chemin (c'est-à-dire déplacer le fichier) ne causera pas trop de problèmes au processus.
John WH Smith
2
Avez-vous un contrôle sur le processus externe? Serait-il possible pour le processus externe de créer un fichier temporaire et de renommer le fichier une fois qu'il a fini d'y écrire?
Jenny D
@JennyD J'ai fait une enquête et il s'avère que c'est vrai. Je n'ai pas du lsoftout besoin de vérifier si l'extension de fichier ne l'est pas .tmp. Cela le rend trivial. Cependant, je suis content d'avoir posé ma question depuis que j'ai appris un peu sur lsofet inotifyet tout ça.
Peter Kovac
@PeterKovac J'ai aussi appris plus à leur sujet en lisant les réponses, donc je suis très heureux que vous l'ayez demandé.
Jenny D
@JohnWHSmith - Cela est normalement vrai si vous déplacez le fichier dans le même système de fichiers, s'il déplace le fichier vers un nouveau système de fichiers avant que l'écrivain ait fini d'y écrire, il perdra des données.
Johnny

Réponses:

11

Depuis la lsofpage de manuel

Lsof renvoie un (1) si une erreur a été détectée, y compris l'échec de la localisation des noms de commande, des noms de fichiers, des adresses ou des fichiers Internet, des noms de connexion, des fichiers NFS, des PID, des PGID ou des UID qu'il a été invité à répertorier. Si l'option -V est spécifiée, lsof indiquera les éléments de recherche qu'il n'a pas réussi à répertorier.

Cela suggère donc que votre lsof failed for some other reasonclause ne sera jamais exécutée.

Avez-vous essayé de simplement déplacer le fichier alors que votre processus externe l'a toujours ouvert? Si le répertoire de destination se trouve sur le même système de fichiers, cela ne devrait poser aucun problème, sauf si vous devez y accéder sous le chemin d'origine à partir d'un troisième processus, car l'inode sous-jacent restera le même. Sinon, je pense mvqu'il échouera de toute façon.

Si vous devez vraiment attendre que votre processus externe soit terminé avec le fichier, il est préférable d'utiliser une commande qui bloque au lieu de l'interrogation répétée. Sous Linux, vous pouvez l'utiliser inotifywaitpour cela. Par exemple:

 inotifywait -e close_write /path/to/file

Si vous devez utiliser lsof(peut-être pour la portabilité), vous pouvez essayer quelque chose comme:

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

Mettre à jour

Comme indiqué par @JohnWHSmith ci-dessous, la conception la plus sûre utiliserait toujours une lsofboucle comme ci-dessus car il est possible que plus d'un processus ait le fichier ouvert pour l'écriture (un exemple de cas peut être un démon d'indexation mal écrit qui ouvre les fichiers avec la lecture / write flag alors qu'il devrait vraiment être en lecture seule). inotifywaitpeut toujours être utilisé à la place du sommeil, remplacez simplement la ligne de sommeil par inotifywait -e close /path/to/file.

Graeme
la source
Merci, je n'étais pas au courant inotify. Malheureusement, il n'est pas installé sur ma boîte mais je suis sûr que je trouverai un paquet quelque part. Voir ma modification pour une raison pour laquelle j'ai besoin que le fichier soit fermé: c'est un ensemble de données et il doit être complet avant de le traiter plus loin.
Peter Kovac
1
Autre remarque: bien que inotifywaitle script ne puisse pas "poller" deux fois souvent, l'OP doit toujours vérifier lsofdans une boucle: si le fichier est ouvert deux fois, une fermeture pourrait déclencher l' inotifyévénement, même si le fichier n'est pas prêt à être manipulé (par exemple, dans votre dernier extrait de code, votre sleepappel pourrait être remplacé par inotifywait).
John WH Smith
@John a close_writedevrait être correct car un seul processus peut ouvrir le fichier en écriture à la fois. Il suppose qu'un autre ne l'ouvrira pas immédiatement après sa fermeture, mais le même problème existe avec l' lsofinterrogation.
Graeme
1
@Graeme Bien que cela puisse être vrai par conception dans le cas de l'OP, le noyau permet d'ouvrir un fichier deux fois pour l'écriture (auquel cas, il CLOSE_WRITEest déclenché deux fois).
John WH Smith
@John, mis à jour.
Graeme
4

Comme approche alternative, c'est le cas parfait pour un tuyau - le deuxième processus traitera la sortie du premier processus dès qu'il sera disponible, plutôt que d'attendre la fin du processus complet:

process1 input_file.dat | process2 > output_file.dat

Avantages:

  • Beaucoup plus rapide en général:
    • N'a pas besoin d'écrire et de lire sur le disque (cela peut être évité si vous utilisez un disque virtuel).
    • Devrait utiliser plus complètement les ressources de la machine.
  • Aucun fichier intermédiaire à retirer après la finition.
  • Aucun verrouillage complexe nécessaire, comme dans OP.

Si vous n'avez aucun moyen de créer directement un pipe mais que vous avez des coreutils GNU, vous pouvez utiliser ceci:

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

Cela commencera à lire le fichier d'entrée depuis le début, quelle que soit la distance parcourue par le premier processus lors de l'écriture du fichier (même s'il n'est pas encore commencé ou déjà terminé).

l0b0
la source
Ouais, ce serait la solution "évidente". Malheureusement, le processus de génération de données est hors de mon contrôle (géré par un autre utilisateur).
Peter Kovac
@PeterKovac Ce n'est pas pertinent: cat input_file.dat | process2 output_file.dat
MariusMatutiae
@MariusMatutiae mais catet process2pourrait terminer avant la process1fin. Ils ne bloqueraient pas.
cpugeniusmv