bash и readline: завершение табуляции в цикле пользовательского ввода? - PullRequest
30 голосов
/ 18 января 2011

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

Код Cli:

#!/bin/bash

cmd1() {
    echo $FUNCNAME: "$@"
}

cmd2() {
    echo $FUNCNAME: "$@"
}

cmdN() {
    echo $FUNCNAME: "$@"
}

__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, я могу вызывать команды, редактировать строку ввода и т. Д. Что я никак не могу сделать - это завершить работу с вкладкой readline !!

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

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

  2. Это уродливый обходной путь

Почему readline не работает правильно при использовании «complete» внутри скрипта? это работает, когда я пробую его из Bash в интерактивном режиме ...

Ответы [ 5 ]

24 голосов
/ 19 января 2011

Попробовав собственный скрипт завершения, который, как я знаю, работает (я использую его каждый день) и столкнулся с той же проблемой (при настройке, аналогичной вашей), я решил просмотреть 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 может быть просто жестко запрограммировано для отключения.

EDIT : еще кое-что об этом: код переноса, чтобы остановить завершение в readвстроенные произошли между bash-2.05a и bash-2.05b.Я нашел это примечание в файле bash-2.05b/CWRU/changelog этой версии:

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

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

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

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

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

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

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;

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

13 голосов
/ 05 марта 2013

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

#!/bin/bash

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

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

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

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

11 голосов
/ 20 января 2011

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

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

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

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

Chet

Так что, если я что-то упустил // rant // , пока bash передает руки программистуМеханизм «read -e» как средство для полного, правильного взаимодействия с пользователем CLI, функциональность ограничена, даже если базовый механизм (readline) работает и безупречно интегрируется с остальной частью bash // end rant //

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

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

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

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

2 голосов
/ 16 мая 2012

Если вы собираетесь приложить столько усилий, почему бы просто не добавить стоимость вилки или двух и использовать что-то, что более чем способно обеспечить все, что вы хотите.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 )

Или, как написано на странице руководства:

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

order=‘rlwrap −S ’Your pizza? ’−H past_orders −P Margherita −o cat‘
1 голос
/ 03 июля 2012

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

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

$ lua<kbd><TAB></kbd>
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

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

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

...