【问题标题】:Bash: read input available (was any key hit?)Bash:读取输入可用(是否有任何按键?)
【发布时间】:2019-09-02 17:45:07
【问题描述】:

问题

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

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

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

非解:读取 -t 0

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

例如:

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

【问题讨论】:

  • 虽然这很有意义,但请注意,添加 -n1 并不能帮助通知 bash 我计划一次读取一个字符而不是逐行读取。也许这应该是一个 bash 功能请求?
  • 什么意思
  • “丑陋”就像“将 stty 状态保存在一个变量中,然后希望在用户点击 ^C 之前恢复它,从而让他们的终端陷入困境”。它应该是一个原子动作。我知道我可以trap EXIT 'stty sane',但这对用户终端的假设并不总是有效。

标签: bash unix nonblocking stty


【解决方案1】:

一个可行的答案,虽然很难看

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

#!/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

这会在read 之前更改用户的终端设置(stty),并在之后尝试将它们写回,但这样做是非原子的。脚本可能会被中断并使用户终端处于不正确的状态。我希望看到一个解决该问题的答案,理想情况下只使用内置于 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 内置程序。它似乎在我的测试中有效,但它总是会吗?调用外部程序或被外部程序调用时,遇到问题的机会会成倍增加。也许不会有任何问题,但需要进行繁琐的测试。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-05-10
    • 2016-11-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-01
    • 2013-07-13
    相关资源
    最近更新 更多