一个有效的答案,虽然很难看
虽然以下有效,但我希望有人有更好的答案。
#!/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 内置程序。它似乎在我的测试中有效,但它总是会吗?调用外部程序或被外部程序调用时,遇到问题的机会会成倍增加。也许不会有任何问题,但需要进行繁琐的测试。