キーの押下に反応するスクリプトを作成するには、何を考慮する必要がありますか?

キーの押下に反応するスクリプトを作成するには、何を考慮する必要がありますか?

私は次のスクリプトで変数に何かを読むことができることを知っています。変数=読み取りしかし、変数に値を送信するにはEnterキーを押す必要があります。 Enterキーを押さずにキー押し値を変数に送信するには、またはキーを押したときに反応しないようにするにはどうすればよいですか?

答え1

を使用すると、組み込み関数の引数をbash使用して、改行なしで読み取る文字数を制限できます。-nread

#!/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

bashshこれはasか呼び出すかに関係なく機能しますbash

詳細については、help readまたはマンページを参照してください。bash

他のシェルは-nパラメータをサポートしていない可能性がありますreaddash、例えばいいえ。

答え2

シェルに関する問題は、read合計バイト数を制限するかどうかにかかわらず、readまだ情報を提供しないことです。ボタン各自readは一部しか得られない安定数量数値毎回read1つ取得しようボタン必要に応じてread入力をブロックするのがおそらく最良の方法ですdd。以下は、数ヶ月前に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設定されています。生ののパターンは、trapエラーが発生したときに最終状態を復元することも、関数が呼び出さINTEXITたときに関数の状態に復元することもできます。(おそらくCTRL + Cまたは割り込みキーを使用して実行できます)。存在する生のモードでは、端末は入力が到着するとすぐにすべてのリーダーに入力をフラッシュします(キー入力ごとにキー入力)。一度に1バイトだけプッシュするわけではありません。できるだけ早くバッファ全体をプッシュします。たとえば、上矢印を押すと、キーボードは次のエスケープシーケンスを送信します。

^[[A

...3バイトですが、一度に押し込みます。

dd指定されたすべての読み取りを満たすように指定されます(設定された高さに関係なく)ibs=。これは、を使用した設定にもかかわらず、ibs=7端末が3バイトだけプッシュしても読み取りがまだ完了することを意味します。他のほとんどのユーティリティはこれを処理するのに問題がありますが、ddこれはconv=syncバイト間の違いを埋めます\0NUL。したがって、キーボードの上矢印を押すとdd3バイトが読み取られ、次のバイトに7が書き込まれます。つまりdd、エスケープシーケンスの 3 バイトと別の 4\0NULバイトです。

ただし、シェルの読み取りでこのデータを取得するには再度ブロックする必要があるため、次のデータはdd入力バッファを72バイトに同期します。しかし実際にはconv=unblockそうです。unblock変換により、dd入力を\newline区切り文字に分割してcbs=数を取得します。ここでは8ですsyncunblock (またはblock変換はsでは同期されddませんが、\0NUL末尾のスペースでは同期されます。したがって、7バイトごとの最初は、2つのddバッファ間のパイプにdd72バイトを書き込みます。最初の数バイトはキーの内容で、次は\0NULs、次には65の空白読み取りの終わりです。

もう一つのことは、末尾のスペースを削除することです。これは、各変換ブロックのunblock末尾に存在できるだけのスペースを占めます。cbs=したがって、各dd書き込みはバイト単位で出力に書き込まれるobs=8ため、各読み取りは9行に書き込まれるため、合計2回の書き込みが出力パイプに書き込まれます。最初の書き込みは、入力パイプから読み取られた7バイトと末尾の改行文字(別のバイト)で構成される最初の行です。次の書き込みは8つの改行(連続書き込み)です。 8バイトが多いです。なぜならdd、8つの変換ブロックのそれぞれが8つの空白をすべて占めるからです。

dd両方のsaシェルの反対側をwhile1行ずつ繰り返しますread。空行を無視できます。なぜなら、ターミナルはオプションに従ってすべての改行を出力のキャリッジリターンに変換するからですinlcr stty。ただし、$cシェルの入力がその値を入力した後にシングルバイトでも検出すると、キーボードがキー入力ごとに多くのバイトを送信しない限り、キーを押すだけでフルキーをread押すこともできます。 7バイト以上(ただ別のブロック要素が必要ですが)

シェルにキー押下がある場合は、$cバイト単位で繰り返し、'文字で区切られた配列に分割し、printf各バイトの10進値を一度に取得します。$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

シェルが変数の値を入力で埋めると、キーが押されるのを防ぐために挿入されたsが消えている\0NULため、sをシェル変数に入れることはできません。dd\0NUL(しかし、どんなシェルでもzsh- この場合はまだ次のように設定できます)。私が知る限り、これはすべてのシェルで動作しなければならず、そしてでもbash動作dashしますksh93。マルチバイト入力も安定して処理します。しかし、それは大胆ではありません。

上記のデモ出力では、作成されるたびに関数の実際の出力の前に作成されなかった追加情報が表示されます。:上記の順序で最初に表示される前に表示される各文字は実際には端末文字であり、使用または設定echoできます。残りは関数の出力として印刷されます。ご覧のとおり、入力直後に印刷されます。stty echo-echo

関連情報