Wie warte ich auf eine Datei im Shell-Skript?

14

Ich versuche, ein Shell-Skript zu schreiben, das darauf wartet, dass eine Datei in dem /tmpaufgerufenen Verzeichnis angezeigt wird, sleep.txtund sobald es gefunden wird, wird das Programm beendet . Nun gehe ich davon aus, dass ich einen Testbefehl verwenden werde. So etwas wie

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

Ich bin brandneu im Schreiben von Shell-Skripten und jede Hilfe wird sehr geschätzt!

Mo Gainz
quelle
Schau mal $MAILPATH.
mikeserv

Antworten:

22

Unter Linux können Sie das inotify- Kernel-Subsystem verwenden, um effizient auf das Erscheinen einer Datei in einem Verzeichnis zu warten:

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

(unter der Annahme von Bash für die <()Ausgabeumleitungssyntax)

Der Vorteil dieses Ansatzes im Vergleich zum festen Zeitintervall Polling wie in

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

ist, dass der Kernel mehr schläft. Bei einer inotify-Ereignisspezifikation create,openist die Ausführung des Skripts nur dann geplant, wenn eine Datei unter /tmperstellt oder geöffnet wird. Mit dem festen Zeitintervall-Polling verschwenden Sie CPU-Zyklen für jedes Zeitinkrement.

Ich habe das openEreignis aufgenommen, um mich auch zu registrieren, touch /tmp/sleep.txtwenn die Datei bereits existiert.

maxschlepzig
quelle
Danke, das ist großartig! Warum brauchen Sie die while-Schleife, wenn Sie den Namen der Datei kennen? Warum konnte das nicht einfach so gewesen sein inotifywait -e create,open --format '%f' --quiet /tmp/sleep.txt --monitor > /dev/null ; # continues once /tmp/sleep.txt is created/opened?
NHDaly
Oh, jk. Nach der Installation des Tools verstehe ich jetzt. inotifywaitist selbst eine while-Schleife und gibt bei jedem Öffnen / Erstellen der Datei eine Zeile aus. Die while-Schleife muss also unterbrochen werden, wenn sie geändert wird.
NHDaly
mit der Ausnahme, dass dies - anders als bei der Test-e / Sleep-Version - zu einer Race-Bedingung führt. Es kann nicht garantiert werden, dass die Datei nicht direkt vor dem Eintritt in die Schleife erstellt wird. In diesem Fall wird das Ereignis übersehen. Sie müssten etwas wie (sleep 1; [[-e /tmp/sleep.txt]] && touch /tmp/sleep.txt;) & vor der Schleife hinzufügen. Was natürlich später zu Problemen führen kann, hängt von der tatsächlichen Geschäftslogik ab
n-alexander
@ n-alexander Nun, in der Antwort wird davon ausgegangen, dass Sie das Snippet ausführen, bevor Sie den Prozess starten, der sleep.txtzu einem bestimmten Zeitpunkt ausgeführt wird. Somit keine Racebedingung. Die Frage enthält keine Hinweise auf ein bestimmtes Szenario.
Maxschlepzig
@maxschlepzig im gegenteil. Sofern keine Bestellung erzwungen wird, kann nicht davon ausgegangen werden. Grundlagen.
n-Alexander
10

Setzen Sie einfach Ihren Test in die whileSchleife:

while [ ! -f /tmp/sleep.txt ]; do sleep 1; done
# next command
jimmij
quelle
So wie Sie es geschrieben haben, ist dies die Logik: Solange die Datei nicht existiert, wird das Skript in den Ruhezustand versetzt. Ansonsten ist es erledigt. Richtig?
Mo Gainz
@ MoGainz Richtig. Die Anzahl nach sleepist die Anzahl der Sekunden, die in jeder Iteration gewartet werden muss. Sie können dies je nach Ihren Anforderungen ändern.
Jimmy
7

Es gibt ein paar Probleme mit einigen der inotifywaitbisher gegebenen Ansätze:

  • Sie können keine sleep.txtDatei finden, die zuerst als temporärer Name erstellt und dann in umbenannt wurde sleep.txt. Man muss für moved_toEvents zusätzlich passencreate
  • Dateinamen können Zeilenumbrüche enthalten. Das Ausdrucken der Dateinamen mit Zeilenumbrüchen reicht nicht aus, um festzustellen, ob eine sleep.txterstellt wurde. Was ist, wenn zum Beispiel eine foo\nsleep.txt\nbarDatei erstellt wurde?
  • Was ist, wenn die Datei erstellt wurde, bevor inotifywait die Uhr gestartet und installiert wurde? Dann inotifywaitwürde ewig auf eine Datei warten, die schon hier ist. Sie müssen sicherstellen, dass die Datei nach der Installation der Uhr noch nicht vorhanden ist.
  • Einige der Lösungen werden inotifywaitausgeführt (mindestens bis eine andere Datei erstellt wurde), nachdem die Datei gefunden wurde.

Um diese zu beheben, können Sie Folgendes tun:

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

Beachten Sie, dass wir sleep.txtim aktuellen Verzeichnis nach der Erstellung von .suchen ( cd /tmp || exitin Ihrem Beispiel haben Sie das also bereits getan ). Das aktuelle Verzeichnis wird nie geändert. Wenn diese Pipeline erfolgreich zurückgegeben wird, befindet sie sich sleep.txtin dem aktuellen Verzeichnis, das erstellt wurde.

