¿Cómo eliminar y reemplazar la última línea en la terminal usando bash?

99

Quiero implementar una barra de progreso que muestre los segundos transcurridos en bash. Para esto, necesito borrar la última línea que se muestra en la pantalla (el comando "borrar" borra toda la pantalla, pero necesito borrar solo la línea de la barra de progreso y reemplazarla con la nueva información).

El resultado final debería verse así:

$ Elapsed time 5 seconds

Luego, después de 10 segundos, quiero reemplazar esta oración (en la misma posición en la pantalla) por:

$ Elapsed time 15 seconds
Depurador
fuente

Respuestas:

116

repite un retorno de carro con \ r

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

del eco del hombre:

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

\r     carriage return
Conocido
fuente
19
for i in {1..100000}; do echo -en "\r$i"; donepara evitar la llamada secundaria :-)
Douglas Leeder
Esto hace exactamente lo que necesito, gracias !! Y el uso del comando seq congela el termianl :-)
Debugger
11
Cuando usa cosas como "para i en $ (...)" o "para i en {1..N}", está generando todos los elementos antes de iterar, eso es muy ineficiente para entradas grandes. Puede aprovechar las canalizaciones: "seq 1 100000 | while read i; do ..." o usar el bucle for bash c-style: "for ((i = 0 ;; i ++)); do ..."
tokland
Gracias Douglas y tokland, aunque la producción de la secuencia no fue directamente parte de la pregunta, cambié al tubo más eficiente de tokland
Ken
1
Mi impresora matricial está arruinando completamente mi papel. Sigue atascando puntos en la misma hoja de papel que ya no está, ¿cuánto tiempo dura este programa?
Rob
185

El retorno de carro por sí solo mueve el cursor al principio de la línea. Eso está bien si cada nueva línea de salida es al menos tan larga como la anterior, pero si la nueva línea es más corta, la línea anterior no se sobrescribirá por completo, por ejemplo:

$ echo -e "abcdefghijklmnopqrstuvwxyz\r0123456789"
0123456789klmnopqrstuvwxyz

Para borrar la línea del nuevo texto, puede agregar \033[Kdespués de \r:

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

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

Derek Veit
fuente
3
Esto funciona muy bien en mi entorno. ¿Algún conocimiento de compatibilidad?
Alexander Olsson
21
En Bash al menos eso se puede acortar a en \e[Klugar de \033[K.
Dave James Miller
¡Gran respuesta! Funciona perfectamente usando put de ruby. Ahora forma parte de mi caja de herramientas.
phatmann
@The Pixel Developer: Gracias por intentar mejorarlo, pero su edición fue incorrecta. El retorno debe ocurrir primero para mover el cursor al principio de la línea, luego la eliminación borra todo desde la ubicación del cursor hasta el final, dejando toda la línea en blanco, que es la intención. Los pones al principio de cada nueva línea, de manera similar a la respuesta de Ken, de modo que esté escrito en una línea vacía.
Derek Veit
1
Solo para que conste, también se puede hacer \033[Gio \rantes, \033[Kaunque obviamente \res mucho más simple. También invisible-island.net/xterm/ctlseqs/ctlseqs.html da más detalles que Wikipedia y es del desarrollador xterm.
jamadagni
19

La respuesta de Derek Veit funciona bien siempre que la longitud de la línea nunca exceda el ancho del terminal. Si este no es el caso, el siguiente código evitará la salida basura:

antes de escribir la línea por primera vez, haz

tput sc

que guarda la posición actual del cursor. Ahora, siempre que desee imprimir su línea, utilice

tput rc
tput ed
echo "your stuff here"

para volver primero a la posición guardada del cursor, luego borrar la pantalla desde el cursor hacia abajo y finalmente escribir la salida.

Um.
fuente
Extraño, esto no hace nada en Terminator. ¿Sabes si existen limitaciones de compatibilidad?
Jamie Pate
1
Nota para Cygwin: Necesita instalar el paquete "ncurses" para usar "tput".
Jack Miller
Hm ... Parece que no funciona si hay más de una línea en la salida. Esto no 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 Necesita usar en tput edlugar de tput el. Se usó el ejemplo de @ Um ed(tal vez lo arregló después de que usted comentó).
studgeek
Para su información, aquí está la lista de comandos tput Colores y movimiento del cursor con tput
studgeek
12

El método \ 033 no me funcionó. El método \ r funciona pero en realidad no borra nada, solo coloca el cursor al principio de la línea. Entonces, si la nueva cadena es más corta que la anterior, puede ver el texto sobrante al final de la línea. Al final, tput fue el mejor camino a seguir. Tiene otros usos además del cursor, además de que viene preinstalado en muchas distribuciones de Linux y BSD, por lo que debería estar disponible para la mayoría de los usuarios de 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

Aquí hay un pequeño guión de cuenta regresiva para jugar:

#!/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"
lee8oi
fuente
Eso aclara toda la línea de hecho, el problema brilla demasiado para mí: '(
smarber
5

En caso de que la salida de progreso sea de varias líneas, o el script ya haya impreso el carácter de nueva línea, puede saltar líneas con algo como:

printf "\033[5A"

lo que hará que el cursor salte 5 líneas hacia arriba. Entonces puedes sobrescribir lo que necesites.

Si eso no funcionaría puede probar printf "\e[5A"o echo -e "\033[5A", lo que debería tener el mismo efecto.

Básicamente, con las secuencias de escape puedes controlar casi todo en la pantalla.

Sr. Goferito
fuente
El equivalente portátil de esto es tput cuu 5, donde 5 es el número de filas ( cuusubir, cudbajar).
Maëlan
4

Utilice el carácter de retorno de carro:

echo -e "Foo\rBar" # Will print "Bar"
Mikael S
fuente
Esta respuesta es la forma más sencilla. Incluso puede lograr el mismo efecto usando: printf "Foo \ rBar"
lee8oi
1

Puede lograrlo colocando retorno de carro \r.

En una sola línea de código 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
Akif
fuente