我希望在 bash 中的命令中使用大括号时具有制表符补全功能。
例如:
cp ~/html/{foo bar.txt whatever} /var/www/html/
我想要大括号中指定的文件的制表符补全
答案1
有两种方法可以解释这个问题:您希望在结束前键入名称时完成}
(实际上是在另一个目录中执行文件的完成);或者,您希望完成扩展并替换结束后的(有效)名称}
。{ }
扩展扩展了所有选项,与扩展的 globbing 不同现存的文件名,但使用“,”而不是空格。
{}
使用 bash,创建单词列表的预期方法是:
cp ~/html/{foo,bar.txt,whatever} ...
有两个选项:将 readline 键绑定与 shell 函数相关联,或可编程完成。
readline 方法使您可以完全重写当前命令,但问题是它不会标记当前命令行,因此您会遇到一个不简单的解析问题。
可编程完成做对命令行进行标记,但您只能修改/替换当前单词,这意味着您无法(轻松)path/{
在编辑过程中保留前缀。
唯一相关的本机 bash 功能是bash 声称执行“所有 shell 单词扩展”的readline
函数shell-expand-line
,默认情况下将其绑定到\M-\C-e
(Esc \C-e
)。人们可能合理地期望这种扩展是手册页(bash-4.3)中指示的所有七种扩展类型:
Expansion is performed on the command line after it has been split into words. There are seven kinds of expansion performed: brace expansion, tilde expansion, parameter and variable expansion, command substitu‐ tion, arithmetic expansion, word splitting, and pathname expansion.
您可以通过键入进行实验(无需按回车键)
echo {1..2} ~root $0 $LANG `echo foo` $((1+2) "a b" /etc/p[aeiou]*
然后MetaCtrlE(如果您使用的是元受损键盘,则为ESC+ )。CtrlE
第一个和最后一个(大括号和路径)扩展都不适合我,因此文档和实现与恕我直言不太匹配。
下面是一个解决方法,而不是使用选项卡进行扩展的完整解决方案。不过,您需要确保使用正确的大括号扩展语法,特别是使用逗号来分隔项目,而不是空格。
function _expand() {
[[ -z "${READLINE_LINE}" ]] && return
eval local aa=( ${READLINE_LINE} ) # not-quoted, eval needed
[[ ${#aa} -eq 0 ]] && return # parse problem
printf -v READLINE_LINE "%s " "${aa[@]}"
READLINE_POINT=${#READLINE_LINE} # eol, predictable at least
}
bind -x '"\C-_":_expand'
这绑定Ctrl_到一个 bash 函数,该函数用于eval
填充数组,导致所有正常扩展(如上所述,不包括历史记录)发生,然后重建命令行。
开始打字,Ctrl_当你想要扩展某些内容时点击,Return准备好后按正常键。
这比 tab 更强大,但不太精确,因为它扩展了完整的输入行。在某些情况下,它不会按预期扩展,特别是在各种 shell 元字符混淆简单eval
逻辑的情况下。
Gille 的建议zsh
值得一看,而不是过度设计 bash 解决方案。
答案2
在 Ubuntu (16.04) 终端中,您可以执行Ctrl+操作{,然后{它会使用目录中的文件自动补全大括号。它使用逗号而不是空格来分隔文件。例如,包含以下文件的目录:
file.cpp file.h file.i file.py file_wrap.cxx
...会自动完成:
$ cp file{.{cpp,h,i,py},_wrap.cxx}
答案3
在这种情况下,大括号(根据定义,是“大括号”)指的是用逗号分隔的文本片段列表。虽然它最常用于文件名,但它会被扩展前处理文件名扩展。因此,不存在明确的扩展功能,因为bash
无法知道您是否实际上正在键入文件名。
答案4
我提供了诊断工具,使OP能够直接观察制表符后的行完成行为,并比较返回后的解析行为。经过观察和分析,我们可以得出结论,虽然编写行完成函数来完成大括号功能是可能的,但由于 bash 限制了抑制大括号之后的任何进一步的行完成功能,因此这是毫无意义的。
以下 bash 脚本启用诊断:
#!/bin/bash
confirm_args(){
logger -t "confirm_args" -- "#=${#}"
for index in `seq 0 ${#}` ; do
eval item=\$$index
logger -t "confirm_args" -- "[$index] ${item}"
done
}
_foo_completion(){
logger -t "_foo_completion" -- "COMP_CWORD=${COMP_CWORD}"
logger -t "_foo_completion" -- "#COMP_WORDS=${#COMP_WORDS[@]}"
for index in `seq 0 $((${#COMP_WORDS[@]}-1))` ; do
logger -t "_foo_completion" -- "[$index] ${COMP_WORDS[$index]}"
done
}
declare -f _foo_completion
complete -F _foo_completion "foo"
alias foo=confirm_args
我已将其作为要点提供foo-completion.sh
。
将其复制并粘贴到文件中/tmp/foo-completion.sh
。
创建两个虚拟文件
touch /tmp/a.{1,2}
为了使诊断正常运行,logger
必须写入系统日志。然后OP将能够通过跟踪系统日志文件来查看诊断输出。如果OP正在运行,systemd
可以通过打开一个新窗口并输入来查看
sudo journalctl -f
激活线路完成和诊断输出:
source /tmp/foo-completion.sh
请注意,_foo_completion
不会执行任何补全,因此不会将候选返回给用户。它所做的只是显示哪些数据被传递给行完成函数。
测试一些输入并观察输出:
输入1
foo a.*[SPACE][TAB]
输出
Jul 23 12:10:34 ub18 _foo_completion[17672]: COMP_CWORD=2
Jul 23 12:10:34 ub18 _foo_completion[17673]: #COMP_WORDS=3
Jul 23 12:10:34 ub18 _foo_completion[17675]: [0] foo
Jul 23 12:10:34 ub18 _foo_completion[17676]: [1] /tmp/a.*
Jul 23 12:10:34 ub18 _foo_completion[17677]: [2]
输入2
foo a.*[SPACE][RETURN]
输出
Jul 23 12:19:43 ub18 confirm_args[18487]: #=2
Jul 23 12:19:43 ub18 confirm_args[18489]: [0] bash
Jul 23 12:19:43 ub18 confirm_args[18490]: [1] /tmp/a.1
Jul 23 12:19:43 ub18 confirm_args[18491]: [2] /tmp/a.2
输入3
foo a.{1,2}[SPACE][TAB]
输出
注意 - 无输出!
输入4
foo a.{1,2}[SPACE][RETURN]
输出
Jul 23 12:28:42 ub18 confirm_args[19098]: #=2
Jul 23 12:28:42 ub18 confirm_args[19100]: [0] bash
Jul 23 12:28:42 ub18 confirm_args[19101]: [1] /tmp/a.1
Jul 23 12:28:42 ub18 confirm_args[19102]: [2] /tmp/a.2
分析
在“INPUT 1”中,它*
作为文字字符“*”传递给行完成函数。然而,在“INPUT 3”中,甚至没有调用行完成函数。显然,大括号会导致 bash 抑制对行完成函数的任何进一步调用。
另一方面,在[RETURN]后解析案例中输入2和输入4,bash 扩展这两种情况a.*
并a.{1,2}
得到相同的结果。
还有一个测试用例:
输入5
foo a.{[TAB]
输出
Jul 23 12:56:52 ub18 _foo_completion[21059]: COMP_CWORD=1
Jul 23 12:56:52 ub18 _foo_completion[21060]: #COMP_WORDS=2
Jul 23 12:56:52 ub18 _foo_completion[21062]: [0] foo
Jul 23 12:56:52 ub18 _foo_completion[21063]: [1] /tmp/a.{
分析
在“INPUT 5”中,制表符之前没有空格,bash做呼叫线路完成。因此,理论上,行完成可以返回单个候选者
/tmp/a.{1,2}
这应该会导致自动完成(尽管我还没有测试过)。
这问题那就是更远无法使用行完成添加文件,因为大括号抑制行完成。
使用预先存在的行完成功能进行测试,例如,
输入检查
ls /tmp/a.{[TAB][TAB][TAB][TAB][TAB][TAB]
输出
(没有什么)
似乎支持这样的论点,即不值得在行完成函数中支持花式,除非 bash 不抑制花式后的行完成。
[CTRL]{{
编辑:使用中描述的方法后这个答案,我发现它已完成,并且(有时)能够遵循默认的完成功能来添加更多文件。但是,此后无法继续使用原始_foo_completion
功能,这可能是也可能不是限制,具体取决于使用情况。