Comment supprimer et remplacer la dernière ligne du terminal en utilisant bash?

99

Je souhaite implémenter une barre de progression indiquant les secondes écoulées dans bash. Pour cela, je dois effacer la dernière ligne affichée à l'écran (la commande "effacer" efface tout l'écran, mais je n'ai besoin d'effacer que la ligne de la barre de progression et de la remplacer par les nouvelles informations).

Le résultat final devrait ressembler à:

$ Elapsed time 5 seconds

Ensuite, après 10 secondes, je veux remplacer cette phrase (dans la même position dans l'écran) par:

$ Elapsed time 15 seconds
Débogueur
la source

Réponses:

116

echo un retour chariot avec \ r

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

de l'écho de l'homme:

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

\r     carriage return
Ken
la source
19
for i in {1..100000}; do echo -en "\r$i"; donepour éviter l'appel suivant :-)
Douglas Leeder
Cela fait exactement ce dont j'ai besoin, merci !! Et l'utilisation de la commande seq gèle en effet le termianl :-)
Débogueur
11
Lorsque vous utilisez des choses comme "for i in $ (...)" ou "for i in {1..N}", vous générez tous les éléments avant d'itérer, ce qui est très inefficace pour les entrées importantes. Vous pouvez soit profiter des tubes: "seq 1 100000 | while read i; do ..." ou utiliser le bash c-style for loop: "for ((i = 0 ;; i ++)); do ..."
tokland
Merci Douglas et tokland - bien que la production de la séquence ne fasse pas directement partie de la question, j'ai changé pour le tuyau plus efficace de tokland
Ken
1
Mon imprimante matricielle gâche complètement mon papier. Il continue de coincer des points sur le même morceau de papier qui n'est plus là, combien de temps ce programme fonctionne-t-il?
Rob du
185

Le retour chariot en lui-même ne déplace le curseur qu'au début de la ligne. C'est OK si chaque nouvelle ligne de sortie est au moins aussi longue que la précédente, mais si la nouvelle ligne est plus courte, la ligne précédente ne sera pas complètement écrasée, par exemple:

$ echo -e "abcdefghijklmnopqrstuvwxyz\r0123456789"
0123456789klmnopqrstuvwxyz

Pour effacer réellement la ligne du nouveau texte, vous pouvez ajouter \033[Kaprès le \r:

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

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

Derek Veit
la source
3
Cela fonctionne très bien dans mon environnement. Une connaissance de la compatibilité?
Alexander Olsson
21
Dans Bash au moins, cela peut être raccourci à la \e[Kplace de \033[K.
Dave James Miller
Très bonne réponse! Fonctionne parfaitement en utilisant des puts de rubis. Maintenant partie de ma boîte à outils.
phatmann
@The Pixel Developer: Merci d'avoir essayé de l'améliorer, mais votre modification était incorrecte. Le retour doit d'abord se produire pour déplacer le curseur au début de la ligne, puis le kill efface tout de cet emplacement du curseur jusqu'à la fin, laissant toute la ligne vide, ce qui est l'intention. Vous les mettez au début de chaque nouvelle ligne, de la même manière que la réponse de Ken, de sorte qu'elle soit écrite sur une ligne vide.
Derek Veit
1
Pour mémoire, on peut aussi faire \033[Gio \ravant, \033[Kbien que ce soit évidemment \rbeaucoup plus simple. Aussi invisible-island.net/xterm/ctlseqs/ctlseqs.html donne plus de détails que Wikipedia et est du développeur xterm.
jamadagni
19

La réponse de Derek Veit fonctionne bien tant que la longueur de la ligne ne dépasse jamais la largeur du terminal. Si ce n'est pas le cas, le code suivant empêchera la sortie indésirable:

avant que la ligne ne soit écrite pour la première fois, faites

tput sc

qui enregistre la position actuelle du curseur. Désormais, chaque fois que vous souhaitez imprimer votre ligne, utilisez

tput rc
tput ed
echo "your stuff here"

pour revenir d'abord à la position enregistrée du curseur, puis effacer l'écran du curseur vers le bas, et enfin écrire la sortie.

Hum.
la source
Wierd, cela ne fait rien dans terminator. Savez-vous s'il existe des limitations de compatibilité?
Jamie Pate
1
Remarque pour Cygwin: Vous devez installer le package "ncurses" pour utiliser "tput".
Jack Miller
Hm ... Semble ne pas fonctionner si plus d'une ligne est dans la sortie. Cela ne fonctionne pas: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 Vous devez utiliser tput edplutôt que tput el. L'exemple de @ Um utilisé ed(peut-être qu'il l'a corrigé après que vous ayez commenté).
studgeek
Pour info, voici la liste des commandes tput Couleurs et mouvement du curseur Avec tput
studgeek
12

La méthode \ 033 n'a pas fonctionné pour moi. La méthode \ r fonctionne mais elle n'efface rien, place simplement le curseur au début de la ligne. Donc, si la nouvelle chaîne est plus courte que l'ancienne, vous pouvez voir le texte restant à la fin de la ligne. En fin de compte, tput était la meilleure voie à suivre. Il a d'autres utilisations en plus du curseur et est pré-installé dans de nombreuses distributions Linux et BSD, il devrait donc être disponible pour la plupart des utilisateurs 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

Voici un petit script de compte à rebours avec lequel jouer:

#!/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
la source
Cela efface toute la ligne en effet, problème ça scintille trop pour moi: '(
smarber
5

Dans le cas où la sortie de progression est sur plusieurs lignes, ou si le script aurait déjà imprimé le caractère de nouvelle ligne, vous pouvez sauter des lignes avec quelque chose comme:

printf "\033[5A"

ce qui fera sauter le curseur de 5 lignes. Ensuite, vous pouvez écraser tout ce dont vous avez besoin.

Si cela ne fonctionne pas, vous pouvez essayer printf "\e[5A"ou echo -e "\033[5A", ce qui devrait avoir le même effet.

Fondamentalement, avec les séquences d'échappement, vous pouvez contrôler presque tout à l'écran.

M. Goferito
la source
L'équivalent portable de ceci est tput cuu 5, où 5 est le nombre de lignes ( cuupour monter, cudpour descendre).
Maëlan le
4

Utilisez le caractère de retour chariot:

echo -e "Foo\rBar" # Will print "Bar"
Mikael S
la source
Cette réponse est la manière la plus simple. Vous pouvez même obtenir le même effet en utilisant: printf "Foo \ rBar"
lee8oi
1

Peut y parvenir en plaçant le retour chariot \r.

En une seule ligne de code avec printf

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

ou avec echo -ne

for i in {10..1}; do echo -ne "Counting down: $i\r" && sleep 1; done
Akif
la source