如何在 Bash 中使用花括号时获得制表符补全

如何在 Bash 中使用花括号时获得制表符补全

我希望在 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功能,这可能是也可能不是限制,具体取决于使用情况。

相关内容