bash和readline:用户输入循环中的制表符完成?

ata*_*ata 31 bash bash-completion

我正在制作一个向用户提供命令行的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
Run Code Online (Sandbox Code Playgroud)

澄清:在Bash 4中制作和测试

因此,"read -e"给出了readline功能,我可以调用命令,编辑输入行等.我不能做的任何方式是让readline的选项卡完成工作!

我尝试了两件事:

  1. 如何应该这样做:使用bash builtins"完成"和"compgen",据报道在这里 工作更新:它没有报告在脚本中工作.

  2. 这个丑陋的解决方法

在脚本中使用"完整"时,为什么readline行为不正确?当我在交互模式下从bash尝试它时它工作...

Sda*_*ons 24

在尝试了我知道可用的自定义完成脚本(我每天都使用它)并遇到同样的问题(当它与你的相似时),我决定窥探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;
Run Code Online (Sandbox Code Playgroud)

似乎在readline()调用之前,它将完成函数重置为null,因为只有bash-hacking长胡子可能知道.因此,使用read内置函数执行此操作可能只是硬编码为禁用.

编辑:更多内容:在read内置中停止完成的包装代码发生在bash-2.05a和bash-2.05b之间.我在该版本的bash-2.05b/CWRU/changelog文件中找到了这个注释:

  • edit_line(由read -e调用)现在通过将rl_attempted_completion_function设置为NULL来执行readline的文件名完成,因为例如,对行上的第一个单词执行命令完成并不是很有用

我认为这是一个遗留的疏忽,而且由于可编程完成已经走了很长的路,你所做的事情是有用的.也许你可以让他们重新添加它,或者只是自己修补它,如果这对你正在做的事情是可行的.

我担心除了你到目前为止所提出的解决方案之外我没有其他解决办法,但至少我们知道它为什么不起作用read.

编辑2:对,这是我刚刚测试过的一个看似"工作"的补丁.通过所有单元和reg测试,并在使用修补的bash运行时显示脚本的输出,如您所愿:

$ ./tabcompl.sh
waiting for commands
-> **<TAB>**
TAB     hit     output  should  these   this    when    words   you
->
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,我只是注释掉这4条线和一些计时器代码重置rl_attempted_completion_functionread -t指定,并且发生超时,这不再是必要的.如果您要向Chet发送一些东西,您可能希望首先删除整个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;
Run Code Online (Sandbox Code Playgroud)

请记住,无论人们使用您的脚本,都必须以某种方式分发或提供修补后的bash ...

  • @Skadz我想我不清楚,所以让我再试一次.一个人不必提供完整的bash.只需要提供内置的读取(比如说GNU/Linux上的ELF代码),它可以在你的包中完成,并通过enable -f <path-to-read-builtin> read加载. (3认同)
  • 由于 read 是内置函数,因此不必严格提供修补过的 bash。相反,我们可以提供一个替代的 _read_ 内置函数。在我看来,更好的方法是将其称为不同的名称,例如_completing_read_。该例程必须针对特定版本的 bash 进行编译,为此您需要 _bash_ 源代码。不太理想,但仍然比修补 bash 更好。 (2认同)
  • 我正在使用的修订代码.到目前为止,它似乎工作:http://bashdb.git.sourceforge.net/git/gitweb.cgi?p = bashdb/bashdb; a = blob; f = builtin/readc.c; h = 085ccc27da9e81ebf6d97590fd1a5bb4f6235ab3; hb = 83f76b9a06268978a4fc58c81d234f80db76a9ae (2认同)

小智 13

我一直在努力解决同样的问题,我认为我有一个有效的解决方案,在我的现实世界中,我正在使用compgen来生成可能的完成.但这是一个说明核心逻辑的例子:

#!/bin/bash

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

设置emacs选项以启用键绑定,将Tab键绑定到函数,更改READLINE_LINE为在提示后更新行,并设置READLINE_POINT为反映该行的新长度.

在我的使用情况下,我竟模仿COMP_WORDS, COMP_CWORDCOMPREPLY变数,但是这应该是足够的了解如何去使用时添加自定义选项卡完成read -ep.

您必须更新READLINE_LINE以更改提示行(完成单一匹配),在提示之前打印到stdin打印,因为readline已将终端置于原始模式并正在捕获输入.


ata*_*ata 11

好吧,似乎我终于难以接受答案,遗憾的是:实际上,当通过"read -e"连接它时,并没有完全支持readline.

答案由BASH维护人员Chet Ramey给出.在这个线程中,解决了完全相同的问题:

我正在编写一个带有命令行解释器的脚本,我可以处理大多数事情(例如历史记录等),除了一件事.文件名完成适用于某些命令,但我想为其他命令使用其他完成选项.从"真正的"命令行运行良好,但我不能让它在我的"read -e,eval"循环中正常工作..

你将无法做到这一点.`read -e'仅使用readline默认完成.

切特

所以,除非我遗漏了某些东西// rant // 同时向程序员猛击"read -e"机制作为完整,正确的CLI用户界面的意思,即使底层机制(readline),功能仍然瘫痪完美地工作并与其余的bash集成 //结束咆哮//

我已经暴露了问题的那种人在freenode的#bash和被建议尝试与像的ReadLine包装rlferlwrap.

最后,我昨天通过邮件联系了Chet,他确认这是设计的,并且他不想将它作为可编程完成的唯一用例更改为"read",即向命令列表中显示脚本用户,看起来不是一个令人信服的理由花时间在这上面工作.尽管如此,他表示如果有人真正开展工作,他肯定会看结果.

恕我直言,没有考虑到能够用简单的5行代码建立一个完整的CLI的能力,这是许多语言中的一个愿望,这是一个错误.

在这种情况下,我认为西蒙的答案非常出色并且恰到好处.我会尝试按照你的步骤,也许有一些运气,我会得到更多的信息.已经有一段时间了,因为我不会攻击C,但我认为我必须掌握的代码量将不会是微不足道的.但无论如何我会尝试.

  • 添加了一个bash-4.1源补丁,似乎适用于我上面的答案. (3认同)