Shell脚本响应按键操作

34

我有一个shell脚本,基本上就像是说:

while true; do
    read -r input
    if ["$input" = "a"]; then 
        echo "hello world"           
    fi
done

这一切都很好,但我意识到在这种情况下必须按ENTER键存在一个严重的问题。我需要的是脚本在按键时响应,而不必按ENTER键。

有没有办法在shell脚本中实现这个功能?


3
如果这是您的真实代码,请注意在if条件中[]周围缺少空格。 - fedorqui
不,这不是我的真实代码。但是谢谢你的建议。 - j0h
1
脚本的应用程序在显示器中。以前的控制面板是使用鼠标和点击作为操作机制制作的。但他们想要添加一个硬币投币装置来进行数据存储。原始计算机已经死了,运行的是Windows 98,而且没有任何硬件或软件可以在新的Windows操作系统上运行。因此,我推断出各个操作,并重新连接了键盘以提供IO功能。但当我回家写剩下的脚本或远程登录时,似乎它不起作用了。显示的受众是儿童。存在随机IO的重大风险。 - j0h
可能是如何在Linux shell脚本中提示输入?的重复问题。 - F. Hauri - Give Up GitHub
特殊键子集:https://dev59.com/IH7aa4cB1Zd3GeqPo1eL - Ciro Santilli OurBigBook.com
6个回答

31
read -rsn1

只期望一个字母(不需等待提交),并保持沉默(不要将该字母写回)。


1
@j0h 你想继续循环吗?因为这会一直阻塞,直到有输入。 - user3442743
2
如果您需要检查输入是否可用,请尝试添加“-t 0.01”以等待几分之一秒,然后如果没有按键等待,则放弃。如果您想要一个非常紧密的循环,较小的数字可能仍然完全可以。非正式实验表明,0.00001有效,但0.000001太小了。 - tripleee
我使用了这个命令来创建一个自更新的状态信息脚本,它可以接受关键输入以更改显示。read -rsn1 -t 5 key 读取一个键到 $key 并退出,否则它会等待 5 秒钟重新渲染状态信息。非常方便,谢谢。 - Gerald
1
@arielnmz 因为 read 是一个 shell 内置命令,所以这个答案依赖于 shell。在 zsh 上使用 -rsk1 - pacholik
我在我的答案这里中详细解释了你的回答。起初,我不知道如何将输入的字符存储到变量中,所以我演示了一下。 - Gabriel Staples
显示剩余3条评论

19

因此最终的工作代码片段如下:

#!/bin/bash

while true; do
read -rsn1 input
if [ "$input" = "a" ]; then
    echo "hello world"
fi
done

8

另一种以非阻塞方式执行的方法(不确定是否符合您的要求)。您可以使用stty将最小读取时间设置为0(如果未使用stty sane,则有一定的危险性)。

stty -icanon time 0 min 0

然后像平常一样运行你的循环,不需要使用-r参数。

while true; do
    read input

    if ["$input" = "a"]; then 
        echo "hello world"           
    fi
done

重要提示!在完成非阻塞操作后,必须记得使用以下命令将stty设置回正常状态:

stty sane

如果你不这样做,终端上什么都看不到,而且会出现卡住的情况。
你可能想要添加一个ctrl-C的陷阱,因为如果在将stty恢复为正常之前退出脚本,你将无法看到任何输入的内容,终端会出现卡死的情况。
trap control_c SIGINT

control_c()
{
    stty sane
}

P.S 还有可能你需要在脚本中加入一个睡眠语句,这样就不会消耗所有的CPU资源,因为它会尽可能快地持续运行。

sleep 0.1

P.S.S 好像只有在我使用了-echo时出现了挂起的问题,所以可能不需要。然而,我仍会将它留在答案中,因为将stty重置为默认值以避免未来问题仍是个好习惯。 如果你不想让你输入的内容显示在屏幕上,可以使用-echo。


2
您可以使用这个getkey函数:
getkey() {
    old_tty_settings=$(stty -g)   # Save old settings.
    stty -icanon
    Keypress=$(head -c1)
    stty "$old_tty_settings"      # Restore old settings.
}

它在终端设置中临时关闭“规范模式”(stty -icanon),然后使用带有-c1选项的“头部”(一个shell内置命令)返回标准输入的一个字节。如果不包括“stty -icanon”,则脚本会回显按下键的字母,然后等待RETURN(这不是我们想要的)。无论是“head”还是“stty”,都是shell内置命令。接收到按键后,保存并恢复旧的终端设置非常重要。
然后可以将getkey()与“case / esac”语句结合使用,用于从条目列表中交互式选择一个键: 示例:
case $Keypress in
   [Rr]*) Command response for "r" key ;;
   [Ww]*) Command response for "w" key ;;
   [Qq]*) Quit or escape command ;;  
esac

这个getkey()/case-esac组合可以用于让许多shell脚本变得交互式。我希望这能有所帮助。


getkey() 的可用性因情况而异。 - Alan Corey

2
如何将单个按键读入变量 c 并将其打印出来。这会立即打印出您按下的键,而无需您先按 Enter
read -n1 c && printf "%s" "$c"

或者,输出打印稍微“美化”一下:
read -n1 c && printf "\nYou Pressed: %s\n" "$c"

后面命令的示例输出:
$ read -n1 c && printf "\nYou Pressed: %s\n" "$c"
M
You Pressed: M
为了防止您的初始按键被回显到屏幕上,请同时添加-s选项,该选项在read --help菜单中说明如下:
-s    do not echo input coming from a terminal
这是最终的命令:
read -sn1 c && printf "You Pressed: %s\n" "$c"

还有一个演示:

$ read -sn1 c && printf "You Pressed: %s\n" "$c"
You Pressed: p
您还可以选择将-sn1参数拆分为两个参数(-s -n1)以提高清晰度。

参考资料:

  1. 我从这里@pacholik了解了read -n1

另请参阅:

  1. 我的eRCaGuy_hello_world存储库中的read_keypress.sh
  2. 我在C和C++系统调用中使用此bash cmd来读取键:
    1. [我的回答] 在不等待按Enter键的情况下捕获标准输入的字符
    2. [我的回答] 在C中读取按键,例如箭头键,Enter键

1
我在我的项目中有一种方法可以做到这一点:https://sourceforge.net/p/playshell/code/ci/master/tree/source/keys.sh 每次调用key_readonce时,它会读取一个单一的密钥。对于特殊的键,还将运行特殊的解析循环以便能够解析它们。
这是其中至关重要的部分:
if read -rn 1 -d '' "${T[@]}" "${S[@]}" K; then
    KEY[0]=$K

    if [[ $K == $'\e' ]]; then
        if [[ BASH_VERSINFO -ge 4 ]]; then
            T=(-t 0.05)
        else
            T=(-t 1)
        fi

        if read -rn 1 -d '' "${T[@]}" "${S[@]}" K; then
            case "$K" in
            \[)
                KEY[1]=$K

                local -i I=2

                while
                    read -rn 1 -d '' "${T[@]}" "${S[@]}" "KEY[$I]" && \
                    [[ ${KEY[I]} != [[:upper:]~] ]]
                do
                    (( ++I ))
                done
                ;;
            O)
                KEY[1]=$K
                read -rn 1 -d '' "${T[@]}" 'KEY[2]'
                ;;
            [[:print:]]|$'\t'|$'\e')
                KEY[1]=$K
                ;;
            *)
                __V1=$K
                ;;
            esac
        fi
    fi

    utils_implode KEY __V0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接