Wie lösche und ersetze ich die letzte Zeile im Terminal mit bash?

99

Ich möchte einen Fortschrittsbalken implementieren, der die verstrichenen Sekunden in Bash anzeigt. Dazu muss ich die letzte auf dem Bildschirm angezeigte Zeile löschen (Befehl "Löschen" löscht den gesamten Bildschirm, aber ich muss nur die Zeile des Fortschrittsbalkens löschen und durch die neuen Informationen ersetzen).

Das Endergebnis sollte folgendermaßen aussehen:

$ Elapsed time 5 seconds

Dann möchte ich nach 10 Sekunden diesen Satz (an derselben Stelle auf dem Bildschirm) ersetzen durch:

$ Elapsed time 15 seconds
Debugger
quelle

Antworten:

116

Echo einen Wagenrücklauf mit \ r

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

vom Menschen Echo:

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

\r     carriage return
Ken
quelle
19
for i in {1..100000}; do echo -en "\r$i"; doneum den seq call zu vermeiden :-)
Douglas Leeder
Das macht genau das, was ich brauche, danke !! Und die Verwendung des Befehls seq friert tatsächlich das Termianl ein :-)
Debugger
11
Wenn Sie Dinge wie "für i in $ (...)" oder "für i in {1..N}" verwenden, generieren Sie alle Elemente vor dem Iterieren. Dies ist für große Eingaben sehr ineffizient. Sie können entweder Pipes nutzen: "seq 1 100000 | while read i; do ..." oder den bash c-style for loop verwenden: "for ((i = 0 ;; i ++)); do ..."
tokland
Vielen Dank an Douglas und tokland - obwohl die Sequenzproduktion nicht direkt Teil der Frage war, die ich in toklands effizientere Pipe geändert habe
Ken
1
Mein Matrixdrucker bringt mein Papier völlig durcheinander. Es klemmt immer wieder Punkte auf demselben Blatt Papier, das nicht mehr vorhanden ist. Wie lange läuft dieses Programm?
Rob
185

Der Wagenrücklauf selbst bewegt den Cursor nur an den Zeilenanfang. Das ist in Ordnung, wenn jede neue Ausgabezeile mindestens so lang ist wie die vorherige, aber wenn die neue Zeile kürzer ist, wird die vorherige Zeile nicht vollständig überschrieben, z.

$ echo -e "abcdefghijklmnopqrstuvwxyz\r0123456789"
0123456789klmnopqrstuvwxyz

Um die Zeile für den neuen Text tatsächlich zu löschen, können Sie hinzufügen \033[K , nachdem das \r:

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

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

Derek Veit
quelle
3
Das funktioniert in meiner Umgebung sehr gut. Kompatibilitätskenntnisse?
Alexander Olsson
21
Zumindest in Bash kann das auf \e[Kstatt gekürzt werden \033[K.
Dave James Miller
Gute Antwort! Funktioniert perfekt mit Puts aus Rubin. Jetzt Teil meines Toolkits.
Phatmann
@ The Pixel Developer: Vielen Dank, dass Sie versucht haben, es zu verbessern, aber Ihre Bearbeitung war falsch. Die Rückkehr muss zuerst erfolgen, um den Cursor an den Anfang der Zeile zu bewegen. Dann löscht der Kill alles von dieser Cursorposition bis zum Ende, wobei die gesamte Zeile leer bleibt, was die Absicht ist. Sie setzen diese am Anfang jeder neuen Zeile, ähnlich wie bei Kens Antwort, so dass sie in eine leere Zeile geschrieben wird.
Derek Veit
1
Nur für das Protokoll, kann man auch tun \033[Gio \rbevor \033[Kobwohl offensichtlich \rist viel einfacher. Auch unsichtbar-island.net/xterm/ctlseqs/ctlseqs.html enthält mehr Details als Wikipedia und stammt vom xterm-Entwickler.
Jamadagni
19

Die Antwort von Derek Veit funktioniert gut, solange die Leitungslänge niemals die Terminalbreite überschreitet. Ist dies nicht der Fall, verhindert der folgende Code die Junk-Ausgabe:

Bevor die Zeile zum ersten Mal geschrieben wird, tun Sie dies

tput sc

Dadurch wird die aktuelle Cursorposition gespeichert. Verwenden Sie jetzt, wann immer Sie Ihre Zeile drucken möchten

tput rc
tput ed
echo "your stuff here"

Um zuerst zur gespeicherten Cursorposition zurückzukehren, löschen Sie den Bildschirm vom Cursor nach unten und schreiben Sie schließlich die Ausgabe.

Äh.
quelle
Seltsam, das macht nichts im Terminator. Wissen Sie, ob es Kompatibilitätsbeschränkungen gibt?
Jamie Pate
1
Hinweis für Cygwin: Sie müssen das Paket "ncurses" installieren, um "tput" verwenden zu können.
Jack Miller
Hm ... Scheint nicht zu funktionieren, wenn mehr als eine Zeile in der Ausgabe ist. Dies funktioniert nicht: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 Sie müssen tput edeher als verwenden tput el. @ Ums Beispiel verwendet ed(vielleicht hat er es behoben, nachdem Sie kommentiert haben).
Studgeek
Zu Ihrer Information, hier ist die Liste der tput-Befehle Farben und Cursorbewegung mit tput
studgeek
12

Die \ 033-Methode hat bei mir nicht funktioniert. Die \ r-Methode funktioniert, löscht jedoch nichts, sondern setzt den Cursor einfach an den Zeilenanfang. Wenn die neue Zeichenfolge kürzer als die alte ist, sehen Sie den verbleibenden Text am Ende der Zeile. Am Ende war tput der beste Weg. Es hat neben dem Cursor-Material noch andere Verwendungszwecke und ist in vielen Linux- und BSD-Distributionen vorinstalliert, sodass es für die meisten Bash-Benutzer verfügbar sein sollte.

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

Hier ist ein kleines Countdown-Skript zum Spielen:

#!/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
quelle
Das klärt in der Tat die ganze Linie, Problem, es funkelt mir zu viel: '(
smarber
5

Wenn die Fortschrittsausgabe mehrzeilig ist oder das Skript das neue Zeilenzeichen bereits gedruckt hätte, können Sie Zeilen mit folgenden Elementen überspringen:

printf "\033[5A"

Dadurch springt der Cursor 5 Zeilen nach oben. Dann können Sie alles überschreiben, was Sie brauchen.

Wenn das nicht funktionieren würde, könnten Sie es versuchen printf "\e[5A"oder echo -e "\033[5A", was den gleichen Effekt haben sollte.

Grundsätzlich können Sie mit Escape-Sequenzen fast alles auf dem Bildschirm steuern.

Herr Goferito
quelle
Das tragbare Äquivalent dazu ist tput cuu 5, wobei 5 die Anzahl der Zeilen ist ( cuunach oben, cudnach unten).
Maëlan
4

Verwenden Sie das Wagenrücklaufzeichen:

echo -e "Foo\rBar" # Will print "Bar"
Mikael S.
quelle
Diese Antwort ist der einfachste Weg. Sie können den gleichen Effekt sogar erzielen mit: printf "Foo \ rBar"
lee8oi
1

Kann es durch Platzieren des Wagenrücklaufs erreichen \r.

In einer einzigen Codezeile mit printf

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

oder mit echo -ne

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