Bash:读取输入可用(是否按下任何键?)

标签 bash unix nonblocking stty

问题

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

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

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

非解:read -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

最佳答案

一个有效的答案,虽然丑陋

虽然以下工作正常,但我希望有人能提供更好的答案。

#!/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) 三次来检查是否没有发生任何事情。循环中的 fork 可能很昂贵。如果我们放弃正确性,我们可以获得一个运行速度快两个数量级 (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 内置函数。它似乎在我的测试中有效,但它会一直有效吗?在调用外部程序或被外部程序调用时,遇到问题的可能性会成倍增加。也许不会有任何问题,但需要进行繁琐的测试。

关于Bash:读取输入可用(是否按下任何键?),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57760967/

相关文章:

c - 阻塞代码的替代方法

linux - 菜单的 Shell 脚本

bash - 如何快速切换docker环境进行开发?

linux - 提示grep只显示匹配结果

linux - sed -e 's/^M/d' 不工作

bash - 想要在 unix 中执行 tail 命令后退出

arrays - 用于简单正弦波发生器的Bash脚本阵列算法的问题

linux - 用 n 步旋转文本文件中的行

当 SSL_read() 返回 WANT_READ 时调用 SSL_write()

javascript - 如何从许多内部非阻塞函数调用之一中提前退出外部函数?