Wie kann man die verbleibende Zeit im „Schlaf“ bestimmen?

11

Ich habe:

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

Laufen als einfacher, schnörkelloser Timer, um mich daran zu erinnern, wann etwas getan werden sollte. Das sleep 210mist PID 25347.

Ich versuche herauszufinden, wie viel Zeit noch im Schlaf ist. Das Beste, was ich mir ausgedacht habe, wenn ich die ursprüngliche Schlafmenge (210 Minuten) eingegeben habe, ist:

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

(Erläuterung: Das erste Bit berechnet die Anzahl der Sekunden im ursprünglichen Ruhezustand. Das $(ps…)Bit erhält die Zeit seit Beginn des Ruhezustands in Sekunden, der Rest subtrahiert sie und wird in Minuten und Sekunden angezeigt.)

Mir fällt ein, dass dies im Allgemeinen nützlich wäre; Gibt es einen besseren Weg, dies zu tun? Oder zumindest eine clevere Methode, um die Schlafzeit zu analysieren ps -o args?

derobert
quelle
1
Beachten Sie, dass Prozesse in ihrem Leben mehr als einen Befehl ausführen können (und dies häufig tun). Bei sh -c 'sleep 1; sleep 2'vielen shImplementierungen wird beispielsweise derselbe Prozess ausgeführt shund später ausgeführt sleep 2(1 Sekunde später).
Stéphane Chazelas
@ StéphaneChazelas Ich glaube nicht, dass dies hier passieren kann. Vermutlich kann die Shell diese Optimierung nur beim letzten Befehl ausführen, den sie ausführt (da sie danach keine Möglichkeit mehr hat, die Kontrolle wiederzugewinnen exec). Aber das ist ein guter Punkt für jede allgemeine Lösung.
Derobert
Beachten Sie auch, dass mehrere Shells ( mkshund ksh93zumindest) integriert sind sleep(also nicht angezeigt werden ps). Sie müssen im Voraus wissen, mit welchen sleepImplementierungen Sie es zu tun haben.
Stéphane Chazelas

Antworten:

5

Unterstützung von GNU- oder Solaris 11- sleepArgumenten (eine oder mehrere <double>[smhd]Dauern, daher auch bei herkömmlichen Implementierungen, die nur eine Dezimalzahl unterstützen (wie bei FreeBSD), jedoch nicht bei solchen, die komplexere Argumente wie die Dauer von ISO-8601 akzeptieren ). Verwenden etimeanstelle von, etimesda dies portabler ist (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'
}

(Das s/\?//gist, um die ?Zeichen loszuwerden procps, psdie als Ersatz für Steuerzeichen verwendet werden. Ohne sie würde es nicht analysiert sleep $'\r1d'oder sleep $'\t1d'... Leider wird in einigen Gebietsschemas, einschließlich des CGebietsschemas, .anstelle von ?. Nicht viel können wir tun In diesem Fall gibt es keine Möglichkeit, einen \t5dvon einem .5d(halben Tag) zu unterscheiden.

Übergeben Sie die PID als Argument.

Dies setzt auch voraus, dass das argv[0]übergebene an sleepkeine Leerzeichen enthält und dass die Anzahl der Argumente klein genug ist, um nicht abgeschnitten zu werden ps.

Beispiele:

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

[[[ddd-]HH:]MM:]SSErsetzen Sie für eine Ausgabe anstelle von nur der Anzahl der Sekunden die print $tdurch:

$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;
Stéphane Chazelas
quelle
1
0xffp-6d... Nun, das ist ein Testfall! Aber eigentlich braucht GNU Schlaf auch Dinge wie sleep 1h 30m...
derobert
@derobert. D'oh, ich hätte schwören können, dass ich das versucht habe. Ich denke, ich muss versucht haben, sleep 1h -1mwas unter Solaris 11 funktioniert, aber nicht mit GNU sleep. Zurück zum Zeichenbrett. Zumindest unterstützt es nicht die iso8601-Dauer wie ksh93, was viel schwieriger wäre.
Stéphane Chazelas
@derobert, aktualisiert, um mehrere Argumente zu unterstützen
Stéphane Chazelas
@ StéphaneChazelas Ihre Lösung ist gut, obwohl es nicht funktioniert, wenn eine sleepPause eingelegt wird. In diesem Fall wird möglicherweise sogar ein negativer Wert angezeigt.
Eile
Ich benutze diese Geldstrafe seit einer Woche, bin aber heute remaining_sleep_time 12838zurückgekehrt -40642. Es könnte auf "Pause" sein, wie @rush angibt, aber nicht sicher, wie man debuggt?
WinEunuuchs2Unix
3

Dies schien ein lustiges Problem zu sein; Da Thrig eine Perl-Option behandelt hat, ist hier ein Bash-Skript, das etwas Ähnliches tut. Es wird nicht genügend Fehler überprüft (es wird davon ausgegangen, dass Sie eine gültige PID eines Schlafbefehls übergeben). Es behandelt dieselbe Syntax wie GNUs Coreutils Sleep , nämlich:

  • S | m | h | d-Suffixe für Sekunden / Minuten / Stunden / Tage
  • Mehrere Zeitparameter werden addiert

#!/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))
Jeff Schaller
quelle
Selbst wenn es auf den GNU-Schlaf beschränkt ist (einige integrierte Schlafimplementierungen wie Solaris 11 oder ksh93 unterstützen viel komplexere Dauerausdrücke), ist dies unvollständig, da es für Gleitkommazahlen (wie sleep 0.5doder sleep 1e5oder sleep infinity) nicht funktioniert . Ein Wechsel von bashzu einer Shell, die Gleitkommazahlen wie zsh, ksh93 oder yash unterstützt, würde helfen. Nicht das etimesist nicht tragbar.
Stéphane Chazelas
Das Kaninchenloch zum Parsen von Gleitkommazahlen ging tiefer als ich wollte, daher kann ich dies hier mit den bekannten Einschränkungen belassen und der Tatsache, dass es nur eine geringfügige Verbesserung gegenüber dem Vorschlag des OP ist (dies kann die Schlafzeit anhand der PID bestimmen versus Hardcodierung, die in).
Jeff Schaller
Einfacher mitperl
Stéphane Chazelas
1

Dies kann besser mit einem Skript durchgeführt werden, das die verbleibende Zeit anzeigt, wenn ein QUIT(normalerweise control+\) oder ein INFOSignal verwendet wird.

#!/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
#
#   snooze 25m bread; make-noise-somehow
#
# one can then elsewhere
#
#   pkill -INFO snooze-bread

use strict;
use warnings;
use Term::ReadKey qw(ReadMode);

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 {
    ReadMode 0;
    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;
}

ReadMode 0;
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;
}
Thrig
quelle
Beantwortet die Frage nicht wirklich (da mein Schlaf bereits lief) - vermeidet sie aber in Zukunft, also immer noch nützlich.
Ich frage
Siehe auch zsh's schedund den atBefehl für einen anderen Ansatz, mit dem die Zeit abgefragt werden kann.
Stéphane Chazelas
0

Sollte mit dem tqdm Python-Modul einfach genug sein.

Zeigt einen schönen visuellen Fortschrittsbalken und kann als Unix-Pipe verwendet werden.

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

Das folgende Python-Snippet zählt 100 Sekunden

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]

Sandeep
quelle
Ich sehe nicht ein, wie einfach es wäre, diese Seite anzusehen (obwohl ich kein Python-Programmierer bin). Sie scheinen etwas im Sinn zu haben - bitte fügen Sie es Ihrer Antwort hinzu.
Derobert