Как удалить и заменить последнюю строку в терминале с помощью bash?
Я хочу реализовать индикатор выполнения, показывающий прошедшие секунды в bash. Для этого мне нужно стереть последнюю строку, показанную на экране (команда "очистить" стирает весь экран, но мне нужно стереть только строку индикатора выполнения и заменить ее новой информацией).
Конечный результат должен выглядеть так:
$ Elapsed time 5 seconds
Затем через 10 секунд я хочу заменить это предложение (в той же позиции на экране) на:
$ Elapsed time 15 seconds
10 ответов
Повторить возврат каретки с \r
seq 1 1000000 | while read i; do echo -en "\r$i"; done
от мужского эха:
-n do not output the trailing newline
-e enable interpretation of backslash escapes
\r carriage return
Возврат каретки сам по себе только перемещает курсор в начало строки. Это нормально, если каждая новая строка вывода, по крайней мере, такая же, как и предыдущая, но если новая строка короче, предыдущая строка не будет полностью перезаписана, например:
$ echo -e "abcdefghijklmnopqrstuvwxyz\r0123456789"
0123456789klmnopqrstuvwxyz
Чтобы на самом деле очистить строку для нового текста, вы можете добавить \033[K
после \r
:
$ echo -e "abcdefghijklmnopqrstuvwxyz\r\033[K0123456789"
0123456789
Ответ Дерека Вейта работает хорошо, если длина линии никогда не превышает ширину терминала. Если это не так, следующий код предотвратит вывод нежелательной почты:
прежде чем строка будет написана в первый раз, сделайте
tput sc
который сохраняет текущую позицию курсора. Теперь, когда вы хотите напечатать свою строку, используйте
tput rc
tput ed
echo "your stuff here"
сначала вернитесь в сохраненную позицию курсора, затем очистите экран от курсора до дна и, наконец, запишите результат.
Метод \033 у меня не сработал. Метод \r работает, но на самом деле он ничего не стирает, просто помещает курсор в начало строки. Поэтому, если новая строка короче старой, вы можете увидеть оставшийся текст в конце строки. В конце концов, tput был лучшим способом. У него есть и другие применения, кроме курсора, плюс он предустановлен во многих дистрибутивах Linux и BSD, поэтому он должен быть доступен для большинства пользователей 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
Вот небольшой скрипт обратного отсчета для игры:
#!/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"
Если результат выполнения многострочный, или сценарий уже напечатал бы символ новой строки, вы можете перейти к следующей строке:
printf "\033[5A"
который заставит курсор прыгать на 5 строк вверх. Тогда вы можете переписать все, что вам нужно.
Если это не сработает, вы можете попробовать printf "\e[5A"
или же echo -e "\033[5A"
, который должен иметь тот же эффект.
По сути, с помощью escape-последовательностей вы можете контролировать практически все на экране.
Можно добиться этого путем размещения возврата каретки \r
,
В одной строке кода с printf
for i in {10..1}; do printf "Counting down: $i\r" && sleep 1; done
или с echo -ne
for i in {10..1}; do echo -ne "Counting down: $i\r" && sleep 1; done
Если вы просто хотите очистить предыдущую строку, следующее может помочь.
printf '\033[1A\033[K'
Для нескольких строк используйте его в цикле:
for i in {1..10}; do
printf '\033[1A\033[K'
done
Это очистит последние 10 строк.
Используйте символ возврата каретки:
echo -e "Foo\rBar" # Will print "Bar"
Вместо использования обратной косой черты в вашем
echo
, вы можете использовать
tput
. Чтобы упростить это, вы можете создать функцию:
#!/bin/bash
function consoleoneline {
# Start process and save all outputs in a temporary directory
tput sc # save current cursor in console
local PIPE_DIRECTORY=$(mktemp -d)
trap "rm -rf '$PIPE_DIRECTORY'" EXIT
mkfifo "$PIPE_DIRECTORY/stdout"
mkfifo "$PIPE_DIRECTORY/stderr"
"$@" >"$PIPE_DIRECTORY/stdout" 2>"$PIPE_DIRECTORY/stderr" &
local CHILD_PID=$!
# Replace all outputs with a leading "›"
# `tput` allows to reset the cursor to the previous line
sed "s/^/`tput rc;tput el`› /" "$PIPE_DIRECTORY/stdout" &
sed "s/^/`tput rc;tput el`› /" "$PIPE_DIRECTORY/stderr" >&2 &
# Wait command has exited, remove temporary directory and reset cursor
wait "$CHILD_PID"
rm -rf "$PIPE_DIRECTORY"
tput ed
}
consoleoneline bash -c 'echo 1 && sleep 1 && echo 2 && sleep 1 && echo 3'
Самый простой способ - это использовать \r
характер, я думаю.
Недостатком является то, что вы не можете иметь полные строки, так как он очищает только текущую строку.
Простой пример:
time=5
echo -n "Elapsed $time seconds"
sleep 10
time=15
echo -n "Elapsed $time seconds"
echo "\nDone"