bashで特殊キーを読み取る


8

私はとりわけ、選択リストをリストするスクリプトで遊んでいます。のように:

1)アイテム1               #(強調表示)
2)アイテム2
3)アイテム3#(選択)
4)アイテム4

  • ユーザーがdown-arrow次のアイテムを押すとハイライトされます
  • ユーザーがup-arrow前のアイテムを押すとハイライトされます
  • ユーザーがtabアイテムを押すと選択されたとき
  • ユーザーが押すと、shift+tabすべてのアイテムが選択/選択解除されます
  • ユーザーがctrl+aすべてのアイテムを選択すると、選択されます
  • ...

これは、現在の使用状況では正常に機能します。これは、入力が自分のセットアップによってフィルターされる私の個人的な使用です。

問題は、さまざまな端末間でこれを信頼できる方法にすることです。


私は入力を読み取るためにややハックなソリューションを使用します:

while read -rsn1 k # Read one key (first byte in key press)
do
    case "$k" in
    [[:graph:]])
        # Normal input handling
        ;;
    $'\x09') # TAB
        # Routine for selecting current item
        ;;
    $'\x7f') # Back-Space
        # Routine for back-space
        ;;
    $'\x01') # Ctrl+A
        # Routine for ctrl+a
        ;;
    ...
    $'\x1b') # ESC
        read -rsn1 k
        [ "$k" == "" ] && return    # Esc-Key
        [ "$k" == "[" ] && read -rsn1 k
        [ "$k" == "O" ] && read -rsn1 k
        case "$k" in
        A) # Up
            # Routine for handling arrow-up-key
            ;;
        B) # Down
            # Routine for handling arrow-down-key
            ;;
        ...
        esac
        read -rsn4 -t .1 # Try to flush out other sequences ...
    esac
done

等々。


述べたように、問題はさまざまな端末でこれをどのように信頼できるようにするかです。つまり、どのバイトシーケンスが特定のキーを定義するかです。bashでさえ実現可能ですか?

1つは、tputorまたはinfocmpand を使用して、それによって与えられた結果でフィルタリングすることでした。しかし、私は両方に障害があり、実際にキーを押したときに実際に読んだものtputとはinfocmp異なります。同じことが、例えばbashでCを使用する場合にも当てはまります。

for t in $(find /lib/terminfo -type f -printf "%f\n"); { 
    printf "%s\n" "$t:"; 
    infocmp -L1 $t | grep -E 'key_(left|right|up|down|home|end)';
}

たとえばlinux、定義どおりに読み取られた歩留まりシーケンスはxterm、によって設定されTERMますが、そうではありません。

たとえば、左矢印:

  • tput/ infocmp\x1 O D
  • read\x1 [ D

何が欠けていますか?


ホイールを再発明する必要はありません。iselectはすでにこれを行っています。あるいは、dialogバリアントの1つを使用するか、まともなncursesサポートのある言語を使用してください(たとえば、「スクリプト」言語を使いたい場合は、perlまたはpython)。
cas

1
組み込みの連想配列をzsh使用した基本的なterminfoクエリに加えて、組み込みのcursesサポート(zsh / cursesモジュール内)があることに注意してください。echoti$terminfo
ステファンChazelas

回答:


5

不足しているのは、ほとんどの端末の説明(linuxここでは少数派ですが、ハードコードされた文字列が広く使用されているため.inputrc)は、特別なキーにアプリケーションモードを使用しています。これにより、カーソルキーは(初期化されていない)端末が送信するものtputinfocmp異なり、異なります。cursesアプリケーションは常に端末を初期化し、その目的で端末データベースが使用されます。

dialogその用途はありますが、この質問には直接触れていません。一方、bashのみのソリューションを提供するのは面倒です(技術的に実行可能で、ほとんど実行されません)。通常、これを行うには他の言語を使用します。

特殊キーを読んでの問題は、彼らはしばしばのような厄介な文字を含む複数のバイト、ということであるescape~。これはbashで実行できますが、これがどの特別なキーであるかを移植可能に決定するという問題を解決する必要があります。

dialogどちらも特殊キーの入力を処理し、ディスプレイを(一時的に)引き継ぎます。単純なコマンドラインプログラムが本当に必要な場合は、そうではありませんdialog

以下は、特殊なキーを読み取り、それを印刷可能な(そして移植可能な)形式で印刷するCの簡単なプログラムです。

#include <curses.h>

int
main(void)
{   
    int ch;
    const char *result;
    char buffer[80];

    filter();
    newterm(NULL, stderr, stdin);
    keypad(stdscr, TRUE);
    noecho();
    cbreak();
    ch = getch();
    if ((result = keyname(ch)) == 0) {
        /* ncurses does the whole thing, other implementations need this */
        if ((result = unctrl((chtype)ch)) == 0) {
            sprintf(buffer, "%#x", ch);
            result = buffer;
        }
    }
    endwin();
    printf("%s\n", result);
    return 0;
}

これがと呼ばれたtgetchとすると、スクリプトで次のように使用します。

case $(tgetch 2>/dev/null) in
KEY_UP)
   echo "got cursor-up"
   ;;
