Como determinar a quantidade de tempo restante em um "sono"?

11

Eu tenho:

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

funcionando como um timer simples e sem frescuras para me lembrar quando algo deve ser feito. Esse sleep 210mé o PID 25347.

Estou tentando descobrir quanto tempo resta no sono. O melhor que eu inventei, colocando a quantidade de sono original (210 minutos) é:

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

(Explicação: O primeiro bit calcula o número de segundos no sono original; o $(ps…)bit obtém o tempo desde o início do sono em segundos e o restante os subtrai e é exibido em minutos e segundos.)

Ocorre-me que isso seria útil em geral; Existe uma maneira melhor de fazer isso? Ou pelo menos uma maneira inteligente de analisar o tempo de sono ps -o args?

derobert
fonte
1
Observe que os processos podem (e geralmente o fazem) executar mais de um comando durante sua vida útil. Por exemplo, sh -c 'sleep 1; sleep 2'com muitas shimplementações, é o mesmo processo que executa she depois sleep 2(1 segundo depois).
Stéphane Chazelas
@ StéphaneChazelas Eu não acho que isso possa acontecer aqui, presumivelmente o shell só pode fazer essa otimização no último comando que está executando (já que não tem como recuperar o controle depois exec). Mas esse é um bom ponto para qualquer solução geral.
Derobert # 5/16
Observe também que vários shells ( mkshe ksh93pelo menos) foram sleepincorporados (para que não apareçam ps)., Você precisa saber com antecedência com quais sleepimplementações está lidando.
Stéphane Chazelas

Respostas:

5

O suporte a sleepargumentos GNU ou Solaris 11 (uma ou mais <double>[smhd]durações, também funcionaria com implementações tradicionais que suportam apenas um número inteiro decimal (como no FreeBSD), mas não com aqueles que aceitam argumentos mais complexos como durações ISO-8601 ). Usando em etimevez de etimesmais portátil (Unix padrão).

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/\?//gé para se livrar dos ?caracteres que procps' psusam como substitutos dos caracteres de controle. Sem ele, ele falharia em analisar sleep $'\r1d'ou sleep $'\t1d'... Infelizmente, em alguns locais, incluindo o Clocal, ele usa em .vez de ?. Não há muito o que fazer nesse caso, como não há como diferenciar a \t5dpartir de .5d(meio dia)).

Passe o pid como argumento.

Isso também pressupõe que o argv[0]passado para sleepnão contém espaços em branco e que o número de argumentos é pequeno o suficiente para que não seja truncado ps.

Exemplos:

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

Para uma [[[ddd-]HH:]MM:]SSsaída em vez de apenas o número de segundos, substitua print $tpor:

$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
fonte
1
0xffp-6d... Agora, esse é um caso de teste! Mas, na verdade GNU sono leva as coisas como sleep 1h 30mbem ...
derobert
@derobert. D'oh, eu poderia jurar que tentei isso. Acho que devo ter tentado o sleep 1h -1mque funciona no Solaris 11, mas não no GNU sleep. De volta à prancheta. Pelo menos, ele não suporta durações de iso8601 como ksh93, o que seria muito mais difícil.
Stéphane Chazelas
@derobert, atualizado para suportar vários argumentos
Stéphane Chazelas
@ StéphaneChazelas sua solução é boa, embora não funcione caso sleepseja colocada em pausa. Pode até exibir valor negativo neste caso.
apressar
Estou usando essa multa há uma semana, mas hoje remaining_sleep_time 12838retornou -40642. Pode estar em "Pausar" como estados @rush, mas não sabe como depurar?
WinEunuuchs2Unix
3

Parecia um problema divertido de resolver; Como o thrig abordou uma opção perl, aqui está um script bash que faz algo semelhante. Não faz verificação de erro suficiente (pressupõe que você esteja passando um PID válido de um comando de suspensão). Ele lida com a mesma sintaxe que o coreutils do GNU dorme , a saber:

  • sufixos s | m | h | d por segundos / minutos / horas / dias
  • vários parâmetros de tempo são adicionados juntos

#!/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
fonte
Mesmo se limitado ao sono GNU (algumas implementações de sono, como o Solaris 11 ou o ksh93, suportam expressões de duração muito mais complexas), isso é incompleto, pois não funciona para números de ponto flutuante (como sleep 0.5dou sleep 1e5ou sleep infinity). Mudar bashpara um shell que suporta pontos flutuantes como zsh, ksh93 ou yash ajudaria. Não que etimesnão seja portátil.
Stéphane Chazelas
A toca do coelho para analisar o ponto flutuante foi mais profunda do que eu estava disposto a ir, então posso deixar isso aqui com as limitações conhecidas e o fato de que é apenas uma pequena melhoria em relação à sugestão do OP (isso pode determinar o tempo de sono do PID versus codificação embutida que em).
Jeff Schaller
Mais fácil comperl
Stéphane Chazelas
1

Isso pode ser melhor realizado com um script que mostre o tempo restante quando o bônus é feito com um QUIT(normalmente control+\) ou INFOsinal.

#!/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;
}
agitar
fonte
Realmente não responde à pergunta (já que meu sono já estava acabando) - mas evita isso no futuro, portanto ainda é útil. Saber se há uma contagem regressiva terminal de utilidade alarme em algum lugar ...
derobert
Veja também zsh's schede o atcomando para outra abordagem que permite consultar o tempo.
Stéphane Chazelas
0

Deve ser fácil o suficiente com o módulo tqdm python.

Mostra uma barra de progresso visual agradável e pode ser usado como um pipe unix.

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

O seguinte snippet de python conta 100 segundos

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
fonte
Não vejo como seria fácil olhar para essa página (embora eu não seja um programador Python). Você parece ter algo em mente - adicione-o à sua resposta.
Derobert