如何让 Unix 脚本每15秒运行一次?

我看到了一些解决方案,包括 watch 和在后台简单地运行循环(和休眠)脚本,但是没有一个是理想的。

我有一个需要每15秒运行一次的脚本,由于 cron 不支持秒,因此我需要解决其他问题。

在 Unix 上每15秒运行一次脚本的最健壮和最有效的方法是什么?脚本在重新启动后也需要运行。

150403 次浏览

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.

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

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

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 */
};

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

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
#! /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

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

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