Як я чекаю на файл у сценарії оболонки?


15

Я намагаюся написати скрипт оболонки, який чекатиме появи файлу у папці, що /tmpназивається, sleep.txtі як тільки його буде знайдено, програма припиниться, інакше я хочу, щоб програма перебувала у режимі сну (призупинення), поки файл не знайде . Тепер я припускаю, що буду використовувати тестову команду. Отже, щось на кшталт

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

Я абсолютно новий в написанні сценарію оболонки, і будь-яка допомога дуже вдячна!


Погляньте $MAILPATH.
mikeserv

Відповіді:


24

У Linux можна використовувати підсистему inotify kernel, щоб ефективно чекати появи файлу в каталозі:

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

(припускаючи Bash для <()синтаксису перенаправлення виводу)

Перевага такого підходу порівняно з фіксованим тимчасовим інтервалом опитування, як у

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

полягає в тому, що ядро ​​спить більше. З специфікацією події inotify подібно create,openдо того, що сценарій просто запланований до виконання, коли створюється файл /tmpабо відкривається файл . З фіксованим інтервалом часу опитування ви витрачаєте цикли процесора на кожен приріст часу.

Я включив openподію, щоб також зареєструватися, touch /tmp/sleep.txtколи файл вже існує.


Дякую, це приголомшливо! Для чого вам потрібен цикл while, якщо ви знаєте ім'я файлу? Чому цього просто не можна було inotifywait -e create,open --format '%f' --quiet /tmp/sleep.txt --monitor > /dev/null ; # continues once /tmp/sleep.txt is created/opened?
NHDaly

Ой, ж. Після установки інструменту я зараз розумію. inotifywaitсама по собі цикл час, і вона видає рядок кожного разу, коли файл відкривається / створюється. Тому вам потрібен цикл while, щоб розірватися, коли він змінений.
NHDaly

за винятком того, що це створює перегонові умови - на відміну від тестової версії -e / сон. Немає способу гарантувати, що файл не буде створений безпосередньо до введення циклу - у такому випадку подія буде пропущена. Вам потрібно буде додати щось на кшталт (спати 1; [[-e /tmp/sleep.txt]] && touch /tmp/sleep.txt;) & перед циклом. Що, звичайно, може створювати проблеми в дорозі, залежно від фактичної логіки бізнесу
n-alexander

@ n-alexander Ну, відповідь передбачає, що ви виконаєте фрагмент, перш ніж запустити процес, який буде вироблятися sleep.txtв якийсь момент часу. Таким чином, немає умови для перегонів. Питання не містить нічого, що натякає на сценарій, який ви маєте на увазі.
maxschlepzig

@maxschlepzig навпаки. Якщо замовлення не виконується, його не можна вважати. Основи.
n-alexander

10

Просто поставте свій тест у whileциклі:

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

Отже, як ви це вже написали, така логіка: поки файл не існує, сценарій вимкне. Інакше це робиться. Правильно?
Пн Гайнц

@MoGainz Правильно. Число після sleep- це кількість секунд, які потрібно чекати в кожній ітерації - ви можете змінити це залежно від ваших потреб.
jimmij

7

Існує кілька проблем з деякими inotifywaitпідходами, що базуються на сьогодні ,:

  • їм не вдалося знайти sleep.txtфайл, який був створений спочатку як тимчасове ім'я, а потім перейменований у sleep.txt. moved_toОкрім того, потрібно долучатися до подійcreate
  • Імена файлів можуть містити символи нового рядка, друк імен файлів з обмеженим рядком недостатньо, щоб визначити, чи sleep.txtбуло створено a . Що робити, наприклад, якщо foo\nsleep.txt\nbarфайл був створений?
  • що робити, якщо файл створений до inotifywait запуску та встановлення годинника? Тоді inotifywaitб вічно чекати файлу, який уже є тут. Вам потрібно буде переконатися, що файл не встановлений після встановлення годинника.
  • деякі рішення залишаються inotifywaitзапущеними (принаймні, поки не буде створений інший файл) після того, як файл знайдений.

Щоб вирішити ці проблеми, ви можете:

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.txtу поточному каталозі .(так що ви зробите cd /tmp || exitраніше у своєму прикладі). Поточний каталог ніколи не змінюється, тому коли ця трубопровідна лінія повертається успішно, вона є sleep.txtу створеному поточному каталозі.

