Подтвердить что ты не робот

Bash и readline: завершение вкладок в цикле ввода пользователя?

Я создаю bash script, который представляет пользователю пользовательскую команду.

Код cli выглядит следующим образом:

#!/bin/bash

cmd1() {
    echo $FUNCNAME: "[email protected]"
}

cmd2() {
    echo $FUNCNAME: "[email protected]"
}

cmdN() {
    echo $FUNCNAME: "[email protected]"
}

__complete() {
    echo $allowed_commands
}

shopt -qs extglob

fn_hide_prefix='__'
allowed_commands="$(declare -f | sed -ne '/^'$fn_hide_prefix'.* ()/!s/ ().*//p' | tr '\n' ' ')"

complete -D -W "this should output these words when you hit TAB"

echo "waiting for commands"
while read -ep"-> "; do
    history -s $REPLY
    case "$REPLY" in
        @(${allowed_commands// /|})?(+([[:space:]])*)) $REPLY ;;
        \?) __complete ;;
        *) echo "invalid command: $REPLY" ;;
    esac
done

Уточнение: сделано и протестировано в bash 4

Итак, "read -e" предоставляет возможности чтения, я могу вызвать команды, отредактировать строку ввода и т.д. Что я не могу сделать каким-либо образом, чтобы завершить работу с readline!

Я пробовал две вещи:

  • Как это должно быть сделано: используя bash встроенные функции "complete" и "compgen", о котором сообщается, что здесь Обновление: он не сообщил о работе в скриптах.

  • Это уродливое обходное решение

Почему строка readline не корректно работает при использовании "полной" внутри script? он работает, когда я пытаюсь использовать его из bash в интерактивном режиме...

4b9b3361

Ответ 1

После выполнения пользовательского завершения script, которое, как я знаю, работает (я использую его каждый день) и сталкиваясь с тем же вопросом (при оснащении его похожим на ваш), я решил проскользнуть через источник bash 4.1, и нашел этот интересный блок в bash-4.1/builtins/read.def:edit_line():

old_attempted_completion_function = rl_attempted_completion_function;
rl_attempted_completion_function = (rl_completion_func_t *)NULL;
if (itext)
  {
    old_startup_hook = rl_startup_hook;
    rl_startup_hook = set_itext;
    deftext = itext;
  }
ret = readline (p);
rl_attempted_completion_function = old_attempted_completion_function;
old_attempted_completion_function = (rl_completion_func_t *)NULL;

Похоже, что до вызова readline() он сбрасывает функцию завершения до нуля по какой-то причине, что может знать только bash -ущая длинная борода. Таким образом, выполнение этого с помощью встроенного read может быть просто жестко закодировано для отключения.

ИЗМЕНИТЬ. Еще несколько слов об этом: код завершения для завершения завершения в read builtin произошел между bash -2.05a и bash -2.05b. Я нашел эту заметку в этой версии bash-2.05b/CWRU/changelog file:

  • edit_line (вызывается read -e) теперь просто завершает работу с readline filename, установив rl_attempted_completion_function в NULL, поскольку, например, выполнение команды для первого слова в строке не было действительно полезным

Я думаю, что это устаревший надзор, и поскольку программируемое завершение прошло долгий путь, то, что вы делаете, полезно. Возможно, вы можете попросить их добавить его обратно или просто исправить самостоятельно, если это будет возможно для того, что вы делаете.

Боюсь, у меня нет другого решения, кроме того, что вы придумали до сих пор, но по крайней мере мы знаем, почему он не работает с read.

EDIT2: Правильно, здесь патч, который я только что проверил, кажется, "работает". Пропускает все тесты unit и reg и показывает этот вывод из вашего script при запуске с помощью исправленного bash, как вы ожидали:

$ ./tabcompl.sh
waiting for commands
-> **<TAB>**
TAB     hit     output  should  these   this    when    words   you
->

Как вы увидите, я просто прокомментировал эти 4 строки и код таймера на reset rl_attempted_completion_function, когда указан read -t, и произойдет тайм-аут, который больше не нужен. Если вы собираетесь отправить что-то Chet, возможно, вы захотите сначала снять весь мусор rl_attempted_completion_function, но это, по крайней мере, позволит вашему script правильно вести себя.

Patch:

--- bash-4.1/builtins/read.def     2009-10-09 00:35:46.000000000 +0900
+++ bash-4.1-patched/builtins/read.def     2011-01-20 07:14:43.000000000 +0900
@@ -394,10 +394,12 @@
        }
       old_alrm = set_signal_handler (SIGALRM, sigalrm);
       add_unwind_protect (reset_alarm, (char *)NULL);
+/*
 #if defined (READLINE)
       if (edit)
        add_unwind_protect (reset_attempted_completion_function, (char *)NULL);
 #endif
+*/
       falarm (tmsec, tmusec);
     }

@@ -914,8 +916,10 @@
   if (bash_readline_initialized == 0)
     initialize_readline ();

+/*
   old_attempted_completion_function = rl_attempted_completion_function;
   rl_attempted_completion_function = (rl_completion_func_t *)NULL;
+*/
   if (itext)
     {
       old_startup_hook = rl_startup_hook;
@@ -923,8 +927,10 @@
       deftext = itext;
     }
   ret = readline (p);
