12

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
user1032531
  • 1,877
  • 6
  • 29
  • 35
  • 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

3 Answers3

18

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.

The Sidhekin
  • 840
  • 1
  • 6
  • 8
  • 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
  • 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? – Ismael Miguel Oct 11 '15 at 13:00
  • @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 Oct 11 '15 at 13:02
  • @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 … – The Sidhekin Oct 11 '15 at 13:08
  • The semicolon I was refering to was after `perform-command &`. – Ismael Miguel Oct 11 '15 at 13:10
  • @IsmaelMiguel Ah. I never use semicolons after `&`. In fact, I that would also be a syntax error, at least in bash. – The Sidhekin Oct 11 '15 at 13:13
  • I guess that clears all the empty holes. – Ismael Miguel Oct 11 '15 at 13:14
  • @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. :) – The Sidhekin Oct 11 '15 at 13:23
  • You're welcome. But won't the `sleep` change the value of `$!`? – Ismael Miguel Oct 11 '15 at 13:26
  • 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
  • Nice one. Thank you. That should be rock-solid now. – Ismael Miguel Oct 11 '15 at 13:30
  • @TheSidhekin you pose a fair point :P My bad. – Ash Oct 11 '15 at 15:54
  • @Sidhekin - no, it isn't basic Bourne shell. `wait` is `bash`. – mikeserv Oct 11 '15 at 18:56
  • @mikeserv Really? Got a reference for that? I'm pretty sure it's been part of Bourne shell since 1979 at least: http://www.in-ulm.de/~mascheck/bourne/v7/ – although I could be wrong (I was not doing Unix at the time), I'd appreciate a reference. :) – The Sidhekin Oct 11 '15 at 19:29
  • no. there's no reference for a command that doesn't exist. `wait` is not Bourne. try it yourself. – mikeserv Oct 11 '15 at 19:31
  • @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? – The Sidhekin Oct 11 '15 at 19:43
  • ok. `wait` *is* bourne. my apologies. it is definitely not POSIX, though. – mikeserv Oct 11 '15 at 19:45
  • @mikeserv … http://pubs.opengroup.org/onlinepubs/9699919799/utilities/wait.html … – The Sidhekin Oct 11 '15 at 19:57
  • 1
    wow. wrong on both counts. when did that happen? – mikeserv Oct 11 '15 at 20:00
10

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
mikeserv
  • 57,448
  • 9
  • 113
  • 229
  • 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 00:15
  • +1 for referencing the manual, not enough answers on this site cites their answers with actual documentation. – Ash Oct 11 '15 at 11:09
  • A note of caution. An edge condition could result in 20 seconds. – user1032531 Oct 11 '15 at 17:09
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.

Dani_l
  • 4,720
  • 1
  • 18
  • 34