Shell – How to determine the amount of time left in a “sleep”

shellsleep

I have:

sleep 210m && for i in $(seq 1 5); do echo -e '\a'; sleep 0.5; done 

running as a simple, no-frills timer to remind me when something should be done. That sleep 210m is PID 25347.

I'm trying to figure out how much time is left in the sleep. The best I've come up with, with me putting in the original sleep amount (210 minutes) is:

$ echo "210 60 * $(ps -o etimes= 25347) - 60 ~ r n [:] P p" | dc
78:11

(Explanation: First bit computes the number of seconds in the original sleep; the $(ps…) bit gets the time since sleep started in seconds, then the rest subtracts them and displays in minutes and seconds.)

It occurs to me this would be useful in general; is there a better way to do this? Or at least a clever way to parse the sleep time from ps -o args?

Best Answer

Supporting GNU or Solaris 11 sleep arguments (one or more <double>[smhd] durations, so would also work with traditional implementations that support only one decimal integer number (like on FreeBSD), but not with those accepting more complex arguments like ISO-8601 durations). Using etime instead of etimes as that's more portable (standard Unix).

remaining_sleep_time() { # arg: pid
  ps -o etime= -o args= -p "$1" | perl -MPOSIX -lane '
    %map = qw(d 86400 h 3600 m 60 s 1);
    $F[0] =~ /(\d+-)?(\d+:)?(\d+):(\d+)/;
    $t = -($4+60*($3+60*($2+24*$1)));
    for (@F[2..$#F]) {
      s/\?//g;
      ($n, $p) = strtod($_);
      $n *= $map{substr($_, -$p)} if $p;
      $t += $n
    }
    print $t'
}

(the s/\?//g is to get rid of the ? characters that procps' ps uses as replacement for control characters. Without it, it would fail to parse sleep $'\r1d' or sleep $'\t1d'... Unfortunately, in some locales, including the C locale, it uses . instead of ?. Not much we can do in that case as there's no way to tell a \t5d from a .5d (half day)).

Pass the pid as argument.

That also assumes the argv[0] passed to sleep doesn't contain blanks and that the number of arguments is small enough that it's not truncated by ps.

Examples:

$ sleep infinity & remaining_sleep_time "$!"
Inf
$ sleep 0xffp-6d &
$ remaining_sleep_time "$!"
344249
$ sleep 1m 1m 1m 1m 1m & remaining_sleep_time "$!"
300

For a [[[ddd-]HH:]MM:]SS output instead of just the number of seconds, replace the print $t with:

$output = "";
for ([60,"%02d\n"],[60,"%02d:"],[24,"%02d:"],[inf,"%d-"]) {
  last unless $t;
  $output = sprintf($_->[1], $t % $_->[0]) . $output;
  $t = int($t / $_->[0])
}
printf "%s", $output;
Related Question