Bash命令完成,并将完整路径扩展注入到vim的历史记录中

时间:2019-02-15 13:34:38

标签: bash readline bash-completion

我花了整整一周的时间在网上进行搜索,并尝试了多种解决棘手问题的方法。基本上,我想使用vim编辑$ PATH中的自定义命令/脚本,而不必先实际cd到其给定目录或手动在命令行中键入它们的完整路径

本质上,当将$ PATH中的脚本指定为vim文件参数时,我希望能够将stock bash命令完成(compgen -c)与同时路径扩展结合起来。顺便说一句,我正在用帽子来阐明什么是棘手的话题,而不是大喊大叫。

向您展示我正在尝试做的事情然后解释它可能会更容易。可以说我在$ PATH上的目录中有脚本

~/bin/x/y/cmd1.sh
~/bin/a/b/cmd2.sh
/ppp/n/m/cmd3.sh

有时这些脚本为存在于其他目录中的文件提供功能,因此我希望能够从文件系统中的任何位置轻松对其进行编辑。有时我只是想能够从其他目录编辑那些脚本,因为它更方便。可以说我当前在以下目录中。

/completely/different/dir

但是现在我需要进行vim编辑

~/bin/a/b/cmd2.sh

我唯一要使用默认bash功能实现此目标的选项是执行以下一项耗时较长的操作

cd ~/bin/a/b/; vim cmd.sh
vim ~/<tab-complete-my-way-to-file>
open a new terminal window plus some combination of the above

因为我知道我的自定义脚本的名称,所以只需执行以下操作就容易得多,这不需要对文件或目录的完整路径进行制表符补全,也不需要对其他目录进行cd更改我的情况!!!

vim cmd2.sh

但这在默认情况下不起作用b / c vim需要脚本的完整路径

我的第一个想法是编写一个vim包装函数,该函数基本上使用which为我执行$ PATH扩展,然后将bash命令完成与我的vc函数绑定在一起,如下所示:

vc () { vim $(which "$@"); }
complete -c vc

我可以在外壳程序中运行以下命令,以完成cmd1.sh, cmd2.sh, cmd3.sh

中以“ c”开头的部分脚本名称。
vc c<tab>

直到我得到我想要的东西,这很棒

vc cmd2.sh

当我按Enter键并执行命令时,一切正常,但不会将扩展路径注入READLINE命令行中,因此'cmd2.sh'的完全扩展路径永远不会在我的命令中消失历史!我的历史将显示此内容

vc cmd2.sh

代替

vc ~/bin/a/b/cmd2.sh

或     vim〜/ bin / a / b / cmd2.sh

我希望在命令历史记录中使用扩展路径,因为在重用命令历史记录时,该脚本文件的将来操作非常容易。即我可以ls,file,diff,mv,cp来扩展路径,而重用历史记录要比为ls,file,diff,mv,cp等编写更多包装器脚本容易得多。就像我上面的vc一样。

问题:

选项1

有一种方法可以将我的vc函数中由which 提供的完整扩展路径直接重新注入到原始vc READLINE中,或者仅注入实际上得到的整个“ vim”命令在vc中执行,以代替进入READLINE的原始vc命令?任何可以使我将扩展的vim命令添加到历史记录中的方法,即使它是对原始vc命令的补充,也可以。

基本上,您如何在bash中以编程方式访问和编辑当前READLINE?

选项2

请注意,我也可以在命令行上直接直接执行类似的操作

vim $(which cmd2.sh) C-x-e 

这给了我我想要的(它扩展了将其放入历史的路径),但是我必须始终在每次迭代时都键入额外的子外壳以及文本和Cxe(以扩展行)。命令,同时失去了命令完成功能,这基本上使该命令无用。换句话说,是否有使用绑定键自动执行上述操作,以便

vc cmd2.sh 

首先自动转换为

vim $(which cmd2.sh) 

,然后自动跟进C-x-e,以便将其扩展到

vim ~/bin/a/b/cmd2.sh

但是所有编辑动作,文本插入和最终命令行扩展是否都发生在同一个bindkey宏中?这可能是最好的解决方案。

选项3

或者,由于bash命令完成自动在READLINE中自动结束,因此历史记录也自动结束,因此自定义完成功能可以解决我的问题。有没有办法使vc使用一个完成函数,当它如上所述用作vim参数时,会同时在$ PATH中同时完成两个命令?
并同时将它们扩展到其完整路径?

