Bash – trap a clock signal in the bash script

bashtrap:

Could I trap a 5-minute signal inside my script? I imagine something like this,

function dosomething {
   echo "It's been 5 minutes."
}

trap dosomething SIGNAL-EVERY-5-MINUTES

while true
do
    sleep 10
done

Note that this example doesn't have sense. It's just to ask if it's possible to trap something every x time.


EDIT: Based on @vinc17 answer, I've noticed that sleep command from the main shell isn't interrupted by USR1 signal, and that's what I want to do.

#!/bin/bash
time=10

(
  set -e
  while true
  do
    sleep 30
    # Since this is run as a subshell (instead of an external command),
    # the parent pid is $$, not $PPID.
    kill -USR1 $$
  done
) &

finish() {
   kill $!
   echo "Closing."
   exit 0
}

changetime() {
   echo "Signal USR1 received."
   time=$(( $RANDOM % 8 + 1))
}

trap changetime USR1
trap finish SIGINT

while true
do
  echo "before sleep"
  sleep $time
  echo "after sleep"
done

outputs,

before sleep
Signal USR1 received.
after sleep
before sleep
Signal USR1 received.
after sleep
before sleep
Signal USR1 received.
Closing.

EDIT2: I've edited the above example, resulting in the same output, but adds one more difficulty: The sleep time is changed by the USR1 trap function. So now, every 30 seconds a random time between 1 and 8 is chosen. SO, I need to kill sleep from the main script when signal changes its time. I insist on that it has no sense, but I need to know if it is possible.

Best Answer

You can run a process that will send a signal (e.g. SIGALRM) to the shell script every x time, and use a trap for this signal. This process could be a script doing something like:

set -e
while true
do
  sleep 300
  kill -ALRM $PPID
done

if started by the main shell script.

The main shell script should kill this process when it is no longer needed, and/or the process should terminate when the pid no longer exists (however there's a race condition on that).

(EDITED) Note: If your main shell script uses the sleep command, it may badly interact with the ALRM signal if sleep is a builtin and sleep(3) is implemented with alarm(2). The POSIX description of the sleep shell utility also says in its rationale: "The exit status is allowed to be zero when sleep is interrupted by the SIGALRM signal because most implementations of this utility rely on the arrival of that signal to notify them that the requested finishing time has been successfully attained. Such implementations thus do not distinguish this situation from the successful completion case. Other implementations are allowed to catch the signal and go back to sleep until the requested time expires or to provide the normal signal termination procedures." To avoid potential issues with some implementations, you can use SIGUSR1 or SIGUSR2 instead of SIGALRM.

Here's an example using a subshell. To make the behavior easier to see, I've replaced the 5-minute period (sleep 300) by a 5-second period (sleep 5).

#!/bin/sh

{
  set -e
  while true
  do
    sleep 5
    # Since this is run as a subshell (instead of an external command),
    # the parent pid is $$, not $PPID.
    kill -USR1 $$
  done
} &

trap 'echo "Signal USR1 received."' USR1

while true
do
  date
  sleep 1
done

The script can be interrupted with Ctrl-C. It doesn't kill the subshell when this happens, but if the pid isn't reused, the subshell terminates automatically after no more than the period (here, 5 seconds) because the kill command fails (kill: No such process).

Related Question