Предотвратить мигание текста/экрана при очистке.

Вопрос или проблема

Мой скрипт делает что-то подобное:

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-скрипте, следует принять во внимание несколько ключевых аспектов. Основной причиной мигания является обычное полное очищение экрана с последующим выводом нового содержимого. Это создает неприятный визуальный эффект, особенно если обновление происходит часто. Следуя концепции двойной буферизации, можно улучшить визуальное восприятие.

Пошаговое руководство по устранению мигания

  1. Буферизация вывода: Соберите весь вывод в переменную до очистки экрана. Это минимизирует время между удалением старых данных и отображением новых. Например:

    while :; do
      output=$(do_a_lot_of_output_here)
      clear
      echo "$output"
      sleep 1
    done

    Однако данный подход не устраняет мигание полностью, но заметно его снижает.

  2. Использование терминальных управляющих последовательностей:
    Вместо полного очистки экрана перемещайте курсор к началу и перерисовывайте только те части экрана, которые изменились. Это позволит избежать полного мигания. Пример:

    while true; do
       printf '\033[0;0H'
       do_a_lot_of_output_here
       sleep 1
    done

    Используя управляющую последовательность \033[0;0H, вы перемещаете курсор к началу экрана без его очистки, перезаписывая данные сверху старых.

  3. Обработка остаточных данных:
    Чтобы остаточные данные не портили экран, можно использовать последовательности для очистки строки до конца (\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

    Это гарантирует, что новые значения полностью перекроют все старые данные на экране.

  4. Использование терминальных заголовков:
    Чтобы улучшить взаимодействие с терминалом и уменьшить мигание, стоит использовать заголовки и управляющие последовательности для временного перехода к альтернативному буферу:

    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 управляют альтернативным буфером терминала, позволяя временно переключаться между буферами и поддерживать аккуратность основного экрана.

Заключение

Подход к уменьшению мигания экрана требует аккуратного использования управляющих последовательностей и минимизации времени между очисткой и заменой старого контента. Используя предложенные техники, можно добиться более гладкого отображения данных без заметного мерцания. Это значительно улучшает пользовательский опыт при выводе часто обновляющейся информации.

Оцените материал
Добавить комментарий

Капча загружается...