在 bash 中读取特殊键

use*_*890 10 bash keyboard

我正在使用一个脚本,其中列出了一个选择列表。如:

1)项目 1               #(突出显示)
2) 第 2 项
3)项目 3 # (已选)
4) 第 4 项

  • 当用户按下down-arrow下一个项目时突出显示
  • 当用户按下up-arrow以前的项目时突出显示
  • 等等。
  • 当用户按下tab项目时
  • 当用户按下shift+tab所有项目被选择/取消选择
  • 当用户按下ctrl+a所有项目时
  • ...

这在当前使用中运行良好,这是我个人使用,其中输入由我自己的设置过滤。

问题是如何使这在各种终端上都可靠。


我使用了一个有点hackish的解决方案来读取输入:

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
Run Code Online (Sandbox Code Playgroud)

等等。


如前所述,问题是如何在各种终端之间实现可靠:即什么字节序列定义了特定的密钥。在 bash 中它甚至可行吗?

一种想法是使用tputorinfocmp并通过给出的结果进行过滤。然而,我在那里遇到了障碍,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)';
}
Run Code Online (Sandbox Code Playgroud)

例如linux,按照定义读取的产量序列,但不是xterm,这是由 设置的TERM

例如向左箭头:

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

我错过了什么?

mar*_*nus 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
Run Code Online (Sandbox Code Playgroud)

你会得到这样的东西:

在此处输入图片说明

输出将是:

 OK was selected.
 Item 1 was selected.
 Item 3 was selected.
Run Code Online (Sandbox Code Playgroud)

(或您选择的任何项目)。

man dialog 将为您提供有关您可以创建的其他类型对话框以及如何自定义外观的信息。


Tho*_*key 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;
}
Run Code Online (Sandbox Code Playgroud)

假设这被称为tgetch,你会在你的脚本中像这样使用它:

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

进一步阅读: