Bash - 正确清除最后一次输出
Bash - Clearing the last output correctly
我正在尝试创建可更新的进度状态。为了做到这一点,我需要能够完全清除最后的输出,以便我可以更新它。 Carriage returns 可以工作,但是当输出长于终端宽度并环绕时,将无法清除最后一个输出。
所以我正在使用 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中有什么方法可以保证光标永远不会结束终端呢?或者可能有其他替代方法来防止这个问题?
编辑:我根据 F. Hauri 的 制作了自己的版本,针对我的用例进行了简化
#!/bin/bash
str=$(head -c 338 < /dev/zero | tr '[=12=]' '1')
len="${#str}"
col=$(tput cols)
lines=$(( ((len + col - 1) / col) - 1 ))
echo -ne "${str}\r"
(( len > col )) && tput cuu "$lines"
sleep 3s
tput ed
是的,但这并不容易。
你的第一个也是最好的选择是ANSI Escape sequences,你可以通过这个代码控制光标。
为了使您的脚本与其他人的终端兼容,您应该计算该终端的宽度/高度。
在这里使用 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
(= shell 选项)启用两个全局变量
>>> shopt | grep win
checkwinsize on
它已打开,所以我们可以使用它们
>> echo $LINES
56
>> echo $COLUMNS
228
注意 python 比使用 bash
更容易完成这样的任务
有点棘手
灵感来自 How to get the cursor position in 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
在每个循环中启动,您可以调整 window 的大小,同时 运行、cuu
参数将被重新计算。
更复杂的样本
- 仅在 window 调整大小时使用
trap WINCH
查询终端大小
- 添加
newlines
用于在 cuu
之前向上滚动
- 减少 forks 到
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'}"
我正在尝试创建可更新的进度状态。为了做到这一点,我需要能够完全清除最后的输出,以便我可以更新它。 Carriage returns 可以工作,但是当输出长于终端宽度并环绕时,将无法清除最后一个输出。 所以我正在使用 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中有什么方法可以保证光标永远不会结束终端呢?或者可能有其他替代方法来防止这个问题?
编辑:我根据 F. Hauri 的
#!/bin/bash
str=$(head -c 338 < /dev/zero | tr '[=12=]' '1')
len="${#str}"
col=$(tput cols)
lines=$(( ((len + col - 1) / col) - 1 ))
echo -ne "${str}\r"
(( len > col )) && tput cuu "$lines"
sleep 3s
tput ed
是的,但这并不容易。
你的第一个也是最好的选择是ANSI Escape sequences,你可以通过这个代码控制光标。
为了使您的脚本与其他人的终端兼容,您应该计算该终端的宽度/高度。
在这里使用 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
(= shell 选项)启用两个全局变量
>>> shopt | grep win
checkwinsize on
它已打开,所以我们可以使用它们
>> echo $LINES
56
>> echo $COLUMNS
228
注意 python 比使用 bash
有点棘手
灵感来自 How to get the cursor position in 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
在每个循环中启动,您可以调整 window 的大小,同时 运行、cuu
参数将被重新计算。
更复杂的样本
- 仅在 window 调整大小时使用
trap WINCH
查询终端大小 - 添加
newlines
用于在cuu
之前向上滚动
- 减少 forks 到
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'}"