Звичайно, ви можете замінити .з /tmpвище, але поки inotifywaitпрацюють, /tmpможуть бути перейменовані в кілька разів (малоймовірно /tmp, але що - то розглянути в загальному випадку) або нова файлова система встановлені на ньому, тому при поверненні трубопроводу, він не може бути тим, /tmp/sleep.txtщо було створено, але /new-name-for-the-original-tmp/sleep.txtзамість цього. Новий /tmpкаталог також міг бути створений в інтервалі, і його не слід було б переглядати, тому sleep.txtстворене там не було б виявлено.


1

Прийнята відповідь справді працює (спасибі maxschlepzig), але залишає моніторинг inotifywait у фоновому режимі, поки ваш сценарій не вийде. Єдиною відповіддю, яка відповідає точно вашим вимогам (тобто очікування, щоб sleep.txt з'явився всередині / tmp), здається, є Стефаном, якщо каталог, який слід контролювати за допомогою inotifywait, буде змінено з крапки (.) На '/ tmp'.

Тим НЕ менше, якщо ви готові використовувати тимчасовий каталог тільки для розміщення вашого прапора sleep.txt і можете посперечатися , що ніхто ще будете помістити будь-який файл в цьому каталозі, просто просимо inotifywait дивитися цей каталог для файлів створення буде досить:

1-й крок: створити каталог, який ви будете контролювати:

directoryToPutSleepFile=$(mktemp -d)

Другий крок: переконайтеся, що каталог справді є

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

3-й крок: зачекайте, поки ВСЕ. Файл з’явиться всередині $directoryToPutSleepFile

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

Файл, який ви $directoryToPutSleepFileвведете, можна назвати sleep.txt awake.txt, як би там не було. У момент, коли будь-який файл буде створений у $directoryToPutSleepFileвашому сценарії, він продовжить минуле inotifywaitоператора.


1
Але це може пропустити створення файлу, якщо інший був створений безпосередньо раніше, що призведе sleep.txtдо створення між двома викликами inotifywait.
Стефан Хазелас

дивіться мою відповідь про альтернативний спосіб її вирішення.
Stéphane Chazelas

Будь ласка, спробуйте мій код. inotifywait викликається лише один раз. Коли створюється sleep.txt (або читається / записується, якщо він вже є), inotifywait виводить "sleep.txt" і виконання продовжується після оператора 'done'.
nikolaos

Його викликають кілька разів, поки не sleep.txtбуде створений названий файл . Спробуйте, наприклад touch /tmp/foo /tmp/sleep.txt, тоді один екземпляр inotifywaitбуде повідомляти fooі виходити, і до запуску наступного inotifywait, sleep.txt давно вже є, тому inotifywait не виявить його створення.
Стефан Хазелас

Ви мали рацію щодо кількох викликів: одне виклик для кожного файлу, створеного під / tmp. Я оновив свою відповідь. Дякуємо за ваш коментар
nikolaos

0

загалом зробити щось важко, окрім простого циклу тесту / сну, досить важко. Основна проблема полягає в тому, що тест змагатиметься зі створенням файлу - якщо тест не повториться, подія створення може бути пропущена. Це, безумовно, найкраща ставка в більшості випадків, коли ви використовуєте Shell для початку:

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

Якщо ви виявите, що це недостатньо через проблеми з продуктивністю, то використання Shell, мабуть, не чудова ідея.


2
Це лише дублікат відповіді jimmij .
maxschlepzig

0

На основі прийнятої відповіді maxschlepzig (і ідеї з прийнятої відповіді на /superuser/270529/monitoring-a-file-until-a-string-is-found ) пропоную наступне вдосконалене ( на мій погляд) відповідь, яка також може працювати з тайм-аутом:

# 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

Якщо файл не буде створений / відкритий протягом заданого тайм-ауту, то стан виходу становить 124 (відповідно до документації таймауту (man page)). У випадку, якщо він створений / відкритий, статус виходу дорівнює 0 (успіх).

Так, inotifywait працює таким чином у підколонці, і ця підрозділ закінчиться, коли закінчиться час очікування або після закінчення основного сценарію (що станеться раніше).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.