Como excluir e substituir a última linha no terminal usando o bash?


99

Quero implementar uma barra de progresso mostrando os segundos decorridos no bash. Para isso, preciso apagar a última linha mostrada na tela (o comando "limpar" apaga toda a tela, mas preciso apagar apenas a linha da barra de progresso e substituí-la pelas novas informações).

O resultado final deve ser semelhante a:

$ Elapsed time 5 seconds

Depois de 10 segundos, quero substituir essa frase (na mesma posição na tela) por:

$ Elapsed time 15 seconds

Respostas:


116

ecoar um retorno de carro com \ r

seq 1 1000000 | while read i; do echo -en "\r$i"; done

do homem echo:

-n     do not output the trailing newline
-e     enable interpretation of backslash escapes

\r     carriage return

19
for i in {1..100000}; do echo -en "\r$i"; donepara evitar a chamada de
sequência

Isso faz exatamente o que eu preciso, obrigado !! E o uso do comando seq de fato congela o termianl :-)
Debugger

11
Quando você usa coisas como "for i in $ (...)" ou "for i in {1..N}", você está gerando todos os elementos antes de iterar, isso é muito ineficiente para grandes entradas. Você pode aproveitar as vantagens dos tubos: "seq 1 100000 | while read i; do ..." ou usar o bash c-style for loop: "for ((i = 0 ;; i ++)); do ..."
Tokland

Obrigado Douglas e tokland - embora a sequência de produção não fosse diretamente parte da questão, mudei para o tubo mais eficiente de tokland
Ken

1
Minha impressora matricial está bagunçando completamente meu papel. Ele fica atolando pontos no mesmo pedaço de papel que não está mais lá, por quanto tempo este programa funciona?
Rob

185

O retorno de carro por si só move o cursor para o início da linha. Tudo bem se cada nova linha de saída for pelo menos tão longa quanto a anterior, mas se a nova linha for mais curta, a linha anterior não será completamente substituída, por exemplo:

$ echo -e "abcdefghijklmnopqrstuvwxyz\r0123456789"
0123456789klmnopqrstuvwxyz

Para realmente limpar a linha do novo texto, você pode adicionar \033[Kdepois de \r:

$ echo -e "abcdefghijklmnopqrstuvwxyz\r\033[K0123456789"
0123456789

http://en.wikipedia.org/wiki/ANSI_escape_code


3
Isso funciona muito bem no meu ambiente. Algum conhecimento de compatibilidade?
Alexander Olsson

21
No Bash, pelo menos isso pode ser encurtado para em \e[Kvez de \033[K.
Dave James Miller

Ótima resposta! Funciona perfeitamente usando puts do ruby. Agora parte do meu kit de ferramentas.
phatmann de

@ Desenvolvedor de Pixel: Obrigado por tentar melhorá-lo, mas sua edição estava incorreta. O retorno deve acontecer primeiro para mover o cursor para o início da linha, então o kill limpa tudo desde a localização do cursor até o final, deixando a linha inteira em branco, que é a intenção. Você os coloca no início de cada nova linha, de maneira semelhante à resposta de Ken, de modo que seja escrito em uma linha vazia.
Derek Veit

1
Só para constar, também se pode fazer \033[Gio \rantes, \033[Kembora obviamente \rseja muito mais simples. Também invisible-island.net/xterm/ctlseqs/ctlseqs.html fornece mais detalhes do que a Wikipedia e é do desenvolvedor xterm.
jamadagni

19

A resposta de Derek Veit funciona bem, desde que o comprimento da linha nunca exceda a largura do terminal. Se este não for o caso, o código a seguir evitará a saída de lixo:

antes que a linha seja escrita pela primeira vez, faça

tput sc

que salva a posição atual do cursor. Agora, sempre que quiser imprimir sua linha, use

tput rc
tput ed
echo "your stuff here"

para primeiro retornar à posição salva do cursor, limpar a tela do cursor para baixo e, finalmente, escrever a saída.


Estranho, isso não faz nada no terminator. Você sabe se existem limitações de compatibilidade?
Jamie Pate

1
Nota para Cygwin: Você precisa instalar o pacote "ncurses" para usar "tput".
Jack Miller

Hm ... Parece não funcionar se houver mais de uma linha na saída. Isso não funciona:tput sc # save cursor echo '' > sessions.log.json while [ 1 ]; do curl 'http://localhost/jolokia/read/*:type=Manager,*/activeSessions,maxActiveSessions' >> sessions.log.json echo '' >> sessions.log.json cat sessions.log.json | jq '.' tput rc;tput el # rc = restore cursor, el = erase to end of line sleep 1 done
Nux

@Nux Você precisa usar em tput edvez de tput el. O exemplo de @ Um usado ed(talvez ele consertou depois que você comentou).
Studgeek

Para sua informação, aqui está a lista de cores dos
studgeek

12

O método \ 033 não funcionou para mim. O método \ r funciona, mas na verdade não apaga nada, apenas coloca o cursor no início da linha. Portanto, se a nova string for mais curta do que a antiga, você poderá ver o texto restante no final da linha. No final das contas, tput foi o melhor caminho a seguir. Ele tem outros usos além do cursor e vem pré-instalado em muitas distros Linux e BSD, então deve estar disponível para a maioria dos usuários do bash.

#/bin/bash
tput sc # save cursor
printf "Something that I made up for this string"
sleep 1
tput rc;tput el # rc = restore cursor, el = erase to end of line
printf "Another message for testing"
sleep 1
tput rc;tput el
printf "Yet another one"
sleep 1
tput rc;tput el

Aqui está um pequeno script de contagem regressiva para brincar:

#!/bin/bash
timeout () {
    tput sc
    time=$1; while [ $time -ge 0 ]; do
        tput rc; tput el
        printf "$2" $time
        ((time--))
        sleep 1
    done
    tput rc; tput ed;
}

timeout 10 "Self-destructing in %s"

Isso limpa toda a linha, de fato, o problema cintila muito para mim: '(
smarber

5

Caso a saída de progresso seja de várias linhas ou o script já tenha impresso o caractere de nova linha, você pode pular as linhas com algo como:

printf "\033[5A"

o que fará com que o cursor salte 5 linhas para cima. Em seguida, você pode sobrescrever o que for necessário.

Se isso não funcionar, você pode tentar printf "\e[5A"ou echo -e "\033[5A", que deve ter o mesmo efeito.

Basicamente, com as sequências de escape, você pode controlar quase tudo na tela.


O equivalente portátil disso é tput cuu 5, onde 5 é o número de linhas ( cuupara mover para cima, cudpara mover para baixo).
Maëlan

4

Use o caractere de retorno de carro:

echo -e "Foo\rBar" # Will print "Bar"

Essa resposta é a maneira mais simples. Você pode até obter o mesmo efeito usando: printf "Foo \ rBar"
lee8oi

1

Pode alcançá-lo colocando o retorno do carro \r.

Em uma única linha de código com printf

for i in {10..1}; do printf "Counting down: $i\r" && sleep 1; done

ou com echo -ne

for i in {10..1}; do echo -ne "Counting down: $i\r" && sleep 1; done
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.