Jak czekać na plik w skrypcie powłoki?


15

Próbuję napisać skrypt powłoki, który będzie oczekiwał na pojawienie się pliku w /tmpkatalogu o nazwie, sleep.txta gdy go znajdzie, program przestanie działać, w przeciwnym razie chcę, aby program był w stanie uśpienia (zawieszenia) do momentu zlokalizowania pliku . Teraz zakładam, że użyję polecenia testowego. Coś w stylu

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

Jestem zupełnie nowy w pisaniu skryptów powłoki i każda pomoc jest mile widziana!


Patrzeć $MAILPATH.
— mikeserv

Odpowiedzi:


23

Pod Linuksem możesz użyć podsystemu jądra inotify, aby efektywnie czekać na pojawienie się pliku w katalogu:

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

(przy założeniu Bash dla <()składni przekierowania wyjściowego)

Zaletą tego podejścia w porównaniu do odpytywania w ustalonych odstępach czasu, jak w

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

jest to, że jądro śpi więcej. Dzięki specyfikacji zdarzenia inotify, takiej jak create,openskrypt, jest właśnie zaplanowany do wykonania po /tmputworzeniu lub otwarciu pliku poniżej . Dzięki ustalonemu odpytywaniu przedziału czasu marnujesz cykle procesora dla każdego przyrostu czasu.

Zawarłem openzdarzenie, aby zarejestrować się, touch /tmp/sleep.txtgdy plik już istnieje.


Dzięki, to jest niesamowite! Dlaczego potrzebujesz pętli while, jeśli znasz nazwę pliku? Dlaczego tak nie było inotifywait -e create,open --format '%f' --quiet /tmp/sleep.txt --monitor > /dev/null ; # continues once /tmp/sleep.txt is created/opened?
— NHDaly,

Och, jk. Po zainstalowaniu narzędzia rozumiem teraz. inotifywaitjest pętlą while i wyświetla linię za każdym razem, gdy plik jest otwierany / tworzony. Więc potrzebujesz pętli while, aby przerwać, gdy zostanie zmieniona.
— NHDaly,

z wyjątkiem tego, że powoduje to wyścig - w przeciwieństwie do wersji testowej -e / sleep. Nie ma sposobu, aby zagwarantować, że plik nie zostanie utworzony tuż przed wejściem w pętlę - w takim przypadku zdarzenie zostanie pominięte. Musisz dodać coś takiego (sleep 1; [[-e /tmp/sleep.txt]] && touch /tmp/sleep.txt;) & przed pętlą. Co oczywiście może powodować problemy w przyszłości, w zależności od rzeczywistej logiki biznesowej
— n-Alexander

@ n-aleksander Cóż, odpowiedź zakłada, że ​​wykonujesz fragment kodu przed rozpoczęciem procesu, który wygeneruje sleep.txtw pewnym momencie. Zatem nie ma warunków rasowych. Pytanie nie zawiera niczego, co sugerowałoby scenariusz, który masz na myśli.
— maxschlepzig

@maxschlepzig przeciwnie. Bez zamówienia nie można tego założyć. Podstawy
— n-aleksander

10

Po prostu umieść test w whilepętli:

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

Tak jak napisałeś, jest to logika: dopóki plik nie istnieje, skrypt będzie spał. W przeciwnym razie jest to zrobione. Poprawny?
— Mo Gainz

@MoGainz Correct. Liczba po sleepto liczba sekund oczekiwania w każdej iteracji - możesz to zmienić w zależności od potrzeb.
— jimmij

7

Istnieje kilka problemów z niektórymi dotychczasowymi inotifywaitpodejściami opartymi na:

  • nie mogą znaleźć sleep.txtpliku, który został najpierw utworzony jako nazwa tymczasowa, a następnie zmieniono jego nazwę na sleep.txt. moved_toOprócz tego trzeba dopasować do wydarzeńcreate
  • nazwy plików mogą zawierać znaki nowej linii, wypisanie nazw plików rozdzielanych nową linią nie wystarczy, aby ustalić, czy plik sleep.txtzostał utworzony. Co jeśli na przykład foo\nsleep.txt\nbarplik został utworzony?
  • co jeśli plik zostanie utworzony przed inotifywait uruchomieniem i zainstalowaniem zegarka? Potem inotifywaitczekałby wiecznie na plik, który już tu jest. Musisz upewnić się, że pliku już nie ma po zainstalowaniu zegarka.
  • niektóre rozwiązania przestają inotifywaitdziałać (przynajmniej do momentu utworzenia innego pliku) po znalezieniu pliku.

Aby rozwiązać te problemy, możesz:

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

Zauważ, że obserwujemy tworzenie sleep.txtw bieżącym katalogu .(więc zrobiłbyś to cd /tmp || exitwcześniej w swoim przykładzie). Bieżący katalog nigdy się nie zmienia, więc gdy ten rurociąg potoczy się pomyślnie, znajduje się sleep.txtw utworzonym katalogu bieżącym.

