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

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

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

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

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

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

5 ответов


после попытки пользовательского сценария завершения, что я знаю работает (я использую его каждый день) и столкнувшись с той же проблемой (при настройке ее, похожей на вашу), я решил пробраться через источник 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() вызывается, он сбрасывает функцию завершения на null по какой-то причине, что только bash-взлом длинной бороды может знать. Таким образом, делая это с read builtin может просто быть жестко закодированным, чтобы быть отключенным.

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

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

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

боюсь, у меня нет иного решения, помимо того, что вы придумали до сих пор, но, по крайней мере, мы знаем!--13-->почему он не работает с 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 указывается и происходит тайм-аут, который больше не требуется. Если вы собираетесь отправить что-то чету, вы можете захотеть акциз в целом rl_attempted_completion_function сначала мусор, но это, по крайней мере, позволит вашему скрипту вести себя правильно.

исправления:

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


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


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

ответ дает сопровождающий BASH, чет Рами. В этой теме тот же самый вопрос решается:

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

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

чет

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

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

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

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

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


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

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

Я не уверен, что это точно отвечает на вопрос OP, но я искал, какую команду можно использовать, чтобы получить значение по умолчанию bash tab завершение известных исполняемых команд (согласно $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

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

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