Bagaimana cara saya menunggu file dalam skrip shell?

14

Saya mencoba untuk menulis skrip shell yang akan menunggu file muncul di /tmpdirektori bernama sleep.txtdan setelah ditemukan program akan berhenti, jika tidak saya ingin program berada dalam keadaan tertidur (ditangguhkan) sampai file tersebut ditemukan . Sekarang, saya berasumsi bahwa saya akan menggunakan perintah uji. Jadi, kira-kira seperti itu

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

Saya baru menulis skrip shell dan bantuan apa pun sangat dihargai!

Mo Gainz
sumber
Lihatlah $MAILPATH.
mikeserv

Jawaban:

22

Di Linux, Anda dapat menggunakan subsistem kernel inotify untuk secara efisien menunggu penampilan file dalam direktori:

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

(dengan asumsi Bash untuk <()sintaks redirection output)

Keuntungan dari pendekatan ini dibandingkan dengan polling interval waktu tetap seperti di

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

adalah bahwa kernel lebih banyak tidur. Dengan spesifikasi peristiwa yang tidak sah seperti create,openskrip hanya dijadwalkan untuk dieksekusi ketika file di bawah /tmpdibuat atau dibuka. Dengan polling interval waktu yang tetap Anda membuang siklus CPU untuk setiap kenaikan waktu.

Saya menyertakan openacara untuk juga mendaftar touch /tmp/sleep.txtketika file sudah ada.

maxschlepzig
sumber
Terima kasih, ini luar biasa! Mengapa Anda memerlukan loop sementara jika Anda tahu nama file? Mengapa itu tidak terjadi inotifywait -e create,open --format '%f' --quiet /tmp/sleep.txt --monitor > /dev/null ; # continues once /tmp/sleep.txt is created/opened?
NHDaly
Oh, jk. Setelah menginstal alat saya mengerti sekarang. inotifywaititu sendiri adalah loop sementara, dan menghasilkan garis setiap kali file dibuka / dibuat. Jadi Anda perlu loop sementara untuk memecah ketika itu diubah.
NHDaly
kecuali bahwa ini menghasilkan kondisi balapan - tidak seperti versi test -e / sleep. Tidak ada cara untuk menjamin bahwa file tidak akan dibuat tepat sebelum loop dimasukkan - dalam hal ini acara akan terlewatkan. Anda perlu menambahkan sesuatu seperti (sleep 1; [[-e /tmp/sleep.txt]] && sentuh /tmp/sleep.txt;) & sebelum loop. Yang tentu saja dapat menciptakan masalah di jalan, tergantung pada logika bisnis yang sebenarnya
n-alexander
@ n-alexander Nah, jawabannya mengasumsikan bahwa Anda mengeksekusi potongan sebelum Anda memulai proses yang akan menghasilkan sleep.txtpada suatu titik waktu. Dengan demikian, tidak ada kondisi balapan. Pertanyaan itu tidak mengandung apa pun yang mengisyaratkan skenario yang ada dalam pikiran Anda.
maxschlepzig
@maxschlepzig sebaliknya. Kecuali jika pemesanan diberlakukan, itu tidak dapat diasumsikan. Dasar-dasar
n-alexander
10

Cukup letakkan tes Anda di whileloop:

while [ ! -f /tmp/sleep.txt ]; do sleep 1; done
# next command
jimmij
sumber
Jadi seperti yang Anda tulis, inilah logikanya: sementara file tidak ada, skrip akan tidur. Kalau tidak, itu dilakukan. Benar?
Mo Gainz
@ MoGainz Benar. Jumlah setelahnya sleepadalah jumlah detik untuk menunggu di setiap iterasi - Anda dapat mengubahnya tergantung pada kebutuhan Anda.
jimmij
7

Ada beberapa masalah dengan beberapa inotifywaitpendekatan berbasis yang diberikan sejauh ini:

  • mereka gagal menemukan sleep.txtfile yang telah dibuat pertama sebagai nama sementara dan kemudian diganti namanya menjadi sleep.txt. Satu harus mencocokkan untuk moved_toacara di sampingcreate
  • nama file dapat berisi karakter baris baru, mencetak nama file yang dibatasi baris baru tidak cukup untuk menentukan apakah sleep.txttelah dibuat. Bagaimana jika foo\nsleep.txt\nbarfile telah dibuat misalnya?
  • bagaimana jika file tersebut dibuat sebelum inotifywait dimulai dan menginstal arloji? Maka inotifywaitakan menunggu selamanya untuk file yang sudah ada di sini. Anda harus memastikan file belum ada di sana setelah arloji diinstal.
  • beberapa solusi tetap inotifywaitberjalan (setidaknya sampai file lain dibuat) setelah file ditemukan.

Untuk mengatasinya, Anda bisa melakukan:

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

Perhatikan bahwa kami sedang mengawasi pembuatan sleep.txtdi direktori saat ini, .(jadi Anda akan melakukan cd /tmp || exitsebelumnya dalam contoh Anda). Direktori saat ini tidak pernah berubah, jadi ketika saluran pipa itu kembali dengan sukses, itu adalah sleep.txtdi direktori saat ini yang telah dibuat.

