shell Bash脚本:取消倒计时并按ENTER(或任何)恢复它

x8goxv8g  于 12个月前  发布在  Shell
关注(0)|答案(1)|浏览(132)

我有一个运行bash脚本的任务调度器。该任务首先打开一个GIT Bash终端,显示一条打开消息(“脚本将在60秒后开始”),并在倒计时结束时运行一个脚本。
现在,我想改善用户体验,允许他/她停止/恢复倒计时或(没有任何干预)离开脚本自动运行。这就是程序流程:
1.在GIT Bash终端打开后,允许用户通过按ENTER或任何其他键在所示的时间范围内暂停脚本;
1.如果用户没有采取任何行动,倒计时将继续,脚本将在时间框架结束时运行;
1.如果用户已经按下ENTER(或任何其他键),则通过再次按下ENTER(或任何其他键),他/她恢复倒计时,并且将立即运行。
我试过使用read -p,但它对我不好:我不希望用户操作触发什么,而是停止/暂停倒计时(然后恢复)。

rkkpypqq

rkkpypqq1#

更新历史记录:

  • 答案的第一部分实现了可暂停的倒计时(打印很多行)
  • 在第二部分中实现了一个不那么冗长的Pausable命令行(在按键时打印一个静态行+额外的消息)
  • 第三个代码片段中有一个更复杂的可暂停的倒计时,它会不断更新同一行
    可暂停倒计时

结合一些类似问题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暂停/恢复,因为这对我来说似乎更直观,因为它是如何工作的。嵌入在浏览器中的视频播放器。
倒计时也可以通过按空格键以外的键来加速(这是一个功能)。

发出警告消息并等待可暂停超时

以下变体实现了一个 pausable timeout,它只打印初始警告消息,除非用户通过按空格键暂停或恢复(内部)倒计时:

# 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"擦除和重置。

相关问题