+/*
   rl_attempted_completion_function = old_attempted_completion_function;
   old_attempted_completion_function = (rl_completion_func_t *)NULL;
+*/

   if (ret == 0)
     return ret;

Имейте в виду, что исправленный bash должен быть распространен или доступен как-то везде, где люди будут использовать ваш script...

Ответ 2

Я борюсь с той же проблемой в течение некоторого времени, и я думаю, что у меня есть решение, которое работает, в моем реальном мире я использую compgen для создания возможных завершений. Но вот пример, иллюстрирующий основную логику:

#!/bin/bash

set -o emacs;
tab() {
  READLINE_LINE="foobar"
  READLINE_POINT="${#READLINE_LINE}"
}
bind -x '"\t":"tab"';
read -ep "$ ";

Установите параметр emacs для включения привязки ключа, привяжите ключ табуляции к функции, измените READLINE_LINE, чтобы обновить строку после запроса, и установите READLINE_POINT, чтобы отобразить новую длинную длину.

В моем случае использования я фактически имитирую переменные COMP_WORDS, COMP_CWORD и COMPREPLY, но этого должно быть достаточно, чтобы понять, как добавить добавление пользовательских вкладок при использовании read -ep.

Вы должны обновить READLINE_LINE, чтобы изменить строку приглашения (завершение одного совпадения), печать на печать stdin перед приглашением, поскольку readline помещает терминал в необработанный режим и захватывает ввод.

Ответ 3

Ну, похоже, я, наконец, запятнал ответ, и это печально: на самом деле нет полной поддержки readline при взаимодействии с ним через "read -e".

Ответ предоставлен сопровождающим BASH, Чет Рами. В этот поток рассматривается одна и та же проблема:

Я пишу script с помощью интерпретатора командной строки, и я могу многое сделать   (например, история и т.д.), за исключением одного. Окончание имени файла   хорошо работает для некоторых команд, но я хотел бы использовать другое завершение   варианты для других. Хорошо работает из "реальной" командной строки, но я не могу   заставить его работать правильно в моем цикле read -e, eval.

Вы не сможете это сделать. `read -e 'использует только значение по умолчанию для readline пополнения.

Chet

Итак, если мне не хватает чего-то//rant//while BASH вручает программисту механизм "read -e" как средство для полного, правильного взаимодействия пользователя CLI, функциональность искалечена, хотя основной механизм (readline) работает и интегрируется с остальной частью BASH безупречно //end rant//

Я выставил вопрос добрым людям в # bash в freenode и предложил попробовать с помощью оболочки Readline, например rlfe или rlwrap.

Наконец, я связался с самим Четом по почте вчера, и он подтвердил, что это по дизайну, и что он не хочет менять его как единственный вариант использования для программируемого завершения в "чтение", то есть представляет список команды пользователю script, не является убедительной причиной для того, чтобы тратить время на это. Тем не менее он выразил, что в случае, если кто-то действительно выполняет эту работу, он, безусловно, будет смотреть на результат.

ИМХО, не считая стоящих усилий возможностью воспитывать полный CLI всего за 5 строк кода, одно желание было возможно на многих языках, это ошибка.

В этом контексте я думаю, что ответ Саймона блестящий и правильный. Я постараюсь выполнить ваши шаги и, возможно, с некоторой удачей, я получу дополнительную информацию. Прошло некоторое время, так как я не взламываю C, но я предполагаю, что количество кода, которое мне нужно будет реализовать, не будет тривиальным. Но в любом случае я попробую.

Ответ 4

Если вы приложите столько усилий, почему бы просто не добавить стоимость вилки или двух и использовать что-то, что более чем способно обеспечить все, что вы хотите. http://utopia.knoware.nl/~hlub/rlwrap/rlwrap.html

#!/bin/bash

which yum && yum install rlwrap
which zypper && zypper install rlwrap
which port && port install rlwrap
which apt-get && apt-get install rlwrap

REPLY=$( rlwrap -o cat )

Или, как на странице man:

В оболочке script используйте rlwrap в однократном режиме в качестве замены для чтения

order=‘rlwrap −S ’Your pizza? ’−H past_orders −P Margherita −o cat‘

Ответ 5

Я не уверен, что это точно отвечает на вопрос OP - но я искал, какую команду можно использовать, чтобы получить по умолчанию вкладку bash выполнения исполняемых команд (согласно $PATH), как показано при нажатии TAB. Поскольку я впервые привел к этому вопросу (который, как мне кажется, связан), я подумал, что разместил здесь записку.

Например, в моей системе введите lua и затем TAB:

$ lua<TAB>
lua       lua5.1    luac      luac5.1   lualatex  luatex    luatools

Оказывается, есть встроенный bash (см. # 949006 Linux команду для перечисления всех доступных команд и псевдонимов), называемых compgen - и я могу передать его той же строкой lua, что и в интерактивном случае, и получить те же результаты, что и при нажатии TAB:

$ compgen -c lua
luac
lua5.1
lua
luac5.1
luatex
lualatex
luatools

... и это именно то, что я искал :)

Надеюсь, это поможет кому-то,
Ура!