Existe uma maneira de exibir uma contagem regressiva ou cronômetro em um terminal?

144

Como posso exibir um temporizador de contagem regressiva em tempo real no terminal Linux? Existe um aplicativo existente ou, melhor ainda, um liner para fazer isso?

tir38
fonte

Respostas:

184

Não sei por que você precisa beep, se tudo o que você quer é um cronômetro, você pode fazer isso:

while true; do echo -ne "`date`\r"; done

Isso mostrará os segundos que passam em tempo real e você poderá interrompê-lo com Ctrl+ C. Se você precisar de maior precisão, poderá usar isso para fornecer nanossegundos:

while true; do echo -ne "`date +%H:%M:%S:%N`\r"; done

Por fim, se você realmente deseja "formato de cronômetro", onde tudo começa em 0 e começa a crescer, você pode fazer algo assim:

date1=`date +%s`; while true; do 
   echo -ne "$(date -u --date @$((`date +%s` - $date1)) +%H:%M:%S)\r";
done

Para um contador regressivo (que não é o que sua pergunta original pediu), você pode fazer isso (alterar os segundos de acordo):

seconds=20; date1=$((`date +%s` + $seconds)); 
while [ "$date1" -ge `date +%s` ]; do 
  echo -ne "$(date -u --date @$(($date1 - `date +%s` )) +%H:%M:%S)\r"; 
done

Você pode combiná-los em comandos simples usando as funções bash (ou qualquer shell que você preferir). No bash, adicione essas linhas ao seu ~/.bashrc( sleep 0.1isso fará com que o sistema aguarde 1/10 de segundo entre cada execução para que você não faça spam na sua CPU):

function countdown(){
   date1=$((`date +%s` + $1)); 
   while [ "$date1" -ge `date +%s` ]; do 
     echo -ne "$(date -u --date @$(($date1 - `date +%s`)) +%H:%M:%S)\r";
     sleep 0.1
   done
}
function stopwatch(){
  date1=`date +%s`; 
   while true; do 
    echo -ne "$(date -u --date @$((`date +%s` - $date1)) +%H:%M:%S)\r"; 
    sleep 0.1
   done
}

Você pode iniciar um contador regressivo de um minuto executando:

countdown 60

Você pode contar duas horas com:

countdown $((2*60*60))

ou um dia inteiro usando:

countdown $((24*60*60))

E inicie o cronômetro executando:

stopwatch

Se você precisar lidar com dias, além de horas, minutos e segundos, poderá fazer algo assim:

countdown(){
    date1=$((`date +%s` + $1));
    while [ "$date1" -ge `date +%s` ]; do 
    ## Is this more than 24h away?
    days=$(($(($(( $date1 - $(date +%s))) * 1 ))/86400))
    echo -ne "$days day(s) and $(date -u --date @$(($date1 - `date +%s`)) +%H:%M:%S)\r"; 
    sleep 0.1
    done
}
stopwatch(){
    date1=`date +%s`; 
    while true; do 
    days=$(( $(($(date +%s) - date1)) / 86400 ))
    echo -ne "$days day(s) and $(date -u --date @$((`date +%s` - $date1)) +%H:%M:%S)\r";
    sleep 0.1
    done
}

Observe que a stopwatchfunção não foi testada há dias, pois eu realmente não queria esperar 24 horas por ela. Deve funcionar, mas por favor me avise se não funcionar.

