Come posso aspettare un file nello script della shell?


14

Sto provando a scrivere uno script shell che attenderà che un file appaia nella /tmpdirectory chiamata sleep.txte una volta trovato il programma cesserà, altrimenti voglio che il programma sia in uno stato di sospensione (sospeso) fino a quando il file non si trova . Ora suppongo che userò un comando di prova. Quindi qualcosa del genere

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

Sono nuovo di zecca nello scrivere shell script e ogni aiuto è molto apprezzato!


Guarda $MAILPATH.
Mikeserv,

Risposte:


22

Sotto Linux, è possibile utilizzare il sottosistema kernel inotify per attendere in modo efficiente la comparsa di un file in una directory:

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

(presupponendo Bash per la <()sintassi di reindirizzamento dell'output)

Il vantaggio di questo approccio rispetto al polling a intervalli di tempo fissi come in

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

è che il kernel dorme di più. Con una specifica di evento inotify come create,openlo script è appena programmato per l'esecuzione quando /tmpviene creato o aperto un file in . Con l'intervallo di tempo fisso di polling si sprecano i cicli della CPU per ogni incremento di tempo.

Ho incluso l' openevento per registrarmi anche touch /tmp/sleep.txtquando il file esiste già.


Grazie, è fantastico! Perché hai bisogno del ciclo while se conosci il nome del file? Perché non potrebbe essere stato inotifywait -e create,open --format '%f' --quiet /tmp/sleep.txt --monitor > /dev/null ; # continues once /tmp/sleep.txt is created/opened?
NHDaly,

Oh, jk. Dopo aver installato lo strumento ho capito ora. inotifywaitè esso stesso un ciclo while e genera una riga ogni volta che il file viene aperto / creato. Quindi è necessario che il ciclo while si interrompa quando viene modificato.
NHDaly,

tranne per il fatto che ciò produce una condizione di competizione, diversamente dalla versione test -e / sleep. Non esiste alcun modo per garantire che il file non verrà creato subito prima dell'inserimento del loop, nel qual caso l'evento verrà perso. Dovresti aggiungere qualcosa del tipo (sleep 1; [[-e /tmp/sleep.txt]] && touch /tmp/sleep.txt;) e prima del loop. Che, naturalmente, potrebbe creare problemi lungo la strada, a seconda della reale logica aziendale
n-alexander

@ n-alexander Bene, la risposta presuppone che tu esegua lo snippet prima di iniziare il processo che produrrà sleep.txtad un certo punto nel tempo. Pertanto, nessuna condizione di gara. La domanda non contiene alcun accenno a uno scenario che hai in mente.
maxschlepzig,

@maxschlepzig al contrario. A meno che non venga eseguito l'ordinamento, non può essere assunto. Nozioni di base.
n-alexander,

10

Metti il ​​tuo test in whileloop:

while [ ! -f /tmp/sleep.txt ]; do sleep 1; done
# next command

Quindi, come l'hai scritto, questa è la logica: mentre il file non esiste, lo script dormirà. Altrimenti, è fatto. Corretta?
Mo Gainz,

@MoGainz Correct. Il numero dopo sleepè il numero di secondi di attesa in ogni iterazione: puoi modificarlo in base alle tue esigenze.
jimmij,

7

Ci sono alcuni problemi con alcuni degli inotifywaitapprocci basati su dati forniti finora:

  • non riescono a trovare un sleep.txtfile che è stato creato prima come nome temporaneo e poi rinominato sleep.txt. Uno deve abbinare per moved_toeventi oltre acreate
  • i nomi dei file possono contenere caratteri di nuova riga, la stampa dei nomi dei file delimitati da nuova riga non è sufficiente per determinare se sleep.txtè stata creata una. Cosa succede se un foo\nsleep.txt\nbarfile è stato creato per esempio?
  • cosa succede se il file viene creato prima che inotifywait sia stato avviato e installato l'orologio? Quindi inotifywaitaspetterebbe per sempre un file che è già qui. Dovresti assicurarti che il file non sia già lì dopo l'installazione dell'orologio.
  • alcune soluzioni rimangono in inotifywaitesecuzione (almeno fino a quando non viene creato un altro file) dopo che il file è stato trovato.

Per risolverli, potresti fare:

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"
}

Tieni presente che stiamo cercando la creazione di sleep.txtnella directory corrente .(quindi cd /tmp || exitnel tuo esempio faresti un esempio). La directory corrente non cambia mai, quindi quando quella linea di pipe ritorna correttamente, sleep.txtè stata creata una nella directory corrente.

