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 to sleep.txt. One needs to match for moved_to events in addition to create
  • 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 a foo\nsleep.txt\nbar file has been created for instance?
  • what if the file is created before inotifywait has been started and installed the watch? Then inotifywait 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.