我知道如何编写基本的完成功能。无数小时的尝试(我选择不放在此处以保持混乱/减少发布时间)失败了,原因很简单,因为我不确定命令完成是否与同时全路径扩展b / c兼容,从而破坏了传统完成。

具有自定义完成功能,当我尝试找到位于“ vim〜/ bin / a / b / cmd2.sh”中的脚本“ cmd2.sh”但以“ c”开头时,会发生以下情况点击“”。

vim c<tab>

不是让我来完成这些选择

cmd1.sh   cmd2.sh   cmd3.sh

它会完成它在$ PATH中找到的第一个,并将其插入到可能为

的READLINE中。
/ppp/n/m/cmd3.sh

当我真正想要的时候

~/bin/a/b/cmd2.sh

这有效地终止了完成查找,因为在READLINE中,我光标之前的词现在以/ppp/n/m/cmd3.sh开头,无法返回到cmd2.sh

我希望这很清楚。

谢谢

2 个答案:

答案 0 :(得分:1)

这需要您的.bashrc文件中有一些样板,但可能对您有用。它利用了目录栈(有人可能会说滥用目录栈,但是如果您不将其用于其他任何事情,那可能没问题)。

在您的.bashrc中,将每个感兴趣的目录添加到目录堆栈中。以您的主目录结束列表,因为pushd还会更改您的当前工​​作目录。

pushd ~/bin/x/y/cmd1.sh
pushd ~/bin/a/b/cmd2.sh
pushd /ppp/n/m/cmd3.sh
pushd ~

是的,它有点重复了您的PATH条目,但是我认为您实际上不需要访问PATH中的每个目录,只需访问您所在的目录有您要编辑的文件。 (您真的要尝试编辑/bin/usr/bin中的任何内容吗?)

现在,在交互式外壳中,您可以运行dirs -v来查看堆栈中的目录及其索引:

$ dirs -v
0 ~
1 /ppp/n/m
2 ~/bin/a/b
3 ~/bin/x/y
4 ~

现在,无论您身在何处,如果要编辑~/bin/x/y/cmd1.sh,都可以使用

$ vi ~3/cmd3.sh

只要您不在其他地方使用popdpushd来修改堆栈,索引将保持不变。 (使用pushd将在堆栈顶部添加一个新目录,从而增加每个索引; popd将在删除顶部目录后减少每个索引。)


一个简单得多的过程是简单地定义一些变量,这些变量的值是所需的目录:

binab=~/bin/a/b
binxy=~/bin/x/y
ppp=/ppp/n/m

然后简单地展开它们

$ vi $ppp/cmd3.sh

shell执行参数名称的完成,因此变量名称不必特别短,但是dirstack方法保证您只需要2或3个字符。 (而且,它不会用其他变量污染全局名称空间。)

答案 1 :(得分:0)

有趣的是,我发现自己想做类似的事情。我一起整理了以下bash脚本。这是不言自明的。如果要编辑我的一个脚本(例如,这个脚本是~/bin/vm),则只需运行vm vm。我可以在路径中打开多个文件,无论是在缓冲区中还是在垂直/水平拆分中……

随便使用它,然后将其粘贴到此处,因为它们都可以使用了:

#!/usr/bin/env bash

Usage() {
    cat <<-__EOF_
${0##*/} Opens scripts in PATH from any location (vim -O)
    Example: ${0##*/} ${0##*/}
                opens this script in vim editor
    -o: Change default behaviour (vim -O) to -o
    -b: Change default behaviour to open in buffers (vim file1 file2)
    -h: Display this message
__EOF_
}

flag="O"

vimopen() {
    local wrapped
    local located
    local found
    found=false
    [ $# -lt 1 ] && echo "No script given" && return
    wrapped=""
    for arg in "$@"; do
        if located=$(which "${arg}" 2> /dev/null); then
            found=true
            wrapped="${wrapped} ${located}"
        else
            echo "${arg} not found!"
        fi
    done
    $found || return
    # We WANT word splitting to occur here
    # shellcheck disable=SC2086
    case ${flag} in
        O)
            vim $wrapped -O
            ;;
        o)
            vim $wrapped -o
            ;;
        *)
            vim $wrapped
    esac
}

while getopts :boh f; do
    case $f in
        h)
            Usage
            exit 0
            ;;
        o)
            flag="o"
            shift
            ;;
        b)
            flag=""
            shift
            ;;
        *)
            echo "Unknown option ${f}-${OPTARG}"
            Usage
            exit 1
            ;;
    esac
done
vimopen "$@"