Tentu saja, Anda dapat menggantinya .dengan yang di /tmpatas, tetapi ketika inotifywaitsedang berjalan, /tmpbisa saja dinamai ulang beberapa kali (tidak mungkin untuk /tmp, tetapi sesuatu untuk dipertimbangkan dalam kasus umum) atau sistem file baru yang dipasang di atasnya, jadi ketika pipa kembali, mungkin tidak menjadi /tmp/sleep.txtyang telah dibuat tetapi /new-name-for-the-original-tmp/sleep.txtsebaliknya. /tmpDirektori baru juga bisa dibuat dalam interval dan yang tidak akan ditonton, jadi yang sleep.txtdibuat di sana tidak akan terdeteksi.

Stéphane Chazelas
sumber
1

Jawaban yang diterima benar-benar berfungsi (terima kasih maxschlepzig) tetapi membiarkan pemantauan inotifywait di latar belakang hingga skrip Anda keluar. Satu-satunya jawaban yang cocok persis dengan kebutuhan Anda (yaitu menunggu sleep.txt muncul di dalam / tmp) tampaknya adalah milik Stephane, jika direktori yang akan dimonitor oleh inotifywait diubah dari titik (.) Ke '/ tmp'.

Namun, jika Anda bersedia menggunakan direktori sementara SAJA untuk meletakkan flag sleep.txt Anda dan dapat bertaruh bahwa tidak ada orang lain yang akan meletakkan file apa pun di direktori itu, cukup meminta inotifyunggu untuk menonton direktori ini karena kreasi file sudah cukup:

Langkah 1: buat direktori yang akan Anda pantau:

directoryToPutSleepFile=$(mktemp -d)

Langkah 2: pastikan direktori benar-benar ada

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

Langkah 3: tunggu sampai file APA SAJA muncul di dalamnya $directoryToPutSleepFile

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

File yang Anda masukkan $directoryToPutSleepFiledapat dinamai sleep.txt awake.txt, apa pun. Saat file apa pun dibuat di dalam $directoryToPutSleepFileskrip Anda akan terus melewati inotifywaitpernyataan.

nikolaos
sumber
1
Tapi itu bisa ketinggalan pembuatan file, jika yang lain dibuat sebelumnya, menyebabkan sleep.txtdibuat di antara dua inotifyunggu permintaan.
Stéphane Chazelas
lihat jawaban saya untuk cara alternatif untuk mengatasinya.
Stéphane Chazelas
Silakan coba kode saya. inotifywait dipanggil hanya sekali. Ketika sleep.txt dibuat (atau dibaca / ditulis jika sudah ada), inotifyunggu keluaran "sleep.txt" dan eksekusi berlanjut setelah pernyataan 'selesai'.
nikolaos
Itu dipanggil beberapa kali sampai file yang dipanggil sleep.txtdibuat. Coba misalnya touch /tmp/foo /tmp/sleep.txt, maka satu instance inotifywaitakan melaporkan foodan keluar, dan pada saat inotifywait berikutnya dimulai, sleep.txt sudah lama ada di sana sehingga inotifywait tidak akan mendeteksi pembuatannya.
Stéphane Chazelas
Anda benar tentang banyak pemanggilan: satu pemanggilan untuk setiap file yang dibuat di bawah / tmp. Saya memperbarui jawaban saya. Terima kasih atas komentar Anda.
nikolaos
0

secara umum sangat sulit untuk membuat apa pun kecuali tes sederhana / loop tidur dapat diandalkan. Masalah utama adalah bahwa tes akan berpacu dengan pembuatan file - kecuali tes diulang, acara buat bisa dilewatkan. Sejauh ini, inilah taruhan terbaik Anda dalam sebagian besar kasus di mana Anda akan menggunakan Shell:

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

Jika Anda merasa ini tidak cukup karena masalah kinerja, maka menggunakan Shell mungkin bukan ide yang bagus.

n-alexander
sumber
2
Ini hanya duplikat dari jawaban jimmij .
maxschlepzig
0

Berdasarkan jawaban yang diterima dari maxschlepzig (dan sebuah ide dari jawaban yang diterima di /superuser/270529/monitoring-a-file-until-a-string-is-found ) Saya mengusulkan yang berikut ditingkatkan ( dalam pandangan saya) jawaban yang juga bisa berfungsi dengan batas waktu:

# 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

Jika file tidak dibuat / dibuka dalam batas waktu yang diberikan, maka status keluar adalah 124 (sesuai dokumentasi batas waktu (halaman manual)). Jika itu dibuat / dibuka, maka status keluar adalah 0 (berhasil).

Ya, inotifywait berjalan dalam sub-shell dengan cara ini dan sub-shell itu hanya akan selesai berjalan ketika batas waktu terjadi atau ketika skrip utama keluar (mana yang lebih dulu).

Attila123
sumber