Comment attendre un fichier dans le script shell?

14

J'essaie d'écrire un script shell qui attendra qu'un fichier apparaisse dans le /tmprépertoire appelé sleep.txtet une fois qu'il est trouvé, le programme cessera, sinon je veux que le programme soit en veille (suspendu) jusqu'à ce que le fichier soit localisé . Maintenant, je suppose que j'utiliserai une commande de test. Donc, quelque chose comme

(if [ -f "/tmp/sleep.txt" ]; 
then stop 
   else sleep.)

Je suis nouveau dans l'écriture de scripts shell et toute aide est grandement appréciée!

Mo Gainz
la source
Regardez $MAILPATH.
mikeserv

Réponses:

22

Sous Linux, vous pouvez utiliser le sous-système du noyau inotify pour attendre efficacement l'apparition d'un fichier dans un répertoire:

while read i; do if [ "$i" = sleep.txt ]; then break; fi; done \
   < <(inotifywait  -e create,open --format '%f' --quiet /tmp --monitor)
# script execution continues ...

(en supposant Bash pour la <()syntaxe de redirection de sortie)

L'avantage de cette approche par rapport à l'interrogation à intervalle de temps fixe comme dans

while [ ! -f /tmp/sleep.txt ]; do sleep 1; done
# script execution continues ...

c'est que le noyau dort plus. Avec une spécification d'événement inotify commecreate,open l'exécution du script est simplement planifiée lorsqu'un fichier sous /tmpest créé ou ouvert. Avec l'interrogation d'intervalle de temps fixe, vous perdez des cycles de processeur pour chaque incrément de temps.

J'ai inclus l' openévénement pour m'inscrire également touch /tmp/sleep.txtlorsque le fichier existe déjà.

maxschlepzig
la source
Merci, c'est génial! Pourquoi avez-vous besoin de la boucle while si vous connaissez le nom du fichier? Pourquoi cela ne pouvait-il pas simplement l'être inotifywait -e create,open --format '%f' --quiet /tmp/sleep.txt --monitor > /dev/null ; # continues once /tmp/sleep.txt is created/opened?
NHDaly
Oh, jk. Après avoir installé l'outil, je comprends maintenant. inotifywaitest lui-même une boucle while, et il génère une ligne chaque fois que le fichier est ouvert / créé. Vous avez donc besoin de la boucle while pour rompre lorsqu'elle est modifiée.
NHDaly
sauf que cela produit une condition de concurrence - contrairement à la version test -e / sleep. Il n'y a aucun moyen de garantir que le fichier ne sera pas créé juste avant l'entrée dans la boucle - auquel cas l'événement sera manqué. Vous devez ajouter quelque chose comme (sleep 1; [[-e /tmp/sleep.txt]] && touch /tmp/sleep.txt;) & avant la boucle. Ce qui bien sûr pourrait créer des problèmes en cours de route, en fonction de la logique métier réelle
n-alexander
@ n-alexander Eh bien, la réponse suppose que vous exécutez l'extrait de code avant de démarrer le processus qui se produira sleep.txtà un moment donné. Ainsi, aucune condition de concurrence. La question ne contient aucun élément faisant allusion à un scénario que vous avez en tête.
maxschlepzig
@maxschlepzig au contraire. À moins que la commande ne soit exécutée, elle ne peut être assumée. Les bases.
n-alexander
10

Mettez votre test dans la whileboucle:

while [ ! -f /tmp/sleep.txt ]; do sleep 1; done
# next command
jimmij
la source
Donc, comme vous l'avez écrit, c'est la logique: alors que le fichier n'existe pas, le script va dormir. Sinon, c'est fait. Correct?
Mo Gainz
@MoGainz Correct. Le nombre après sleepest le nombre de secondes à attendre à chaque itération - vous pouvez changer cela en fonction de vos besoins.
jimmij
7

Il y a quelques problèmes avec certaines des inotifywaitapproches basées sur les données données jusqu'à présent:

  • ils ne parviennent pas à trouver un sleep.txtfichier qui a été créé d'abord en tant que nom temporaire, puis renommé en sleep.txt. Il faut correspondremoved_to événements en plus decreate
  • les noms de fichiers peuvent contenir des caractères de nouvelle ligne, l'impression des noms des fichiers délimités par la nouvelle ligne n'est pas suffisante pour déterminer si un sleep.txta été créé. Que faire si un foo\nsleep.txt\nbarfichier a été créé par exemple?
  • que faire si le fichier est créé avant le inotifywait démarrage et l'installation de la montre? Ensuite , inotifywaitattendrait toujours pour un fichier qui est déjà là. Vous devez vous assurer que le fichier n'est pas déjà là après l' installation de la montre.
  • certaines solutions restent en inotifywaitcours d'exécution (au moins jusqu'à ce qu'un autre fichier soit créé) une fois le fichier trouvé.

Pour y remédier, vous pouvez faire:

sh -c 'echo "$$" &&
        LC_ALL=C exec inotifywait -me create,moved_to --format=/%f/ . 2>&1' | {
  IFS= read pid &&
    while IFS= read -r line && [ "$line" != "Watches established." ]; do
      : wait for watches to be established
    done
  [ -e sleep.txt ] || [ -L sleep.txt ] || grep -qxF /sleep.txt/ && kill "$pid"
}

