Як визначити кількість часу, що залишився у режимі "сну"?


11

Я маю:

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

працює як простий таймер без зайвих зусиль, щоб нагадати мені, коли щось потрібно зробити. Це sleep 210mPID 25347.

Я намагаюся розібратися, скільки часу залишилось уві сні. Найкраще, що я придумав, коли мені вводиться початкова кількість сну (210 хвилин):

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

(Пояснення: Спочатку біт обчислює кількість секунд у початковому режимі сну; $(ps…)біт отримує час, коли сон починається за секунди, потім решта віднімає їх і відображається через хвилини та секунди.)

Мені здається, це взагалі було б корисно; чи є кращий спосіб це зробити? Або хоча б розумний спосіб розібрати час сну ps -o args?


1
Зауважте, що процеси можуть (і часто це роблять) виконувати більше однієї команди протягом свого життя. Наприклад, у sh -c 'sleep 1; sleep 2'багатьох shреалізаціях - це той самий процес, який виконується shі пізніше sleep 2(1 секунда пізніше).
Стефан Шазелас

@ StéphaneChazelas Я не думаю, що це може статися тут, імовірно, оболонка може робити лише таку оптимізацію за останньою командою, яку вона виконує (оскільки після цього не має можливості відновити контроль exec). Але це хороший момент для будь-якого загального рішення.
дероберт

Також зауважте, що декілька оболонок ( mkshі ksh93принаймні) мають sleepвбудований (так що він не відображатиметься ps). Вам слід заздалегідь знати, з якими sleepреалізаціями ви маєте справу.
Стефан Шазелас

Відповіді:


5

Підтримка sleepаргументів GNU або Solaris 11 (одна або більше <double>[smhd]тривалості, тому також буде працювати з традиційними реалізаціями, які підтримують лише одне десяткове ціле число (як у FreeBSD), але не з тими, хто приймає більш складні аргументи, такі як тривалість ISO-8601 ). Використання etimeзамість того etimes, що є більш портативним (стандартний 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/\?//gполягає в тому, щоб позбутися ?символів, які procps" psвикористовують як заміну для керуючих символів. Без нього він би не зміг розібратися sleep $'\r1d'або sleep $'\t1d'... На жаль, у деяких локалях, включаючи Cлокаль, він використовується .замість ?. Не так багато ми можемо зробити в цьому випадку , оскільки немає ніякого способу , щоб розповісти \t5dвід .5d(півдня)).

Передайте pid як аргумент.

Це також передбачає, що argv[0]передане sleepне містить пробілів і що кількість аргументів є достатньо малою, щоб не усічено ps.

Приклади:

$ 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:]SSвиводу, а не лише кількості секунд, замініть на 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... Тепер, це тестовий випадок! Але насправді сон GNU сприймає такі речі, як sleep 1h 30m...
derobert

@derobert. О, я міг би присягнути, що спробував це. Я думаю, я, мабуть, спробував, sleep 1h -1mщо працює на Solaris 11, але не з GNU sleep. Назад до дошки для малювання. Принаймні, він не підтримує iso8601 тривалість типу ksh93, що було б набагато важче.
Стефан Шазелас

@derobert, оновлено для підтримки кількох аргументів
Stéphane Chazelas

@ StéphaneChazelas ваше рішення хороше, хоча воно не спрацює у випадку, якщо sleepбуде поставлено паузу. У цьому випадку воно може навіть відображати негативне значення.
пік

Я використовую цей штраф протягом тижня, але сьогодні remaining_sleep_time 12838повернувся -40642. Це може бути на "Пауза", як заявляє @rush, але не знаєте, як налагодити?
WinEunuuchs2Unix

3

Це здавалося цікавою проблемою для вирішення; Оскільки тріг охоплював параметр perl, ось баш сценарій, який робить щось подібне. Він не робить достатньо перевірки помилок (передбачає, що ви передаєте дійсний PID команди сну). Він обробляє той самий синтаксис, що і основний режим GNU , а саме:

  • s | m | h | d суфікси секунд / хвилин / годин / днів
  • кілька параметрів часу збираються разом

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

Навіть якщо це обмежено режимом сну GNU (деякі варіанти сну на зразок Solaris 11 або ksh93 вбудовані підтримують набагато складніші вирази тривалості), це неповно, оскільки це не працює для чисел з плаваючою комою (наприклад, sleep 0.5dабо sleep 1e5або sleep infinity). Перехід від bashоболонки, яка підтримує плаваючі точки, такі як zsh, ksh93 або yash, допоможе. Не те, що etimesне є портативним.
Стефан Шазелас

Кроляча нора для розбору плаваючої точки заглибилася, ніж я хотів піти, тому я можу залишити це тут з відомими обмеженнями і тим фактом, що це лише незначне поліпшення щодо пропозиції ОП (це може визначити час сну від ПІД проти жорсткого кодування, що в).
Jeff Schaller


1

Це може бути краще зробити зі сценарієм, який може відображати час, що залишився, коли його отримують QUIT(як правило control+\) або INFOсигналом.

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

Насправді не відповідає на запитання (оскільки мій сон уже працював) - але все-таки уникати цього в майбутньому, тому все ще корисно. Цікаво, чи є десь утиліта тривоги зворотного відліку терміналу ...
derobert

Дивіться також zsh's schedта atкоманду щодо іншого підходу, який дозволяє запитувати час.
Стефан Шазелас

0

Має бути досить легко за допомогою модуля python tqdm.

Показує гарну візуальну смужку прогресу і може використовуватися як труба Unix.

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

Наступний фрагмент пітона триває 100 секунд

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]


Я не бачу, як було б легко переглядати цю сторінку (хоча я не програміст Python). Здається, ви щось маєте на увазі - додайте це до своєї відповіді.
дероберт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.