关于bash:shell脚本响应按键

shell script respond to keypress

我有一个shell脚本,基本上说

1
2
3
4
5
6
while true; do
    read -r input
    if ["$input" ="a"]; then
        echo"hello world"          
    fi
done

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

在shell脚本中是否有实现此功能的方法?


1
read -rsn1

希望只有一封信(不要等待提交),保持沉默(不要写回那封信)。


所以最后的工作片段是:

1
2
3
4
5
6
7
8
#!/bin/bash

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


另一种方法,以非阻塞方式(不确定您是否需要它)。您可以使用stty将最小读取时间设置为0。(如果在之后不使用stty sane,则有点危险)

1
stty -icanon time 0 min 0

然后像正常一样运行循环。不需要-R。

1
2
3
4
5
6
7
while true; do
    read input

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

重要!完成非阻塞后,必须记住将stty设置回正常使用状态。

1
stty sane

如果你不这样做,你将看不到终端上的任何东西,它将似乎挂起。

您可能希望为ctrl-c包含一个陷阱,就好像在您将stty恢复正常之前脚本已经退出,您将无法看到您键入的任何内容,并且它将显示终端已经冻结。

1
2
3
4
5
6
trap control_c SIGINT

control_c()
{
    stty sane
}

另外,你可能想在你的脚本中加入一个睡眠语句,这样你就不会耗尽你所有的CPU,因为这将继续以尽可能快的速度运行。

1
sleep 0.1

P.S.S.看来,挂起的问题只有在我使用过的时候才出现——可能不需要像以前那样使用echo。我将把它留在答案中,因为将stty重置为默认值以避免将来的问题仍然是好的。如果您不希望您键入的内容出现在屏幕上,可以使用-echo。


您可以使用此getkey功能:

1
2
3
4
5
6
getkey() {
    old_tty_settings=$(stty -g)   # Save old settings.
    stty -icanon
    Keypress=$(head -c1)
    stty"$old_tty_settings"      # Restore old settings.
}

它暂时关闭终端设置中的"规范模式"(stty -icanon然后返回"head"(shell内置)的输入,其中-c1选项返回一个字节的标准输入。如果您不包括"stty-icanon",那么脚本将回响按键的字母,然后等待返回(而不是我们想要的)。"head"和"stty"都是shell内置命令。收到按键后,保存和恢复旧的终端设置非常重要。

然后,getkey()可以与"case / esac号"语句结合使用,用于从条目列表中交互式选择一个键:例子:

1
2
3
4
5
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脚本交互。我希望这有帮助。


在我的项目中,我有一种方法可以做到这一点:https://sourceforge.net/p/playshell/code/ci/master/tree/source/keys.sh

每次调用密钥读取时,它都读取一个密钥。对于特殊键,将运行一个特殊的解析循环,以便能够解析它们。

这是它的关键部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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