Perform command every X seconds

7,515

Solution 1

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.

Solution 2

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

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

Share:
7,515

Related videos on Youtube

user1032531
Author by

user1032531

Updated on September 18, 2022

Comments

  • user1032531
    user1032531 over 1 year

    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
    
  • ctrl-alt-delor
    ctrl-alt-delor over 8 years
    To avoid resetting SECONDS do sleep "$((10-(($SECONDS-$start_time)%10)))". You will have to do start_time=$SECONDS before loop.
  • mikeserv
    mikeserv over 8 years
    @richard - why avoid it?
  • ctrl-alt-delor
    ctrl-alt-delor over 8 years
    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.
  • user1032531
    user1032531 over 8 years
    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.
  • Ismael Miguel
    Ismael Miguel over 8 years
    Can you explain the reasoning behind your code dump?
  • Ash
    Ash over 8 years
    +1 for referencing the manual, not enough answers on this site cites their answers with actual documentation.
  • Ash
    Ash over 8 years
    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 :)
  • The Sidhekin
    The Sidhekin over 8 years
    @IsmaelMiguel I figured the comments would explain the reasoning. I'm not sure what's unclear.
  • Ismael Miguel
    Ismael Miguel over 8 years
    For example: Why the first line, after the while doesn't end with ;? Why the wait? Why you don't store the value of $! inside a variable, after you call the command?
  • The Sidhekin
    The Sidhekin over 8 years
    @Ash The question was tagged bash, so that's where I tested it, but it's pretty basic Bourne shell, isn't it? I haven't tested it all over, but it should also work in sh, dash, ksh, zsh, etc … the only shells I'm familiar with that won't handle it, are the C shells (csh, tcsh). :)
  • The Sidhekin
    The Sidhekin over 8 years
    @IsmaelMiguel You mean a semicolon after the do? That would be a syntax error; I didn't figure I'd need to explain shell basics. The wait (or not) is if you want to wait (or not) for a perform-command that happens to run for more than the ten allotted seconds. (Now that you mention it, an alternative would be kill $!, when you don't want to wait, but don't want to let it continue either.) Why not storing $! in another variable? I didn't think of it. :) And now I'm thinking of it, I think I'd prefer running this in a subshell. Hmm …
  • Ismael Miguel
    Ismael Miguel over 8 years
    The semicolon I was refering to was after perform-command &.
  • The Sidhekin
    The Sidhekin over 8 years
    @IsmaelMiguel Ah. I never use semicolons after &. In fact, I that would also be a syntax error, at least in bash.
  • Ismael Miguel
    Ismael Miguel over 8 years
    I guess that clears all the empty holes.
  • The Sidhekin
    The Sidhekin over 8 years
    @IsmaelMiguel I've updated the code with rephrasing of the wait explanation, adding the kill alternative you made me think of (funny how that's a good thing, eh?), and a subshell for protecting $!, as you point out it might need. I think that's better; thanks. :)
  • Ismael Miguel
    Ismael Miguel over 8 years
    You're welcome. But won't the sleep change the value of $!?
  • The Sidhekin
    The Sidhekin over 8 years
    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. :)
  • Ismael Miguel
    Ismael Miguel over 8 years
    Nice one. Thank you. That should be rock-solid now.
  • Ash
    Ash over 8 years
    @TheSidhekin you pose a fair point :P My bad.
  • user1032531
    user1032531 over 8 years
    A note of caution. An edge condition could result in 20 seconds.
  • mikeserv
    mikeserv over 8 years
    @Sidhekin - no, it isn't basic Bourne shell. wait is bash.
  • The Sidhekin
    The Sidhekin over 8 years
    @mikeserv Really? Got a reference for that? I'm pretty sure it's been part of Bourne shell since 1979 at least: in-ulm.de/~mascheck/bourne/v7 – although I could be wrong (I was not doing Unix at the time), I'd appreciate a reference. :)
  • mikeserv
    mikeserv over 8 years
    no. there's no reference for a command that doesn't exist. wait is not Bourne. try it yourself.
  • The Sidhekin
    The Sidhekin over 8 years
    @mikeserv Not only have I tried it, and found it working in every Bourne shell implementation I have at hand, I've found it in several manual pages, including the linked one from 1979. Have you tried it? If so, in which version of the Bourne shell?
  • mikeserv
    mikeserv over 8 years
    ok. wait is bourne. my apologies. it is definitely not POSIX, though.
  • The Sidhekin
    The Sidhekin over 8 years
  • mikeserv
    mikeserv over 8 years
    wow. wrong on both counts. when did that happen?