Déterminer si le fichier est en cours d'écriture?

25

J'ai besoin de déployer un processus automatisé (via un script cron de 1 min) qui recherche les fichiers tar dans un répertoire spécifique. Si un fichier tar est trouvé, il n'est pas marqué à l'emplacement approprié, puis le fichier tar est supprimé.

Les fichiers tar sont automatiquement copiés sur ce serveur via SSH à partir d'un autre serveur. Dans certains cas, les fichiers tar sont extrêmement volumineux, avec beaucoup de fichiers.

Le problème que je m'attends à rencontrer: s'il faut> 1 minute pour que le fichier tar soit copié sur le serveur, et que le script cron s'exécute une fois par minute, il va voir le fichier .tar.gz et essayer de le faire décompressez-le, même si le fichier tar est toujours en cours d'écriture.

Existe-t-il un moyen (via les commandes bash) de tester si un fichier est en cours d'écriture, ou s'il ne s'agit que d'un fichier partiel, etc.?

Une alternative à laquelle je pensais était de faire copier le fichier sous une extension de fichier différente (comme .tar.gz.part) puis de le renommer une .tar.gzfois le transfert terminé. Mais je me suis dit que j'essaierais de déterminer s'il existe simplement un moyen de déterminer si le fichier est entier sur la ligne de commande en premier ... Des indices?

Jake Wilson
la source
2
Comment le fichier est-il transféré exactement? Par exemple, rsyncutilise un nom de fichier temporaire pendant le transfert (par défaut) et uniquement après le transfert complet du fichier, le renomme le nom de fichier réel.
Piskvor

Réponses:

12

Vous êtes sur la bonne voie, renommer le fichier est une opération atomique, donc effectuer le changement de nom après le téléchargement est simple, élégant et sans risque d'erreur. Une autre approche à laquelle je pense est d'utiliser lsof | grep filename.tar.gzpour vérifier si le fichier est accédé par un autre processus.

Alex
la source
7
( lsof filename.tar.gzest plus efficace et plus précis que lsof | grep filename.tar.gz)
Rich
BTW, ce devrait être un chemin absolu de nom de fichier
DennisLi
14

Votre meilleur pari est d'utiliser lsofpour déterminer si un fichier a été ouvert par un processus:

#  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

Vous ne pouvez pas facilement dire s'il est en cours d'écriture, mais s'il est en cours d'écriture, il DOIT être ouvert.


Edit: résolvons le problème réel ici plutôt que d'essayer de mettre en œuvre la solution proposée!

Utilisez rsync pour transférer le fichier:

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

De cette façon, le fichier ne sera pas copié par-dessus celui existant mais copié dans un fichier temporaire ( .big.tar.gz.XXXXXX) jusqu'à ce que le transfert soit terminé, puis déplacé en place.

MikeyB
la source
6

Un peu vieux, mais la plupart des réponses manquent complètement le point de la question:

Mais je me suis dit que j'essaierais de déterminer s'il existe simplement un moyen de déterminer si le fichier est entier sur la ligne de commande en premier ...

En général, il n'y en a pas. Vous n'avez tout simplement pas suffisamment d'informations pour le déterminer.

Parce que déterminer que le fichier est fermé n'est pas la même chose que déterminer si le fichier est entier . Par exemple, un fichier sera "fermé" si la connexion est perdue au cours du transfert.

Seule la réponse d'Alex @ a donné raison. Et même il est tombé pour lsofquelque chose.

Pour déterminer si le fichier a été entièrement transféré avec succès, il faut plus de données. Tel que:

Une alternative à laquelle je pensais était de faire copier le fichier sous une extension de fichier différente (comme .tar.gz.part) puis de le renommer une .tar.gzfois le transfert terminé.

C'est une excellente façon de communiquer que le fichier a été entièrement et correctement transféré. Vous pouvez également déplacer des fichiers d'un répertoire à un autre tant que vous restez dans le même système de fichiers. Ou demandez à l'expéditeur d'envoyer un filename.donefichier vide pour signaler la fin.

Mais toutes les méthodes doivent s'appuyer sur l'expéditeur pour signaler que le transfert s'est terminé avec succès. Parce que seul l'expéditeur dispose de ces informations.

Certains formats de fichiers (tels que les fichiers PDF) contiennent des données qui vous permettent de déterminer si le fichier est complet. Mais vous devez ouvrir et lire à peu près tout le fichier pour le découvrir.

lsofvous dira simplement que le fichier n'est plus ouvert - il ne vous dira pas pourquoi il n'est plus ouvert. Il ne vous indiquera pas non plus la taille du fichier.

Andrew Henle
la source
1
Je ne peux pas voter assez. Bon travail pour résoudre le problème XY ici.
Beefster
5

La meilleure façon de procéder est d'utiliser incron ("inotify cron system"). Il vous permet de définir une veille inotify sur un répertoire qui vous informera ensuite des opérations sur les fichiers. Dans ce cas, vous devez regarder le répertoire pour un close_write. Cela vous permettra ensuite d'exécuter votre commande une fois le fichier fermé après une écriture.

Kyle
la source
2

Il semble que lsof puisse détecter sous quel mode un fichier est ouvert sous:

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

Vous voyez où il est écrit 1w? Cela signifie que le numéro de descripteur de fichier est 1 et que le mode est w, ou écriture.

Kevin Baragona
la source
Le FDchamp indique 3rpour moi lorsque le fichier est ouvert à la lecture.
Sopalajo de Arrierez
0

L'utilisation inotifywaitpeut réaliser ce que vous recherchez - elle a la capacité d'attendre la fin de l'écriture d'un fichier avant d'exécuter une commande.

Ce qui suit surveillera en continu un dossier pour les nouveaux fichiers et exécutera la commande dans la boucle lorsque l'écriture dans le fichier est terminée.

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

Pour plus d'options de configuration, voir https://linux.die.net/man/1/inotifywatch

teeedubb
la source