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

bash和readline:用户输入循环中的制表符完成?,bash,bash-completion,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='__' allow

我正在制作一个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
澄清:在Bash4中制作和测试

因此,“read-e”提供了readline功能,我可以调用命令、编辑输入行等。我不能以任何方式做的是让readline的制表符完成工作

我尝试了两件事:

  • 应该如何做:使用bash内置的“complete”和“compgen”,这两个命令会被报告为工作更新:它不会在脚本中工作


  • 为什么readline在脚本中使用“complete”时不能正常运行?当我在互动模式下从bash尝试它时,它会工作…

    在尝试了一个我知道有效的自定义完成脚本(我每天都使用它)后,遇到了相同的问题(当装配它时与您的类似),我决定窥探bash 4.1源代码,并在
    bash-4.1/builtins/read.def:edit_line()中找到了这个有趣的块

    似乎在调用
    readline()
    之前,它会将completion函数重置为null,原因可能只有bash黑客长胡子才知道。因此,使用内置的
    read
    执行此操作可能只是被硬编码为禁用

    编辑:更多信息:在
    读取
    内置中停止完成的包装代码发生在bash-2.05a和bash-2.05b之间。我在该版本的
    bash-2.05b/CWRU/changelog
    文件中发现了此注释:

    • edit_line(由read-e调用)现在只通过将rl_尝试的_completion_函数设置为NULL来完成readline的文件名完成,因为例如,对行上的第一个字执行命令完成并不是真正有用的
    我认为这是一个遗留的疏忽,由于可编程完成已经走过了很长的一段路,您所做的是有用的。也许你可以要求他们重新添加,或者自己修补,如果这对你的工作是可行的话

    恐怕除了您目前提出的解决方案之外,我没有其他解决方案,但至少我们知道为什么它不能与
    read
    一起使用

    EDIT2:好的,我刚刚测试了一个补丁,它似乎“起作用”。通过所有单元和注册表测试,并在使用修补bash运行时显示脚本的输出,如您所料:

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

    请记住,经过修补的bash必须分发或以某种方式提供给任何使用您的脚本的地方……

    好吧,看来我终于找到了答案,遗憾的是:实际上,通过“read-e”与readline接口时,readline并没有得到完全支持

    BASH维护人员Chet Ramey给出了答案。同样的问题也被提到:

    我正在用命令行解释器编写一个脚本,我可以完成大多数事情 工作(如历史等),除了一件事。文件名完成 对于某些命令来说效果很好,但是我想使用其他的补全 其他人的选择。从“真实”命令行可以很好地工作,但我不能 让它在我的“read-e,eval”循环中正常工作

    你做不到`read-e'仅使用readline默认值 完成

    切特

    因此,除非我遗漏了什么//rant//bash将“read-e”机制作为完整、正确的CLI用户接口的手段交给程序员,否则该功能将被削弱,即使底层机制(readline)可以正常工作并与bash的其余部分完美集成//end rant//

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

    最后,我昨天通过邮件联系了切特本人,他确认这是设计的,他不想将其作为可编程完成的唯一用例更改为“读取”,即向脚本用户提供一个命令列表,这看起来并不是花时间做这件事的令人信服的理由。不过,他表示,如果有人真的做了这项工作,他肯定会看看结果

    IMHO没有考虑到使用5行代码实现完整CLI的能力的价值,这在很多语言中都是可能的,这是一个错误


    在这种情况下,我认为西蒙的答案是明智和正确的。我将尝试按照你的步骤,也许运气好的话,我会得到更多的信息。然而,我已经有一段时间没有使用C语言了,我假设我必须掌握的代码数量不会是微不足道的。但无论如何,我会试试。

    如果你要付出那么多的努力,为什么不增加一两把叉子的成本,使用一些能够提供你想要的一切的东西呢

    或者正如手册页所说:

    在shell脚本中,在一个脚本中使用
    rlwrap
    −“放炮”模式代替
    read


    我不确定这是否准确地回答了OP的问题,但我正在搜索可以使用哪个命令,以获得已知可执行命令的默认
    bash
    tab完成(按照
    $PATH
    ),如按tab键时所示。由于我第一次被带到这个问题(我认为是相关的),我想我应该在这里贴一张便条

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

    。。。这正是我想要的
    :)

    希望这对某人有所帮助,

    干杯

    我已经为同一问题挣扎了一段时间,我想我有一个可行的解决方案,在我的现实世界中,我正在使用compgen生成可能的补全。但这里有一个例子说明了核心逻辑:

    #!/bin/bash
    
    set -o emacs;
    tab() {
      READLINE_LINE="foobar"
      READLINE_POINT="${#READLINE_LINE}"
    }
    bind -x '"\t":"tab"';
    read -ep "$ ";
    
    设置emacs选项以启用键绑定,将tab键绑定到函数,更改
    READLINE\u LINE
    以在提示后更新该行,并设置
    READLINE\u POINT
    以反映该行新的较长长度

    在我的用例中,我采取行动
    --- 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;
    
    #!/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 )
    
    order=$(rlwrap -p Yellow -S 'Your pizza? ' -H past_orders -P Margherita -o cat)
    
    $ lua<TAB> lua lua5.1 luac luac5.1 lualatex luatex luatools
    $ compgen -c lua
    luac
    lua5.1
    lua
    luac5.1
    luatex
    lualatex
    luatools
    
    #!/bin/bash
    
    set -o emacs;
    tab() {
      READLINE_LINE="foobar"
      READLINE_POINT="${#READLINE_LINE}"
    }
    bind -x '"\t":"tab"';
    read -ep "$ ";