Как мне ждать файл в скрипте оболочки?

14

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

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

Я новичок в написании сценариев оболочки, и любая помощь очень ценится!

Мо Гайнц
источник
Посмотрите $MAILPATH.
mikeserv

Ответы:

22

В Linux вы можете использовать подсистему ядра inotify, чтобы эффективно ожидать появления файла в каталоге:

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когда файл уже существует.

maxschlepzig
источник
Спасибо, это круто! Зачем вам цикл 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 и выводит строку каждый раз, когда файл открывается / создается. Так что вам нужно, чтобы цикл while прерывался при его изменении.
NHDaly
за исключением того, что это приводит к состоянию гонки - в отличие от версии test -e / sleep. Невозможно гарантировать, что файл не будет создан непосредственно перед вводом цикла - в этом случае событие будет пропущено. Вам нужно добавить что-то вроде (sleep 1; [[-e /tmp/sleep.txt]] && touch /tmp/sleep.txt;) & перед циклом. Что, конечно, может создать проблемы в будущем, в зависимости от реальной бизнес-логики
n-alexander
@ n-alexander Ну, ответ предполагает, что вы выполняете фрагмент до того, как начнете процесс, который произойдет sleep.txtв какой-то момент времени. Таким образом, нет расы. Вопрос не содержит ничего, намекающего на сценарий, который вы имеете в виду.
maxschlepzig
@maxschlepzig наоборот. Если порядок не применяется, его нельзя принять. Основы.
н-александр
10

Просто поместите свой тест в whileцикл:

while [ ! -f /tmp/sleep.txt ]; do sleep 1; done
# next command
jimmij
источник
Итак, как вы написали, это логика: пока файл не существует, скрипт будет спать. В противном случае это сделано. Верный?
Мо Гейнц
@MoGainz Правильно. Число после sleep- это количество секунд ожидания в каждой итерации - вы можете изменить это в зависимости от ваших потребностей.
Джимми
7

Есть несколько проблем с некоторыми из inotifywaitоснованных на этом подходах:

  • им не удается найти sleep.txtфайл, который был создан сначала как временное имя, а затем переименован в sleep.txt. Нужно соответствовать для moved_toсобытий в дополнение кcreate
  • Имена файлов могут содержать символы новой строки, печать имен файлов с разделителями новой строки недостаточна, чтобы определить sleep.txt, был ли создан файл . Что, если 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)

2-й шаг: убедитесь, что каталог действительно есть

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.
Стефан
см. мой ответ для альтернативного способа решения этой проблемы.
Стефан
Пожалуйста, попробуйте мой код. 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
Это просто дубликат ответа Джимми .
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 (согласно документации по времени ожидания (страница руководства)). В случае, если он создан / открыт, статус выхода равен 0 (успех).

Да, inotifywait запускается в под-оболочке таким образом, и эта под-оболочка будет работать только тогда, когда истечет время ожидания или завершится основной сценарий (в зависимости от того, что наступит раньше).

Attila123
источник