How to get a unix script to run every 15 seconds?
Asked Answered
M

9

85

I've seen a few solutions, including watch and simply running a looping (and sleeping) script in the background, but nothing has been ideal.

I have a script that needs to run every 15 seconds, and since cron won't support seconds, I'm left to figuring out something else.

What's the most robust and efficient way to run a script every 15 seconds on unix? The script needs to also run after a reboot.

Monniemono answered 23/6, 2009 at 18:17 Comment(1)
How long does it take to run?Exemplar
E
78

I would use cron to run a script every minute, and make that script run your script four times with a 15-second sleep between runs.

(That assumes your script is quick to run - you could adjust the sleep times if not.)

That way, you get all the benefits of cron as well as your 15 second run period.

Edit: See also @bmb's comment below.

Enrapture answered 23/6, 2009 at 18:19 Comment(7)
@Aiden: Ha! My nemesis, we meet again!Enrapture
If the script is not consistent in how long it takes to run, make four copies of the script. One sleeps 15 seconds before starting, another 30, another 45, and another zero. Then run all four every minute.Poulterer
@Enrapture - Have no fear, I got assassinated for not granulating the minutes into seconds. But i'm watching you :PCampos
How can this be cron is triggered every 1 minuteDerte
Actually the outter-script should run the inner script three times, not four. Otherwise the last run of the last minute will overlap the frist run of the next minute. That would be running the inner script 5 times every minute instead of four.Sherd
@user1598390: No. With 15-second sleeps, the four runs will be at zero seconds, 15 seconds, 30 seconds and 45 seconds. Four runs, three sleeps between.Enrapture
to add to @Poulterer suggestion, i use sleep 15;[command] then sleep 30;[command] then sleep 45;[command]Amin
S
300

If you insist of running your script from cron:

* * * * * /foo/bar/your_script
* * * * * sleep 15; /foo/bar/your_script
* * * * * sleep 30; /foo/bar/your_script
* * * * * sleep 45; /foo/bar/your_script

and replace your script name&path to /foo/bar/your_script

Subteen answered 23/6, 2009 at 18:28 Comment(5)
This worked perfectly for me. The soltion above this on using a background task was spawning several child processes and causing memory issues on my end.Lipocaic
if running php script do this:* * * * * sleep 15; php /foo/bar/your_scriptVisceral
if running php script you can prepend the line #!/usr/bin/php to the top of your php script and make it executableExhortation
I feel embarrassed that I had to google for this solution. Maybe stackoverflow makes me think less.Elyse
In response to @Hacknightly, it only happens when the scripts exceed runtime of 15 seconds and/or tasks are not able to release enough memory that can be used by the systemLuthern
E
78

I would use cron to run a script every minute, and make that script run your script four times with a 15-second sleep between runs.

(That assumes your script is quick to run - you could adjust the sleep times if not.)

That way, you get all the benefits of cron as well as your 15 second run period.

Edit: See also @bmb's comment below.

Enrapture answered 23/6, 2009 at 18:19 Comment(7)
@Aiden: Ha! My nemesis, we meet again!Enrapture
If the script is not consistent in how long it takes to run, make four copies of the script. One sleeps 15 seconds before starting, another 30, another 45, and another zero. Then run all four every minute.Poulterer
@Enrapture - Have no fear, I got assassinated for not granulating the minutes into seconds. But i'm watching you :PCampos
How can this be cron is triggered every 1 minuteDerte
Actually the outter-script should run the inner script three times, not four. Otherwise the last run of the last minute will overlap the frist run of the next minute. That would be running the inner script 5 times every minute instead of four.Sherd
@user1598390: No. With 15-second sleeps, the four runs will be at zero seconds, 15 seconds, 30 seconds and 45 seconds. Four runs, three sleeps between.Enrapture
to add to @Poulterer suggestion, i use sleep 15;[command] then sleep 30;[command] then sleep 45;[command]Amin
A
15

Modified version of the above:

mkdir /etc/cron.15sec
mkdir /etc/cron.minute
mkdir /etc/cron.5minute

