3

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.

  • The problem is that if you attempt to kill the sleep, there is a high risk to kill another command in the script. The easiest solution is to do a shorter sleep run in a loop, e.g.: for i in $(seq 10); do sleep 1; done – vinc17 Sep 18 '14 at 7:37
  • Alternatively, instead of sleep 10, you can do ./do_sleep; rm -f some_file where do_sleep is a script that does echo $$ > some_file; exec sleep 10, so that the code that sends the signal can also kill the sleep by reading its pid in the created file. Not tested. – vinc17 Sep 18 '14 at 8:09
  • I'll try that last one. I think it'll work although it is isn't very clean, considering I'm having a script just to do sleep. The script I'm working on has a long sleep inside the main script, which should be interruped by the signal. Moreover, inside the trap associated with USR1 I need to change a global variable, which I guess I commented before, but forgot to include it on the example above. – whitenoisedb Sep 18 '14 at 15:38
  • Just edited again, adding that factor. – whitenoisedb Sep 18 '14 at 16:07
5

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).

Note: if your main shell script uses the sleep command, it will interact with the ALRM signal, but you can use SIGUSR1 or SIGUSR2 instead.

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).

  • thanks! Just one thing. I don't want to kill the process, just send the signal to execute the trap function, is it possible? – whitenoisedb Sep 17 '14 at 23:48
  • @redraw If you define the trap, the signal won't kill the shell script. But what I meant is that before the shell script terminates, it should kill the process that sends the signals, otherwise its pid may be reused and the new process might receive this signal and be killed. But if the shell script never terminates, this is no longer a problem. – vinc17 Sep 17 '14 at 23:57
  • I got a little confused. Would you just edit your answer and add a simple script that recieves the trap? My intention is to run a main script which traps a signal. Another script would be sending that signal every 5 minutes. – whitenoisedb Sep 18 '14 at 0:03
  • @redraw I've just added an example: it contains the script that receives the signal, and the script (started in background as a subshell) that sends the signal (here every 5 seconds so that you don't wait too much to see how it behaves). – vinc17 Sep 18 '14 at 1:21
  • thanks vinc17. Ah, kill returns error and the subshell terminates because of set -e – whitenoisedb Sep 18 '14 at 2:04
2

Yes. The easiest way is to do it pretty much exactly as you originally describe in your question. It's just that part of that something you do should be spawning another child process to send your process a signal 5 minutes in the future. Taking a page from @vinc17's book, I'll shorten the span to 5 seconds also.

trap 'ME=$$
     : $( (sleep 5
     ps -p $ME >/dev/null 2>&1 && { 
        echo hey >/dev/tty
        kill -USR1 $ME
     })&)
' USR1; kill -USR1 $$

That way you don't have some long-running process just asking to be zombied. Instead you have a new, single-purposed counter that gets a fresh reference to the parent's PID at every interval. I also incorporate a little check just to make sure the child does not attempt to do its one job in the event its parent has died since it detached.

Run that and every 5 seconds you should get:

hey
hey
hey
hey
hey
hey
hey
hey
hey
hey

This example will also likely hang the terminal in the meantime, else it will do nothing at all if you try to background the whole process. This is due to its hold on /dev/tty - choose a file instead if you mean run the whole thing in the background, else do a direct stty call and re-exec the tty when you want it.

  • Would you put your code in place using my example? I got confused because you call the subshell inside the trap command, and I was doing that from my main script and let the trap command do something else. – whitenoisedb Sep 18 '14 at 15:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.