How do I wait for a file in the shell script?
Under Linux, you can use the inotify kernel subsystem to efficiently wait for the appearance of a file in a directory:
while read i; do if [ "$i" = sleep.txt ]; then break; fi; done \
< <(inotifywait -e create,open --format '%f' --quiet /tmp --monitor)
# script execution continues ...
(assuming Bash for the <()
output redirection syntax)
The advantage of this approach in comparison to fixed time interval polling like in
while [ ! -f /tmp/sleep.txt ]; do sleep 1; done
# script execution continues ...
is that the kernel sleeps more. With an inotify event specification like create,open
the script is just scheduled for execution when a file under /tmp
is created or opened. With the fixed time interval polling you waste CPU cycles for each time increment.
I included the open
event to also register touch /tmp/sleep.txt
when the file already exists.
Just put your test in the while
loop:
while [ ! -f /tmp/sleep.txt ]; do sleep 1; done
# next command
There are a few problems with some of the inotifywait
-based approaches given so far:
- they fail to find a
sleep.txt
file that has been created first as a temporary name and then renamed tosleep.txt
. One needs to match formoved_to
events in addition tocreate
- file names can contain newline characters, printing the names of the files newline delimited is not enough to determine if a
sleep.txt
has been created. What if afoo\nsleep.txt\nbar
file has been created for instance? - what if the file is created before
inotifywait
has been started and installed the watch? Theninotifywait
would wait forever for a file that is already here. You'd need to make sure the file is not already there after the watch has been installed. - some of the solutions leave
inotifywait
running (at least until another file is created) after the file has been found.
To address those, you could do:
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"
}
Note that we're watching for the creation of sleep.txt
in the current directory, .
(so you'd do a cd /tmp || exit
before in your example). The current directory never changes, so when that pipe line returns successfully, it is a sleep.txt
in the current directory that has been created.
Of course, you can replace .
with /tmp
above, but while inotifywait
is running, /tmp
could have been renamed several times (unlikely for /tmp
, but something to consider in the general case) or a new filesystem mounted on it, so when the pipeline returns, it may not be a /tmp/sleep.txt
that has been created but a /new-name-for-the-original-tmp/sleep.txt
instead. A new /tmp
directory could also have been created in the interval and that one wouldn't be watched, so a sleep.txt
created there wouldn't be detected.