bash shell 脚本响应按键

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/24016046/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-10 00:52:01  来源:igfitidea点击:

shell script respond to keypress

bashshellinputkeypress

提问by j0h

I have a shell script that essentially says something like

我有一个 shell 脚本,它本质上是这样说的

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

That is all well, and good, but I just realized having to hit ENTER presents a serious problem in this situation. What I need is for the script to respond when a key is pressed, without having to hit enter.

这一切都很好,但我刚刚意识到在这种情况下必须按 ENTER 会出现严重的问题。我需要的是让脚本在按下某个键时做出响应,而无需按 Enter。

Is there a way to achieve this functionality within a shell script?

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

回答by pacholik

read -rsn1

Expect only one letter (and don't wait for submitting) and be silent (don't write that letter back).

只期待一封信(不要等待提交)并保持沉默(不要回信)。

回答by Donald Derek

so the final working snippet is the following:

所以最终的工作片段如下:

#!/bin/bash

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

回答by Donald Derek

Another way of doing it, in a non blocking way(not sure if its what you want). You can use stty to set the min read time to 0.(bit dangerous if stty sane is not used after)

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

stty -icanon time 0 min 0

Then just run your loop like normal. No need for -r.

然后像往常一样运行你的循环。不需要 -r。

while true; do
    read input

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

IMPORTANT! After you have finished with non blocking you must remember to set stty back to normal using

重要的!完成非阻塞后,您必须记住使用设置 stty 恢复正常

stty sane

If you dont you will not be able to see anything on the terminal and it will appear to hang.

如果您不这样做,您将无法在终端上看到任何内容,并且它似乎会挂起。

You will probably want to inlcude a trap for ctrl-C as if the script is quit before you revert stty back to normal you will not be able to see anything you type and it will appear the terminal has frozen.

您可能希望为 ctrl-C 设置一个陷阱,就好像脚本在您将 stty 恢复正常之前退出一样,您将看不到您输入的任何内容,并且终端似乎已冻结。

trap control_c SIGINT

control_c()
{
    stty sane
}

P.S Also you may want to put a sleep statement in your script so you dont use up all your CPU as this will just continuously run as fast as it can.

PS 此外,您可能还想在脚本中放置一个 sleep 语句,这样您就不会用完所有的 CPU,因为这将尽可能快地持续运行。

sleep 0.1

P.S.S It appears that the hanging issue was only when i had used -echo as i used to so is probably not needed. Im going to leave it in the answer though as it is still good to reset stty to its default to avoid future problems. You can use -echo if you dont want what you have typed to appear on screen.

PSS 似乎只有当我像以前一样使用 -echo 时才会出现挂起问题,因此可能不需要。我将把它留在答案中,因为将 stty 重置为其默认值以避免将来出现问题仍然很好。如果您不希望您输入的内容出现在屏幕上,您可以使用 -echo。

回答by Ian Forsyth

You can use this getkeyfunction:

您可以使用此getkey功能:

getkey() {
    old_tty_settings=$(stty -g)   # Save old settings.
    stty -icanon
    Keypress=$(head -c1)
    stty "$old_tty_settings"      # Restore old settings.
}

It temporarily turns off "canonical mode" in the terminal settings (stty -icanon) then returns the input of "head" (a shell built-in) with the -c1 option which is returning ONE byte of standard input. If you don't include the "stty -icanon" then the script echoes the letter of the key pressed and then waits for RETURN (not what we want). Both "head" and "stty" are shell built-in commands. It is important to save and restore the old terminal settings after the key-press is received.

它在终端设置 ( stty -icanon) 中暂时关闭“规范模式”,然后使用 -c1 选项返回“head”(内置 shell)的输入,该选项返回一个字节的标准输入。如果您不包含“stty -icanon”,则脚本会回显按下的键的字母,然后等待 RETURN(不是我们想要的)。“head”和“stty”都是shell内置命令。收到按键后保存和恢复旧的终端设置很重要。

Then getkey() can be used in combination with a "case / esac" statement for interactive one-key selection from a list of entries: example:

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

case $Keypress in
   [Rr]*) Command response for "r" key ;;
   [Ww]*) Command response for "w" key ;;
   [Qq]*) Quit or escape command ;;  
esac

This getkey()/case-esaccombination can be used to make many shell scripts interactive. I hope this helps.

这种getkey()/case-esac组合可用于使许多 shell 脚本具有交互性。我希望这有帮助。

回答by konsolebox

I have a way to do this in my project: https://sourceforge.net/p/playshell/code/ci/master/tree/source/keys.sh

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

It reads a single key everytime key_readonce is called. For special keys, a special parsing loop would run to also be able to parse them.

每次调用 key_readonce 时,它​​都会读取一个键。对于特殊键,将运行一个特殊的解析循环来解析它们。

This is the crucial part of it:

这是其中的关键部分:

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