阅读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中甚至可行吗?

一种想法是使用tputor,infocmp然后根据该结果给出过滤。但是,我在这方面遇到了麻烦,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,但不是定义xtermTERM

例如左箭头:

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

我想念什么?


无需重新发明轮子,iselect已经做到了。或者,使用其中一种dialog变体,或使用具有适当ncurses支持的语言(例如,如果要坚持使用“脚本”语言,则为perl或python)。
cas

1
请注意,zsh除了基本的terminfo查询及其echoti内置和$terminfo关联数组之外,它还具有内置curses支持(在zsh / curses模块中)。
斯特凡Chazelas

Answers:


5

您所缺少的是,大多数终端描述(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

进一步阅读:


谢谢。是的,inputrc的确是我要找的罪魁祸首。必须多看一些。考虑过要使用python或C,但也可以将它作为bash脚本来学习。我还尝试查看ncurses源,以查看是否可以提取所需的位,但是在挖掘源一段时间后,我将其留在了冰上。该“项目”开始作为一个简单的命令,然后成为一个简单的交互式脚本,然后对再次延长。一路上我本应该使用其他语言,但是有些固执(如上所述,在bash 2中进行
编程

在中找到了序列/usr/share/doc/readline-common/inputrc.arrows。因为我已经在脚本中使用了通用的“ read_key”函数,所以我希望有一种更简便的方法可以根据按下键时实际显示的内容来定义序列(在脚本中)。即类似于从中提取定义infocmp。但是不要猜测,要么必须保持原状,要么继续使用另一种语言。当然,折衷方案可能是使用您的漂亮C代码片段。但是然后我可以用C编写整个内容。(对不起,过分分享。)
user367890

那是完整的C代码吗?当我尝试在Debian 9上使用gcc编译此错误时,遇到了十几个错误
InterLinked

你可能省略-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是努力的结果,但Dickey更能满足我的要求。对于所描述的问题来说,从一个更广泛的意义上讲,该清单仅是为了提供一些背景信息。其次,我快速浏览了对话框-坦白地说,我还没有彻底研究过它,我的案例(要扩展一下)是sqlite数据库的前沿,该数据库具有数千条记录,例如,我有Page-Up / Down to滚动槽选择。滚动区域,滚动缓冲区,状态行,带模式输入的ex行,用于过滤的子函数等。总之,这听起来很复杂,但相当简单……
user367890 2016年

……但是对话似乎并不能完全满足需要,或者对于我的情况来说有点麻烦。
user367890 '16

@ user367890您的应用程序听起来像是perl的完美匹配CursesDBIDBD::SQLite模块。或它们的python等价物。
cas

@cas:是的。之前已经使用python和C编写了类似的应用程序-尽管我不得不重新学习很多内容。这个“项目”更多地是探索bash可能性和“为了它的乐趣”的冒险:)尽管我接近放弃它或将其移植到另一种语言。感谢您的输入。
user367890 2016年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.