Natürlich können Sie ersetzen .mit /tmpoben, aber während inotifywaitausgeführt wird , /tmpwurde mehrmals umbenannt hätte (für unwahrscheinlich /tmp, aber etwas im allgemeinen Fall zu betrachten) oder ein neues Dateisystem gemountet drauf, also , wenn die Pipeline zurückkehrt, kann es nicht Sei ein /tmp/sleep.txt, der erschaffen wurde, sondern ein /new-name-for-the-original-tmp/sleep.txt. Es /tmpkönnte auch ein neues Verzeichnis in dem Intervall erstellt worden sein, und dieses würde nicht überwacht, so dass ein dort sleep.txterstelltes Verzeichnis nicht erkannt würde.

Stéphane Chazelas
quelle
1

Die akzeptierte Antwort funktioniert wirklich (danke maxschlepzig), aber die inotifywait-Überwachung bleibt im Hintergrund, bis Ihr Skript beendet wird. Die einzige Antwort, die genau Ihren Anforderungen entspricht (dh darauf zu warten, dass sleep.txt in / tmp angezeigt wird), scheint die von Stephane zu sein, wenn das von inotifywait zu überwachende Verzeichnis von Punkt (.) In '/ tmp' geändert wird.

Wenn Sie jedoch bereit sind, ein temporäres Verzeichnis NUR zum Setzen Ihres sleep.txt-Flags zu verwenden und wetten können, dass niemand anderes eine Datei in dieses Verzeichnis legt, reicht es aus, inotifywait zu bitten, dieses Verzeichnis auf Dateierzeugungen zu überwachen:

1. Schritt: Erstellen Sie das zu überwachende Verzeichnis:

directoryToPutSleepFile=$(mktemp -d)

2. Schritt: Stellen Sie sicher, dass das Verzeichnis wirklich vorhanden ist

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

3. Schritt: Warten Sie, bis JEDE Datei angezeigt wird $directoryToPutSleepFile

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

Die Datei, die Sie $directoryToPutSleepFileeinfügen, kann den Namen sleep.txt awake.txt haben. In dem Moment, in dem eine Datei in $directoryToPutSleepFileIhrem Skript erstellt wird, wird die inotifywaitAnweisung fortgesetzt .

Nikolaos
quelle
1
Dies kann jedoch dazu führen, dass die Erstellung der Datei sleep.txtfehlschlägt , wenn kurz zuvor eine andere erstellt wurde, und die Datei zwischen zwei inotifywait-Aufrufen erstellt wird.
Stéphane Chazelas
siehe meine Antwort für einen alternativen Weg, um es anzugehen.
Stéphane Chazelas
Bitte versuchen Sie es mit meinem Code. inotifywait wird nur einmal aufgerufen. Wenn sleep.txt erstellt wird (oder wenn bereits vorhanden, gelesen / geschrieben), gibt inotifywait "sleep.txt" aus und die Ausführung wird nach der 'done'-Anweisung fortgesetzt.
Nikolaos
Es wird mehrmals aufgerufen, bis eine aufgerufene Datei sleep.txterstellt wird. Versuchen Sie zum Beispiel touch /tmp/foo /tmp/sleep.txt, dass eine Instanz von inotifywaitmeldet foound beendet wird. Zum Zeitpunkt des nächsten Starts von inotifywait ist die Datei sleep.txt bereits seit langem vorhanden, sodass inotifywait ihre Erstellung nicht erkennt.
Stéphane Chazelas
Sie hatten Recht mit den mehreren Aufrufen: Ein Aufruf für jede unter / tmp erstellte Datei. Ich habe meine Antwort aktualisiert. Vielen Dank für Ihren Kommentar.
Nikolaos
0

Im Allgemeinen ist es ziemlich schwierig, etwas anderes als die einfache Test- / Schlafschleife zuverlässig zu machen. Das Hauptproblem besteht darin, dass der Test mit der Erstellung der Datei beginnt. Wenn der Test nicht wiederholt wird, kann das Erstellungsereignis übersehen werden. Dies ist bei weitem die beste Wahl in den meisten Fällen, in denen Sie zunächst Shell verwenden würden:

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

Wenn Sie feststellen, dass dies aufgrund von Leistungsproblemen nicht ausreicht, ist die Verwendung von Shell wahrscheinlich keine gute Idee.

n-alexander
quelle
2
Dies ist nur ein Duplikat von jimmijs Antwort .
Maxschlepzig
0

Basierend auf der akzeptierten Antwort von maxschlepzig (und einer Idee aus der akzeptierten Antwort unter /superuser/270529/monitoring-a-file-until-a-string-is-found ) schlage ich Folgendes vor: aus meiner sicht) antwort was auch mit timeout klappen kann:

# 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

Falls die Datei nicht innerhalb des angegebenen Timeouts erstellt / geöffnet wird, lautet der Beendigungsstatus 124 (gemäß Timeout-Dokumentation (Manpage)). Wenn es erstellt / geöffnet wird, ist der Exit-Status 0 (Erfolg).

Ja, inotifywait wird auf diese Weise in einer Sub-Shell ausgeführt und diese Sub-Shell wird erst beendet, wenn das Timeout auftritt oder wenn das Hauptskript beendet wird (je nachdem, was zuerst eintritt).

Attila123
quelle