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

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

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


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.


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"

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.


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"

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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.