Kabuk betiğindeki bir dosyayı nasıl beklerim?


15

Ben /tmpdenilen dizinde bir dosya görünmesini bekleyecek bir kabuk komut dosyası yazmaya çalışıyorum sleep.txtve bir kez program durdurulur, aksi takdirde ben dosya bulunana kadar programın uyku (askıya alınmış) durumda olmasını istiyorum . Şimdi, bir test komutu kullanacağımı varsayıyorum. Yani, böyle bir şey

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

Kabuk betiği yazma konusunda yepyeni ve herhangi bir yardım büyük beğeni topluyor!


Şuna bak $MAILPATH.
mikeserv

Yanıtlar:


24

Linux altında, bir dizindeki bir dosyanın görünümünü verimli bir şekilde beklemek için inotify çekirdek alt sistemini kullanabilirsiniz:

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

( <()çıkış yeniden yönlendirme sözdizimi için Bash olduğu varsayılır )

Bu yaklaşımın, aşağıdaki gibi sabit zaman aralığı yoklamasına kıyasla avantajı

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

çekirdek daha fazla uyuyor. create,openKomut dosyası gibi inotify olay spesifikasyonuyla , altında bir dosya /tmpoluşturulduğunda veya açıldığında sadece yürütme için zamanlanır . Sabit zaman aralığı yoklaması ile CPU döngülerini her zaman artışı için boşa harcarsınız.

Dosya zaten mevcut olduğunda openda kayıt olmak için olayı dahil ettim touch /tmp/sleep.txt.


Teşekkürler, bu harika! Dosyanın adını biliyorsanız neden while döngüsüne ihtiyacınız var? Neden olmasın inotifywait -e create,open --format '%f' --quiet /tmp/sleep.txt --monitor > /dev/null ; # continues once /tmp/sleep.txt is created/opened?
NHDaly

Oh, jk. Aracı yükledikten sonra şimdi anlıyorum. inotifywaitkendisi bir while döngüsüdür ve dosya her açıldığında / oluşturulduğunda bir satır çıkarır. Bu yüzden değiştiğinde kırmak için while döngüsüne ihtiyacınız var.
NHDaly

tek fark, bunun bir yarış durumu üretmesi - test -e / sleep versiyonunun aksine. Döngü girilmeden hemen önce dosyanın oluşturulmayacağını garanti etmenin bir yolu yoktur - bu durumda etkinlik kaçırılır. Döngüden önce (uyku 1; [[-e /tmp/sleep.txt]] && touch /tmp/sleep.txt;) & gibi bir şey eklemeniz gerekir. Tabii ki asıl iş mantığına bağlı olarak yolda sorunlar yaratabilir
n-alexander

@ n-alexander Peki, cevap snippet'i belirli bir zamanda üretilecek işleme başlamadan önce yürüttüğünüzü varsayar sleep.txt. Böylece ırk koşulu yok. Soru, aklınızdaki bir senaryoyu ima eden hiçbir şey içermiyor.
maxschlepzig

@maxschlepzig aksine. Sipariş uygulanmadığı sürece kabul edilemez. Temelleri.
n-alexander

10

Testinizi whiledöngüye koymanız yeterlidir:

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

Yazdığınız gibi, bu mantık: dosya mevcut değilken, komut dosyası uyuyacaktır. Aksi takdirde yapılır. Doğru?
Mo Gainz

@MoGainz Doğru. Sonraki sayı sleep, her bir yinelemede beklenecek saniye sayısıdır - bunu ihtiyaçlarınıza göre değiştirebilirsiniz.
jimmij

7

inotifywaitŞimdiye kadar verilen bazı temel yaklaşımlarla ilgili birkaç sorun var :

  • sleep.txtönce geçici bir ad olarak oluşturulan ve daha sonra yeniden adlandırılan bir dosyayı bulamazlar sleep.txt. moved_toAyrıca, etkinlikler için eşleşmesi gerekircreate
  • dosya adları yeni satır karakterleri içerebilir, yeni satır sınırlandırılan dosyaların adlarını yazdırmak için bir a sleep.txtoluşturulup oluşturulmadığını belirlemek yeterli değildir . foo\nsleep.txt\nbarÖrneğin bir dosya oluşturulduysa ne olur ?
  • dosya daha önce oluşturulduysa inotifywaitve saati yüklerse ne olur ? O zaman inotifywaitzaten burada olan bir dosyayı sonsuza kadar beklerdim. Saat yüklendikten sonra dosyanın zaten orada olmadığından emin olmanız gerekir.
  • inotifywaitdosya bulunduktan sonra bazı çözümler çalışmaya devam eder (en azından başka bir dosya oluşturulana kadar).

Bunları ele almak için şunları yapabilirsiniz:

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

sleep.txtGeçerli dizinde oluşturulmasını izlediğimizi unutmayın .(bu nedenle cd /tmp || exitörneğinizde daha önce bir tane yaparsınız ). Geçerli dizin asla değişmez, bu nedenle bu boru hattı başarıyla döndüğünde, sleep.txtoluşturulmuş olan geçerli dizinde bir olur.