KEY_BACKSPACE|"^H")
   echo "got backspace"
   ;;
esac

参考文献:


ありがとうございました。はい、inputrc確かに私が探していた犯人でした。もう少し見てみます。PythonやCへの移行を検討してきましたが、bashスクリプトとしてハッキングするのも楽しいと思います。また、必要なビットを抽出できるかどうかを確認するために、ncursesソースを確認しようとしましたが、かなりの時間をかけてソースを掘り下げた後、それを氷の上に置きました。「プロジェクト」の単純なコマンドとして始まったが、その後、簡単な対話型のスクリプトになってから、再度その上に拡張します。途中で私は他の言語に行くべきでしたが、少し頑固になりました(そして前述のように、bash 2でハッキングするのは楽しいです:)
user367890

特に、でシーケンスが見つかりました/usr/share/doc/readline-common/inputrc.arrows。スクリプト全体で使用する汎用の"read_key"関数を既に持っているので、キーが押されたときに実際に表示されるものから(スクリプト内で)シーケンスを定義する簡単な方法があると思いました。つまり、からの定義の抽出に似ていinfocmpます。しかし、そうではなく、そのままにしておくか、別の言語に移行する必要があります。もちろん、妥協案は、あなたの素敵なC-スニペットを使うことかもしれません。しかし、代わりにすべてをCで書くことができます。(オーバーシェアして申し訳ありません。)
user367890

それは完全なCコードですか?私はDebianの9に、この使用してGCCをコンパイルしようとしたとき、私はダースのエラーに関する取得
連結

おそらく、省略-lncursesなどを
トーマス・ディッキー

6

使ってみましたdialogか?ほとんどのLinuxディストリビューションに標準で付属しており、チェックリストを含むあらゆる種類のテキストベースのダイアログを作成できます。

例えば:

exec 3>&1 # open temporary file handle and redirect it to stdout

#                           type      title        width height n-items    
items=$(dialog --no-lines --checklist "Title here" 20    70     4 \
          1 "Item 1" on \
          2 "Item 2" off \
          3 "Item 3" on \
          4 "Item 4" off \
            2>&1 1>&3) # redirect stderr to stdout to catch output, 
                       # redirect stdout to temporary file
selected_OK=$? # store result value
exec 3>&- # close new file handle 

# handle output
if [ $selected_OK = 0 ]; then
    echo "OK was selected."
    for item in $items; do
        echo "Item $item was selected."
    done
else
    echo "Cancel was selected."
fi

あなたはこのようなものを得るでしょう:

ここに画像の説明を入力してください

そして出力は次のようになります:

 OK was selected.
 Item 1 was selected.
 Item 3 was selected.

(または選択したアイテム)。

man dialog 作成できる他の種類のダイアログ、および外観のカスタマイズ方法に関する情報を取得します。


努力のために+1しましたが、ディッキーは私が求めていることのほうがましでした。問題の説明の1つとして、より一般的な意味では、リストは単にいくつかのコンテキストを提供するためのものでした。第二に、ダイアログをざっと見てみました。確かに、私はそれを完全には見ていません。それを拡張すると、私のページは、たとえばPage-Up / Downなどの数千のレコードを持つsqliteデータベースのフロントです。選択範囲をスクロールします。スクロール領域、スクロールバッファ、ステータスライン、モーダル入力を備えたex行、フィルタリング用のサブ関数など。要するに、複雑に聞こえるかもしれませんが、かなりシンプルです…
user367890

…しかし、対話はニーズを十分に満たしていないか、私の場合はやや面倒ではありませんでした。
user367890

@ user367890アプリケーションのperlに完璧にマッチのような音CursesDBIおよびDBD::SQLiteモジュール。または同等のpython。
cas

@cas:はい。以前にpythonとCを使用して同様のアプリケーションを作成したことがありますが、多くのことを再学習する必要があります。この「プロジェクト」は、bashの可能性への冒険であり、「それを楽しむための」ものです。入力いただきありがとうございます。
user367890
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.