There's a built-in Unix command repeat whose first argument is the number of times to repeat a command, where the command (with any arguments) is specified by the remaining arguments to repeat.

For example,

% repeat 100 echo "I will not automate this punishment."

will echo the given string 100 times and then stop.

I'd like a similar command – let's call it forever – that works similarly except the first argument is the number of seconds to pause between repeats, and it repeats forever. For example,

% forever 5 echo "This will get echoed every 5 seconds forever and ever."

I thought I'd ask if such a thing exists before I write it. I know it's like a 2-line Perl or Python script, but maybe there's a more standard way to do this. If not, feel free to post a solution in your favorite scripting language, Rosetta Stone style.

PS: Maybe a better way to do this would be to generalize repeat to take both the number of times to repeat (with -1 meaning infinity) and the number of seconds to sleep between repeats. The above examples would then become:

% repeat 100 0 echo "I will not automate this punishment."
% repeat -1 5 echo "This will get echoed every 5 seconds forever."

migrated from stackoverflow.com Apr 5 '11 at 9:44

This question came from our site for professional and enthusiast programmers.

  • 1
    Why not: repeat [-t time] [-n number] command [args...]? – Jonathan Leffler Feb 17 '09 at 0:22
  • 14
    Cool. Unfortunately both on my Ubuntu and my AIX repeat is command not found. Can you do a type repeat and let me know where's coming from? – Davide Nov 22 '09 at 23:47
  • 3
    I'm also on Ubuntu and don't have repeat installed. Any information on how to install this would be helpful. – Rory Dec 21 '09 at 13:02
  • 7
    repeat is a builtin command in csh and tcsh. – Keith Thompson Jan 12 '12 at 9:25
  • 2
    @KeithThompson, csh, tcsh and zsh. Though it's more a keyword than a builtin – Stéphane Chazelas Oct 21 '12 at 9:54

25 Answers 25

Try the watch command.

