Читайте специальные ключи в 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?

Одна мысль состояла в том, чтобы использовать или tputили infocmpи фильтровать результат, данный этим. Я, однако, в затруднении, поскольку оба 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

Что мне не хватает?

user367890
источник
не нужно изобретать велосипед, iselect уже делает это. В качестве альтернативы, используйте один из dialogвариантов или используйте язык с достойной ncursesподдержкой (например, perl или python, если вы хотите придерживаться языков «сценариев»).
Cas
1
Обратите внимание, что zshимеется встроенная поддержка curses (в модуле zsh / curses) в дополнение к базовым запросам terminfo с его echotiвстроенным и $terminfoассоциативным массивом.
Стефан Шазелас

Ответы:

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 интересно взломать :)
user367890
Найдена последовательность в, среди прочего, /usr/share/doc/readline-common/inputrc.arrows. Поскольку у меня уже есть универсальная функция «read_key», которую я использую в сценарии, я надеялся, что существует более простой способ определить последовательности (в сценарии) из того, что фактически отображается при нажатии клавиши. Т.е. похоже на извлечение определений из infocmp. Но не угадайте и либо оставьте все как есть, либо переходите на другой язык. Конечно, компромиссом может быть использование вашего красивого C-сниппета. Но тогда я могу написать все это на С вместо этого. (Извините за превышение.)
user367890
Это полный код C? Я получаю около десятка ошибок при попытке компиляции это с помощью GCC на Debian 9
взаимосвязанная
Вы, вероятно, пропустили и -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 за усилие, но Дики был ближе к тому, о чем я спрашиваю. Например, описанная проблема - в более общем смысле, список должен был просто дать некоторый контекст. Во-вторых, я быстро взглянул на диалоговое окно - и по общему признанию, я не рассмотрел его полностью, мой случай, чтобы расширить его, это фронт для базы данных sqlite с несколькими тысячами записей, где у меня есть, например, Page-Up / Down to прокрутите выбор желоба. Область прокрутки, буфер прокрутки, строка состояния, строка ex с модальным входом,
подфункции
... но диалог не совсем соответствовал потребностям или был несколько громоздким для моего случая.
user367890 10.07.16
@ user367890 ваших звуки приложения , такие как идеальный матч для Perl Curses, DBIи DBD::SQLiteмодулей. или их эквиваленты Python.
Cas
@cas: Да. Вы писали подобные приложения с использованием Python и C ранее - хотя мне приходится много переучивать. Этот «проект» - это скорее приключение с возможностями bash и «ради удовольствия» :) Хотя я уже близок к тому, чтобы отказаться от него или перенести его на другой язык. Спасибо за вклад.
user367890