3. Traps. Asynchronous execution. Named pipes.
-
We know that programs can respond to signals. We can add this capability to our scripts too. Bash provides a mechanism for this purpose known as a trap. Let's see a simple example:
vim trap-demo.sh
trap-demo.sh
#!/bin/bash
# trap-demo: simple signal handling demo
trap "echo 'I am ignoring you.'" SIGINT SIGTERM
for i in {1..5}; do
echo "Iteration $i of 5"
sleep 5
doneWhen we press Ctrl-c while the script is running, the script will intercept the signal and will respond to it by running the
echo
command. Let's try it:./trap-demo.sh
Press Ctrl-c a few times and see what happens.
-
It is more convenient to tell
trap
to call a function in response to a signal, instead of a complex command. Let's see another example:vim trap-demo2.sh
trap-demo2.sh
#!/bin/bash
# trap-demo2: simple signal handling demo
exit_on_signal_SIGINT () {
echo "Script interrupted." 2>&1
exit 0
}
exit_on_signal_SIGTERM () {
echo "Script terminated." 2>&1
exit 0
}
trap exit_on_signal_SIGINT SIGINT
trap exit_on_signal_SIGTERM SIGTERM
for i in {1..5}; do
echo "Iteration $i of 5"
sleep 5
doneNote the inclusion of an exit command in each of the signal-handling functions. Without an exit, the script would continue after completing the function.
./trap-demo2.sh
Press Ctrl-c.
-
Bash has a builtin command to help manage asynchronous execution. The
wait
command causes a parent script to pause until a specified process (i.e., the child script) finishes.This can be best explained by an example. We will need two scripts, a parent script, and a child script:
vim async-child.sh
async-child.sh
#!/bin/bash
# async-child: Asynchronous execution demo (child)
echo "Child: child is running..."
sleep 5
echo "Child: child is done. Exiting."This is a simple script that runs for 5 seconds.
vim async-parent.sh
async-parent.sh
#!/bin/bash
# async-parent: Asynchronous execution demo (parent)
echo "Parent: starting..."
echo "Parent: launching child script..."
./async-child.sh &
pid=$!
echo "Parent: child (PID= $pid) launched."
echo "Parent: continuing..."
sleep 2
echo "Parent: pausing to wait for child to finish..."
wait "$pid"
echo "Parent: child is finished. Continuing..."
echo "Parent: parent is done. Exiting."From this script we launch the child script. Since we are appending
&
after it, the parent script will not wait for the child to finish executing but will continue running. Both of the scripts are now running in parallel. Immediately after launching the child, the parent uses the special variable$!
to get the process ID (PID) of the child. This variable always contains the PID of the last job put into the background. Then, later in the parent script, we use the commandwait
to stop the parent from running any further, until the child script is finished. Let's try it:./async-parent.sh
All the messages output from the parent are prefixed with
Parent:
and all the messages output from the child are prefixed withChild:
. This helps us understand the flow of execution. -
Named pipes behave like files but actually form first-in first-out (FIFO) buffers. As with ordinary (unnamed) pipes, data goes in one end and emerges out the other.
With named pipes, it is possible to set up something like this:
process1 > named_pipe
, an this:process2 < named_pipe
and it will behave like this:process1 | process2
. The only difference is thatprocess1
andprocess2
run in the current shell, not in a subshell, which makes named pipes more useful, even if they are a bit less convenient than using a pipe operator (|
).A named pipe can be created with the command
mkfifo
:mkfifo pipe1
ls -l pipe1
Notice that the first letter in the attributes field is "p", indicating that it is a named pipe.
ls -l > pipe1 &
cat < pipe1
This is similar to:
ls -l | cat
However the named pipe is more flexible, because the two commands connected by the pipe can be executed even in different terminals.
However these two examples are not the same thing:
echo "abc" | read
echo $REPLY
echo "abc" > pipe1 &
read < pipe1
echo $REPLY
Let's remove
pipe1
:rm pipe1
-
Another example with a named pipe:
mkfifo pipe1
while true; do read line < pipe1; echo "You said: '$line'"; done &
echo Hi > pipe1
echo Hello > pipe1
echo "The quick brown fox jumped over the lazy dog." > pipe1
fg
Press Ctrl-c to stop the
while
loop.rm pipe1