Tabii ki, yukarıdaki .ile değiştirebilirsiniz /tmp, ancak çalışırken inotifywait, /tmpbirkaç kez yeniden adlandırılmış olabilir (olası /tmpdeğil, ancak genel durumda dikkate alınması gereken bir şey) veya üzerine monte edilmiş yeni bir dosya sistemi, bu nedenle boru hattı döndüğünde, olmayabilir bir olmak /tmp/sleep.txtoluşturulduğunu ancak /new-name-for-the-original-tmp/sleep.txtbunun yerine. /tmpAralıkta yeni bir dizin de oluşturulabilirdi ve bu dizin izlenmeyecekti, bu yüzden sleep.txtoluşturulmuş bir dosya algılanmayacaktı.


1

Kabul edilen cevap gerçekten işe yarar (teşekkürler maxschlepzig), ancak betiğiniz bitene kadar inotifywait izlemeyi arka planda bırakır. Eğer inotifywait tarafından izlenecek dizin nokta (.) 'Dan' / tmp 'olarak değiştirilirse, gereksinimlerinize tam olarak uyan tek cevap (yani sleep.txt dosyasının / tmp içinde görünmesini beklemek) Stephane gibi görünüyor.

Bununla birlikte, SADECE sleep.txt bayrağınızı koymak için geçici bir dizin kullanmak istiyorsanız ve başka hiç kimsenin bu dizine herhangi bir dosya koymayacağına bahse girebilirseniz, sadece inotify'dan bu dizini dosya oluşturmaları için izlemesini istemek yeterli olacaktır:

1. adım: izleyeceğiniz dizini oluşturun:

directoryToPutSleepFile=$(mktemp -d)

2. adım: dizinin gerçekten orada olduğundan emin olun

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

3. adım: HERHANGİ BİR dosya içeride görünene kadar bekleyin $directoryToPutSleepFile

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

İçinde koyacağız dosya $directoryToPutSleepFilesleep.txt awake.txt, her neyse adlandırılabilir. Komut dosyanızın içinde herhangi bir dosya oluşturulduğu anda ifadeyi $directoryToPutSleepFilegeçmeye devam eder inotifywait.


1
Ancak, daha önce başka bir dosya oluşturulduysa, dosyanın sleep.txtinotifywait çağrıları arasında oluşturulmasına neden olan dosya oluşturmayı kaçırabilir .
Stéphane Chazelas

cevabınızı ele almanın alternatif bir yolu için cevabımı görün .
Stéphane Chazelas

Lütfen kodumu deneyin. inotifywait yalnızca bir kez çağrılır. Sleep.txt oluşturulduğunda (veya zaten varsa okuma / yazma), inotifywait "sleep.txt" çıktısını verir ve yürütme 'done' ifadesinden sonra devam eder.
nikolaos

Adlı bir dosya sleep.txtoluşturulana kadar birkaç kez çağrılır . Örneğin touch /tmp/foo /tmp/sleep.txtbir inotifywaitirade raporu foove çıkışı örneği deneyin ve bir sonraki inotifywait başlatıldığında, sleep.txt uzun zamandır orada olacak, bu yüzden inotifywait oluşturulduğunu algılamayacak.
Stéphane Chazelas

Birden çok çağırma konusunda haklıydınız: / tmp altında oluşturulan her dosya için bir çağırma. Cevabımı güncelledim. Yorumun için teşekkürler.
nikolaos

0

genel olarak basit test / uyku döngüsü dışında güvenilir bir şey yapmak oldukça zordur. Ana sorun, testin dosyanın oluşturulmasıyla yarışmasıdır - test tekrarlanmadıkça create olayı kaçırılabilir. Başlamak için Shell'i kullanacağınız çoğu durumda bu en iyi bahistir:

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

Performans sorunları nedeniyle bunun yetersiz olduğunu fark ederseniz, Shell'i kullanmak büyük olasılıkla harika bir fikir değildir.


2
Bu jimmij'in cevabının sadece bir kopyası .
maxschlepzig

0

Maxschlepzig'in kabul edilen cevabına (ve /superuser/270529/monitoring-a-file-until-a-string-is-found adresindeki kabul edilen cevaptan bir fikre dayanarak ) Aşağıdakileri geliştirdim ( benim görüşüme göre) cevap da zaman aşımı ile çalışabilir:

# 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

Dosyanın belirtilen zaman aşımı içinde oluşturulmaması / açılmaması durumunda, çıkış durumu 124'tür (zaman aşımı belgelerine göre (man sayfası)). Oluşturulması / açılması durumunda çıkış durumu 0'dır (başarılı).

Evet, inotifywait bir alt kabukta bu şekilde çalışır ve bu alt kabuk yalnızca zaman aşımı olduğunda veya ana komut dosyası çıktığında (hangisi önce gelirse) çalışmayı bitirir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.