Terdon
fonte
9
Adicionei essas boas funções à minha .zshrcdireita depois de ler sua resposta. Hoje eu usei countdowna primeira vez e notei um uso bastante alto da CPU. Eu adicionei um sleep 0.1(não sei se um tempo de sono de um segundo fracionário é suportado em todos os sistemas), o que melhorou bastante. A desvantagem é, obviamente, uma precisão de exibição menos precisa, mas posso viver com um desvio de no máximo. 100ms.
mpy
@ mpy obrigado por mencionar isso. Estou recebendo ~ 3% de uso da CPU ao fazer isso bash, posso conviver com isso (embora seja maior do que eu esperava e também adicionei o sono ao meu .bashrc).
terdon
3
Acabei de encontrar esta resposta. Descobri que colocar o retorno de carro no início da instrução echo na função de cronômetro era útil, pois significava que matar o cronômetro não substituia o horário atual do cronômetro: echo -ne "\ r $ (date -u --date @ $ (( date +%s- $ date1)) +% H:% M:% S) ";
mkingston
3
@chishaku: No OS X, isso pareceu funcionar para mim: echo -ne "$(date -ju -f %s $(($date1 - data +% s )) +%H:%M:%S)\r";
user1071847
11
Com pacote sox eu gostaria de acrescentar um som agradável para jogar no final da contagem: play -q -n synth 2 pluck C5.
Pablo A
94

Minha maneira favorita é:

Começar:

time cat

Pare:

ctrl+c

Como o @wjandrea comentou abaixo, outra versão deve ser executada:

time read

e pressione Enterpara parar

alfasin
fonte
8
semelhante, mas você pode pressionar Enter para pará-lo:time read
wjandrea
10
time readfalha no zsh, mas time (read)funciona.
Sparhawk #
O problema é que você não pode ver a hora sem cancelá-la ou terminá-la. Quando você o reinicia, o timer começa novamente.
Zelphir Kaltstahl
39

Eu estava procurando a mesma coisa e acabei escrevendo algo mais elaborado em Python:

Isso fornecerá uma contagem regressiva simples de 10 segundos:

sudo pip install termdown
termdown 10

Fonte: https://github.com/trehn/termdown

Trehn
fonte
11
@DoktoroReichard: Bem, este é um link para download.
harrymc
11
@ Suhaib: Deve e faz por mim. Por favor, levante um problema no GitHub com mais informações.
trehn
11
Eu adoro isso - é o meu beco #
Wayne Werner
11
Muito bom, eu gosto do ASCII
Guillermo
11
Isso é muito legal! 😄
orschiro
13

Eu usei este:

