我正在制作一个向用户提供命令行的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的标签完成工作!!
我尝试了两件事:
如何应该这样做:使用bash内置的“完整”和“compgen”,据报道它可以正常工作here 更新:它没有报告在脚本中工作
为什么在脚本中使用“完整”时readline行为不正确?当我在交互模式下从bash尝试它时,它可以工作...
答案 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_CWORD
和COMPREPLY
变量,但这应该足以让我们了解在使用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的好心人提出了这个问题,并建议尝试使用像rlfe或rlwrap这样的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}}
希望这有助于某人,
干杯!