10

I wish to perform a command ever 10 seconds, and have it executed in the background (thereby eliminating watch?). All the answers show something like the following, but this will execute ever 11 to 14 seconds. How can this be accomplished?

while true; do
    # perform command that takes between 1 and 4 seconds
    sleep 10
done
  • maybe with real-time signals. and proper handling of leap seconds. good luck. – mikeserv Oct 10 '15 at 20:45
  • @mikeserv So, don't bother trying? I don't care about milli-second accuracy, just say half a second. – user1032531 Oct 10 '15 at 20:49
  • What are you trying to accomplish? I find that a task that has to run with that sort of frequency is usually a workaround for another problem. – Sobrique Oct 11 '15 at 15:26
  • @Sobrique Exactly. I am doing a proof of concept of a polling data logger. In reality, it will be programmed in some other language, but this works for now. – user1032531 Oct 11 '15 at 17:08
16

How about:

( # In a subshell, for isolation, protecting $!
  while true; do
    perform-command & # in the background
    sleep 10 ;
    ### If you want to wait for a perform-command
    ### that happens to run for more than ten seconds,
    ### uncomment the following line:
    # wait $! ;
    ### If you prefer to kill a perform-command
    ### that happens to run for more than ten seconds,
    ### uncomment the following line instead:
    # kill $! ;
    ### (If you prefer to ignore it, uncomment neither.)
  done
)

ETA: With all those comments, alternatives, and the subshell for extra protection, that looks a whole lot more complicated than it started. So, for comparison, here's what it looked like before I started worrying about wait or kill, with their $! and need for isolation:

while true; do perform-command & sleep 10 ; done

The rest is really just for when you need it.

  • Can you explain the reasoning behind your code dump? – Ismael Miguel Oct 11 '15 at 10:45
  • 2
    Also, to cite which shell your code works for (like mikeserv's answer) so people don't get confused when a different shell behaves differently :) – ash Oct 11 '15 at 11:15
  • @IsmaelMiguel I figured the comments would explain the reasoning. I'm not sure what's unclear. – The Sidhekin Oct 11 '15 at 12:56
  • 1
    The bash manual says of $!: "Expands to the process ID of the job most recently placed into the background," and the sleep is not placed into the background, so no, it won't. :) – The Sidhekin Oct 11 '15 at 13:28
  • 1
    wow. wrong on both counts. when did that happen? – mikeserv Oct 11 '15 at 20:00
9

You can do something like the following in bash, zsh, or ksh:

SECONDS=0
while   command
do      sleep "$((10-(SECONDS%10)))"
done

Here's what the bash manual says about $SECONDS:

$SECONDS

  • Each time this parameter is referenced, the number of seconds since shell invocation is returned. If a value is assigned to $SECONDS, the value returned upon subsequent references is the number of seconds since the assignment plus the value assigned. If $SECONDS is unset, it loses its special properties, even if it is subsequently reset.

Here's a working example:

(   SECONDS=0
    while   sleep   "$((RANDOM%10))"
    do      sleep   "$((10-(SECONDS%10)))"
            echo    "$SECONDS"
    done
)

10
20
30
40
50
60
70
80
90
100
  • 2
    To avoid resetting SECONDS do sleep "$((10-(($SECONDS-$start_time)%10)))". You will have to do start_time=$SECONDS before loop. – ctrl-alt-delor Oct 10 '15 at 21:15
  • @richard - why avoid it? – mikeserv Oct 10 '15 at 21:20
  • because one day you will have it in a script that you call from with-in such a loop. And you will spend all day wondering why it is not working properly. In short, it does not nest, if you reset SECONDS. Spawning a sub-shell as in your latest edit, should also avoid this problem. – ctrl-alt-delor Oct 10 '15 at 21:31
  • 1
    @richard - since the question was regarding a while true loop I didn't figure any shell state was expected to survive it. Wrapping a loop like that in its own context is usually best practice, anyway. In a situation like that I would be more concerned about doing so for trap's sake than for $SECONDS' sake, though. – mikeserv Oct 10 '15 at 21:38
  • Thanks Mike, I was thinking to do something similar (but didn't yet know the specifics how). But Sidhekin's solution seems to do the same thing, no? I am not qualified to make judgement on the better answer, but felt obligated to pick something. – user1032531 Oct 11 '15 at 0:15
3

BASH only - You could also compute time spent by your command and subtract from 10:

TIMEFORMAT=$'%0R'
while true; do
    T=$({ time command; } 2>&1)
    sleep $(( 10-T ))

From BASH man:

TIMEFORMAT The value of this parameter is used as a format string specifying how the timing information for pipelines prefixed with the time reserved word should be displayed. The % character introduces an escape sequence that is expanded to a time value or other information. The escape sequences and their meanings are as follows; the braces denote optional portions.
%% A literal %.
%[p][l]R The elapsed time in seconds.
%[p][l]U The number of CPU seconds spent in user mode.
%[p][l]S The number of CPU seconds spent in system mode.
%P The CPU percentage, computed as (%U + %S) / %R.

The optional p is a digit specifying the precision, the number of fractional digits after a decimal point. A value of 0 causes no decimal point or fraction to be output.

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.