Shell script реагировать на нажатия клавиш

У меня есть оболочка script, которая по сути говорит что-то вроде

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

Все хорошо, и хорошо, но я просто понял, что нужно нажать ENTER, это серьезная проблема в этой ситуации. Мне нужно, чтобы script отвечал, когда нажата клавиша, без нажатия клавиши ввода.

Есть ли способ достичь этой функциональности в оболочке script?

Ответ 1

read -rsn1

Ожидайте только одну букву (и не ждите отправки) и молчите (не пишите это письмо назад).

Ответ 2

поэтому окончательный рабочий фрагмент следующий:

#!/bin/bash

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

Ответ 3

Другой способ сделать это, не блокируя (не уверен, что это то, что вы хотите). Вы можете использовать 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, как будто script завершает работу до того, как вы вернете stty обратно в нормальное состояние, вы не сможете увидеть что-либо, что вы набираете, и он будет выглядеть замороженным.

trap control_c SIGINT

control_c()
{
    stty sane
}

P.S Кроме того, вы можете захотеть поставить оператор сна в свой script, чтобы не использовать весь ваш процессор, так как он будет постоянно работать так быстро, как только может.

sleep 0.1

P.S.S Похоже, проблема зависания была только тогда, когда я использовал -echo, как я привык, поэтому, вероятно, не нужен. Я собираюсь оставить его в ответе, хотя по-прежнему полезно reset stty по умолчанию, чтобы избежать будущих проблем. Вы можете использовать -echo, если вы не хотите, чтобы вы отображались на экране.

Ответ 4

Вы можете использовать эту функцию getkey:

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

Он временно отключает "канонический режим" в настройках терминала (stty -icanon), а затем возвращает вход "head" (встроенная оболочка) с опцией -c1, которая возвращает ОДИН байт стандартного ввода. Если вы не включите "stty -icanon", то script повторяет букву нажатой клавиши, а затем ждет RETURN (не то, что мы хотим). Оба "head" и "stty" - это встроенные команды оболочки. Важно сохранить и восстановить старые параметры терминала после нажатия клавиши.

Затем 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 может использоваться для интерактивного взаимодействия многих сценариев оболочки. Надеюсь, это поможет.

Ответ 5

У меня есть способ сделать это в моем проекте: 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