【问题标题】:How to write a bash completion script for all executables on path?如何为路径上的所有可执行文件编写 bash 完成脚本?
【发布时间】:2015-06-08 14:22:25
【问题描述】:

我已经为我编写或修改的几个不同的脚本提出了这个用例。本质上,我希望选项“-x”的 bash 完成以完成 PATH 上的可执行文件。这是一个包含两个问题的问题。

到目前为止,我遇到了一些麻烦,因为 bash 不能轻易地区分别名、内置函数、函数等和 PATH 上的可执行文件。 /usr/share/bash-completion/bash_completion 中的_commands 包装函数完成了上述所有操作,但我没有使用别名、内置函数、函数等,只想完成碰巧的命令PATH 上的可执行文件。

例如...如果我输入scriptname -x bas[TAB],它应该以base64、bash、basename、bashbug 完成。

这是我的完成脚本现在的样子:

_have pygsparkle && {

_pygsparkle(){
    local cur prev

    COMPREPLY=()
    cur=${COMP_WORDS[COMP_CWORD]}
    prev=${COMP_WORDS[COMP_CWORD-1]}

    case $prev in
    -x|--executable)
        # _command
        executables=$({ compgen -c; compgen -abkA function; } | sort | uniq -u)
        COMPREPLY=( $( compgen -W "$executables" -- "$cur" ) )
        return 0
        ;;
    esac

    if [[ $cur = -* ]]; then
        COMPREPLY=( $( compgen -W '--executable -h --help -x' -- "$cur" ) )
    fi

}

complete -F _pygsparkle pygsparkle

}

它似乎按预期工作,但{ compgen -c; compgen -abkA function; } | sort | uniq -u 是一个非常肮脏的黑客。在 zsh 中,您可以获得运行 print -rl -- ${(ko)commands} 的 PATH 上的可执行文件的排序列表。所以看来我至少缺少 60 多个 exec,可能是因为 uniq -u 正在转储与别名或函数同名的 exec。

有没有更好的方法来做到这一点?是获取 PATH 上所有可执行文件的更好命令,还是服务于相同目的的预先存在的完成函数?

更新: 好的,所以下面的函数在 1/6 秒内执行,看起来是最好的选择。除非有任何其他建议,否则我可能会直接关闭问题。

_executables(){
    while read -d $'\0' path; do
        echo "${path##*/}"
    done < <(echo -n "$PATH" | xargs -d: -n1 -I% -- find -L '%' -maxdepth 1 -mindepth 1 -type f -executable -print0 2>/dev/null) | sort -u
}

【问题讨论】:

  • 如果您在末尾设置: 可选,sed 命令将起作用:sed -r 's/([^:]+):*/find "\1" -maxdepth 1 -type f -executable -exec basename "{}" \\;\n/eg' &lt;&lt;&lt; $PATH | sort | uniq ... 但是,您的 bash 尝试看起来不错。那有什么问题?表现如何?
  • 运行大量的 find 命令来完成在实践中似乎并不能很好地工作,每个 [TAB] 都会冻结终端几秒钟。只是希望已经有一个完成功能或选项可以做得更好,否则我可能不得不废弃它。
  • 我真的不明白compgen -A function -abck 有什么问题?
  • compgen -A function -abck 包括各种命令(别名、内置命令、函数、关键字、PATH 上的可执行文件)。我只想要路径上的可执行文件。例如,其中一个脚本是 pygmentize 的包装器。我希望能够轻松地对已安装的脚本进行语法突出显示。让别名或关键字完整会适得其反。无论如何,这只是一个例子,但我在很多情况下都遇到过。
  • 然后只使用compgen -c..

标签: linux bash shell completion


【解决方案1】:

您似乎在寻找compgen -c

