【问题标题】:Bash command completion with full path expansion injected into history for vim带有完整路径扩展的 Bash 命令完成注入 vim 的历史记录
【发布时间】:2019-02-15 13:34:38
【问题描述】:

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

本质上,当将我的 $PATH 中的脚本指定为 vim 文件参数时,我希望能够将常用的 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

因为我知道我的自定义脚本的名称,所以只需执行以下操作就容易多了我的语境!!!

vim cmd2.sh

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

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

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

我可以在shell中运行以下命令来完成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 代替原来的vc 命令变成READLINE?任何允许我将扩展的 vim 命令放入历史记录的方法,即使它是除了原始 vc 命令之外的任何方法,我都可以。

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

选项 2

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

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

这给了我我想要的东西(它扩展了路径,然后将其放入历史记录)但我必须在每次迭代时输入额外的子外壳和文本以及 C-x-e(扩展行)命令同时失去了命令完成功能,这基本上使它无用。换句话说,有没有使用绑定键自动执行上述操作,以便

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 的 READLINE 中

/ppp/n/m/cmd3.sh

当我真正想要的时候

~/bin/a/b/cmd2.sh

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

我希望这很清楚。

谢谢

【问题讨论】:

  • 有趣的问题。我个人会对vc 别名感到满意,并且可能将另一个别名添加到cd 到目录中,然后在那里编辑文件。然后,您有两个别名,一个仅用于编辑,另一个用于对文件进行编辑和执行其他操作(现在在本地)。缺点是你需要事先知道你打算做什么,但这可以避免你以同样的方式重载所有其他命令。
  • 一个真正不同的看法是,如果您发现自己需要经常编辑PATH 目录中的脚本,它们就不够通用。你需要的是让脚本更有用,而不是更容易编辑。
  • @chepner 我明白你在说什么,在某些方面你是对的。问题是 1) 一个人正在开发你所说的那些更有用和更通用的脚本,我在这篇文章中要求的内容仍然非常有用和实用,可以帮助你实现目标。 2)我写了很多脚本,其中一些寿命很短,所以花在使它们更通用和有用上的时间不值得额外的投资......也就是说,我想做的事情具有真正的价值和只是解决问题的另一种方式:)
  • @IngoKarkat 我可以做到这一点,但我会在我不记得的太多其他别名首字母缩写词中设置更多别名和函数,所以我从不使用其中的一半;)即只是另一个级别的间接性使我无法轻松实现自动化,因此它可以在许多情况下仅使用一个功能即可重用。谢谢你的建议!

标签: bash readline bash-completion


【解决方案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 中的 every 目录,只需访问您想要的文件所在的目录编辑。 (你真的要尝试编辑/bin/usr/bin 中的任何内容吗?)

现在,在交互式 shell 中,您可以运行 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 个字符。 (另外,它不会用额外的变量污染全局命名空间。)

【讨论】:

  • 感谢@chepner 的提示。变量不会像我想要的那样扩展并放入历史记录中,但是带有 vars 的路径确实允许从历史记录中重用,因此这是一种解决方法。我仍然想知道我的原始解决方案是否可以重新注入 READLINE 或通过完成功能。那将是最无缝的。就个人而言,我可能只是安装 fzf 来完成我的路径并完成此操作,但同样,它不是具有股票 bash readline / complete 的本机解决方案。感谢您的建议!
【解决方案2】:

有趣的是,不久前我发现自己想做类似的事情。我一起破解了以下 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 "$@"

【讨论】:

    猜你喜欢
    • 2011-10-01
    • 2011-06-16
    • 1970-01-01
    • 2020-06-02
    • 1970-01-01
    • 2022-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多