How to determine the amount of time left in a "sleep"?

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;

This seemed like a fun problem to solve; since thrig covered a perl option, here's a bash script that does something similar. It does not do enough error-checking (it assumes that you're passing in a valid PID of a sleep command). It handles the same syntax that GNU's coreutils sleep does, namely:

  • s|m|h|d suffixes for seconds/minutes/hours/days
  • multiple time parameters get added together

#!/usr/bin/env bash

# input: PID of a sleep command
# output: seconds left in the sleep command

function parse_it_like_its_sleep {
  # $1 = one sleep parameter
  # print out the seconds it translates to

  mult=1
  [[ $1 =~ ([0-9][0-9]*)(s|m|h|d) ]] && {
    n=${BASH_REMATCH[1]}
    suffix=${BASH_REMATCH[2]}
  } || {
    n=$1
  }
  case $suffix in
    # no change for 's'
    (m) mult=60;;
    (h) mult=$((60 * 60));;
    (d) mult=$((60 * 60 * 24));;
  esac
  printf %d $((n * mult))
}

# TODO - some sanity-checking for $1
set -- $(ps -o etimes=,args= $1)
[[ $2 = "sleep" ]] || exit 1
elapsed=$1
shift 2
total=0
for arg
do
  # TODO - sanity-check $arg
  s=$(parse_it_like_its_sleep $arg)
  total=$((total + s))
done
printf "%d seconds left\n" $((total - elapsed))

Tags:

Shell

Sleep