Jak określić czas pozostały do ​​„snu”?


11

Mam:

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

działa jako prosty, bezproblemowy licznik czasu, który przypomina mi, kiedy należy coś zrobić. To sleep 210mjest PID 25347.

Próbuję dowiedzieć się, ile czasu pozostało we śnie. Najlepsze, co wymyśliłem, gdy wprowadziłem oryginalną ilość snu (210 minut) to:

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

(Objaśnienie: Pierwszy bit oblicza liczbę sekund w pierwotnym śnie; $(ps…)bit uzyskuje czas od rozpoczęcia snu w sekundach, a następnie reszta odejmuje je i wyświetla w minutach i sekundach.)

Wydaje mi się, że byłoby to ogólnie przydatne; czy jest na to lepszy sposób? A przynajmniej sprytny sposób na przeanalizowanie czasu snu ps -o args?


1
Zauważ, że procesy mogą (i często tak robią) uruchamiać więcej niż jedno polecenie w ciągu swojego życia. Na przykład w sh -c 'sleep 1; sleep 2'przypadku wielu shimplementacji jest to ten sam proces, który wykonuje się shi wykonuje później sleep 2(1 sekunda później).
Stéphane Chazelas

@ StéphaneChazelas Nie sądzę, że może się to zdarzyć tutaj, przypuszczalnie powłoka może wykonać tę optymalizację tylko na ostatnim wykonywanym poleceniu (ponieważ nie ma możliwości odzyskania kontroli po exec). Ale to dobry punkt na każde ogólne rozwiązanie.
derobert

Zauważ też, że kilka powłok ( mksha ksh93przynajmniej) ma sleepwbudowanych (więc się nie pojawiło ps), musisz wcześniej wiedzieć, z którymi sleepimplementacjami masz do czynienia.
Stéphane Chazelas

Odpowiedzi:


5

Obsługa sleepargumentów GNU lub Solaris 11 (jeden lub więcej <double>[smhd]czasów trwania, więc działałoby to również z tradycyjnymi implementacjami, które obsługują tylko jedną dziesiętną liczbę całkowitą (jak w FreeBSD), ale nie z tymi, które akceptują bardziej złożone argumenty, takie jak czasy trwania ISO-8601 ). Używanie etimezamiast etimestego jest bardziej przenośne (standardowy 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'
}

( s/\?//gpolega na pozbyciu się ?znaków, które procpspszastępują znaki kontrolne. Bez niego nie byłby parsowany sleep $'\r1d'lub sleep $'\t1d'… Niestety, w niektórych lokalizacjach, w tym w Custawieniach regionalnych, używa .zamiast tego ?. Niewiele możemy zrobić w takim przypadku, ponieważ nie ma możliwości odróżnienia \t5dod .5d(pół dnia).

Podaj pid jako argument.

Zakłada to również, że argv[0]przekazane do sleepnie zawiera spacji i że liczba argumentów jest na tyle mała, że ​​nie jest obcinana ps.

Przykłady:

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

Aby uzyskać [[[ddd-]HH:]MM:]SSwynik zamiast samej liczby sekund, zamień na print $t:

$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;

1
0xffp-6d... To jest przypadek testowy! Ale tak naprawdę sen GNU wymaga również takich rzeczy sleep 1h 30m...
derobert

@derobert. Nie mógłbym przysiąc, że tego spróbowałem. Chyba musiałem spróbować, sleep 1h -1mktóry działa na Solarisie 11, ale nie na GNU sleep. Powrót do tablicy kreślarskiej. Przynajmniej nie obsługuje czasów trwania iso8601, takich jak ksh93, co byłoby znacznie trudniejsze.
Stéphane Chazelas

@derobert, zaktualizowano w celu obsługi kilku argumentów
Stéphane Chazelas,

@ StéphaneChazelas Twoje rozwiązanie jest dobre, ale nie zadziała, jeśli sleepzostanie wstrzymane. W tym przypadku może nawet wyświetlać wartość ujemną.
pędzi

Korzystałem z tej grzywny od tygodnia, ale dziś remaining_sleep_time 12838wróciłem -40642. Może być na „Pause”, jak mówi @rush, ale nie wiesz, jak debugować?
WinEunuuchs2Unix,

3

To wydawało się zabawnym problemem do rozwiązania; ponieważ thrig obejmował opcję perla, oto skrypt bash, który robi coś podobnego. Nie wykonuje wystarczającej kontroli błędów (zakłada, że ​​przekazujesz prawidłowy PID polecenia uśpienia). Obsługuje tę samą składnię, co snu coreutils GNU , a mianowicie:

  • s | m | h | d sufiksy dla sekund / minut / godzin / dni
  • wiele parametrów czasu jest sumowanych

#!/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))

Nawet jeśli ogranicza się do trybu uśpienia GNU (niektóre implementacje trybu uśpienia, takie jak Solaris 11 lub ksh93, obsługują znacznie bardziej złożone wyrażenia dotyczące czasu trwania), jest to niepełne, ponieważ nie działa dla liczb zmiennoprzecinkowych (takich jak sleep 0.5dlub sleep 1e5lub sleep infinity). bashPomocne byłoby przełączenie z powłoki obsługującej zmiennoprzecinkowe, takie jak zsh, ksh93 lub yash. Nie etimesto nie jest przenośne.
Stéphane Chazelas

Otwór królika do analizowania zmiennoprzecinkowego poszedł głębiej, niż byłem gotów, więc mogę zostawić to tutaj ze znanymi ograniczeniami i faktem, że jest to tylko niewielka poprawa w stosunku do sugestii PO (może to określić czas snu na podstawie PID w porównaniu z kodowaniem na stałe).
Jeff Schaller


1

Można to lepiej zrobić za pomocą skryptu, który może pokazywać pozostały czas, gdy zostaniesz ostrzeżony QUIT(zwykle control+\) lub INFOsygnałem.

#!/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;
}

Naprawdę nie odpowiada na pytanie (ponieważ mój sen już działa) - ale unika go w przyszłości, więc nadal jest przydatny. Zastanawiam się, czy gdzieś jest narzędzie do alarmowania odliczania terminali ...
derobert

Zobacz także zsh„s schedoraz atpolecenia dla innego podejścia, które pozwala na wysłanie zapytania do czasu.
Stéphane Chazelas,

0

Powinno to być dość łatwe dzięki modułowi python tqdm.

Pokazuje ładny wizualny pasek postępu i może być używany jako potok unix.

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

Poniższy fragment kodu w Pythonie liczy 100 sekund

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]


Nie widzę, jak łatwo byłoby patrzeć na tę stronę (choć nie jestem programistą w języku Python). Wygląda na to, że masz coś na myśli - dodaj to do swojej odpowiedzi.
derobert,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.