Come eliminare e sostituire l'ultima riga nel terminale usando bash?


99

Voglio implementare una barra di avanzamento che mostra i secondi trascorsi in bash. Per questo, ho bisogno di cancellare l'ultima riga mostrata sullo schermo (il comando "cancella" cancella tutto lo schermo, ma devo cancellare solo la riga della barra di avanzamento e sostituirla con le nuove informazioni).

Il risultato finale dovrebbe essere simile a:

$ Elapsed time 5 seconds

Quindi, dopo 10 secondi, voglio sostituire questa frase (nella stessa posizione sullo schermo) con:

$ Elapsed time 15 seconds

Risposte:


116

echo un ritorno a capo con \ r

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

da man 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"; doneper evitare la seq call :-)
Douglas Leeder

Questo fa esattamente quello che mi serve, grazie !! E l'uso del comando seq infatti congela il termianl :-)
Debugger

11
Quando usi cose come "for i in $ (...)" o "for i in {1..N}" stai generando tutti gli elementi prima dell'iterazione, è molto inefficiente per input di grandi dimensioni. Puoi sfruttare le pipe: "seq 1 100000 | while read i; do ..." o usare il ciclo for in stile c bash: "for ((i = 0 ;; i ++)); do ..."
Tokland

Grazie Douglas e tokland - anche se la produzione in sequenza non era direttamente parte della domanda che ho cambiato con il tubo più efficiente di tokland
Ken

1
La mia stampante a matrice sta completamente rovinando la mia carta. Mantiene i punti inceppati sullo stesso pezzo di carta che non c'è più, quanto dura questo programma?
Rob il

185

Il ritorno a capo da solo sposta il cursore all'inizio della riga. Va bene se ogni nuova riga di output è lunga almeno quanto la precedente, ma se la nuova riga è più corta, la riga precedente non verrà completamente sovrascritta, ad esempio:

$ echo -e "abcdefghijklmnopqrstuvwxyz\r0123456789"
0123456789klmnopqrstuvwxyz

Per cancellare effettivamente la riga per il nuovo testo, puoi aggiungere \033[Kdopo \r:

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

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


3
Funziona davvero bene nel mio ambiente. Qualche conoscenza di compatibilità?
Alexander Olsson

21
In Bash almeno questo può essere abbreviato a \e[Kinvece di \033[K.
Dave James Miller,

Bella risposta! Funziona perfettamente utilizzando put dal rubino. Ora parte del mio toolkit.
phatmann

@ The Pixel Developer: grazie per aver cercato di migliorarlo, ma la tua modifica non era corretta. Il ritorno deve avvenire prima per spostare il cursore all'inizio della riga, quindi il kill cancella tutto dalla posizione del cursore alla fine, lasciando l'intera riga vuota, che è l'intento. Li metti all'inizio di ogni nuova riga, in modo simile alla risposta di Ken, in modo che sia scritto su una riga vuota.
Derek Veit

1
Per la cronaca, si può anche fare \033[Gio \rprima, \033[Kanche se ovviamente \rè molto più semplice. Anche invisible-island.net/xterm/ctlseqs/ctlseqs.html fornisce maggiori dettagli di Wikipedia ed è dello sviluppatore xterm.
jamadagni

19

La risposta di Derek Veit funziona bene fintanto che la lunghezza della linea non supera mai la larghezza del terminale. Se questo non è il caso, il codice seguente impedirà l'output indesiderato:

prima che la riga venga scritta per la prima volta, fare

tput sc

che salva la posizione corrente del cursore. Ora ogni volta che vuoi stampare la tua linea, usa

tput rc
tput ed
echo "your stuff here"

per tornare prima alla posizione del cursore salvata, quindi cancellare lo schermo dal cursore in fondo e infine scrivere l'output.


Strano, questo non fa nulla nel terminatore. Sai se ci sono limitazioni di compatibilità?
Jamie Pate

1
Nota per Cygwin: è necessario installare il pacchetto "ncurses" per utilizzare "tput".
Jack Miller

Hm ... Sembra non funzionare se più di una riga è nell'output. Questo non funziona: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 Devi usare tput edpiuttosto che tput el. @ Um l'esempio usato ed(forse l'ha corretto dopo che hai commentato).
studgeek

Cordiali saluti, ecco l'elenco dei comandi tput Colori e movimento del cursore con tput
studgeek

12

Il metodo \ 033 non ha funzionato per me. Il metodo \ r funziona ma in realtà non cancella nulla, posiziona semplicemente il cursore all'inizio della riga. Quindi, se la nuova stringa è più corta di quella vecchia, puoi vedere il testo rimanente alla fine della riga. Alla fine tput era il modo migliore per andare. Ha altri usi oltre alle cose del cursore ed è preinstallato in molte distribuzioni Linux e BSD, quindi dovrebbe essere disponibile per la maggior parte degli utenti 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

Ecco un piccolo script per il conto alla rovescia con cui giocare:

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

Questo cancella l'intera linea in effetti, il problema brilla troppo per me: '(
smarber

5

Nel caso in cui l'output del progresso sia su più righe, o lo script avrebbe già stampato il nuovo carattere di riga, puoi saltare le righe con qualcosa come:

printf "\033[5A"

che farà saltare il cursore di 5 righe in alto. Quindi puoi sovrascrivere tutto ciò di cui hai bisogno.

Se non funzionasse potresti provare printf "\e[5A"o echo -e "\033[5A", che dovrebbe avere lo stesso effetto.

Fondamentalmente, con le sequenze di escape puoi controllare quasi tutto sullo schermo.


L'equivalente portatile di questo è tput cuu 5, dove 5 è il numero di righe ( cuuper spostarsi verso l'alto, cudper spostarsi verso il basso).
Maëlan

4

Usa il carattere di ritorno a capo:

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

Questa risposta è il modo più semplice. Puoi anche ottenere lo stesso effetto usando: printf "Foo \ rBar"
lee8oi

1

Puoi ottenerlo inserendo il ritorno a capo \r.

In una singola riga di codice con printf

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

o con echo -ne

for i in {10..1}; do echo -ne "Counting down: $i\r" && sleep 1; done
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.