Bashから特殊キーを読む

Bashから特殊キーを読む

私は何よりも選択リストを作成するスクリプトを作業しています。良い:

1)プロジェクト1              #(最も明るい部分)
2)項目2
三)アイテム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

など。


前述のように、問題はさまざまな端末でこれを安定させる方法です。つまり、特定のキーを定義するバイトシーケンスが何であるかです。バッシュでこれは可能ですか?

1つのアイデアは、与えられた結果に基づいてtputorとフィルタリングを使用することです。しかし、実際にキーを押したときに実際に読んだ内容と両方がinfocmp違うという点で難関にぶつかりました。たとえば、bashの代わりにCを使用している場合も同様です。tputinfocmp

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ありません。xtermTERM

たとえば、左矢印は次のようになります。

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

私は何を見逃していますか?

答え1

あなたが見逃しているのは、ほとんどの端末記述(linuxハードコードされた文字列の一般的な使用のため、ここでは少数のみ.inputrc)が以下を使用することです。申請方法特殊キーに使用されます。これにより、カーソルキーが示されているように表示されtputinfocmp初期化されていない)端末が送信するのとは異なります。呪いアプリケーションは常に端末を初期化し、端末データベースが使用されます。それ目的。

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

追加資料:

答え2

を使ってみました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作成できるさまざまな種類のダイアログボックスと、外観をカスタマイズする方法に関する情報が提供されます。

関連情報