Naturalmente, è possibile sostituire .con /tmpsopra, ma mentre inotifywaitè in esecuzione, /tmpavrebbe potuto essere rinominato più volte (improbabile /tmp, ma qualcosa da considerare nel caso generale) o un nuovo filesystem montato su di esso, quindi quando la pipeline ritorna, potrebbe non essere un /tmp/sleep.txtche è stato creato ma un /new-name-for-the-original-tmp/sleep.txtinvece. Una nuova /tmpdirectory potrebbe anche essere stata creata nell'intervallo e quella non sarebbe stata guardata, quindi una sleep.txtcreata lì non sarebbe stata rilevata.


1

La risposta accettata funziona davvero (grazie a maxschlepzig) ma lascia il controllo inotify in background fino alla chiusura dello script. L'unica risposta che soddisfa esattamente i tuoi requisiti (cioè aspettando che sleep.txt compaia all'interno di / tmp) sembra essere quella di Stephane, se la directory che deve essere monitorata da inotifywait viene cambiata da punto (.) A '/ tmp'.

Tuttavia, se sei disposto a utilizzare una directory temporanea SOLO per mettere il tuo flag sleep.txt e puoi scommettere che nessun altro inserirà alcun file in quella directory, sarebbe sufficiente chiedere a inotifywait di guardare questa directory per la creazione di file:

1 ° passo: crea la directory che monitorerai:

directoryToPutSleepFile=$(mktemp -d)

2 ° passo: assicurati che la directory sia davvero lì

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

3 ° passaggio: attendere fino a quando TUTTO il file non viene visualizzato all'interno $directoryToPutSleepFile

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

Il file che $directoryToPutSleepFileinserirai può essere chiamato sleep.txt awake.txt, qualunque cosa. Nel momento in cui un file viene creato all'interno dello $directoryToPutSleepFilescript continuerà oltre l' inotifywaitistruzione.


1
Ma ciò potrebbe perdere la creazione del file, se ne fosse creato un altro poco prima, causando la sleep.txtcreazione tra due invocazioni inotifywait.
Stéphane Chazelas,

vedi la mia risposta per un modo alternativo di affrontarlo.
Stéphane Chazelas,

Per favore prova il mio codice. inotifywait viene invocato una sola volta. Quando viene creato sleep.txt (o letto / scritto se già presente), inotify attende output "sleep.txt" e l'esecuzione continua dopo l'istruzione 'done'.
Nikolaos,

Viene richiamato più volte fino a quando non sleep.txtviene creato un file chiamato . Prova ad esempio touch /tmp/foo /tmp/sleep.txt, quindi un'istanza di inotifywaitriporterà fooe uscirà , e quando verrà avviata la successiva inotifywait, sleep.txt sarà già lì da tempo, quindi inotifywait non rileverà la sua creazione.
Stéphane Chazelas,

Avevi ragione sulle invocazioni multiple: una chiamata per ogni file creato in / tmp. Ho aggiornato la mia risposta. Grazie per il tuo commento.
Nikolaos,

0

in generale è abbastanza difficile rendere affidabile solo il semplice ciclo test / sleep. Il problema principale è che il test correrà con la creazione del file - a meno che il test non venga ripetuto, l'evento create può essere perso. Questa è di gran lunga la migliore scommessa nella maggior parte dei casi in cui useresti Shell per iniziare:

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

Se ritieni che ciò non sia sufficiente a causa di problemi di prestazioni, l'utilizzo di Shell probabilmente non è una buona idea in primo luogo.


2
Questo è solo un duplicato della risposta di Jimmij .
Maxschlepzig,

0

Sulla base della risposta accettata di maxschlepzig (e di un'idea della risposta accettata su /superuser/270529/monitoring-a-file-until-a-string-is-found ), propongo di migliorare quanto segue ( a mio avviso) risposta che può funzionare anche con il 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

Nel caso in cui il file non venga creato / aperto entro il timeout specificato, lo stato di uscita è 124 (come da documentazione di timeout (pagina man)). Nel caso in cui venga creato / aperto, lo stato di uscita è 0 (successo).

Sì, inotifywait viene eseguito in una sub-shell in questo modo e tale sub-shell terminerà l'esecuzione solo quando si verifica il timeout o quando termina lo script principale (a seconda dell'evento che si verifica per primo).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.