Bagaimana cara menghapus dan mengganti baris terakhir di terminal menggunakan bash?

99

Saya ingin menerapkan bilah kemajuan yang menunjukkan detik-detik yang telah berlalu di pesta. Untuk ini, saya perlu menghapus baris terakhir yang ditampilkan di layar (perintah "hapus" menghapus semua layar, tetapi saya hanya perlu menghapus garis bilah kemajuan dan menggantinya dengan informasi baru).

Hasil akhirnya akan terlihat seperti ini:

$ Elapsed time 5 seconds

Kemudian setelah 10 detik saya ingin mengganti kalimat ini (dalam posisi yang sama di layar) dengan:

$ Elapsed time 15 seconds
Debugger
sumber

Jawaban:

117

echo kembali kereta dengan \ r

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

dari man echo:

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

\r     carriage return
Ken
sumber
19
for i in {1..100000}; do echo -en "\r$i"; doneuntuk menghindari panggilan seq :-)
Douglas Leeder
Ini melakukan apa yang saya butuhkan, terima kasih !! Dan penggunaan perintah seq memang membekukan termianl :-)
Debugger
11
Saat Anda menggunakan hal-hal seperti "for i in $ (...)" atau "for i in {1..N}" Anda menghasilkan semua elemen sebelum melakukan iterasi, itu sangat tidak efisien untuk input besar. Anda dapat memanfaatkan pipa: "seq 1 100000 | while read i; do ..." atau gunakan bash c-style for loop: "for ((i = 0 ;; i ++)); do ..."
tokland
Terima kasih Douglas dan tokland - meskipun produksi urutan tidak secara langsung menjadi bagian dari pertanyaan, saya telah mengubah pipa tokland yang lebih efisien
Ken
1
Printer matriks saya benar-benar mengacaukan kertas saya. Itu membuat titik-titik macet pada kertas yang sama yang sudah tidak ada lagi, berapa lama program ini berjalan?
Rob
185

Kereta kembali dengan sendirinya hanya memindahkan kursor ke awal baris. Tidak masalah jika setiap baris keluaran baru setidaknya sepanjang baris sebelumnya, tetapi jika baris baru lebih pendek, baris sebelumnya tidak akan sepenuhnya ditimpa, misalnya:

$ echo -e "abcdefghijklmnopqrstuvwxyz\r0123456789"
0123456789klmnopqrstuvwxyz

Untuk benar-benar menghapus baris untuk teks baru, Anda dapat menambahkan \033[Ksetelah \r:

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

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

Derek Veit
sumber
3
Ini bekerja dengan sangat baik di lingkungan saya. Ada pengetahuan tentang kompatibilitas?
Alexander Olsson
21
Dalam Bash setidaknya yang dapat disingkat menjadi \e[Kbukan \033[K.
Dave James Miller
Jawaban yang bagus! Bekerja dengan sempurna menggunakan put dari ruby. Sekarang bagian dari perangkat saya.
phatmann
@Pengembang Piksel: Terima kasih telah mencoba memperbaikinya, tetapi pengeditan Anda salah. Pengembalian harus terjadi terlebih dahulu untuk memindahkan kursor ke awal baris, kemudian kill menghapus semuanya dari lokasi kursor tersebut hingga akhir, membiarkan seluruh baris kosong, yang merupakan maksudnya. Anda meletakkannya di awal setiap baris baru, mirip dengan jawaban Ken, sehingga ditulis di baris kosong.
Derek Veit
1
Sekadar catatan, seseorang juga dapat melakukan \033[Gio \rsebelumnya \033[Kmeskipun jelas\r jauh lebih sederhana. Juga invisible-island.net/xterm/ctlseqs/ctlseqs.html memberikan rincian lebih dari Wikipedia dan berasal dari pengembang xterm.
jamadagni
19

Jawaban Derek Veit berfungsi dengan baik selama panjang garis tidak pernah melebihi lebar terminal. Jika tidak demikian, kode berikut akan mencegah keluaran sampah:

sebelum baris ditulis untuk pertama kali, lakukan

tput sc

yang menyimpan posisi kursor saat ini. Sekarang kapan pun Anda ingin mencetak baris Anda, gunakan

tput rc
tput ed
echo "your stuff here"

untuk pertama-tama kembali ke posisi kursor yang disimpan, lalu bersihkan layar dari kursor ke bawah, dan terakhir tulis hasilnya.

Um.
sumber
Aneh, ini tidak melakukan apa pun di terminator. Tahukah Anda jika ada batasan kompatibilitas?
Jamie Pate
1
Catatan untuk Cygwin: Anda perlu menginstal paket "ncurses" untuk menggunakan "tput".
Jack Miller
Hm ... Sepertinya tidak berfungsi jika lebih dari satu baris di output. Ini tidak berhasil: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 Anda perlu menggunakan tput eddaripada tput el. Contoh @ Um digunakan ed(mungkin dia memperbaikinya setelah Anda berkomentar).
studgeek
FYI, Berikut adalah daftar perintah tput Warna dan Gerakan Kursor Dengan
tput
12

Metode \ 033 tidak berhasil untuk saya. Metode \ r berfungsi tetapi tidak benar-benar menghapus apa pun, cukup letakkan kursor di awal baris. Jadi jika string baru lebih pendek dari yang lama, Anda dapat melihat teks sisa di akhir baris. Pada akhirnya tput adalah cara terbaik untuk pergi. Ini memiliki kegunaan lain selain hal-hal kursor ditambah itu datang pra-instal di banyak distro Linux & BSD sehingga harus tersedia untuk sebagian besar pengguna 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

Berikut sedikit skrip hitung mundur untuk dimainkan:

#!/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
sumber
Itu benar-benar membersihkan keseluruhan baris, masalah itu terlalu
berkedip
5

Jika hasil kemajuan adalah multi baris, atau skrip sudah mencetak karakter baris baru, Anda dapat melompati baris dengan sesuatu seperti:

printf "\033[5A"

yang akan membuat kursor melompat 5 baris ke atas. Kemudian Anda dapat menimpa apa pun yang Anda butuhkan.

Jika itu tidak berhasil, Anda dapat mencoba printf "\e[5A"atau echo -e "\033[5A", yang seharusnya memiliki efek yang sama.

Pada dasarnya, dengan escape sequence Anda dapat mengontrol hampir semua yang ada di layar.

Tuan Goferito
sumber
Padanan portabelnya adalah tput cuu 5, di mana 5 adalah jumlah baris ( cuuuntuk naik, cudturun).
Maëlan
4

Gunakan karakter carriage return:

echo -e "Foo\rBar" # Will print "Bar"
Mikael S
sumber
Jawaban ini adalah cara yang paling sederhana. Anda bahkan dapat mencapai efek yang sama dengan menggunakan: printf "Foo \ rBar"
lee8oi
1

Dapat mencapainya dengan menempatkan kereta kembali \r.

Dalam satu baris kode dengan printf

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

atau dengan echo -ne

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