add to /etc/crontab:

* * * * * root run-parts /etc/cron.15sec > /dev/null 2> /dev/null
* * * * * root sleep 15; run-parts /etc/cron.15sec > /dev/null 2> /dev/null
* * * * * root sleep 30; run-parts /etc/cron.15sec > /dev/null 2> /dev/null
* * * * * root sleep 45; run-parts /etc/cron.15sec > /dev/null 2> /dev/null

* * * * * root run-parts /etc/cron.minute > /dev/null 2> /dev/null
*/5 * * * * root run-parts /etc/cron.5minute > /dev/null 2> /dev/null
Adjacency answered 23/10, 2013 at 14:44 Comment(0)
B
14

Won't running this in the background do it?

#!/bin/sh
while [ 1 ]; do
    echo "Hell yeah!" &
    sleep 15
done

This is about as efficient as it gets. The important part only gets executed every 15 seconds and the script sleeps the rest of the time (thus not wasting cycles).

Bughouse answered 23/6, 2009 at 18:28 Comment(1)
Edits must be at least 8 characters (which is idiotic, IMHO) so I couldn't add the & at the end of line 3. In any case, as is, this doesn't run every 15 seconds. This runs every "15 seconds + however long echo hello takes to run". Which could be 0.01 seconds; could be 19 hours.Blanco
J
1

I wrote a scheduler faster than cron. I have also implemented an overlapping guard. You can configure the scheduler to not start new process if previous one is still running. Take a look at https://github.com/sioux1977/scheduler/wiki

Judi answered 15/9, 2015 at 10:40 Comment(0)
L
0

Use nanosleep(2). It uses structure timespec that is used to specify intervals of time with nanosecond precision.

struct timespec {
           time_t tv_sec;        /* seconds */
           long   tv_nsec;       /* nanoseconds */
       };
Lejeune answered 15/1, 2013 at 11:39 Comment(2)
I'm going to go ahead and guess they don't need nanosecond precision, since the thing they're spawning every 15 seconds is a shell script, not a kernel thread.Blanco
@ParthianShot maybe but you never know.Lejeune
T
0
#! /bin/sh

# Run all programs in a directory in parallel
# Usage: run-parallel directory delay
# Copyright 2013 by Marc Perkel
# docs at http://wiki.junkemailfilter.com/index.php/How_to_run_a_Linux_script_every_few_seconds_under_cron"
# Free to use with attribution

