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

时间:2011-01-18 16:52:46

标签: 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

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

所以,“read -e”给出了readline功能,我可以调用命令,编辑输入行等。我不能做的任何方式是让readline的标签完成工作!!

我尝试了两件事:

  1. 如何应该这样做:使用bash内置的“完整”和“compgen”,据报道它可以正常工作here 更新:它没有报告在脚本中工作

  2. This ugly workaround

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

5 个答案:

答案 0 :(得分: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;

似乎在调用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。< / p>

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垃圾,但这至少会让您的脚本正常运行。

修补程序:

--- 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 ...

答案 1 :(得分:13)

我一直在努力解决同样的问题,我认为我有一个有效的解决方案,在我的现实世界中,我正在使用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以反映该行的新长度。< / p>

在我的用例中,我实际上模仿了COMP_WORDS, COMP_CWORDCOMPREPLY变量,但这应该足以让我们了解在使用read -ep时如何添加自定义标签页。

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

答案 2 :(得分:11)

好吧,似乎我终于难以接受答案了,遗憾的是:实际上,当通过&#34; read -e&#34;来连接readline时,它并不完全支持readline。

答案由BASH维护者Chet Ramey给出。在this thread中解决了完全相同的问题:

  
    

我用命令行解释器编写脚本,我可以做大多数事情     工作(例如历史等)除了一件事。文件名完成     适用于某些命令,但我想使用其他完成     别人的选择。适用于&#34;真实&#34;命令行,但我不能     让它在我的&#34; read -e,eval&#34;中正常工作环..

  
     

你无法做到这一点。 `read -e&#39;仅使用readline默认值   完井。

     

切特

所以,除非我错过了某些内容//咆哮// 而bash手给程序员&#34; read -e&#34;机制作为完整,适当的CLI用户接口的平均值,即使底层机制(readline)工作并且与其余的bash完美地集成 // end rant //

,该功能也会瘫痪

我已经向freenode中#bash的好心人提出了这个问题,并建议尝试使用像rlferlwrap这样的Readline包装。

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

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

在这种情况下,我认为西蒙的答案非常棒,而且恰到好处。我会尝试按照您的步骤进行操作,也许运气好的话我会获得更多信息。已经有一段时间了,因为我不会在C中进行攻击,并且我认为我必须掌握实施的代码量并不是微不足道的。但无论如何我会尝试。

答案 3 :(得分:2)

如果你要付出那么多努力,为什么不只是增加一两个叉子的成本并使用能够提供你想要的东西的东西。 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 )

或者正如手册页所说:

在shell脚本中,在“一次性”模式下使用rlwrap作为读取

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

答案 4 :(得分:1)

我不确定这是否完全解决了OP问题 - 但我正在搜索可以使用哪个命令,以获取已知可执行命令的默认bash选项卡完成(按$PATH ),如按 TAB 时所示。自从我第一次被引导到这个问题(我认为这是相关的)以来,我想我会在这里发一个注释。

例如,在我的系统上,键入lua然后 TAB 会给出:

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

事实证明,有一个bash内置(请参阅#949006 Linux command to list all available commands and aliases),名为compgen - 我可以使用与lua相同的字符串$ compgen -c lua luac lua5.1 lua luac5.1 luatex lualatex luatools 来提供它交互式案例,并获得与我按下 TAB 时相同的结果:

:)

......这正是我所寻找的{{1}}

希望这有助于某人,
干杯!