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

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`?

• Note that processes can (and often do) run more than one command in their lifetime. For instance in `sh -c 'sleep 1; sleep 2'` with many `sh` implementations, it's the same process that executes `sh` and later executes `sleep 2` (1 second later). Oct 5, 2016 at 16:27
• @StéphaneChazelas I don't think that can happen here, presumably the shell can only do that optimization on the last command it's executing (as it has no way to regain control after `exec`). But that's a good point for any general solution. Oct 5, 2016 at 16:33
• Also note that several shells (`mksh` and `ksh93` at least) have `sleep` built-in (so wouldn't show-up in `ps`)., you'd need to know in advance which `sleep` implementations you're dealing with. Oct 5, 2016 at 16:37

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;
``````
• `0xffp-6d` ... Now, that's a test case! But actually GNU sleep takes things like `sleep 1h 30m` as well... Oct 6, 2016 at 16:55
• @derobert. D'oh I could have sworn I'd tried that. I guess I must have tried `sleep 1h -1m` which works on Solaris 11 but not with GNU `sleep`. Back to the drawing board. At least it doesn't support the iso8601 durations like ksh93 which would be a lot harder. Oct 6, 2016 at 20:02
• @derobert, updated to support several arguments Oct 6, 2016 at 20:17
• @StéphaneChazelas your solution is good, though it won't work in case `sleep` is put on pause. It might even display negative value in this case.
– rush
Apr 3, 2018 at 19:36
• Have been using this fine for a week but today `remaining_sleep_time 12838` returned `-40642`. It might be on "Pause" as @rush states but not sure how to debug? Sep 19, 2018 at 0:29

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))
``````
• Even if limited to GNU sleep (some sleep implementations like Solaris 11 or ksh93 builtin support much more complex duration expressions), that's incomplete as it doesn't work for floating point numbers (like `sleep 0.5d` or `sleep 1e5` or `sleep infinity`). Switching from `bash` to a shell supporting floating points like zsh, ksh93 or yash would help. Not that `etimes` is not portable. Oct 6, 2016 at 8:24
• The rabbit hole for parsing floating point went deeper than I was willing to go, so I may leave this here with the known limitations, and the fact that it's just a minor improvement over the OP's suggestion (this can determine the sleep time from the PID versus hard-coding that in). Oct 6, 2016 at 16:09
• Easier with `perl` Oct 6, 2016 at 16:39

If you're looking for a simple solution, you can just use `ps aux`, which shows the START time. But you might want to use it only for a quick approximation rather ran a precise value.

In this example, the start time is 15:47. So the sleep will end around 15:57

This might be better done with a script that can show the time remaining when bonked with a `QUIT` (usually control+\) or `INFO` signal.

``````#!/usr/bin/env perl
#
# snooze - sleep for a given duration, with SIGINFO or SIGQUIT
# (control+\ typically) showing how much time remains. Usage:
#
#   snooze 3m; make-noise-somehow
#
# or with
#
#
# one can then elsewhere
#

use strict;
use warnings;

my %factors = ( s => 1, m => 60, h => 3600, d => 86400 );

my \$arg = shift or die "Usage: \$0 sleep-time [label]\n";
my \$to_sleep = 0;
while ( \$arg =~ m/([0-9]+)([smhd])?/g ) {
my \$value  = \$1;
my \$factor = \$2;
\$value *= \$factors{\$factor} if \$factor;
\$to_sleep += \$value;
}
die "nothing to die to sleep to sleep no more for\n" if \$to_sleep == 0;

my \$label = shift;
\$0 = \$label ? "snooze-\$label" : "snooze";

ReadMode 2;    # noecho to hide control+\s from gunking up the message

sub remainder { warn "\$0: " . deltatimefmt(\$to_sleep) . " remaining\n" }

sub restore {
warn "\$0: " . deltatimefmt(\$to_sleep) . " remainds\n";
exit 1;
}

# expect user to mash on control+\ or whatever generates SIGINFO
for my \$name (qw/ALRM INFO QUIT/) {
\$SIG{\$name} = \&remainder;
}

# back to original term settings if get blown away
for my \$name (qw/HUP INT TERM USR1 USR2/) {
\$SIG{\$name} = \&restore;
}

\$SIG{TSTP} = 'IGNORE';    # no Zees for you!

while ( \$to_sleep > 0 ) {
\$to_sleep -= sleep \$to_sleep;
}

exit;

sub deltatimefmt {
my \$difference = shift;

return "0s" if \$difference == 0;

my \$seconds = \$difference % 60;
\$difference = ( \$difference - \$seconds ) / 60;
my \$minutes = \$difference % 60;
\$difference = ( \$difference - \$minutes ) / 60;

#  my \$hours = \$difference;
my \$hours = \$difference % 24;
\$difference = ( \$difference - \$hours ) / 24;
my \$days  = \$difference % 7;
my \$weeks = ( \$difference - \$days ) / 7;

# better way to do this?
my \$temp = (\$weeks) ? "\${weeks}w " : q{};
\$temp .= (\$days)    ? "\${days}d "    : q{};
\$temp .= (\$hours)   ? "\${hours}h "   : q{};
\$temp .= (\$minutes) ? "\${minutes}m " : q{};
\$temp .= (\$seconds) ? "\${seconds}s"  : q{};
return \$temp;
}
``````
• Doesn't really answer the question (since my sleep was already running)—but does avoid it in the future, so still useful. Wonder if there is a terminal countdown alarm utility somewhere... Oct 5, 2016 at 19:08
• See also `zsh`'s `sched` and the `at` command for another approach that allows querying the time. Oct 5, 2016 at 19:16

Should be easy enough with tqdm python module.

Shows a nice visual progress bar and can be used as a unix pipe.

https://pypi.python.org/pypi/tqdm

The following python snippet counts 100 seconds

``````import time
from tqdm import tqdm
for i in tqdm(range(100)):
time.sleep(1)
``````

55%|██████████ | 55/100 [00:55<00:45, 1.00s/it]

• I don't see how it'd be easy from looking at that page (though I'm not a Python programmer). You seem to have something in mind—please add it to your answer. Nov 17, 2016 at 17:26