Usage: watch [-dhntv] [--differences[=cumulative]] [--help] [--interval=<n>] 
             [--no-title] [--version] <command>`

So that:

watch -n1  command

will run the command every second (well, technically, every one second plus the time it takes for command to run as watch (at least the procps and busybox implementations) just sleeps one second in between two runs of command), forever.

Would you want to pass the command to exec instead of sh -c, use -x option:

watch -n1 -x command

On macOS, you can get watch from Mac Ports:

port install watch

Or you can get it from Homebrew:

brew install watch
  • 2
    you can use MacPorts to install it. sudo port install watch – pope Feb 17 '09 at 0:27
  • 10
    Also available in homebrew (brew install watch). – Lyle May 12 '11 at 21:45
  • 21
    +1 For a new powerful tool. I used to while true; do X; sleep Y; done - This is way better. – Adam Matan May 16 '11 at 11:14
  • 3
    Problem with this tool (on Ubuntu at least) is that it forces fullscreen, so if I'm dumping some periodic info, I lose the previous info. – scorpiodawg Oct 12 '12 at 1:00
  • 4
    It is called cmdwatch on FreeBSD (from the ports: sysutils/cmdwatch). – Ouki Mar 25 '14 at 21:29

Bash

while + sleep:

while true
do 
    echo "Hi"
    sleep 1
done

Here's the same thing as a shorthand one-liner (From the comments below):

while sleep 1; do echo "Hi"; done

Uses ; to separate commands and uses sleep 1 for the while test since it always returns true. You can put more commands in the loop - just separate them with ;

  • 2
    I believe it works as written in a generic Bourne shell, as well. – dmckee Feb 17 '09 at 0:24
  • 5
    @dreeves, using ";" as separator, commands can be execeted in a single line. Look at Toony's answer as a example – bbaja42 Jan 12 '12 at 9:00
  • 52
    sleep 1 always returns true so you can use it as the while condition. Not the most readable thing in the world but absolutely legit: while sleep 1; do echo "Hi"; done – David Costa Jan 12 '12 at 10:46
  • 2
    @David Costa: You made a reasonable point, but sleep doesn’t always return true. Ī̲ used such loops (albeit with longer delay) namely because there is a way to terminate it gracefully with killing the sleep process. – Incnis Mrsi Sep 12 '15 at 22:53
  • 2
    @IncnisMrsi I didn't think of it, it actually returns zero only when the time has passed and non-zero when it is terminated by a signal. Thanks for pointing it out – David Costa Sep 13 '15 at 9:58

This is just a shorter version of other while+sleep answers, if you are running this kind of tasks often as your daily routine, using this saves you from unnecessary key presses, and if your command line starts to get longer understanding this one is a bit easier. But this one starts with sleeping first.

This is generally useful if you need to follow something has one-line output like machine load:

while sleep 1; do uptime; done
  • Yes. This should be documented next to UUOC. – Johan Mar 15 '13 at 9:05
  • 8
    And if you want it to operate like "watch" you can do while clear; do date; command;sleep 5; done – Johan Mar 15 '13 at 9:12
  • Wow, thanks for while sleep combination, saves keystokes! – George Y. Jun 5 '16 at 2:34

One problem that all the answers posted so far have is that the time the command is executed can drift. For example, if you do a sleep 10 between commands, and the command takes 2 seconds to run, then it's going to run every 12 seconds; if it takes a variable amount of time to run, then over the long term the time when it runs can be unpredictable.

This might be just what you want; if so, use one of the other solutions, or use this one but simplify the sleep call.

For one-minute resolution, cron jobs will run at the specified time, regardless of how long each command takes. (In fact a new cron job will launch even if the previous one is still running.)

Here's a simple Perl script that sleeps until the next interval, so for example with an interval of 10 seconds the command might run at 12:34:00, 12:34:10, 12:34:20, etc., even if the command itself takes several seconds. If the command runs more than interval seconds, the next interval will be skipped (unlike cron). The interval is computed relative to the epoch, so an interval of 86400 seconds (1 day) will run at midnight UTC.

#!/usr/bin/perl

use strict;
use warnings;

if (scalar @ARGV < 2) {
    die "Usage: $0 seconds command [args...]\n";
}

$| = 1;  # Ensure output appears

my($interval, @command) = @ARGV;

# print ">>> interval=$interval command=(@command)\n";

while (1) {
    print "sleep ", $interval - time % $interval, "\n";
    sleep $interval - time % $interval;
    system @command; # TODO: Handle errors (how?)
}
  • See also this other question – Stéphane Chazelas Oct 21 '12 at 9:52
  • 1
    simpler in bash to minimize drift (though it may drift over very long usage windows due to the sleep command and bash itself accumulating its tiny execution times): while :; do sleep 1m & command; wait; done – math Sep 14 '17 at 13:53
  • 1
    @math: Clever. It doesn't work if you have other background processes running in the current shell (wait will wait for all of them). That's fixable by changing wait to wait $!, where $! is the PID of the sleep process. Post this as an answer and I'll upvote it. But it does produce some extraneous output in an interactive shell. – Keith Thompson Sep 14 '17 at 20:59

I think all the answer here so far are either too convoluted, or instead answer a different question:

  • "How to run a program repeatedly so that there are X seconds delay between when the program finished, and the next starts".

The real question was:

  • "How to run a program every X seconds"

These are two very different things when the command takes time to finish.

Take for instance the script foo.sh (pretend this is a program that takes a few seconds to complete).

#!/bin/bash
# foo.sh
echo `date +"%H:%M:%S"` >> output.txt;
sleep 2.5;
# ---

You wish to run this every second, and most would suggest watch -n1 ./foo.sh, or while sleep 1; do ./foo.sh; done. However, this gives the output:

15:21:20
15:21:23
15:21:27
15:21:30
15:21:34

Which is not exactly being run every second. Even with the -p flag, as the man watch page suggests might solve this, the result is the same.

An easy way to accomplish the desired task, which some touch on, is to run the command in the background. In other words:

while sleep 1; do (./foo.sh &) ; done

And that is all there is to it.

You could run it every 500 ms with sleep 0.5, or what have you.

  • Those who voted on different answers, do not understand the elegance of your answer... – Serge Stroobandt Jun 26 '15 at 22:58
  • This is good unless your program has side effects. If the program takes longer than the interval, the side effects can interfere (like overwriting a file). If the program waits for something else, and that something else is taking forever, then you're starting more and more background jobs indefinitely. Use with caution. – John Moeller Jul 16 '15 at 20:36
  • 2
    could invert the order of the backgrounding to ensure that not more than one ./foo.sh is run at a time, but no faster than 1 per second: while :; do sleep 1 & ./foo.sh; wait; done – math Sep 14 '17 at 13:57
  • Yes, it will drift, a few milliseconds on each loop, but it will. Make date +"%H:%M:%S.%N" in foo.sh to see how the fraction will increase slowly on each loop. – Isaac Jul 24 at 12:53
  • Yes, it's true that most answers don't literally address the question. But it's an almost safe bet that they answer what the OP wanted. It would be a safer bet if the OP had actually accepted an answer... But this is good, provided you are aware of possible side effects and you are certain that the average run time of the command is not going to exceed the cycle time – Auspex Jul 26 at 11:17

In bash:

bash -c 'while [ 0 ]; do echo "I will not automate this punishment in absurdum."; done'

(echo could be replaced by any command...

Or in perl:

perl -e 'for (;1;) {print "I will not automate this punishment in absurdum.\n"}'

Where print "I will not automate this punishment in absurdum.\n" could be replaced with "any" command surrounded with backticks (`).