if [ $# -eq 0 ]
then
   echo
   echo "run-parallel by Marc Perkel"
   echo
   echo "This program is used to run all programs in a directory in parallel" 
   echo "or to rerun them every X seconds for one minute."
   echo "Think of this program as cron with seconds resolution."
   echo
   echo "Usage: run-parallel [directory] [delay]"
   echo
   echo "Examples:"
   echo "   run-parallel /etc/cron.20sec 20"
   echo "   run-parallel 20"
   echo "   # Runs all executable files in /etc/cron.20sec every 20 seconds or 3 times a minute."
   echo 
   echo "If delay parameter is missing it runs everything once and exits."
   echo "If only delay is passed then the directory /etc/cron.[delay]sec is assumed."
   echo
   echo 'if "cronsec" is passed then it runs all of these delays 2 3 4 5 6 10 12 15 20 30'
   echo "resulting in 30 20 15 12 10 6 5 4 3 2 executions per minute." 
   echo
   exit
fi

# If "cronsec" is passed as a parameter then run all the delays in parallel

if [ $1 = cronsec ]
then
   $0 2 &
   $0 3 &
   $0 4 &
   $0 5 &
   $0 6 &
   $0 10 &
   $0 12 &
   $0 15 &
   $0 20 &
   $0 30 &
   exit
fi

# Set the directory to first prameter and delay to second parameter

dir=$1
delay=$2

# If only parameter is 2,3,4,5,6,10,12,15,20,30 then automatically calculate 
# the standard directory name /etc/cron.[delay]sec

if [[ "$1" =~ ^(2|3|4|5|6|10|12|15|20|30)$ ]]
then
   dir="/etc/cron.$1sec"
   delay=$1
fi

# Exit if directory doesn't exist or has no files

if [ ! "$(ls -A $dir/)" ]
then
   exit
fi

# Sleep if both $delay and $counter are set

if [ ! -z $delay ] && [ ! -z $counter ]
then
   sleep $delay
fi

# Set counter to 0 if not set

if [ -z $counter ]
then
   counter=0
fi

# Run all the programs in the directory in parallel
# Use of timeout ensures that the processes are killed if they run too long

for program in $dir/* ; do
   if [ -x $program ] 
   then
      if [ "0$delay" -gt 1 ] 
      then
         timeout $delay $program &> /dev/null &
      else
         $program &> /dev/null &
      fi
   fi
done

# If delay not set then we're done

if [ -z $delay ]
then
   exit
fi

# Add delay to counter

counter=$(( $counter + $delay ))

# If minute is not up - call self recursively

if [ $counter -lt 60 ]
then
   . $0 $dir $delay &
fi

# Otherwise we're done
Therontheropod answered 9/1, 2014 at 14:36 Comment(0)
T
0

Since my previous answer I came up with another solution that is different and perhaps better. This code allows processes to be run more than 60 times a minute with microsecond precision. You need the usleep program to make this work. Should be good to up to 50 times a second.

#! /bin/sh

# Microsecond Cron
# Usage: cron-ms start
# Copyright 2014 by Marc Perkel
# docs at http://wiki.junkemailfilter.com/index.php/How_to_run_a_Linux_script_every_few_seconds_under_cron"
# Free to use with attribution

basedir=/etc/cron-ms

if [ $# -eq 0 ]
then
   echo
   echo "cron-ms by Marc Perkel"
   echo
   echo "This program is used to run all programs in a directory in parallel every X times per minute."
   echo "Think of this program as cron with microseconds resolution."
   echo
   echo "Usage: cron-ms start"
   echo
   echo "The scheduling is done by creating directories with the number of"
   echo "executions per minute as part of the directory name."
   echo
   echo "Examples:"
   echo "  /etc/cron-ms/7      # Executes everything in that directory  7 times a minute"
   echo "  /etc/cron-ms/30     # Executes everything in that directory 30 times a minute"
   echo "  /etc/cron-ms/600    # Executes everything in that directory 10 times a second"
   echo "  /etc/cron-ms/2400   # Executes everything in that directory 40 times a second"
   echo
   exit
fi

# If "start" is passed as a parameter then run all the loops in parallel
# The number of the directory is the number of executions per minute
# Since cron isn't accurate we need to start at top of next minute

if [ $1 = start ]
then
   for dir in $basedir/* ; do
      $0 ${dir##*/} 60000000 &
   done
   exit
fi

# Loops per minute and the next interval are passed on the command line with each loop

loops=$1
next_interval=$2

# Sleeps until a specific part of a minute with microsecond resolution. 60000000 is full minute

usleep $(( $next_interval - 10#$(date +%S%N) / 1000 ))

# Run all the programs in the directory in parallel

for program in $basedir/$loops/* ; do
   if [ -x $program ] 
   then
      $program &> /dev/null &
   fi
done

# Calculate next_interval

next_interval=$(($next_interval % 60000000 + (60000000 / $loops) ))

# If minute is not up - call self recursively

if [ $next_interval -lt $(( 60000000 / $loops * $loops)) ]
then
   . $0 $loops $next_interval &
fi

# Otherwise we're done
Therontheropod answered 23/1, 2014 at 15:31 Comment(1)
Edit the original one instead of posting again!Bitstock
B
-1

To avoid possible overlapping of execution, use a locking mechanism as described in that thread.

Bilocular answered 23/1, 2013 at 11:20 Comment(1)
-1 The OP didn't say he needed to avoid overlapping execution; the thing could be reentrant. Plus, this doesn't answer the question.Blanco

© 2022 - 2024 — McMap. All rights reserved.