有没有办法让我的脚本等待用户按下任意键,然后继续执行而无需用户按下任何键Enter?我想让它在 Bourne shell ( sh
) 中工作,而不是在 Bash 中工作。
答案1
初步说明
如今sh
不一定是传统的 Bourne shell. 现在sh
是一个支持至少POSIX 所需的特性(假设正确实现)。POSIX 指定sh
公用事业和Shell 命令语言。
我假设您希望您的代码能够在 POSIX shell 中运行并且具有可移植性。
问题
dd ibs=1 count=…
是一种读取精确字节数的 POSIX 方式。它似乎是唯一可以可靠地完成这项工作的便携式命令行实用程序。但dd
读取字节,而read -n
在 Bash 中读取人物。POSIX 允许在除以下语言环境中使用多字节字符:POSIX
。即使你用 运行整个脚本LC_ALL=POSIX
,终端(终端仿真器)仍可能在某个按键时生成多字节序列。这样的序列可能不是多字节字符;它可能是转义序列(例如F1,另见这个答案)。
在您读取一个字节之后,其余部分将保留,并且它将被稍后尝试从终端读取的任何内容读取(它可能是脚本的一部分或运行脚本的交互式 shell 的一部分)。
此外,如果你单独跑步dd ibs=1 count=1
,那么你很可能会发现,dd
除非你超过{MAX_INPUT}
或者{MAX_CANON}
,或按Enter或Ctrl+D(如果count
超过1
,如果您想提供更少的字节,那么您可能需要按Ctrl+D 两次)。
为了解决这些问题,你需要非规范模式。 使用stty -icanon
。总体来说,从stty raw
看起来是个好主意。
代码
以下示例是概念验证。因为它操纵终端的线路设置并从终端读取你一定不将其粘贴到交互式 shell 中,这不起作用. 将其粘贴到文件中并运行该文件。
#!/bin/sh
echo "Press any key to continue."
saveterm="$(stty -g)" # save terminal state
stty raw
stty -echo -icanon min 1 time 0 # prepare to read one byte
dd ibs=1 count=1 >/dev/null 2>/dev/null # read one byte
stty -icanon min 0 time 0 # prepare to read lefotvers
while read none; do :; done # read leftovers
stty "$saveterm" # restore terminal state
笔记
该脚本不会检查其 stdin 是否是终端,但一般来说它应该(
[ -t 0 ]
)。在合理的设置中,修饰键(例如Shift)单独按下时,不会向终端读取的任何内容发送任何输入;因此我们的代码不会将此类键注册为“任何键”。
“阅读剩余内容”技巧来自这个答案。
由于
stty raw
Ctrl+C或Ctrl+Z也可以是“任意键”,而不是分别发送SIGINT
或SIGSTOP
。但是,解释脚本的 shell 可能会从其他地方收到信号,因此一般情况下,它可能会在到达 之前退出stty "$saveterm"
;因此,它可能会使终端处于不适合交互使用的状态。无论如何,您可能希望捕获相关信号并恢复终端的初始状态。这是真的应该用双引号引起变量一般来说。这里$saveterm
故意不加引号,因为stty -g
生成的输出未指定。的实现stty
允许生成带空格的输出,然后希望 shell 将其拆分并传递多个参数。指定的是一条约束,即stty -g
不加引号时的输出必须是安全的,它不能在 shell 中触发字扩展。为了便于移植,不加引号$saveterm
更好。编辑:这是 POSIX 规范的一个缺陷。以上代码已修复。
要知道读取了哪个字节,您需要将 的输出保存
dd
到稍后将检查的常规文件 (>some_file
) 或变量 (variable="$(dd …)"
)。但是:- 终端能够生成空字节(通常使用Ctrl+ @),但大多数(所有?)实现都
sh
无法将空字节存储在变量中。存储在文件中是可以的,只要您无需将其读入变量即可检查/操作其内容。 dd
从上面的代码中只能得到一个字节,可能不足以区分字符或键。另一方面,单个字节应该足以区分其他Q任何东西,因此Press Q to quit or any other key to continue.
似乎P - proceed; B - back; Q - quit; H - help
可以在不分析更多字节的情况下实现。
- 终端能够生成空字节(通常使用Ctrl+ @),但大多数(所有?)实现都
那么更多的字节呢?
上面的代码对于“按任意键”来说应该没问题。 的真正等价版本read -n
应该一次读取一个字节并解码序列,直到获得所需的人物。我不会尝试在这里建造它。