And for a pause, add a sleep statement inside the for loop:

bash -c 'while [ 0 ]; do echo "I will not automate this punishment in absurdum."; sleep 1; done'

and

perl -e 'for (;1;) {print "I will not automate this punishment in absurdum.\n"; sleep 1}'
  • 16
    while [ 0 ] is a bad way to write it. It means 0 is a string with length > 0. while [ 1 ] or while [ jeff ] would do the same thing. Better to write while true. – Mikel Apr 5 '11 at 10:17
  • 5
    @Mikel: Or while : – Keith Thompson Jan 12 '12 at 9:27

Recent bash >= 4.2 under recent Linux kernel, based answer.

In order to limit execution time, there is no forks! Only built-in are used.

For this, I use read builtin function instead of sleep. Unfortunely this won't work with notty sessions.

Quick function "repeat" as requested:

repeat () {
   local repeat_times=$1 repeat_delay=$2 repeat_foo repeat_sleep
   read -t .0001 repeat_foo
   if [ $? = 1 ] ;then
       repeat_sleep() { sleep $1 ;}
   else
       repeat_sleep() { read -t $1 repeat_foo; }
   fi
   shift 2
   while ((repeat_times)); do
        ((repeat_times=repeat_times>0?repeat_times-1:repeat_times))
        "${@}"
        ((repeat_times))&& ((10#${repeat_delay//.})) &&
            repeat_sleep $repeat_delay
   done
}

Little test with quoted strings:

repeat 3 0 printf "Now: %(%T)T, Hello %s.\n" -1 Guy
Now: 15:13:43, Hello Guy.
Now: 15:13:43, Hello Guy.
Now: 15:13:43, Hello Guy.

repeat -1 .5 printf "Now: %(%T)T, Hello %s.\n" -1 Guy
Now: 15:14:14, Hello Guy.
Now: 15:14:14, Hello Guy.
Now: 15:14:15, Hello Guy.
Now: 15:14:15, Hello Guy.
Now: 15:14:16, Hello Guy.
Now: 15:14:16, Hello Guy.
Now: 15:14:17, Hello Guy.
Now: 15:14:17, Hello Guy.
Now: 15:14:18, Hello Guy.
Now: 15:14:18, Hello Guy.
^C

Depending on granularity and duration of submitted command...

Under recent Linux kernels, there is a procfile /proc/timer_list containing time information in nanoseconds.

If you want to run a command exactly once by second, your command has to end in less than a second! And from there, you have to sleep only the rest of current second.

If the delay is more important and your command doesn't require significant time, you could:

command=(echo 'Hello world.')
delay=10
while :;do
    printf -v now "%(%s)T" -1
    read -t $(( delay-(now%delay) )) foo
    ${command[@]}
  done.

But if your goal is to obtain finer granularity, you have to:

Use nanoseconds information to wait until begin of a second...

For this, I wrote a little bash function:

# bash source file for nano wait-until-next-second

mapfile  </proc/timer_list _timer_list
for ((_i=0;_i<${#_timer_list[@]};_i++));do
    ((_c+=${#_timer_list[_i]}))
    [[ ${_timer_list[_i]} =~ ^now ]] && TIMER_LIST_READ=$_c
    [[ ${_timer_list[_i]} =~ offset:.*[1-9] ]] && \
        TIMER_LIST_OFFSET=${_timer_list[_i]//[a-z.: ]} && \
        break
done
unset _i _timer_list _c
readonly TIMER_LIST_OFFSET TIMER_LIST_READ
waitNextSecondHires() {
    local nsnow nsslp
    read -N$TIMER_LIST_READ nsnow </proc/timer_list
    nsnow=${nsnow%% nsecs*}
    nsnow=$((${nsnow##* }+TIMER_LIST_OFFSET))
    nsslp=$((2000000000-10#${nsnow:${#nsnow}-9}))
    read -t .${nsslp:1} foo
}

After sourcing them, you could:

command=(echo 'Hello world.')
while :;do
    waitNextSecondHires
    ${command[@]}
  done.

run ${command[@]} directly on command line, than compare to

command=(eval "echo 'Hello world.';sleep .3")
while :;do
    waitNextSecondHires
    ${command[@]}
  done.

this must give exactly same result.

Hires function "repeat" as requested:

You could source this:

mapfile  </proc/timer_list _timer_list
for ((_i=0;_i<${#_timer_list[@]};_i++));do
    ((_c+=${#_timer_list[_i]}))
    [[ ${_timer_list[_i]} =~ ^now ]] && TIMER_LIST_READ=$_c
    [[ ${_timer_list[_i]} =~ offset:.*[1-9] ]] && \
        TIMER_LIST_OFFSET=${_timer_list[_i]//[a-z.: ]} && \
        break
done
unset _i _timer_list _c
readonly TIMER_LIST_OFFSET TIMER_LIST_READ

repeat_hires () {
    local repeat_times=$1 repeat_delay=$2 repeat_foo repeat_sleep repeat_count
    read -t .0001 repeat_foo
    if [ $? = 1 ] ;then
        repeat_sleep() { sleep $1 ;}
    else
        repeat_sleep() { read -t $1 repeat_foo; }
    fi
    shift 2
    printf -v repeat_delay "%.9f" $repeat_delay
    repeat_delay=${repeat_delay//.}
    read -N$TIMER_LIST_READ nsnow </proc/timer_list
    nsnow=${nsnow%% nsec*}
    started=${nsnow##* }
    while ((repeat_times)); do
        ((repeat_times=repeat_times>0?repeat_times-1:repeat_times))
        "${@}"
        ((repeat_times)) && ((10#$repeat_delay)) && {
            read -N$TIMER_LIST_READ nsnow </proc/timer_list
            nsnow=${nsnow%% nsec*}
            nsnow=${nsnow##* }
            (( (nsnow - started) / 10#$repeat_delay - repeat_count++ )) &&
                printf >&2 "WARNING: Command '%s' too long for %f delay.\n" \
                           "${*}" ${repeat_delay:0:${#repeat_delay}-9
                           }.${repeat_delay:${#repeat_delay}-9}
            printf -v sleep "%010d" $((
                10#$repeat_delay - ( ( nsnow - started ) % 10#$repeat_delay ) ))
            repeat_sleep ${sleep:0:${#sleep}-9}.${sleep:${#sleep}-9}
        }
    done
}

Then try it:

time repeat_hires 21 .05 sh -c 'date +%s.%N;sleep .01'
1480867565.152022457
1480867565.201249108
1480867565.251333284
1480867565.301224905
1480867565.351236725
1480867565.400930482
1480867565.451207075
1480867565.501212329
1480867565.550927738
1480867565.601199721
1480867565.651500618
1480867565.700889792
1480867565.750963074
1480867565.800987954
1480867565.853671458
1480867565.901232296
1480867565.951171898
1480867566.000917199
1480867566.050942638
1480867566.101171249
1480867566.150913407

real    0m1.013s
user    0m0.000s
sys     0m0.016s

time repeat_hires 3 .05 sh -c 'date +%s.%N;sleep .05'
1480867635.380561067
WARNING: Command 'sh -c date +%s.%N;sleep .05' too long for 0.050000 delay.
1480867635.486503367
WARNING: Command 'sh -c date +%s.%N;sleep .05' too long for 0.050000 delay.
1480867635.582332617

real    0m0.257s
user    0m0.000s
sys     0m0.004s
  • read -t is a built-in, while sleep is not. This could have border effect (ie hitting [key:return] interrupt quietly the sleep), but this could be treated by testing $foo – F. Hauri Jan 29 '14 at 17:34
  • Very impressive answer – gena2x Jun 4 '14 at 13:37

Care to try this out (Bash)?

forever ()   {
    TIMES=shift;
    SLEEP=shift;
    if [ "$TIMES" = "-1" ]; then  
        while true;
        do 
            $@
            sleep $SLEEP
        done
    else
        repeat "$TIMES" $@ 
    fi; }
  • I'm not to hot at shell, so improvements welcome. And I don't seem to have a "repeat" command in my distro. – Gregg Lind Feb 17 '09 at 0:40
  • 2
    repeat is specific to csh and tcsh. – Keith Thompson Jan 12 '12 at 9:42
  • 2
    @KeithThompson: and zsh – Thor Aug 2 '17 at 9:57

Perl

#!/usr/bin/env perl
# First argument is number of seconds to sleep between repeats, remaining
# arguments give the command to repeat forever.

$sleep = shift;
$cmd = join(' ', @ARGV);

while(1) {
  system($cmd);
  sleep($sleep); 
}

Bash and some of its kindred shells have the convenient (( ... )) notation wherein arithmetic expressions can be evaluated.

So as an answer to your third challenge, where both the repeat count and delay between each repeat should be configurable, here's one way to do it:

repeat=10
delay=1

i=0
while (( i++ < repeat )); do
  echo Repetition $i
  sleep $delay
done

This answer also suffers from the timing drift covered in Keith's answer.

  • This was the best one. – Ahmad Awais Jul 17 at 23:49

As mentioned by gbrandt, if the watch command is available, definitely use it. Some Unix systems, however, don't have it installed by default (at least they don't where I work).

Here's another solution with slightly different syntax and output (works in BASH and SH):

while [ 1 ] ; do
    <cmd>
    sleep <x>
    echo ">>>>>>>>>>>>>" `date` ">>>>>>>>>>>>>>"
done

Edit: I removed some "." in the last echo statement...holdover from my Perl days ;)

  • 3
    while [ 1 ] only works because 1 is treated as a string, just like while [ -n 1 ]. while [ 0 ] or while [ jeff ] would do the same thing. while true makes much more sense. – Mikel Apr 5 '11 at 10:19

If your intention is not to display a message to your screen, and if you could afford to repeat the job in terms of minutes, crontab, perhaps, would be your best tool. For example, if you wish to execute your command every minute, you would write something like this in your crontab file:

* * * * * my_precious_command

Please check out the tutorial for further example. Also, you can set the timings easily using Crontab Code Generator.

What if we had both ?

Here's the idea : with only "interval", it repeats forever. With "interval" and "times", it repeats this number of times, separated by "interval".

The usage :

$ loop [interval [times]] command

So, the algorithm will be :

  • List item
  • if $1 contains only digits, it is the interval (default 2)
  • if $2 contains only digits, it is the number of times (default infinite)
  • while loop with these parameters
    • sleep "interval"
    • if a number of times has been given, decrement a var until it is reached

Therefore :

loop() {
    local i=2 t=1 cond

    [ -z ${1//[0-9]/} ] && i=$1 && shift
    [ -z ${1//[0-9]/} ] && t=$1 && shift && cond=1
    while [ $t -gt 0 ]; do 
        sleep $i
        [ $cond ] && : $[--t]
        $@
    done
}
  • Note: it does not work with floats, despite sleep does accept them. – Baronsed Nov 11 '13 at 22:37

I wound up up creating a variant on swalog's answer. With his you had to wait X seconds for the first iteration, I'm also running my in the foreground so..

./foo.sh;while sleep 1; do (./foo.sh) ; done

Quick, dirty and probably dangerous to boot, but if you're adventurous and know what you're doing, put this into repeat.sh and chmod 755 it,

while true
do 
    eval $1 
    sleep $2 
done

Invoke it with ./repeat.sh <command> <interval>

My spidey sense says this is probably an evil way of doing this, is my spidey sense right?

  • 7
    Eval!? What for? sleep $1; shift; "$@" or similar would be much better. – Mikel Apr 20 '12 at 2:01
  • Dunno why this is -2. eval may be overkill... except when you need it. Eval lets you get things like redirection onto the command line. – Johan Mar 15 '13 at 9:07

You could run a script from init (adding a line to /etc/inittab). This script must run your command, sleep for the time you want to wait until run the script again, and exit it. Init will start your script again after exit.

Easy way to repeat a job from crontab with an interval less than one minute (20 seconds example) :

crontab: * * * * * script.sh

script.sh:

#!/bin/bash
>>type your commands here.

sleep 20
>>retype your commands here.

sleep 20
>>retype your commands here.

You can obtain the power from python interpreter from shell.

python3 -c "import time, os
while True:
    os.system('your command')
    time.sleep(5)

replace five in any time(sec) you like.

Attention: the return use SHIFT+ENTER to return input in shell. And do not forget to type 4 SPACE indent in each line in while loop.

  • Note that python's os.system() executes a shell to interpret the command line, so invoking python to run shells in a loop to run a command periodically is a bit overkill. You might as well do everything in the shell. – Stéphane Chazelas Jun 26 at 11:26
  • I agree with you. However, there is a 5 second sleep as you described in each loop. Thus the extra cost from starting a new shell can be ignored. If this cost make the period longer than expectation, you can reduce some sleep time as requirement. – pah8J Jun 27 at 8:09

This solution works in MacOSX 10.7. It works wonderfully.

bash -c 'while [ 0 ]; do \
      echo "I will not automate this punishment in absurdum."; done'

In my case

bash -c 'while [ 0 ]; do ls; done'

or

bash -c 'while [ 0 ]; do mv "Desktop/* Documents/Cleanup"; done'

to clean up my desktop constantly.

  • Did you mean to have a sleep command there? – Keith Thompson Jan 12 '12 at 9:41
  • 3
    while [ 0 ] is an odd way to write an infinite loop; it works because the test command (also known as [) treats a non-empty string as true. while : ; do ... ; done is more idiomatic, or if you prefer you can use while true ; do ... ; done. – Keith Thompson Jan 17 '12 at 8:11

You could source this recursive function:

#!/bin/bash
ininterval () {
    delay=$1
    shift
    $*
    sleep $delay
    ininterval $delay $*
}

or add an:

ininterval $*

and call the script.

To repeatedly run a command in a console window I usually run something like this:

while true; do (run command here); done

This works for multiple commands as well, for example, to display a continually updating clock in a console window:

while true; do clear; date; sleep 1; done

  • Why the downvote? You can see this is a working solution by copy and pasting the example into a terminal window. – Thomas Bratt Jan 27 '13 at 14:20
  • I think you got down voted because it is a little bit dangerous and consumes the CPU. – ojblass Jul 6 '13 at 20:22

... I wonder how much complicated solutions can be created when solving this problem.

It can be so easy...

open /etc/crontab 

put there 1 next line to the end of file like:

*/NumberOfSeconds * * * * user /path/to/file.sh

If you want to run something every 1 second, just put there:

*/60 * * * * root /path/to/file.sh 

where that file.sh could be chmod 750 /path/to/file.sh

and inside of that file.sh should be:

#!/bin/bash 
#What does it do
#What is it runned by

your code or commands

and thats all!

ENJOY!

  • 5
    This is not correct. */60 is going to run every 60 minutes, and */5, for example, every 5 minutes. – mika Oct 2 '13 at 17:47
  • 1
    seconds is not allowed on crontab – GAD3R Nov 30 '17 at 12:12
#! /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

With zsh and a sleep implementation that accepts floating-point arguments:

typeset -F SECONDS=0 n=0
repeat 100 {cmd; sleep $(((n+=3) - SECONDS))}

Or for forever:

for ((;;)) {cmd; sleep $(((n+=3) - SECONDS))}

If your sleep doesn't support floats, you can always redefine it as a wrapper around zsh's zselect builtin:

zmodload zsh/zselect
sleep() zselect -t $((($1 * 100) | 0))
until ! sleep 60; do echo $(date); command; command; command; done

works for me.

  1. I don´t need it exactly every 60 seconds
  2. "watch" does not watch commands on all systems
  3. "watch" can take only one command (or a script file)
  4. putting the sleep in the condition instead of the body makes the loop better interruptable (so a claim in a reply to a similar question on stackoverflow. unfortunately I can find it at the moment)
  5. I want to execute it once before the delay, so I use until instead of while
  • I just noticed that "until" apparently executes the sleep before the first iteration too. is there any way of putting the condition at the end of the loop in bash? – Titus Jun 11 '16 at 13:04

protected by jasonwryan Nov 16 '16 at 18:09

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

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