Вопрос или проблема
Мой скрипт делает что-то подобное:
while :;
clear
do_a_lot_of_output_here
sleep 1
done
Есть ли какие-либо варианты, чтобы предотвратить мигание экрана, когда я использую clear и вывожу что-либо? Я хочу сделать это как в команде watch
(но она написана на C
). Какие-нибудь советы?
clear | hexdump -C
00000000 1b 5b 48 1b 5b 32 4a |.[H.[2J|
00000007
PS. Я использую только bash
.
Один из способов помочь предотвратить мерцание — получить весь вывод перед очисткой экрана, чтобы минимизировать время между очисткой и перерисовкой. Это похоже на концепцию двойной буферизации:
while :; do
output=$(do_a_lot_of_output_here)
clear
echo "$output"
sleep 1
done
Это не полностью устраняет мерцание, но, по моему опыту, оно происходит значительно реже.
Мерцание происходит из-за того, что скрипт очищает весь экран. Если записывать поверх существующего текста и очищать только при необходимости, тогда мерцания не будет.
Вот пример:
#!/bin/sh
watchit() {
HOME=$(tput cup 0 0)
ED=$(tput ed)
EL=$(tput el)
ROWS=$(tput lines)
COLS=$(tput cols)
printf '%s%s' "$HOME" "$ED"
while true
do
CMD="$@"
${SHELL:=sh} -c "$CMD" | head -n $ROWS | while IFS= read LINE; do
printf '%-*.*s%s\n' $COLS $COLS "$LINE" "$EL"
done
printf '%s%s' "$ED" "$HOME"
sleep 1
done
}
watchit top -b -n 1
Это делает следующее:
- выводит результаты команды, которые поместятся на экране (без переноса или прокрутки)
- записывает поверх существующих строк, очищая часть каждой строки, которая не была перезаписана
- использует возможность
ed
вашего терминала для печати от текущего местоположения до конца экрана.
Если вы хотите обрабатывать изменяемый размер экрана, вы можете переместить присваивания в ROWS
и COLS
внутрь внешнего цикла, например,
#!/bin/sh
watchit() {
HOME=$(tput cup 0 0)
ED=$(tput ed)
EL=$(tput el)
printf '%s%s' "$HOME" "$ED"
while true
do
ROWS=$(tput lines)
COLS=$(tput cols)
CMD="$@"
${SHELL:=sh} -c "$CMD" | head -n $ROWS | while IFS= read LINE; do
printf '%-*.*s%s\n' $COLS $COLS "$LINE" "$EL"
done
printf '%s%s' "$ED" "$HOME"
sleep 1
done
}
watchit top -b -n 1
потому что tput
запрашивает текущий размер экрана у системы.
Дальнейшее чтение:
Мерцание является неизбежным результатом очистки экрана каждый раз, когда цикл проходит. Вы можете переместить курсор в начало экрана и записать поверх частей вашего старого вывода.
# Возможно, вы захотите сделать это, если ваш код находится в скрипте.
unhide_cursor() {
printf '\e[?25h'
}
trap unhide_cursor EXIT
# Скрыть курсор (есть, вероятно, гораздо лучший способ сделать это)
printf '\e[?25l'
clear
while true ; do
# Переместить курсор в начало экрана, но не очистить экран
printf '\033[;H'
do_a_lot_of_output_here
sleep 1
done
Этот скрипт оставит артефакты, если ваш вывод уменьшится. Он также не очень вероятно будет портативным. Я протестировал его только с urxvt, xterm и st.
Как расширение ответа Кристиана, я сделал следующую функцию bash, которая работает, если окно консоли меньше вывода команды:
function watcher()
{
lines=$(tput lines)
while true; do
output="$($@ | head -n $lines)"
clear
echo -e "$output"
sleep 2
done
}
Это позволяет вам передавать любую команду в watcher. Если вы используете git, используйте git config --global color.status always
, а затем:
watcher git status
Будет отображаться постоянно обновляющийся вывод статуса git.
Зависит от того, у вас одна строка или несколько строк. tput не является плохим вариантом, но мне нравится использовать esc-коды…
while true
do
printf "^[[H^[[0K$do_a_lot_of_output"
sleep 0.01
done
^[[H перемещает курсор в 0,0 или в верхний левый угол, а ^[[0K очищает от текущей позиции курсора до конца строки.
^[[0K хорош в использовании, потому что очищает строку от текущей позиции курсора до конца строки и не оставит никаких остаточных данных, если следующая строка окажется короче предыдущей.
Например, если $do_a_lot_of_output = 20 в первой итерации, а затем $do_a_lot_of_output = 5 во второй, вывод будет 50. Если вы не включите ^[[0K, так как он будет выдавать только там, где это необходимо, 2 в 20 будет перезаписана новой 5, но 0 останется, давая вводящую в заблуждение 50.
Думаю, можно использовать и ^[[K
Начните последовательность esc-кода, нажав ctrl+v, затем ESC, чтобы получить ^[
затем добавьте еще один [ + код, в данном случае H или K
вы соберете все это, чтобы получить ^[[H или ^[[K
список кодов ANSI escape:
https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
Большинство других решений здесь приводят к какому-то мерцанию при перерисовке, даже если минимальному. Я хотел решение без мерцания, так как хотел отображать его в многочисленных окнах tmux, обновляющихся с высокой частотой, и мерцания отвлекают.
Ниже представлена функция без мерцания или артефактов вывода:
function watch() {
sleep="$1"
shift
(
trap 'tput rmcup; tput cnorm' EXIT # восстановить терминал при выходе
tput smcup # альтернативный буфер
tput civis # скрыть курсор
clear="$(tput clear)"
cursor_home="$(tput cup 0 0)"
clear_eol="$(tput el)" # очистить от курсора до конца строки
clear_eos="$(tput ed)" # очистить от курсора до конца экрана
while true; do
buf="$(eval "$@" 2>&1 | sed "s/\$/$clear_eol/" )"
echo -n "$cursor_home$buf$clear_eos"
sleep "$sleep"
done
)
}
Не должно быть трудно понять из кода. Но в основном каждое обновление выполняется команда и буферизуется вывод. Перемещается курсор в верхний левый угол и выводится буфер. Однако при этом части экрана, которые не были перезаписаны, не очищаются, поэтому на экране останется старый мусор. Поэтому мы используем sed
, чтобы добавить код очистки ($clear_eol
) в каждую строку для очистки остатка строки. И затем издаем еще один escape-код ($clear_eos
) в конце, чтобы очистить все оставшееся.
(как примечание, моя причина для этого вместо нормальной утилиты watch
заключается в том, что watch
не поддерживает 24-битовые цветовые коды.)
Ответ или решение
Чтобы предотвратить мигание экрана при использовании команды clear
в вашем Bash-скрипте, следует принять во внимание несколько ключевых аспектов. Основной причиной мигания является обычное полное очищение экрана с последующим выводом нового содержимого. Это создает неприятный визуальный эффект, особенно если обновление происходит часто. Следуя концепции двойной буферизации, можно улучшить визуальное восприятие.
Пошаговое руководство по устранению мигания
-
Буферизация вывода: Соберите весь вывод в переменную до очистки экрана. Это минимизирует время между удалением старых данных и отображением новых. Например:
while :; do output=$(do_a_lot_of_output_here) clear echo "$output" sleep 1 done
Однако данный подход не устраняет мигание полностью, но заметно его снижает.
-
Использование терминальных управляющих последовательностей:
Вместо полного очистки экрана перемещайте курсор к началу и перерисовывайте только те части экрана, которые изменились. Это позволит избежать полного мигания. Пример:while true; do printf '\033[0;0H' do_a_lot_of_output_here sleep 1 done
Используя управляющую последовательность
\033[0;0H
, вы перемещаете курсор к началу экрана без его очистки, перезаписывая данные сверху старых. -
Обработка остаточных данных:
Чтобы остаточные данные не портили экран, можно использовать последовательности для очистки строки до конца (\033[K
) или экрана до конца (\033[J
). Пример:while true; do printf '\033[0;0H' do_a_lot_of_output_here | sed 's/$/\033[K/' printf '\033[J' sleep 1 done
Это гарантирует, что новые значения полностью перекроют все старые данные на экране.
-
Использование терминальных заголовков:
Чтобы улучшить взаимодействие с терминалом и уменьшить мигание, стоит использовать заголовки и управляющие последовательности для временного перехода к альтернативному буферу:function watch_no_flicker() { trap 'tput rmcup; tput cnorm' EXIT tput smcup tput civis while true; do clear do_a_lot_of_output_here sleep 1 done }
Здесь
tput smcup
иtput rmcup
управляют альтернативным буфером терминала, позволяя временно переключаться между буферами и поддерживать аккуратность основного экрана.
Заключение
Подход к уменьшению мигания экрана требует аккуратного использования управляющих последовательностей и минимизации времени между очисткой и заменой старого контента. Используя предложенные техники, можно добиться более гладкого отображения данных без заметного мерцания. Это значительно улучшает пользовательский опыт при выводе часто обновляющейся информации.