bash 和 readline:用户输入循环中的选项卡完成?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4726695/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-17 23:17:15  来源:igfitidea点击:

bash and readline: tab completion in a user input loop?

bashbash-completion

提问by ata

I'm making a bash script which presents a command line to the user.

我正在制作一个向用户显示命令行的 bash 脚本。

The cli code is as this:

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

Clarification: made and tested in Bash 4

说明:在 Bash 4 中制作和测试

So, "read -e" gives readline capabilities, i can recall commands, edit the input line, etc. What i cannot do in any wayis to have readline's tab completion to work!!

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

I tried two things:

我尝试了两件事:

  1. How it should be supposedly done: using the bash builtins "complete" and "compgen", which is reported to work hereUpdate: it's not reported to work in scripts.

  2. This ugly workaround

  1. 应该如何完成:使用 bash 内置的“完整”和“compgen”,据报道可以在这里工作更新:据报道它在脚本中无法工作

  2. 这个丑陋的解决方法

Why doesn't readline behave correctly when using "complete" inside the script? it works when i try it from bash in interactive mode...

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

采纳答案by Sdaz MacSkibbons

After trying a custom completion script that I knowworks (I use it every day) and running into the same issue (when rigging it up similar to yours), I decided to snoop through the bash 4.1 source, and found this interesting block in bash-4.1/builtins/read.def:edit_line():

在尝试了一个我知道有效的自定义完成脚本(我每天都使用它)并遇到了同样的问题(当它与你的类似)之后,我决定窥探 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;

It appears that before readline()is called, it resets the completion function to null for some reason that only a bash-hacking long beard might know. Thus, doing this with the readbuiltin may simply be hard-coded to be disabled.

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

EDIT: Some more on this: The wrapping code to stop completion in the readbuiltin occurred between bash-2.05a and bash-2.05b. I found this note in that version's bash-2.05b/CWRU/changelogfile:

编辑:有关此内容的更多信息:read在 bash-2.05a 和 bash-2.05b 之间停止内置完成的包装代码。我在那个版本的bash-2.05b/CWRU/changelog文件中找到了这个注释:

  • edit_line (called by read -e) now just does readline's filename completion by setting rl_attempted_completion_function to NULL, since e.g., doing command completion for the first word on the line wasn't really useful
  • edit_line(由 read -e 调用)现在只是通过将 rl_attempted_completion_function 设置为 NULL 来完成 readline 的文件名完成,因为例如,对行上的第一个单词进行命令完成并没有真正有用

I think it's a legacy oversight, and since programmable completion has come a long way, what you're doing is useful. Maybe you can ask them to add it back in, or just patch it yourself, if that'd be feasible for what you're doing.

我认为这是一个遗留问题,并且由于可编程完成已经走了很长一段路,你正在做的事情很有用。也许你可以要求他们重新添加它,或者自己修补它,如果这对你正在做的事情可行的话。

Afraid I don't have a different solution aside from what you've come up with so far, but at least we know whyit doesn't work with read.

恐怕除了您迄今为止提出的解决方案之外,我没有其他解决方案,但至少我们知道为什么它不适用于read.

EDIT2: Right, here's a patch I just tested that seems to "work". Passes all unit and reg tests, and shows this output from your script when run using the patched bash, as you expected:

EDIT2:是的,这是我刚刚测试过的似乎“有效”的补丁。通过所有单元和注册测试,并在使用修补的 bash 运行时显示脚本的输出,如您所料:

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

As you'll see, I just commented out those 4 lines and some timer code to reset the rl_attempted_completion_functionwhen read -tis specified and a timeout occurs, which is no longer necessary. If you're going to send Chet something, you may wish to excise the entirety of the rl_attempted_completion_functionjunk first, but this will at least allow your script to behave properly.

正如你所看到的,我只是注释掉这4条线和一些计时器代码重置rl_attempted_completion_functionread -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;

Keep in mind the patched bash would have to be distributed or made available somehow wherever people would be using your script...

请记住,打补丁的 bash 必须以某种方式分发或以某种方式提供给人们使用您的脚本的任何地方...

回答by muji

I've been struggling with same issue for some time now and I think I have a solution that works, in my real world case I'm using compgen to generate possible completions. But here is an example that illustrates the core logic:

一段时间以来,我一直在为同样的问题苦苦挣扎,我想我有一个可行的解决方案,在我的现实世界中,我使用 compgen 来生成可能的完成。但这是一个说明核心逻辑的示例:

#!/bin/bash

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

Set the emacs option to enable key binding, bind the tab key to a function, change READLINE_LINEto update the line after the prompt, and set READLINE_POINTto reflect the line's new longer length.

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

In my use case I actually mimic the COMP_WORDS, COMP_CWORDand COMPREPLYvariables but this should be sufficient to understand how to go about adding custom tab completion when using read -ep.

在我的用例中,我实际上模仿了COMP_WORDS, COMP_CWORDCOMPREPLY变量,但这应该足以了解如何在使用read -ep.

