Comment déterminer le temps restant dans un «sommeil»?

11

J'ai:

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

fonctionnant comme une minuterie simple et sans fioritures pour me rappeler quand quelque chose doit être fait. C'est le sleep 210mPID 25347.

J'essaie de comprendre combien de temps il reste dans le sommeil. Le meilleur que j'ai trouvé, avec moi en mettant la quantité de sommeil originale (210 minutes) est:

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

(Explication: le premier bit calcule le nombre de secondes dans la veille d'origine; le $(ps…)bit obtient le temps écoulé depuis le début de la veille en secondes, puis le reste les soustrait et s'affiche en minutes et secondes.)

Il me semble que ce serait utile en général; Y a-t-il une meilleure manière de faire cela? Ou au moins une façon intelligente d'analyser le temps de sommeil ps -o args?

derobert
la source
1
Notez que les processus peuvent exécuter (et exécutent souvent) plusieurs commandes au cours de leur durée de vie. Par exemple, sh -c 'sleep 1; sleep 2'avec de nombreuses shimplémentations, c'est le même processus qui s'exécute shet s'exécute plus tard sleep 2(1 seconde plus tard).
Stéphane Chazelas
@ StéphaneChazelas Je ne pense pas que cela puisse arriver ici, sans doute le shell ne peut faire que l'optimisation sur la dernière commande qu'il exécute (car il n'a aucun moyen de reprendre le contrôle après exec). Mais c'est un bon point pour toute solution générale.
derobert
Notez également que plusieurs shells ( mkshet ksh93au moins) sont sleepintégrés (donc ne s'afficheraient pas ps)., Vous devez savoir à l'avance avec quelles sleepimplémentations vous avez affaire.
Stéphane Chazelas

Réponses:

5

Prenant en charge les sleeparguments GNU ou Solaris 11 (une ou plusieurs <double>[smhd]durées, cela fonctionnerait également avec les implémentations traditionnelles qui prennent en charge un seul nombre entier décimal (comme sur FreeBSD), mais pas avec celles acceptant des arguments plus complexes comme les durées ISO-8601 ). Utiliser etimeau lieu de etimescar c'est plus portable (Unix standard).

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'
}

( s/\?//gest de se débarrasser des ?caractères qui procps« psremplacent les caractères de contrôle. Sans lui, il ne parviendrait pas à analyser sleep $'\r1d'ou sleep $'\t1d'... Malheureusement, dans certains paramètres régionaux, y compris les Cparamètres régionaux, il utilise à la .place de ?. Pas grand-chose que nous pouvons faire dans ce cas, car il n'y a aucun moyen de distinguer un à \t5dpartir d'une .5d(demi-journée)).

Passez le pid comme argument.

Cela suppose également que le argv[0]passé à sleepne contient pas de blancs et que le nombre d'arguments est suffisamment petit pour qu'il ne soit pas tronqué par ps.

Exemples:

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

Pour une [[[ddd-]HH:]MM:]SSsortie au lieu de simplement le nombre de secondes, remplacez le print $tpar:

$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
la source
1
0xffp-6d... Maintenant, c'est un cas de test! Mais en fait, GNU sleep prend des choses comme ça sleep 1h 30maussi ...
derobert
@derobert. D'oh j'aurais pu jurer que j'avais essayé ça. Je suppose que j'ai dû essayer sleep 1h -1mce qui fonctionne sur Solaris 11 mais pas avec GNU sleep. Retour à la planche à dessin. Au moins, il ne prend pas en charge les durées iso8601 comme ksh93, ce qui serait beaucoup plus difficile.
Stéphane Chazelas
@derobert, mis à jour pour supporter plusieurs arguments
Stéphane Chazelas
@ StéphaneChazelas votre solution est bonne, mais elle ne fonctionnera pas au cas où elle sleepserait mise en pause. Il pourrait même afficher une valeur négative dans ce cas.
précipiter le
J'utilise cette amende depuis une semaine mais je suis remaining_sleep_time 12838revenu aujourd'hui -40642. Cela pourrait être sur "Pause" comme le dit @rush mais vous ne savez pas comment déboguer?
WinEunuuchs2Unix
3

Cela semblait être un problème amusant à résoudre; depuis que thrig a couvert une option perl, voici un script bash qui fait quelque chose de similaire. Il ne fait pas assez de vérification d'erreurs (il suppose que vous passez un PID valide d'une commande sleep). Il gère la même syntaxe que le sommeil coreutils de GNU , à savoir:

  • s | m | h | d suffixes pour secondes / minutes / heures / jours
  • plusieurs paramètres de temps sont ajoutés ensemble

#!/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
la source
Même s'il est limité à la mise en veille GNU (certaines implémentations de mise en veille comme Solaris 11 ou ksh93 prennent en charge des expressions de durée beaucoup plus complexes), c'est incomplet car cela ne fonctionne pas pour les nombres à virgule flottante (comme sleep 0.5dou sleep 1e5ou sleep infinity). Passer d' bashun shell prenant en charge les virgules flottantes comme zsh, ksh93 ou yash serait utile. Ce etimesn'est pas portable.
Stéphane Chazelas
Le trou du lapin pour analyser la virgule flottante est allé plus loin que je ne le souhaitais, donc je peux laisser cela ici avec les limites connues, et le fait que ce n'est qu'une amélioration mineure par rapport à la suggestion de l'OP (cela peut déterminer le temps de sommeil à partir du PID contre codage en dur que dans).
Jeff Schaller
Plus facile avecperl
Stéphane Chazelas
1

Cela pourrait être mieux fait avec un script qui peut montrer le temps restant quand bonked avec un QUIT(généralement control+\) ou un INFOsignal.

#!/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;
}
branler
la source
Ne répond pas vraiment à la question (puisque mon sommeil était déjà en cours), mais l'évite à l'avenir, donc toujours utile.
Je me
Voir aussi zsh's schedet la atcommande pour une autre approche qui permet d'interroger l'heure.
Stéphane Chazelas
0

Devrait être assez facile avec le module python tqdm.

Affiche une belle barre de progression visuelle et peut être utilisé comme un tube Unix.

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

L'extrait de code python suivant compte 100 secondes

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
la source
Je ne vois pas comment ce serait facile de regarder cette page (même si je ne suis pas un programmeur Python). Vous semblez avoir quelque chose en tête, veuillez l'ajouter à votre réponse.
derobert