更新历史:
- 答案的第一部分实现了Pausable Countdown(打印很多行)
- 第二部分实现了一个不太冗长的Pausable Timeout(打印一个静态行+按键时的附加消息)
- 第三个代码 sn-p 中有一个更复杂的可暂停倒计时,不断更新同一行。
可暂停倒计时
结合类似问题here的一些提示和一些关于如何读取单个字符(e.g. here, otherwise everywhere on the internet)的外部资源,并添加一个额外的循环用于恢复,这就是我想出的:
#!/bin/bash
# Starts a pausable/resumable countdown.
#
# Starts the countdown that runs for the
# specified number of seconds. The
# countdown can be paused and resumed by pressing the
# spacebar.
#
# The countdown can be sped up by holding down any button
# that is no the space bar.
#
# Expects the number of seconds as single
# argument.
#
# @param $1 number of seconds for the countdown
function resumableCountdown() {
local totalSeconds=$1
while (( $totalSeconds > 0 ))
do
IFS= read -n1 -t 1 -p "Countdown $totalSeconds seconds (press <Space> to pause)" userKey
echo ""
if [ "$userKey" == " " ]
then
userKey=not_space
while [ "$userKey" != " " ]
do
IFS= read -n1 -p "Paused, $totalSeconds seconds left (press <Space> to resume)" userKey
echo ""
done
elif [ -n "$userKey" ]
then
echo "You pressed '$userKey', press <Space> to pause!"
fi
totalSeconds=$((totalSeconds - 1))
done
}
# little test
resumableCountdown 60
这可以作为独立脚本保存和运行。该功能可以在其他地方重用。它使用SPACE 暂停/恢复,因为这对我来说似乎更直观,因为它就是这样工作的,例如在嵌入浏览器的视频播放器中。
还可以通过按空格键以外的键来加快倒计时(这是一项功能)。
发出警告消息并等待可暂停的超时
以下变体实现了一个可暂停超时,它只打印初始警告消息,除非用户通过按空格键暂停或恢复(内部)倒计时:
# Prints a warning and then waits for a
# timeout. The timeout is pausable.
#
# If the user presses the spacebar, the
# internal countdown for the timeout is
# paused. It can be resumed by pressing
# spacebar once again.
#
# @param $1 timeout in seconds
# @param $2 warning message
warningWithPausableTimeout() {
local remainingSeconds="$1"
local warningMessage="$2"
echo -n "$warningMessage $remainingSeconds seconds (Press <SPACE> to pause)"
while (( "$remainingSeconds" > 0 ))
do
readStartSeconds="$SECONDS"
pressedKey=""
IFS= read -n1 -t "$remainingSeconds" pressedKey
nowSeconds="$SECONDS"
readSeconds=$(( nowSeconds - readStartSeconds ))
remainingSeconds=$(( remainingSeconds - readSeconds ))
if [ "$pressedKey" == " " ]
then
echo ""
echo -n "Paused ($remainingSeconds seconds remaining, press <SPACE> to resume)"
pressedKey=""
while [ "$pressedKey" != " " ]
do
IFS= read -n1 pressedKey
done
echo ""
echo "Resumed"
fi
done
echo ""
}
warningWithPausableTimeout 10 "Program will end in"
echo "end."
更新同一行的可暂停倒计时
这是一个倒计时,类似于第一个,但它只需要一行。依靠echo -e 擦除和覆盖以前打印的消息。
# A pausable countdown that repeatedly updates the same line.
#
# Repeatedly prints the message, the remaining time, and the state of
# the countdown, overriding the previously printed messages.
#
# @param $1 number of seconds for the countdown
# @param $2 message
singleLinePausableCountdown() {
local remainingSeconds="$1"
local message="$2"
local state="run"
local stateMessage=""
local pressedKey=""
while (( $remainingSeconds > 0 ))
do
if [ "$state" == "run" ]
then
stateMessage="[$remainingSeconds sec] Running, press <SPACE> to pause"
else
stateMessage="[$remainingSeconds sec] Paused, press <SPACE> to continue"
fi
echo -n "$message $stateMessage"
pressedKey=""
if [ "$state" == "run" ]
then
IFS= read -n1 -t 1 pressedKey
if [ "$pressedKey" == " " ]
then
state="pause"
else
remainingSeconds=$(( remainingSeconds - 1 ))
fi
else
IFS= read -n1 pressedKey
if [ "$pressedKey" == " " ]
then
state="run"
fi
fi
echo -ne "\033[1K\r"
done
echo "$message [Done]"
}
如果线条长于控制台宽度(它不会完全擦除线条),这可能会表现得很奇怪。
未分类的提示集合,供任何尝试做某事的人使用。类似的:
-
IFS= read -n1 读取单个字符
-
read -t <seconds> 为 read 设置超时。一旦超时到期,read 会以非零值退出,并将变量设置为空。
- Magic bash 内置变量
$SECONDS 以秒为单位测量从脚本开始的时间。
- 如果一行已经用
echo -n 打印,那么它可以用echo -ne "\033[1K\r" 擦除和重置。