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.
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.
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.
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
bash
shell scripting tutorials provided by Xmodulo.This website is made possible by minimal ads and your gracious donation via PayPal or credit card
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 ‒ About ‒ Write for Us ‒ Feed ‒ Powered by DigitalOcean