要编写对按键做出反应的脚本,我必须考虑什么?

要编写对按键做出反应的脚本,我必须考虑什么?

我知道我可以在脚本中将某些内容读入变量,如下所示:variable = read 但我必须按 Enter 键才能将值提交到变量中。为了在不按 Enter 键的情况下将按键的值提交到变量中,或者如果不提交到变量中只是为了在按下某个键时做出反应,我必须知道什么?

答案1

使用bash,您可以使用-n内置read函数的参数来限制读取的字符数,而不需要换行符:

#!/bin/bash

echo "Ready? [Y/n]: "
read -n 1 y_or_n
echo

case "$y_or_n" in
    [Yy]|"")
        echo "you said yes"
        ;;
    *)
        echo "you said no"
        ;;
esac

bash无论是作为sh或调用,这都有效bash

有关更多详细信息,请参阅help read或联机帮助页。bash

请注意,其他 shell 可能不支持-n的参数readdash,例如,没有。

答案2

关于 shell 的问题read是 - 无论你是否限制它的总字节数read- 它仍然不会给你一个按键read.只会得到一些固定的数量人物read.为了得到一个按键根据read您需要阻止您的输入 - 最好的方法可能是dd。这是我几个月前编写的一个 shell 函数,当时我正在尝试dd在终端 I/O 上进行阻塞:

printkeys()(    IFS= n=1
    set -f -- "$(tty <&2)"
    exec    <${1##[!/]*};set \\t
    _dd()(  c=conv=sync,unblock i=ibs=72 b=bs=8
            dd bs=7 ${c%,*} |   dd $i o$b c$b $c
    )       2>/dev/null
    trap "  trap '' TTOU TTIN
            stty '$(stty -g
            stty raw isig inlcr)'   "   INT EXIT
    _dd |while  ${2:+:} read -r c   ||  exit 2
            do  case $#:$c in (1:): ;; 
        (*:?*)  set ":%d$@"         \
                    \'${c%"${c#?}"} ;
                c=${c#?}            ;; 
        (*)     printf "$@"         ;
        [ 0 -eq $((n=n<4?n+1:0)) ]  &&
                set '\n\r'||set \\t ;;  esac
        done
)

您会看到,在上面的情况下,终端输入dd在管道中的两个进程上进行过滤。终端设置stty生的a 中的模式trap还可以在发生错误时恢复最终状态INTEXIT恢复到调用函数时的任何状态(可能可以使用 CTRL+C 或任何中断键来完成)。在生的模式下,终端在输入到达时立即将其刷新到任何读取器 - 逐个按键。它不只是一次推送一个字节;而是一次推送一个字节。它会尽快推送整个缓冲区。例如,当您按下向上箭头时,键盘会发送一个转义序列,例如:

^[[A

...这是三个字节,并且它一次将其全部推送。

dd指定为一旦提供即可满足任何读取 - 无论您将其设置多高ibs=。这意味着,尽管我使用 进行设置ibs=7,但当终端仅推送三个字节时,读取仍然完成。现在,大多数其他实用程序都很难处理这一点,但dd'sconv=sync填补了字节之间的差异\0NUL。因此,当您按键盘上的向上箭头时,dd会读取三个字节并将 7 写入下一个字节dd- 转义序列中的三个字节和另外 4 个\0NUL字节。

但是为了将这些数据与 shell 的读取一起拉入,您必须再次阻止它,因此下一个将dd其输入缓冲区同步到 72 字节 - 但它也conv=unblock如此。通过unblock转换,dd将其输入拆分为\newline 分隔符以获取其cbs=计数 - 这里是 8 syncunblock (或者block转换,dd不会在\0NULs 上同步,而是在尾随空格上同步。因此,对于每 7 个字节,第一个dd写入它们之间的管道,第二个dd缓冲 72 个字节 - 前几个字节是按键的内容,然后是\0NULs,然后是每次读取末尾的 65 个空格。

另一件事是删除尾随空格 - 它将吃掉每个转换块unblock末尾可能出现的尽可能多的空格。cbs=因此,由于每次dd写入以字节为单位写入输出obs=8,因此每次读取会写入 9 行,总共 2 次写入输出管道。第一次写入是第一行,由从输入管道读取的 7 个字节和一个尾随换行符(又一个字节)组成。下一次写入是 8 个换行符 - 一次连续写入 - 多 8 个字节 - 因为dd这 8 个转换块中的每一个都占用了所有 8 个空间。

dd在这两个sa shell的另一侧while逐行read循环。它可以忽略空行 - 因为终端无论如何都会根据选项将所有换行符转换为输出上的回车符inlcr stty。但是,当它在$cshell 的输入进入其值后检测到哪怕是一个字节时read,那么您就有了一次按键 - 并且也有一个完整的按键,只要您的键盘每次击键发送的字节数不超过 7 个字节(尽管那只需要不同的阻塞因子)

当 shell 中有一个按键时,$c它会逐个字节地对其进行迭代,并将其拆分为按'字符划分的数组,然后一次性printf获取每个字节的十进制值。$c如果运行该函数,您应该得到如下输出:

a:97    b:98    c:99    d:100   e:101
f:102   ;:59    ^M:13   ^M:13   ^M:13
s:115   a:97    d:100   f:102    :32
':39    ':39    ':39    a:97    s:115
d:100   f:102   ;:59    ':39    ^[[A:27:91:65
^[[D:27:91:68   ^[[B:27:91:66   ^[[C:27:91:67   ^[[D:27:91:68   ^[[C:27:91:67
^[[A:27:91:65   ^[[D:27:91:68   ^[[C:27:91:67   ^[[B:27:91:66   ^[[D:27:91:68
^[[C:27:91:67   ^[[A:27:91:65   ^[[D:27:91:68   ^[[C:27:91:67   ^[[B:27:91:66

因为一旦 shell 用其输入填充变量的值,插入以阻止按键的\0NULs就会消失 - 您不能将s 放入 shell 变量中dd\0NUL(在任何 shell 中,但是zsh- 在这种情况下它仍然可以这样配置)。据我所知,这应该适用于任何 shell - 并且它确实适用于bashdashksh93。它甚至可以可靠地处理多字节输入 - 但我不会发誓。

在上面的演示输出中,每次写入时,函数的实际输出之前都有其未写入的其他信息。:在上述任何序列中出现的每个第一个之前显示的每个字符实际上是终端的echo,可以使用stty echo或进行配置-echo。其余的内容是作为函数的输出打印的 - 正如您所看到的,它是在键入后立即打印的。

相关内容