读取 bash 中的特殊键

读取 bash 中的特殊键

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

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

  • 当用户按下down-arrow下一个项目时突出显示
  • 当用户按下up-arrow前一个项目时会突出显示
  • ETC。
  • 当用户按下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 中是否可行?

一种想法是使用tputorinfocmp并根据给出的结果进行过滤。然而,我遇到了一个障碍,因为两者tput都与infocmp我实际按键时实际读到的内容不同。例如使用 C 而不是 bash 也是如此。

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

我缺少什么?

答案1

您缺少的是大多数终端描述(linux这里只占少数,因为硬编码字符串的普遍使用.inputrc)使用应用方式用于特殊键。这使得光标键如图所示tput并且infocmp与您的(未初始化的)终端发送的内容不同。 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

进一步阅读:

答案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将为您提供有关可以创建的其他类型的对话框以及如何自定义外观的信息。

相关内容