How do I wait for a file in the shell script?

I’m trying to write a shell script that will wait for a file to appear in the /tmp directory called sleep.txt and once it is found the program will cease, otherwise I want the program to be in a sleep (suspended) state until the file is located. Now, I’m assuming that I will use a test command. So, something like

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

I’m brand new to writing shell script and any help is greatly appreciated!

Asked By: Mo Gainz

||

Just put your test in the while loop:

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

Under Linux, you can use the inotify kernel subsystem to efficiently wait for the appearance of a file in a directory:

inotifywait  -e create,moved_to,attrib --include '/sleep.txt$' -qq /tmp
# script execution continues ...

NB: I included the attrib event to also register touch /tmp/sleep.txt when the file already exists.


In cases where there is a race condition between sleep.txt showing up and the inotifywait invocation, i.e. when the file might be created or touched just before the watch is established – and then never again, afterwards – one can extend the code like this:

coproc inw {
    LC_ALL=C inotifywait  -e create,moved_to --include '/sleep.txt$'  /tmp 2>&1
}
while IFS= read -r -u "${inw[0]}" line; do
    if [ "$line" = "Watches established." ]; then
        break
    fi
done
if [ -f /tmp/sleep.txt ]; then
    kill $inw_PID
else
    wait
fi
# script execution continues ...

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 your process sleeps more. With an inotify event specification like create,attrib 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.

Answered By: maxschlepzig

The accepted answer really works (thanks maxschlepzig) but leaves the inotifywait monitoring in the background until your script exits. The only answer that matches exactly your requirements (ie waiting for sleep.txt to show up inside /tmp) seems to be Stephane’s, if the directory to be monitored by inotifywait is changed from dot (.) to ‘/tmp’.

However, if you are willing to use a temporary directory ONLY for putting your sleep.txt flag and can bet that nobody else will put any file in that directory, just asking inotifywait to watch this directory for file creations would be sufficient:

1st step: create the directory you will monitor:

directoryToPutSleepFile=$(mktemp -d)

2nd step: make sure the directory is really there

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

3rd step: wait until ANY file shows up inside $directoryToPutSleepFile

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

The file you will put in $directoryToPutSleepFile can be named sleep.txt awake.txt, whatever. The moment any file is created inside $directoryToPutSleepFile your script will continue past the inotifywait statement.

Answered By: nikolaos

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 foonsleep.txtnbar 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.

Answered By: Stéphane Chazelas

Based on the accepted answer of maxschlepzig (and an idea from the accepted answer at https://superuser.com/questions/270529/monitoring-a-file-until-a-string-is-found) I propose the following improved (in my view) answer which can also work with timeout:

# 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

In case the file does not get created/opened within the given timeout, then the exit status is 124 (as per timeout documentation (man page)).
In case it gets created/opened, then the exit status is 0 (success).

Yes, inotifywait runs in a sub-shell this way and that sub-shell will only finish running when the timeout happens or when the main script exits (whichever comes first).

Answered By: Attila123
Categories: Answers Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.