3

问题

我正在使用 Bash 5 并且有一个长时间运行的循环,需要偶尔检查用户可能按下的各种键。我知道如何使用stty- 请参阅下面的答案 - 但它比应该的更难看。

本质上,我正在寻找一种干净的方法来做到这一点:

keyhit-p() {
  if "read -n1 would block"; then
    return false
  else
    return true
  fi
}

非解决方案:读取 -t 0

我已阅读bash手册并了解read -t 0. 这不符合我的要求,即检测是否有任何输入可用。相反,它仅在用户按 ENTER(完整的输入行)时才返回 true。

例如:

while true; do
  if read -n1 -t0; then
    echo "This only works if you hit enter"
    break
  fi
done
4

1 回答 1

2

一个有效的答案,虽然很难看

虽然以下有效,但我希望有人有更好的答案。

#!/bin/bash
# Reset terminal's stty to previous values on exit.
trap 'stty $(stty --save)' EXIT

keyhit-p() {
    # Return true if input is available on stdin (any key has been hit).
    local sttysave=$(stty --save)
    stty -icanon min 1 time 0   # ⎫
    read -t0                    # ⎬ Ugly: This ought to be atomic so the
    local status=$?             # ⎪ terminal's stty is always restored.
    stty $sttysave              # ⎭
    return $status
}

while true; do
    echo -n .
    if ! keyhit-p; then
        continue
    else
        while keyhit-p; do
            read -n1
            echo Key: $REPLY
        done
        break
    fi
done

这会在 之前更改用户的终端设置 (stty) 并在read之后尝试将它们写回,但这样做是非原子的。脚本可能会被中断并使用户终端处于不正确的状态。我希望看到一个解决该问题的答案,理想情况下只使用内置于 bash 的工具。

更快、更丑的答案

上述例程中的另一个缺陷是,它需要大量的 CPU 时间才能使一切正确。它需要调用外部程序 ( stty) 三次以检查是否没有发生任何事情。分叉在循环中可能很昂贵。如果我们放弃正确性,我们可以得到一个运行速度快两个数量级(256×)的例程。

#!/bin/bash

# Reset terminal's stty to previous values on exit.
trap 'stty $(stty --save)' EXIT

# Set one character at a time input for the whole script.
stty -icanon min 1 time 0

while true; do
    echo -n .
    # We save time by presuming `read -t0` no longer waits for lines.
    # This may cause problems and can be wrong, for example, with ^Z.
    if ! read -t0; then
        continue
    else
        while read -t0; do
            read -n1
            echo Key: $REPLY
        done
        break
    fi
done

该脚本不是仅在读取测试期间更改为非规范模式,而是在开始时将其设置一次,并在脚本退出时使用异常处理程序来撤消它。

虽然我喜欢代码看起来更干净,但由于未处理 SUSPEND 信号,原始版本的原子性缺陷更加严重。如果用户的 shell 是 bash,则 icanon 在进程挂起时启用,但在进程处于前台时不会禁用。read -t0即使按下键(Enter 除外),这也会返回 FALSE。其他用户 shell 可能无法像 bash 那样在 ^Z 上启用 icanon,但更糟糕的是,输入命令将不再像往常一样工作。

此外,要求始终保持非规范模式可能会导致其他问题,因为脚本变得比这个简单的示例更长。没有记录非规范模式应该如何影响read其他 bash 内置程序。它似乎在我的测试中有效,但它总是会吗?调用外部程序或被外部程序调用时,遇到问题的机会会成倍增加。也许不会有任何问题,但需要进行繁琐的测试。

于 2019-09-02T21:15:06.980 回答