Bash - Правильная очистка последнего вывода - PullRequest
5 голосов
/ 27 мая 2020

Я пытаюсь создать статус прогресса с возможностью обновления. Для этого мне нужно полностью очистить последний вывод, чтобы я мог его обновить. Возврат каретки может работать, но когда вывод длиннее ширины терминала и оборачивается, он не сможет очистить последний вывод. Итак, я использую tput:

n=0
while [[ $n -ne 100 ]]; do
    n=$((n+1))
    tput ed #clear
    tput sc #save cursor
    echo -n "Progress: ${n}%"
    tput rc #restore cursor
    sleep 1s
done
echo

Но это не сработает, если вывод будет достаточно длинным, чтобы терминал начал прокручиваться вверх. Когда это произойдет, сохраненная позиция курсора больше не будет правильной, и он не сможет правильно очистить последний вывод.

Например, если курсор в настоящее время находится внизу терминала, а вывод длиннее, чем ширина терминала, это заставит терминал прокручиваться вверх, аннулируя ранее сохраненную позицию курсора.

Итак, есть ли какие-либо способы гарантировать, что курсор никогда не окажется в конце терминала в Bash? Или, может быть, какие-то другие альтернативные методы предотвращения этой проблемы?

РЕДАКТИРОВАТЬ: Я создал свою собственную версию на основе ответа Ф. Хаури, упрощенного для моего варианта использования

#!/bin/bash
str=$(head -c 338 < /dev/zero | tr '\0' '\141')
len="${#str}"
col=$(tput cols)
lines=$(( ((len + col - 1) / col) - 1 ))

echo -ne "${str}\r"
(( len > col )) && tput cuu "$lines"

sleep 3s

tput ed

Ответы [ 2 ]

2 голосов
/ 27 мая 2020

Что-то хитрое

Вдохновлено Как получить позицию курсора в bash?

#!/bin/bash

lineformat="This is a very long line with a lot of stuff so they will take " 
lineformat+="more than standard terminal width (80) columns... Progress %3d%%" 

n=0
while [[ $n -ne 100 ]]; do
    n=$((n+1))
    printf -v outputstring "$lineformat" $n
    twidth=$(tput cols)      # Get terminal width
    theight=$(tput lines)    # Get terminal height
    oldstty=$(stty -g)       # Save stty settings
    stty raw -echo min 0     # Suppres echo on terminal
    # echo -en "\E[6n"
    tput u7                  # Inquire for cursor position
    read -sdR CURPOS         # Read cursor position
    stty $oldstty            # Restore stty settings
    IFS=\; read cv ch <<<"${CURPOS#$'\e['}" # split $CURPOS
    uplines=$(((${#outputstring}/twidth)+cv-theight))
    ((uplines>0)) &&
        tput cuu $uplines    # cursor up one or more lines
    tput ed                  # clear to end of screen
    tput sc                  # save cursor position
    echo -n "$outputstring"
    tput rc                  # restore cursor
    sleep .0331s
done
echo

Поскольку tput cols и tput lines инициируются в каждый l oop, вы можете изменять размер окна во время работы, cuu аргумент будет повторно вычислен.

Более сложный образец

  • Использование trap WINCH только для запроса размера терминала при изменении размера окна
  • Добавить newlines для прокрутки вверх перед cuu
  • Уменьшение вилок до tput

Там:

#!/bin/bash

lineformat="This is a very long line with a lot of stuff so they will take " 
lineformat+="more than standard terminal width (80) columns... Progress %3d%%" 

getWinSize() {
    {
        read twidth
        read theight
    } < <(
        tput -S - <<<$'cols\nlines'
    )
}
trap getWinSize WINCH
getWinSize

getCpos=$(tput u7)
getCurPos() {
    stty raw -echo min 0
    echo -en "$getCpos"
    read -sdR CURPOS
    stty $oldstty
    IFS=\; read curv curh <<<"${CURPOS#$'\e['}"
}
oldstty=$(stty -g)

before=$(tput -S - <<<$'ed\nsc')
after=$(tput rc)
n=0
while [[ $n -ne 100 ]]; do
    n=$((n+1))
    printf -v outputstring "$lineformat" $n
    getCurPos
    uplines=$(((${#outputstring}/twidth)+curv-theight))
    if ((uplines>0)) ;then
        printf -v movedown "%${uplines}s" ''
        echo -en "${movedown// /\\n}"
        tput cuu $uplines
    fi
    printf "%s%s%s" "$before" "$outputstring" "$after"
    sleep .05
done

downlines=$((${#outputstring}/twidth))
printf -v movedown "%${downlines}s" ''
echo "${movedown// /$'\n'}"
0 голосов
/ 27 мая 2020

Да, но это непросто.

Ваш первый и лучший вариант - escape-последовательности ANSI , и вы можете управлять курсором с помощью этих кодов.

Для совместимости вашего скрипта со всеми остальными терминалами вы должны вычислить ширину / высоту этого терминала.
Здесь, используя Xwininfow, вы можете проверить свой

wininfo | egrep -e Wid+ -e H+ -e A+

и вывод для меня будет:

  Absolute upper-left X:  0
  Absolute upper-left Y:  45
  Width: 1600
  Height: 855

Затем в вашем скрипте на основе ширины / высоты вы должны обновить положение курсора или очистить неиспользуемый текст в Терминале.

Как user1934428 прокомментировал, что у нас есть лучший вариант, включив две глобальные переменные, используя shopt (= параметры оболочки)

>>> shopt | grep win
checkwinsize    on

, он включен, поэтому мы можем их использовать

>> echo $LINES
56
>> echo $COLUMNS
228

ОБРАТИТЕ ВНИМАНИЕ, что такую ​​задачу с помощью python сделать намного проще, чем с помощью bash

...