Jak usunąć i zamienić ostatnią linię w terminalu za pomocą basha?

99

Chcę zaimplementować pasek postępu pokazujący upływające sekundy w bash. W tym celu muszę wykasować ostatnią linię wyświetlaną na ekranie (polecenie „wyczyść” wymazuje cały ekran, ale muszę wymazać tylko linię paska postępu i zastąpić ją nowymi informacjami).

Efekt końcowy powinien wyglądać następująco:

$ Elapsed time 5 seconds

Następnie po 10 sekundach chcę zamienić to zdanie (w tej samej pozycji na ekranie) przez:

$ Elapsed time 15 seconds
Debugger
źródło

Odpowiedzi:

117

echo powrotu karetki z \ r

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

od człowieka echo:

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

\r     carriage return
Rozpoznać
źródło
19
for i in {1..100000}; do echo -en "\r$i"; doneaby uniknąć kolejnego wezwania :-)
Douglas Leeder
Robi dokładnie to, czego potrzebuję, dzięki! A użycie polecenia seq rzeczywiście zamraża termianl :-)
Debugger
11
Kiedy używasz takich rzeczy jak „for i in $ (...)” lub „for i in {1..N}” generujesz wszystkie elementy przed iteracją, jest to bardzo nieefektywne w przypadku dużych danych wejściowych. Możesz albo skorzystać z potoków: "seq 1 100000 | while read i; do ..." lub użyć pętli for w stylu bash c: "for ((i = 0 ;; i ++)); do ..."
tokland
Dzięki Douglas i Tokland - chociaż produkcja sekwencji nie była bezpośrednio częścią pytania, zmieniłem na bardziej wydajną fajkę toklanda
Ken
1
Moja drukarka igłowa kompletnie psuje mi papier. Ciągle zacina kropki na tej samej kartce papieru, której już nie ma, jak długo ten program działa?
Rob
185

Sam powrót karetki przenosi tylko kursor na początek wiersza. To jest OK, jeśli każdy nowy wiersz wyjścia jest co najmniej tak długi jak poprzedni, ale jeśli nowy wiersz jest krótszy, poprzednia linia nie zostanie całkowicie nadpisana, np .:

$ echo -e "abcdefghijklmnopqrstuvwxyz\r0123456789"
0123456789klmnopqrstuvwxyz

Aby rzeczywiście wyczyścić wiersz nowego tekstu, możesz dodać \033[Kpo \r:

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

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

Derek Veit
źródło
3
To działa naprawdę dobrze w moim środowisku. Jakaś wiedza na temat kompatybilności?
Alexander Olsson,
21
W Bash przynajmniej można to skrócić do \e[Kzamiast \033[K.
Dave James Miller
Świetna odpowiedź! Doskonale sprawdza się przy użyciu putsów z rubinu. Teraz część mojego zestawu narzędzi.
phatmann
@The Pixel Developer: Dziękujemy za próbę ulepszenia, ale Twoja edycja była niepoprawna. Powrót musi nastąpić najpierw, aby przesunąć kursor na początek wiersza, a następnie zabicie czyści wszystko od tego położenia kursora do końca, pozostawiając całą linię pustą, co jest celem. Umieszczasz je na początku każdego nowego wiersza, podobnie jak odpowiedź Kena, tak aby była zapisana w pustym wierszu.
Derek Veit
1
Tak dla przypomnienia, można również zrobić \033[Gio \rwcześniej, \033[Kchoć oczywiście \rjest to znacznie prostsze. Również invisible-island.net/xterm/ctlseqs/ctlseqs.html zawiera więcej szczegółów niż Wikipedia i pochodzi od programisty xterm.
jamadagni
19

Odpowiedź Dereka Veita działa dobrze, o ile długość linii nigdy nie przekracza szerokości terminala. Jeśli tak nie jest, poniższy kod zapobiegnie wysyłaniu śmieci:

zanim wiersz zostanie napisany po raz pierwszy, zrób

tput sc

który zapisuje aktualną pozycję kursora. Teraz, kiedy chcesz wydrukować swoją linię, użyj

tput rc
tput ed
echo "your stuff here"

aby najpierw powrócić do zapisanej pozycji kursora, następnie wyczyść ekran od kursora do dołu, a na koniec zapisz wynik.

Um.
źródło
Dziwne, to nic nie robi w przypadku terminatora. Czy wiesz, czy istnieją ograniczenia zgodności?
Jamie Pate
1
Uwaga dla Cygwin: Musisz zainstalować pakiet „ncurses”, aby używać „tput”.
Jack Miller
Hm ... Wydaje się nie działać, jeśli na wyjściu jest więcej niż jedna linia. To nie działa: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 Musisz użyć tput edzamiast tput el. Zastosowano przykład @ Um ed(być może naprawił go po tym, jak skomentowałeś).
studgeek
Do Twojej wiadomości, Oto lista poleceń tput Kolory i ruch kursora z tput
studgeek
12

Metoda \ 033 nie zadziałała dla mnie. Metoda \ r działa, ale tak naprawdę niczego nie kasuje, po prostu umieszcza kursor na początku wiersza. Więc jeśli nowy ciąg jest krótszy niż stary, możesz zobaczyć pozostały tekst na końcu linii. Ostatecznie tput był najlepszą drogą. Ma inne zastosowania poza funkcjami kursora, a ponadto jest preinstalowany w wielu dystrybucjach Linuksa i BSD, więc powinien być dostępny dla większości użytkowników basha.

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

Oto mały skrypt odliczający do gry:

#!/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
źródło
To rzeczywiście usuwa całą linię, problem, dla mnie za bardzo
migocze
5

W przypadku, gdy wynik postępu jest wieloliniowy lub skrypt już wypisałby znak nowego wiersza, możesz przeskoczyć linie w górę za pomocą czegoś takiego:

printf "\033[5A"

co spowoduje, że kursor przeskoczy o 5 linii w górę. Następnie możesz nadpisać wszystko, czego potrzebujesz.

Jeśli to nie zadziała, możesz spróbować printf "\e[5A"lub echo -e "\033[5A", co powinno przynieść ten sam efekt.

Zasadniczo dzięki sekwencjom ucieczki możesz kontrolować prawie wszystko na ekranie.

Panie Goferito
źródło
Przenośny odpowiednik tego to tput cuu 5, gdzie 5 to liczba rzędów ( cuuaby przejść w górę, cudaby przejść w dół).
Maëlan
4

Użyj znaku powrotu karetki:

echo -e "Foo\rBar" # Will print "Bar"
Mikael S
źródło
Ta odpowiedź jest najprostsza. Możesz nawet osiągnąć ten sam efekt, używając: printf "Foo \ rBar"
lee8oi
1

Można to osiągnąć poprzez umieszczenie powrotu karetki \r.

W jednej linii kodu z printf

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

lub z echo -ne

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