Oczywiście, można zastąpić .z /tmppowyżej, ale gdy inotifywaitjest uruchomiony, /tmpmogła zostać zmieniona na kilka razy (mało prawdopodobne /tmp, ale coś do rozważenia w przypadku ogólnym) lub nowy system plików zamontowany na nim, więc kiedy wraca do rurociągów, nie mogą być /tmp/sleep.txtstworzonym, ale /new-name-for-the-original-tmp/sleep.txtzamiast niego. W /tmptym przedziale czasu można także utworzyć nowy katalog, którego nie można oglądać, więc sleep.txtutworzony tam katalog nie zostanie wykryty.


1

Akceptowana odpowiedź naprawdę działa (dzięki maxschlepzig), ale pozostawia monitorowanie inotifywait w tle, dopóki skrypt nie zakończy działania. Jedyną odpowiedzią, która dokładnie odpowiada twoim wymaganiom (tj. Oczekiwanie na pojawienie się pliku sleep.txt w / tmp) wydaje się być odpowiedź Stephane'a, jeśli katalog, który ma być monitorowany przez inotifywait, zostanie zmieniony z kropki (.) Na '/ tmp'.

Jeśli jednak chcesz użyć katalogu tymczasowego TYLKO do umieszczenia flagi sleep.txt i możesz się założyć, że nikt inny nie umieści żadnego pliku w tym katalogu, wystarczy poprosić inotifywait o obejrzenie tego katalogu pod kątem tworzenia plików:

Pierwszy krok: utwórz katalog, który będziesz monitorować:

directoryToPutSleepFile=$(mktemp -d)

Drugi krok: upewnij się, że katalog naprawdę tam jest

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

Trzeci krok: poczekaj, aż pojawi się ŻADNY plik $directoryToPutSleepFile

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

Plik, który umieścisz, $directoryToPutSleepFilemoże mieć nazwę sleep.txt awake.txt, cokolwiek. Moment utworzenia dowolnego pliku w $directoryToPutSleepFileskrypcie będzie przebiegał poza inotifywaitinstrukcją.


1
Może to jednak pomijać tworzenie pliku, jeśli inny został utworzony tuż przedtem, powodując sleep.txtutworzenie między dwoma wywołaniami inotifywait.
— Stéphane Chazelas

zobacz moją odpowiedź na alternatywny sposób rozwiązania tego problemu.
— Stéphane Chazelas

Proszę wypróbować mój kod. inotifywait jest wywoływany tylko raz. Kiedy tworzony jest sleep.txt (lub czytany / zapisywany, jeśli już tam jest), inotifywait wyświetla „sleep.txt”, a wykonywanie jest kontynuowane po instrukcji „done”.
— Nikolaos

Jest wywoływany kilka razy, dopóki nie sleep.txtzostanie utworzony plik o nazwie . Spróbuj na przykład touch /tmp/foo /tmp/sleep.txt, wtedy jedno wystąpienie inotifywaitzgłosi fooi zakończy działanie, a do czasu uruchomienia następnego inotifywait sleep.txt już długo tam będzie, więc inotifywait nie wykryje jego utworzenia.
— Stéphane Chazelas

Miałeś rację co do wielu wywołań: jedno wywołanie dla każdego pliku utworzonego w / tmp. Zaktualizowałem swoją odpowiedź. Dzięki za komentarz.
— Nikolaos

0

ogólnie rzecz biorąc, bardzo trudno jest uczynić cokolwiek innego niż prostą pętlę test / sen. Głównym problemem jest to, że test będzie się ścigał z tworzeniem pliku - chyba że test się powtórzy, zdarzenie tworzenia może zostać pominięte. Jest to zdecydowanie najlepszy wybór w większości przypadków, w których powinieneś używać Shell na początek:

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

Jeśli okaże się, że jest to niewystarczające ze względu na problemy z wydajnością, to użycie Shell prawdopodobnie nie jest dobrym pomysłem.



0

W oparciu o zaakceptowaną odpowiedź maxschlepzig (i pomysł z zaakceptowanej odpowiedzi na /superuser/270529/monitoring-a-file-until-a-string-is-found ) proponuję następujące ulepszenia ( moim zdaniem) odpowiedź, która może również działać z limitem czasu:

# 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

W przypadku, gdy plik nie zostanie utworzony / otwarty w podanym limicie czasu, status wyjścia to 124 (zgodnie z dokumentacją limitu czasu (strona podręcznika)). W przypadku, gdy zostanie utworzony / otwarty, kodem wyjścia jest 0 (sukces).

Tak, inotifywait działa w ten sposób w podpowłoce i ta podpowłoka zakończy działanie dopiero po przekroczeniu limitu czasu lub po zakończeniu skryptu głównego (w zależności od tego, co nastąpi wcześniej).

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.