Notez que nous attendons la création de sleep.txtdans le répertoire courant, .(vous feriez donc un cd /tmp || exitavant dans votre exemple). Le répertoire en cours ne change jamais, donc quand ce pipeline revient avec succès, c'est unsleep.txt dans le répertoire en cours qui a été créé.

Bien sûr, vous pouvez remplacer .par /tmpci-dessus, mais pendant qu'il inotifywaitest en cours d'exécution, il /tmppourrait avoir été renommé plusieurs fois (peu probable /tmp, mais quelque chose à considérer dans le cas général) ou un nouveau système de fichiers monté dessus, donc lorsque le pipeline revient, il peut ne pas être un /tmp/sleep.txtqui a été créé mais à la /new-name-for-the-original-tmp/sleep.txtplace. Un nouveau /tmprépertoire aurait également pu être créé dans l'intervalle et celui-là ne serait pas surveillé, donc un sleep.txtcréé là-bas ne serait pas détecté.

Stéphane Chazelas
la source
1

La réponse acceptée fonctionne vraiment (merci maxschlepzig) mais laisse la surveillance inotifywait en arrière-plan jusqu'à la fin de votre script. La seule réponse qui correspond exactement vos besoins (c'est-à-dire attendre que sleep.txt apparaisse dans / tmp) semble être celle de Stéphane, si le répertoire à surveiller par inotifywait passe de dot (.) À '/ tmp'.

Cependant, si vous souhaitez utiliser UNIQUEMENT un répertoire temporaire pour placer votre indicateur sleep.txt et pouvez parier que personne d'autre ne mettra aucun fichier dans ce répertoire, il suffit de demander à inotifywait de regarder ce répertoire pour les créations de fichiers:

1ère étape: créez le répertoire que vous allez surveiller:

directoryToPutSleepFile=$(mktemp -d)

2ème étape: assurez-vous que le répertoire est vraiment là

until [ -d $directoryToPutSleepFile ]; do sleep 0.1; done

3ème étape: attendez que TOUT fichier apparaisse à l'intérieur $directoryToPutSleepFile

inotifywait -e create --format '%f' --quiet $directoryToPutSleepFile

Le fichier que vous mettrez $directoryToPutSleepFilepeut s'appeler sleep.txt awake.txt, peu importe. Le moment où un fichier est créé dans $directoryToPutSleepFilevotre script continuera après l' inotifywaitinstruction.

nikolaos
la source
1
Mais cela pourrait manquer la création du fichier, si un autre a été créé juste avant, provoquant la sleep.txtcréation entre deux appels inotifywait.
Stéphane Chazelas
voir ma réponse pour une manière alternative de l'aborder.
Stéphane Chazelas
Veuillez essayer mon code. inotifywait n'est invoqué qu'une seule fois. Lorsque sleep.txt est créé (ou lu / écrit s'il existe déjà), inotifywait affiche "sleep.txt" et l'exécution se poursuit après l'instruction "done".
nikolaos
Il sleep.txtest appelé plusieurs fois jusqu'à ce qu'un fichier appelé soit créé. Essayez par exemple touch /tmp/foo /tmp/sleep.txt, puis une instance de inotifywaitrapportera fooet quittera, et au moment où le prochain inotifywait sera démarré, le sleep.txt sera déjà là depuis longtemps donc inotifywait ne détectera pas sa création.
Stéphane Chazelas
Vous aviez raison sur les appels multiples: un appel pour chaque fichier créé sous / tmp. J'ai mis à jour ma réponse. Merci pour votre commentaire.
nikolaos
0

en général, il est assez difficile de faire autre chose que la simple boucle de test / sommeil fiable. Le problème principal est que le test va courir avec la création du fichier - à moins que le test ne soit répété, l'événement create peut être manqué. C'est de loin votre meilleur pari dans la plupart des cas où vous utiliseriez Shell pour commencer:

#!/bin/bash
while [[ ! -e /tmp/file ]] ; do
    sleep 1
done

Si vous trouvez que cela est insuffisant en raison de problèmes de performances, l'utilisation de Shell n'est probablement pas une bonne idée en premier lieu.

n-alexander
la source
2
Ceci est juste un double de la réponse de jimmij .
maxschlepzig
0

Sur la base de la réponse acceptée de maxschlepzig (et d'une idée de la réponse acceptée sur /superuser/270529/monitoring-a-file-until-a-string-is-found ), je propose ce qui suit amélioré ( à mon avis) réponse qui peut également fonctionner avec timeout:

# Enable pipefail, so if the left side of the pipe fails it does not get silently ignored
set -o pipefail
( timeout 120 inotifywait -e create,open --format '%f' --quiet /tmp --monitor & ) | while read i; do if [ "$i" == 'sleep.txt' ]; then break; fi; done
EXIT_STATUS=$?
if [ "${EXIT_STATUS}" == '124' ]; then
  echo "Timeout happened"
fi

Si le fichier n'est pas créé / ouvert dans le délai imparti, l'état de sortie est 124 (selon la documentation du délai (page de manuel)). Dans le cas où il est créé / ouvert, l'état de sortie est 0 (succès).

Oui, inotifywait s'exécute dans un sous-shell de cette façon et ce sous-shell ne se terminera que lorsque le délai d'expiration se produit ou lorsque le script principal se termine (selon la première éventualité).

Attila123
la source