How to wake up a sleeping bash script

Last updated on March 10, 2021 by Dan Nanni

Suppose you want to have a bash script that sleeps in the background normally, and wakes up to perform some task only when you send a signal to the script. Once the script completes the task, it then goes back to sleep. This kind of "wake-on-demand" behavior might be useful if you do not want to terminate and re-start the script for whatever reason, for example, because the script needs to maintain some sort of internal state or history across task runs.

In this tutorial, let's find out how you can wake up a sleeping bash script. Before describing the technique, let me go over the concept of a named pipe, which is useful to implement the wake-on-demand feature.

What is a Named Pipe?

A pipe is an inter-process communication mechanism available on GNU/Linux, which allows the output of one process to be re-directed as the input to another process. The "|" character you often type from the Linux command line is an "unnamed pipe", which implies that you do not explicitly name/open it like a regular file. Instead, the kernel automatically creates and destroys it, and manages message passing through the pipe, without associated processes doing anything about it.

On the other hand, a "named pipe" can be explicitly created by a regular process with open(), and given a name like a regular file. Message passing through a named pipe is FIFO-based ("First In, First Out"), meaning that the order in which bytes are written to the pipe is the same as the order in which those bytes are read from the pipe. You can create a named pipe, and any two aribtrary processes (e.g., shell scripts) can communicate with each other through the pipe as long as they have read/write permission to do so.

You can create a named pipe with mkfifo command. The leftmost character p in the file mode indicates that the file is actually a named pipe. With the default mode of rw-rw-r--, any process created by alice or users belonging to the same group as her can access the pipe.

$ mkfifo <name-of-pipe>

With this knowledge in mind, let's find out in the rest of the tutorial how you can use a named pipe to wake up a sleeping bash script.

Wake up a Sleeping Bash Script with a Named Pipe

One way for a bash script to wait for input from a named pipe is to utilize the read command.

read -n1 < mypipe

The -n <N> option above implies that read will return after reading <N> characters, rather than waiting for a newline character. Thus, when a shell script calls the above command, it will wait for an input from mypipe and return as soon as any character is received from it.

From another terminal session, if you type the following command, this will trigger the read command to return, which essentially wakes up the script that is waiting on read.

$ echo x > mypipe

In a more advanced case, you may not want read to wait for an input indefinitely, but rather, want it to return after a timeout period if no input is received during the period. For this you can use -t <timeout> option. For example, the following command waits for an input for upto 30 seconds, and then times out and returns.

read -n1 -t30 <> mypipe

Note that we are using <> (i.e., open the pipe for read and write), so that the bash shell will not be blocked to proceed after read times out when no other process opens the pipe for write.

Now that the read command could return under two conditions (i.e., either received an input or timed out), how can we tell which event has triggered read to return? In fact, we can tell it from the exit status of the read command. If read returns because it has received something from the pipe, its exit status will be 0 (successful). On the other hand, if read returns because no data was read within the timeout period, its exit code will be 142, which corresponds to SIGALRM. You can retrieve the exti status of read by accessing a built-in variable $? right after read returns.

Wake-On-Demand Bash Script Example

Combining what I described so far, the following bash script example implements the "wake-on-demand" feature. It creates a named pipe /home/dan/signalme if it does not already exist, and then enters an infinite while loop, sleeping while waiting for an input from the pipe. Whenever there is any input, the script wakes up, performs some task that varies depending on the exit status, and finally goes back to sleep.

function create_pipe {
    local pipe=$1
    if [ ! -p "$pipe" ]; then
        mkfifo "$pipe";
    fi
}

function wait_for_signal {
    local pipe=$1
    local timeout=$2
    read -n1 -t${timeout} <> "$pipe"
}

NAMED_PIPE="/home/dan/signalme"

create_pipe $NAMED_PIPE

echo "I am sleeping"
wait_for_signal $NAMED_PIPE 10
status=$?

# perform post-wakeup tasks
if [[ $status == 0 ]]; then
    # received input from named pipe
    echo "I woke up by signal: $status"
else
    # read function timed out
    echo "I woke up after timeout: $status"
fi

To wake up the script, simply type the following command from another terminal.

$ echo x > /home/dan/signalme

For example, the above script prints out the following output when it times out the first time, and then wakes up twice after that.

I am sleeping now
I woke up after timeout: 142
I am sleeping now
I woke up by signal: 0
I am sleeping now
I woke up by signal: 0
I am sleeping now

If you find this tutorial helpful, I recommend you check out the series of bash shell scripting tutorials provided by Xmodulo.

Support Xmodulo

This website is made possible by minimal ads and your gracious donation via PayPal (Credit Card) or Bitcoin (1M161JGAkz3oaHNvTiPFjNYkeABox8rb4g).

Please note that this article is published by Xmodulo.com under a Creative Commons Attribution-ShareAlike 3.0 Unported License. If you would like to use the whole or any part of this article, you need to cite this web page at Xmodulo.com as the original source.

Xmodulo © 2021 ‒ AboutWrite for Us ‒ Feed ‒ Powered by DigitalOcean