如何等待shell脚本中的文件?


15

我正在尝试编写一个shell脚本,该脚本将等待文件出现在/tmp名为的目录中sleep.txt,一旦找到该程序,它将停止运行,否则我希望该程序处于睡眠(已暂停)状态,直到找到该文件为止。现在,我假设我将使用测试命令。所以,像

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

我是刚开始编写Shell脚本的人,对您的帮助非常感谢!


看看$MAILPATH
mikeserv

Answers:


23

在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创建或打开文件时调度脚本执行。使用固定的时间间隔轮询,您浪费每个时间增量的CPU周期。

我包含了该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

哦,JK。安装该工具后,我现在了解了。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相反。除非强制执行订购,否则无法假定。基本。
n-alexander

10

只需将您的测试放入while循环中:

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

因此,正如您所写的,这是一种逻辑:当文件不存在时,脚本将进入休眠状态。否则,它就完成了。正确?
Mo Gainz

@MoGainz正确。后面sleep的数字是每次迭代要等待的秒数-您可以根据需要进行更改。
jimmij

7

inotifywait到目前为止,使用某些基于方法的方法存在一些问题:

  • 他们无法找到sleep.txt先创建为临时名称然后重命名为的文件sleep.txtmoved_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监视留在后台,直到脚本退出。如果要由inotifywait监视的目录从点(。)更改为'/ tmp',则唯一与您的要求完全匹配(即等待sleep.txt在/ tmp中显示)的答案似乎是Stephane。

不过,如果你愿意用一个临时目录为把你sleep.txt标志,并可以打赌,没人会提出任何文件在该目录中,只是要求inotifywait到这个目录看文件的创作就足够了:

第一步:创建要监视的目录:

directoryToPutSleepFile=$(mktemp -d)

第二步:确保目录确实存在

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

第三步:等到任何文件显示在里面 $directoryToPutSleepFile

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

您将放入的文件$directoryToPutSleepFile可以命名为sleep.txt awake.txt,无论如何。在脚本中创建任何文件的那一刻$directoryToPutSleepFile将继续执行该inotifywait语句。


1
但这可能会丢失文件的创建,如果之前刚创建了另一个文件,则会sleep.txt在两次inotifywait调用之间创建该文件。
斯特凡Chazelas

请参阅我的答案以找到其他解决方法。
斯特凡Chazelas

请尝试我的代码。inotifywait仅被调用一次。创建sleep.txt时(如果已经存在,则进行读写),inotifywait输出“ sleep.txt”,并且在“ done”语句之后继续执行。
nikolaos

它被调用了多次,直到sleep.txt创建了一个名为的文件。例如尝试touch /tmp/foo /tmp/sleep.txt,然后inotifywait将报告的一个实例foo并退出,到下一次inotifywait启动时,sleep.txt早就已经存在,因此inotifywait将不会检测到它的创建。
斯特凡Chazelas

您对多个调用是正确的:对在/ tmp下创建的每个文件都调用一个。我更新了答案。谢谢你的评论。
nikolaos

0

通常,要使简单的测试/睡眠循环可靠,就很难做到任何事情。主要问题是测试将与文件创建竞争-除非重复测试,否则会错过create事件。在大多数情况下,这是您最好的选择,在这些情况下,您将使用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以这种方式在子Shell中运行,并且该子Shell仅在发生超时或主脚本退出时(以先到者为准)完成运行。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.