【讨论】:

  • 我尝试了上面的命令,它实际上得到了一些不应该出现的权限错误。运行这个命令sed -r 's/([^:]+):/find "\1" -maxdepth 1 -type f -executable -exec basename {} \\;\n/eg' &lt;&lt;&lt; $PATH | sort | uniq | wc -l 得到2929。我考虑使用的另一个命令是find ${PATH//:/ } -maxdepth 1 -executable -exec basename '{}' \; 2&gt;/dev/null | sort -u | wc -l,我得到3701,这是正确的数字。这两个命令都需要大约 3-4 秒,所以不是很好,但至少看起来很准确。
  • 嗯,我明白了,在我的机器上也是如此。 (顺便说一句,你的 bash 命令比我的 sed 方法好得多!看起来我玩得太多了 sed
【解决方案2】:

对于如何列出用户 PATH 上所有可用的可执行文件的问题,似乎没有一个简单的答案。唉,我已经到处寻找答案了。

compgen 乍一看似乎是正确的方向,但它缺少仅显示可执行文件的选项

compgen -c 将显示所有命令(包括别名、内置命令、可执行文件、函数和关键字)

compgen -abkA function 将显示所有命令除了可执行文件

因此,可以通过“区分”两者来推测可用可执行文件的近似值,即{ compgen -c; compgen -abkA function; } | sort | uniq -u,但这显然存在一些问题。

注意:要确认compgen -c 不起作用,您所要做的就是扫描结果并识别许多不可执行的条目。您也可以尝试diff -u &lt;(compgen -A function -abck | sort -u) &lt;(compgen -c | sort -u) 并查看命令是否相同(当然,一旦删除重复项)。

所以似乎最好的选择是扫描路径上的每个目录。

总而言之,以下是现代系统上的最佳选择。它具有与compgen -c 相当的快速运行时间(约0.05 秒)并且适合完成。

executables(){
    echo -n "$PATH" | xargs -d: -I{} -r -- find -L {} -maxdepth 1 -mindepth 1 -type f -executable -printf '%P\n' 2>/dev/null | sort -u
}

如果您使用的是古老版本的 find/xargs(即 busybox 或 BSD)和/或使用不支持进程替换的 mksh(Android 的默认 shell),您将需要以下内容。没那么快(~3.5 秒)。

executables(){
    find ${PATH//:/ } -follow -maxdepth 1 -type f -exec test -x '{}' \; -print0 2>/dev/null \
    | while read -d $'\0' path; do
        echo "${path##*/}"
    done \
    | sort -u
}

如果您使用上述设置并且由于某种原因在 PATH 中有空格,那么这应该可以工作。

executables(){
    echo "$PATH" \
    | tr ':' '\n' \
    | while IFS= read path; do
        find "$path" -follow -maxdepth 1 -type f -exec test -x '{}' \; -print0 2>/dev/null
    done \
    | while read -d $'\0' path; do
        echo "${path##*/}"
    done \
    | sort -u
}

【讨论】:

    【解决方案3】:

    我有一个run-in-background script。为了获得自动完成,我使用 bash 的内置 complete:

    > complete -c <script-name>
    > <script-name> ech[TAB]
    > <script-name> echo 
    

    来自the docs

    command
       Command names. May also be specified as -c.
    

    为了处理非可执行文件,我的第一个想法是使用whichtype -P 进行过滤,但这非常慢。更好的方法是只检查未知数。使用@Six 的解决方案或其他解决方案在compgen -c 中查找项目,而不是在compgen -abkA function 中查找项目。对于这两个列表中的所有内容,请与type -P 联系。例如:

    function _executables {
        local exclude=$(compgen -abkA function | sort)
        local executables=$(
            comm -23 <(compgen -c) <(echo $exclude)
            type -tP $( comm -12 <(compgen -c) <(echo $exclude) )
        )
        COMPREPLY=( $(compgen -W "$executables" -- ${COMP_WORDS[COMP_CWORD]}) )
    }
    complete -F _executables <script-name>
    

    我已经尝试了几次,但将-F 引入complete 似乎是一种缓慢的做事方式。更不用说现在必须处理可执行文件的绝对/相对路径完成、非常繁琐的完成目录但不添加空格位以及正确处理路径中的空格。

    【讨论】:

    • complete -c 提供与compgen -c 相同的补全,包括所有上述不受欢迎的东西,如别名、内置函数、函数和关键字。给你一个真实的例子,就在上周,我正在编写一些包装脚本,一个用于在运行某些命令时启用 XFCE 的演示模式(禁止屏幕保护程序和挂起),而另一个只禁止挂起。如果我使用complete -c presentation-wrapper,我会得到一堆不需要的补全,比如commanddeclaredonewhile。我想它们对您的脚本也有问题。
    • @Six Aah 对,感谢您对我的耐心!我已经尝试过了,但仍然有 0.1-0.2 秒的延迟,还有很多边缘情况需要处理。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-18
    • 2010-11-11
    • 1970-01-01
    • 2023-01-18
    相关资源
    最近更新 更多