按任意键停止循环

按任意键停止循环

下面的事情该怎么办呢?

  • 我有一个循环脚本。
  • 我想当我按下一个键时停止它,否则它会在 5 秒后继续。

答案1

需要记住的是,shell 或通常在终端中运行的任何应用程序不会与键盘和屏幕交互。

它们从标准输入中获取输入作为字节流。当该标准输入来自常规文件时,字节就来自那里,当它是管道时,数据通常是由另一个进程发送的,当它是可以到达连接到计算机的物理设备的某个设备文件时。例如,当它是 tty 字符设备时,它是通常由终端通过某些串行线路发送的数据。终端是某种形式的设备,它将键盘事件转换为字节序列。

这就是终端应用程序的全部功能所在。输入机制已为它们抽象出来,因此它们可以在脚本中交互或自动使用。

在这里,如果你要发出这种提示,并期待钥匙在新闻发布会上,您可能希望您的应用程序(您的脚本)只是一个交互式应用程序。要么期望 stdin 成为终端,要么从终端获取输入,而不管 stdin 在哪个上打开。

现在,如上所示,所有应用程序看到的都是字节流,这就是终端(或终端仿真器)和 tty 设备线路规则的作用,将按下的按键转换为字节序列。举几个例子:

  • 当您按下该a键时,ASCII 终端发送一个 0x61 字节,
  • 当您按下该£键时,UTF-8 终端会发送两个字节 0xc2 和 0xa3。
  • 当您按下该Enter键时,ASCII 终端会发送一个 0x0d 字节,在基于 ASCII 的系统(如 Linux)上,tty 线路规则通常将其转换为 0x0a
  • 当您Ctrl单独按下时,终端不会发送任何内容,但如果您用 按下它C,终端会发送一个 0x03 字节,tty 线路纪律拦截该字节以向前台任务发送 SIGINT 信号
  • 当您按 时Left,终端通常会发送一系列字节(因终端而异,应用程序可以查询 terminfo 数据库来翻译它),其中第一个字节是 0x1b。例如,根据其所处的模式xterm,在基于 ASCII 的系统上,将发送 0x1b 0x4f 0x44 或 0x1b 0x5b 0x44(<ESC>[A<ESC>OA)。

所以在这里,我要问的问题是:

  1. 如果 stdin 不是终端,您是否仍要提示用户
  2. 如果 1 的答案是肯定的,那么您想在终端上提示用户,还是通过 stdin/stdout 提示用户?
  3. 如果 1 的答案是否定的,您是否仍要在每次迭代之间等待 5 秒?
  4. 如果2的答案是通过终端,如果脚本无法检测到控制终端或回退到非终端模式,它是否应该中止?
  5. 您是否只想考虑发出提示后按下的按键。 IOW,如果用户在发出提示之前不小心键入了某个键。
  6. 您愿意花多长时间来确保只读取单次按键发出的字节?

在这里,我假设您希望您的脚本仅成为终端交互式应用程序,并且仅通过控制终端进行交互,而仅保留 stdin/stdout 。

#! /bin/sh -

# ":" being a special builtin, POSIX requires it to exit if a
# redirection fails, which makes this a way to easily check if a
# controlling terminal is present and readable:
:</dev/tty

# if using bash however not in POSIX conformance mode, you'll need to
# change it to something like:
exec 3< /dev/tty 3<&- || exit

read_key_with_timeout() (
  timeout=$1 prompt=$2
  saved_tty_settings=$(stty -g) || exit

  # if we're killed, restore the tty settings, the convoluted part about
  # killing the subshell process is to work around a problem in shells
  # like bash that ignore a SIGINT if the current command being run handles
  # it.
  for sig in INT TERM QUIT; do
    trap '
      stty "$saved_tty_settings"
      trap - '"$sig"'
      pid=$(exec sh -c '\''echo "$PPID"'\'')
      kill -s '"$sig"' "$pid"

      # fall back if kill failed above
      exit 2' "$sig"
  done

  # drain the tty's buffer
  stty -icanon min 0 time 0; cat > /dev/null

  printf '%s\n' "$prompt"

  # use the tty line discipline features to say the next read()
  # should wait at most the given number of deciseconds (limited to 255)
  stty time "$((timeout * 10))" -echo

  # do one read and count the bytes returned
  count=$(dd 2> /dev/null count=1 | wc -c)

  # If the user pressed a key like the £ or Home ones described above
  # it's likely all the corresponding bytes will have been read by dd
  # above, but not guaranteed, so we may want to drain the tty buffer
  # again to make sure we don't leave part of the sequence sent by a
  # key press to be read by the next thing that reads from the tty device
  # thereafter. Here allowing the terminal to send bytes as slow as 10
  # per second. Doing so however, we may end up reading the bytes sent
  # upon subsequent key presses though.
  stty time 1; cat > /dev/null

  stty "$saved_tty_settings"

  # return whether at least one byte was read:
  [ "$(($count))" -gt 0 ]

) <> /dev/tty >&0 2>&0

until
  echo "Hello World"
  sleep 1
  echo "Done greeting the world"
  read_key_with_timeout 5 "Press any key to stop"
do
  continue
done

答案2

while true; do
    echo 'Looping, press Ctrl+C to exit'
    sleep 5
done

没有必要让它变得更复杂。

要求如下bash

while true; do
    echo 'Press any key to exit, or wait 5 seconds'
    if read -r -N 1 -t 5; then
        break
    fi
done

如果read失败(由于超时),循环将继续。

相关内容