You must update READLINE_LINEto change the prompt line (completion single match), printing to stdin prints before the prompt as readline has put the terminal in raw mode and is capturing input.

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

回答by ata

Well, it seems i finally stumped on the answer, and it sadly is: actually there isn't full support for readline when interfacing it via "read -e".

好吧,看来我终于难倒了答案,可悲的是:实际上,通过“read -e”连接时并没有完全支持 readline。

The answer is given by the BASH maintainer, Chet Ramey. In this threadthe exact same issue is addressed:

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

I'm writing a script with a command line interpreter and I can most things working (eg. history etc.) except for one thing. The filename completion works well for some of the commands, but I'd like to use other completion options for others. Works well from the "real" command line, but I can't get it to work properly in my "read -e, eval" loop..

You won't be able to do it. `read -e' uses only the readline default completions.

Chet

我正在用命令行解释器编写脚本,除了一件事之外,我可以处理大多数事情(例如历史记录等)。文件名补全适用于某些命令,但我想对其他命令使用其他补全选项。从“真实”命令行运行良好,但我无法让它在我的“read -e,eval”循环中正常工作..

你将无法做到。`read -e' 只使用 readline 默认补全。

切特

So, unless i'm missing something //rant// while bash hands to the programmer the "read -e" mechanism as the mean for full, proper CLI user interfacing, the functionality is crippled, even though the underlying mechanism (readline) works and integrates with the rest of bash flawlessly//end rant//

因此,除非我遗漏了一些东西 //rant//而 bash 将“read -e”机制作为完整、正确的 CLI 用户界面的意思,即使底层机制(readline)可以完美地工作并与 bash 的其余部分集成// 结束咆哮 //

I have exposed the question to the kind folks at #bash in freenode and been suggested to try with a Readline wrapper like rlfeor rlwrap.

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

Finally, i contacted Chet himself by mail yesterday, and he confirmed that this is by design, and that he doesn't feel like changing it as the only use case for programmable completion into "read", i.e. presenting a list of commands to the script user, doesn't look like a compelling reason to spend time working on this. Nevertheless he expressed that in case someone actually does the work he would certainly look at the result.

最后,我昨天通过邮件联系了 Chet 本人,他确认这是设计使然,并且他不想将其更改为可编程完成到“读取”的唯一用例,即向脚本用户,这似乎不是花时间在此工作的令人信服的理由。不过他表示,如果真的有人做,他肯定会看结果。

IMHO, not considering worth of the effort the ability to bring up a full CLI with just 5 lines of code, something one wish were possible in a lot of languages, is a mistake.

恕我直言,不考虑用 5 行代码就可以启动一个完整的 CLI 的能力是一个错误,这是一个错误。

In this context, i think Simon's answer is brilliant and right in place. I'll try to follow your steps and perhaps with some luck i'll get more info. Been a while since i don't hack in C however, and i assume the amount of code i'll have to grasp to implement will not be trivial. But anyway i'll try.

在这种情况下,我认为西蒙的回答非常出色且恰到好处。我会尝试按照您的步骤进行操作,也许运气好的话我会得到更多信息。然而,自从我不使用 C 语言以来已经有一段时间了,而且我认为我必须掌握的代码量来实现不会是微不足道的。但无论如何我会努力的。

回答by Orwellophile

If you're going to that much effort, why not just add the cost of a fork or two and use something that is more than capable of providing everything you want. http://utopia.knoware.nl/~hlub/rlwrap/rlwrap.html

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

Or as the man page puts it:

或者正如手册页所说:

In a shell script, use rlwrap in 'one?shot' mode as a replacement for read

在 shell 脚本中,在“one?shot”模式下使用 rlwrap 作为 read 的替代

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

回答by sdaau

I'm not sure if this exactly answers the OP question - but I was searching for which command could one use, to obtain the default bashtab completion of known executable commands (as per $PATH), as shown when pressing TAB. Since I was first led to this question (which I think is related), I thought I'd post a note here.

我不确定这是否完全回答了 OP 问题 - 但我正在搜索可以使用哪个命令,以获得bash已知可执行命令的默认选项卡完成(按$PATH),如按 时所示TAB。由于我第一次被引到这个问题(我认为这是相关的),我想我会在这里张贴说明。

For instance, on my system, typing luaand then TABgives:

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

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

It turns out, there is a bashbuilt-in (see #949006 Linux command to list all available commands and aliases), called compgen- and I can feed it with the same string luaas in the interactive case, and obtain the same results as if I pressed TAB:

事实证明,有一个bash内置的(请参阅#949006 Linux 命令以列出所有可用的命令和别名),称为compgen- 我可以使用与lua交互式案例中相同的字符串提供它,并获得与我相同的结果按下TAB

$ compgen -c lua
luac
lua5.1
lua
luac5.1
luatex
lualatex
luatools

... and that is exactly what I was looking for :)

……这正是我要找的 :)

Hope this helps someone,
Cheers!

希望这对某人
有所帮助,干杯!