countdown()
(
  IFS=:
  set -- $*
  secs=$(( ${1#0} * 3600 + ${2#0} * 60 + ${3#0} ))
  while [ $secs -gt 0 ]
  do
    sleep 1 &
    printf "\r%02d:%02d:%02d" $((secs/3600)) $(( (secs/60)%60)) $((secs%60))
    secs=$(( $secs - 1 ))
    wait
  done
  echo
)

Exemplo:

 countdown "00:07:55"

Aqui está uma fonte .

Adobe
fonte
2
Compatível com POSIX - funciona no OS X :) #
7897 djule5
13
sh-3.2# man leave

definir um temporizador por 15 minutos

sh-3.2# leave +0015
Alarm set for Thu Nov  3 14:19:31 CDT 2016. (pid 94317)
sh-3.2#

edit: Eu tinha um monte de links abertos, e eu pensei que isso era específico para osx, desculpe por isso. Deixando minha resposta para que outros estejam cientes da licença nos BSDs.

efk
fonte
11
O leave também funciona no Linux, mas primeiro você precisa instalar o programa leave a partir dos repositórios padrão.
karel
6

Isto é para um cronômetro com centésimos de segundo:

#!/usr/bin/awk -f
function z() {
  getline < "/proc/uptime"
  close("/proc/uptime")
  return $0
}
BEGIN {
  x = z()
  while (1) {
    y = z()
    printf "%02d:%05.2f\r", (y - x) / 60, (y - x) % 60
  }
}

Exemplo

Steven Penny
fonte
Ótima solução apenas para Linux! Eu amo a falta de chamadas externas. Note, meu sistema Debian tem dois valores em / proc / uptime, com o segundo presumivelmente se referindo ao meu uptime anterior. Esse script pode ser ajustado para remediar isso, alterando a linha 5 parareturn $1
Adam Katz
4

Combinei a muito boa resposta do terdon, em uma função que ao mesmo tempo mostra o tempo desde o início e o tempo até o fim. Também existem três variantes, portanto é mais fácil chamar (você não precisa fazer bash math) e também é abstraído. Exemplo de uso :

{ ~ }  » time_minutes 15
Counting to 15 minutes
Start at 11:55:34     Will finish at 12:10:34
     Since start: 00:00:08     Till end:  00:14:51

E algo como temporizador de trabalho:

{ ~ }  » time_hours 8
Counting to 8 hours
Start at 11:59:35   Will finish at 19:59:35
     Since start: 00:32:41     Till end:  07:27:19

E se você precisar de um tempo muito específico:

{ ~ }  » time_flexible 3:23:00
Counting to 3:23:00 hours
Start at 12:35:11   Will finish at 15:58:11
     Since start: 00:00:14     Till end:  03:22:46

Aqui está o código para colocar no seu .bashrc

function time_func()
{
   date2=$((`date +%s` + $1));
   date1=`date +%s`;
   date_finish="$(date --date @$(($date2)) +%T )"

   echo "Start at `date +%T`   Will finish at $date_finish"

    while [ "$date2" -ne `date +%s` ]; do
     echo -ne "     Since start: $(date -u --date @$((`date +%s` - $date1)) +%H:%M:%S)     Till end:  $(date -u --date @$(($date2 - `date +%s`)) +%H:%M:%S)\r";
     sleep 1
    done

    printf "\nTimer finished!\n"
    play_sound ~/finished.wav
}

function time_seconds()
{
  echo "Counting to $1 seconds"
  time_func $1
}

function time_minutes()
{
  echo "Counting to $1 minutes"
  time_func $1*60
}

function time_hours()
{
  echo "Counting to $1 hours"
  time_func $1*60*60
}

function time_flexible()  # accepts flexible input hh:mm:ss
{
    echo "Counting to $1"
    secs=$(time2seconds $1)
    time_func $secs
}

function play_sound()  # adjust to your system
{
    cat $1 > /dev/dsp
}

function time2seconds() # changes hh:mm:ss to seconds, found on some other stack answer
{ 
    a=( ${1//:/ }) 
    echo $((${a[0]}*3600+${a[1]}*60+${a[2]})) 
}

Combine isso com alguma maneira de reproduzir som no terminal linux ( reproduza arquivos mp3 ou wav via linha de comando do Linux ) ou cygwin ( cat /path/foo.wav > /dev/dsp funciona para mim no babun / win7) e você terá um timer flexível e simples com alarme !

Koshmaar
fonte
4

Outra abordagem

countdown=60 now=$(date +%s) watch -tpn1 echo '$((now-$(date +%s)+countdown))'

Para Mac:

countdown=60 now=$(date +%s) watch -tn1 echo '$((now-$(date +%s)+countdown))'
#no p option on mac for watch

Se alguém quer um sinal quando atinge zero, pode-se, por exemplo, construí-lo com um comando que retorne um status de saída diferente de zero em zero e combiná-lo com watch -b, ou algo assim, mas se você quiser criar um script mais elaborado, provavelmente isso não é o caminho a percorrer; é mais uma solução do tipo "rápido e sujo de uma linha".


Eu gosto do watchprograma em geral. Vi pela primeira vez depois de já ter escrito inúmeras while sleep 5; dovoltas com efeitos diferentes. watchfoi comprovadamente melhor.

Daniel Andersson
fonte
3

Acabei escrevendo meu próprio script shell: github gist

#!/bin/sh
# script to create timer in terminal
# Jason Atwood
# 2013/6/22

# start up
echo "starting timer script ..."
sleep 1 # seconds

# get input from user
read -p "Timer for how many minutes?" -e DURATION
DURATION=$(( $DURATION*60 )) # convert minutes to seconds

# get start time
START=$(date +%s)

# infinite loop
while [ -1 ]; do
clear # clear window

# do math
NOW=$(date +%s) # get time now in seconds
DIF=$(( $NOW-$START ))  # compute diff in seconds
ELAPSE=$(( $DURATION-$DIF ))    # compute elapsed time in seconds
MINS=$(( $ELAPSE/60 ))  # convert to minutes... (dumps remainder from division)
SECS=$(( $ELAPSE - ($MINS*60) )) # ... and seconds

# conditional
if [ $MINS == 0 ] && [ $SECS == 0 ] # if mins = 0 and secs = 0 (i.e. if time expired)
then # blink screen
for i in `seq 1 180`; # for i = 1:180 (i.e. 180 seconds)
do
clear # flash on
setterm -term linux -back red -fore white # use setterm to change background color
echo "00:00 " # extra tabs for visibiltiy

sleep 0.5

clear # flash off
setterm -term linux -default # clear setterm changes from above
echo "00:00" # (i.e. go back to white text on black background)
sleep 0.5
done # end for loop
break   # end script

else # else, time is not expired
echo "$MINS:$SECS"  # display time
sleep 1 # sleep 1 second
fi  # end if
done    # end while loop 
tir38
fonte
11
Bom roteiro, +1. Só para você saber, esse é um cronômetro de contagem regressiva, não um cronômetro.
terdon
ha você está certo, é isso que eu realmente queria. Vou atualizar meu nome.
tir38
3

Estou surpreso que ninguém tenha usado a sleepenhferramenta em seus scripts. Em vez disso, as soluções propostas usam um sleep 1entre as saídas subsequentes do timer ou um loop ocupado que produz o mais rápido possível. A primeira é inadequada porque, devido ao pequeno tempo gasto na impressão, a saída não ocorre de fato uma vez por segundo, mas um pouco menos do que a subótima. Após um tempo suficiente, o contador pulará um segundo. O último é inadequado porque mantém a CPU ocupada sem um bom motivo.

A ferramenta que eu tenho na minha $PATHaparência é assim:

#!/bin/sh
if [ $# -eq 0 ]; then
    TIMESTAMP=$(sleepenh 0)
    before=$(date +%s)
    while true; do
        diff=$(($(date +%s) - before))
        printf "%02d:%02d:%02d\r" $((diff/3600)) $(((diff%3600)/60)) $((diff%60))
        TIMESTAMP=$(sleepenh $TIMESTAMP 1.0);
    done
    exit 1 # this should never be reached
fi
echo "counting up to $@"
"$0" &
counterpid=$!
trap "exit" INT TERM
trap "kill 0" EXIT
sleep "$@"
kill $counterpid

O script pode ser usado como um cronômetro (contando até a interrupção) ou como um cronômetro que é executado pelo período de tempo especificado. Como o sleepcomando é usado, este script permite especificar a duração pela qual contar com a mesma precisão que você sleeppermite. No Debian e derivados, isso inclui sub-segundos de espera e uma boa maneira legível por humanos para especificar a hora. Então, por exemplo, você pode dizer:

$ time countdown 2m 4.6s
countdown 2m 4.6s  0.00s user 0.00s system 0% cpu 2:04.60 total

E como você pode ver, o comando foi executado exatamente por 2 minutos e 4,6 segundos sem muita mágica no próprio script.

EDIT :

A ferramenta sleepenh vem do pacote com o mesmo nome no Debian e seus derivados como o Ubuntu. Para distribuições que não possuem, ele vem de https://github.com/nsc-deb/sleepenh

A vantagem do sono é que ele pode levar em consideração o pequeno atraso que se acumula ao longo do tempo do processamento de outras coisas além do sono durante um loop. Mesmo se alguém fizesse sleep 1um loop apenas 10 vezes, a execução geral levaria um pouco mais de 10 segundos por causa da pequena sobrecarga resultante da execução sleepe da iteração do loop. Esse erro se acumula lentamente e, com o tempo, tornaria nosso cronômetro cada vez mais impreciso. Para corrigir esse problema, é necessário que cada iteração de loop calcule o tempo preciso para dormir, que geralmente é um pouco menos de um segundo (para temporizadores de intervalo de um segundo). A ferramenta sleepenh faz isso por você.

Josch
fonte
Não entendo que benefício isso traz sobre a minha resposta, que também usa sleep 0.1. O que é sleepnh(não consigo encontrá-lo nos repositórios do Arch) e como ele difere sleep? Até onde eu sei, você está basicamente fazendo a mesma coisa que a minha resposta acima. o que estou perdendo?
terdon 26/01
@terdon Sleepenh vem daqui github.com/nsc-deb/sleepenh O problema de apenas dizer sleep 5é que você não dorme por exatamente 5 segundos. Tente, por exemplo, time sleep 5e você verá que a execução do comando leva um pouco mais de 5 segundos. Com o tempo, os erros se acumulam. O utilitário sleepenh permite evitar facilmente esse acúmulo de erros.
Josch
ESTÁ BEM. No meu sistema, vejo um erro de 0,002 segundos. Eu realmente duvido que alguém usaria esse tipo de ferramenta e esperasse melhor que a precisão de milissegundos, mas seria melhor se você pelo menos editasse sua resposta e i) explique por que sleepnhé melhor do que sleep(você apenas diz que outras respostas usam sleep 1- o que elas não , apenas o OP usa isso) e ii) onde obtê-lo e como instalá-lo, pois não é uma ferramenta padrão.
terdon
11
@terdon Expliquei a diferença entre sleepe sleepenhno primeiro parágrafo. De qualquer forma, eu provavelmente não estava claro o suficiente sobre isso, então expandi mais sobre isso no final. Os problemas de precisão em milissegundos são os que você obtém ao ligar sleepuma vez. Eles se acumulam com o tempo e, em algum momento, é perceptível. Eu não disse que os outros usam apenas sleep 1. Eu disse que eles usam sleep 1ou um busyloop. O que eles ainda fazem. Mostre-me um exemplo contrário. As respostas sleep 0.1são as mesmas que as respostas, sleep 1exceto que acumulam erros ainda mais rapidamente.
Josch
Desci as respostas procurando por alguém pelo menos reconhecendo a existência do problema que você resolveu.
mariotomo
2

Resposta curta:

for i in `seq 60 -1 1` ; do echo -ne "\r$i " ; sleep 1 ; done

Explicação:

Sei que há muitas respostas, mas só quero postar algo muito próximo à pergunta do OP, que pessoalmente eu aceitaria como " contagem regressiva oneliner no terminal ". Meus objetivos eram:

  1. Um forro.
  2. Contagem regressiva.
  3. Fácil de lembrar e digitar no console (sem funções e lógica pesada, somente bash).
  4. Não requer software adicional para instalar (pode ser usado em qualquer servidor que eu vá via ssh, mesmo que não possua root).

Como funciona:

  1. seq imprime números de 60 a 1.
  2. echo -ne "\r$i "retorna o sinal de intercalação para o início da sequência e imprime o $ivalor atual . Espaço depois que é necessário substituir o valor anterior, se for maior por caracteres do que o atual $i(10 -> 9).
cronfy
fonte
11
Isso funcionou melhor no meu caso de uso no Mac OS para uso em um script bash. A explicação de como funciona é extremamente útil.
James Campbell
1

Para referência futura, existe uma ferramenta de linha de comando chamada µTimer com opções muito simples de linha de comando para um temporizador de contagem regressiva / contagem regressiva.

Tom
fonte
1

Finja que você é uma pessoa do OSX procurando um cronômetro de linha de comando. Finja que você não deseja instalar as ferramentas gnu e apenas deseja executar com o unixdate

nesse caso, faça como o @terdon diz, mas com esta modificação:

function stopwatch(){
    date1=`date +%s`; 
    while true; do 
        echo -ne "$(date -jf "%s" $((`date +%s` - $date1)) +%H:%M:%S)\r"; 
        sleep 0.1
    done
}
joem
fonte
11
Eu tentei no OS X El Capitan, por algum motivo, está começando com 16:00:00
nopole 29/10/2015
1

Basta usar o relógio + data no horário UTC. Você também pode instalar algum pacote para exibição grande ...

export now="`date +%s -u`";
watch -n 0,1 'date +%T -u -d @$((`date +%s` - $now ))'

#Big plain characters
watch -n 0,1 'date +%T -u -d @$((`date +%s` - $now )) | toilet -f mono12'

#Big empty charaters
watch -n 0,1 'date +%T -u -d @$((`date +%s` - $now )) | figlet -c -f big'

Tente!

Consulte também http://www.cyberciti.biz/faq/create-large-colorful-text-banner-on-screen/

MUY Bélgica
fonte
1

sw é um cronômetro simples que funcionará para sempre.

sw

Instalar

wget -q -O - http://git.io/sinister | sh -s -- -u https://raw.githubusercontent.com/coryfklein/sw/master/sw

Uso

sw
 - start a stopwatch from 0, save start time in ~/.sw
sw [-r|--resume]
 - start a stopwatch from the last saved start time (or current time if no last saved start time exists)
 - "-r" stands for --resume
Cory Klein
fonte
1

Um exemplo de python:

#!/usr/bin/python

def stopwatch ( atom = .01 ):
    import time, sys, math

    start = time.time()
    last = start
    sleep = atom/2
    fmt = "\r%%.%sfs" % (int(abs(round(math.log(atom,10))))  if atom<1 else "")
    while True:
        curr = time.time()
        subatom = (curr-last)
        if subatom>atom:
            # sys.stdout.write( "\r%.2fs" % (curr-start))
            sys.stdout.write( fmt % (curr-start))
            sys.stdout.flush()
            last = curr
        else:
            time.sleep(atom-subatom)

stopwatch()

demonstração

user84207
fonte
1

Dê uma olhada no TermTime , é um bom relógio e cronômetro baseado em terminal:

pip install termtime

vimista
fonte
0

Isso é semelhante à resposta aceita, mas o Terdon countdown()me deu erros de sintaxe. Este funciona muito bem para mim, no entanto:

function timer() { case "$1" in -s) shift;; *) set $(($1 * 60));; esac; local S=" "; for i in $(seq "$1" -1 1); do echo -ne "$S\r $i\r"; sleep 1; done; echo -e "$S\rTime's up!"; }

Você pode inseri-lo .bashrce executar com: timer t(onde t é o tempo em minutos).

Andy Forceno
fonte
0

Encontrei essa pergunta hoje mais cedo, ao procurar um aplicativo de termo para exibir um grande cronômetro de contagem regressiva para um workshop. Nenhuma das sugestões era exatamente o que eu precisava, então rapidamente montei outra em Go: https://github.com/bnaucler/cdown

Como a pergunta já está suficientemente respondida, considere que isso é uma questão de posteridade.

B Nauclér
fonte
0

Uma versão GUI do cronômetro

date1=`date +%s`
date1_f=`date +%H:%M:%S____%d/%m`
(
  while true; do 
    date2=$(date -u --date @$((`date +%s` - $date1)) +%H:%M:%S)
    echo "# started at $date1_f \n$date2"
  done
) |
zenity --progress \
  --title="Stop Watch" \
  --text="Stop Watch..." \
  --percentage=0
Mohamed Samy
fonte
-2

Se você desejar um programa compilável por qualquer motivo, o seguinte funcionará:

#include <iostream>
#include <string>
#include <chrono>

int timer(seconds count) {
  auto t1 = high_resolution_clock::now();
  auto t2 = t1+count;
  while ( t2 > high_resolution_clock::now()) {
    std::cout << "Seconds Left:" <<
    std::endl <<
      duration_cast<duration<double>>(count-(high_resolution_clock::now()-t1)).count() << 
    std::endl << "\033[2A\033[K";
    std::this_thread::sleep_for(milliseconds(100));
  }
  std::cout << "Finished" << std::endl;
  return 0;
}

Isso também pode ser usado em outros programas e facilmente transportado, se um ambiente bash não estiver disponível ou se você preferir usar um programa compilado

github

elder4222
fonte