Bash 各种操作系统上的完成功能:完成主机时@的问题

Bash completion function on various OSs: problem with @ while completing hosts

我正在编写一个模仿 ssh 命令主机完成的函数;主要区别在于它使用在 ~/.ssh/known_hosts 中找到的 xyz.com 域的主机来完成。

函数如下:

_xyzssh() {
    COMPREPLY=()

    local arg="${COMP_WORDS[COMP_CWORD]}"

    [[ $arg != -* ]] || return 0

    [ -f ~/.ssh/known_hosts ] || return 0

    local -a known_hosts
    read -d '' -a known_hosts < <(awk '{
        n = split(,arr,",");
        for (i = 1; i <= n; i++)
            if (arr[i] ~ /\.xyz\.com$/)
                print arr[i] 
    }' ~/.ssh/known_hosts)

    if [[ $arg == *@* ]]
    then
        known_hosts=( ${known_hosts[@]/#/${arg%%@*}@} )
    fi

    COMPREPLY=( $(compgen -W "${known_hosts[*]}" -- $arg) )

    return 0
}
complete -F _xyzssh xyzssh

此代码在 Linux (bash 4.2) 中运行良好,但在 macOS (bash 3.2) 中有一个奇怪的行为。例如,当我键入:

xyzssh -X user@server 选项卡

我猜 @ 有问题,因为当我输入时:

xyzssh -X user\@server 选项卡

它在 Linux macOS 上都能正确完成。

有没有办法在 macOS 上修复此行为? 已修复:请参阅下面已接受的答案


更新

刚发现 COMP_WORDBREAKS,它在 macOS 中包含 @,而在 Linux 中则不包含。

当我在 shell 中设置 COMP_WORDBREAKS="${COMP_WORDBREAKS//@}" 时,我可以解决这个问题,但在 macOS 中进行弹性更改并不容易(在我的 .bash_profile.bashrc 似乎不起作用)...


更新

我遇到了其他问题,这次是 Solaris (bash 4.4),其中 ${COMP_WORDS[COMP_CWORD]} 在调用完成函数时甚至不包含 user@ 部分。 .我仍在研究如何修复它已修复:请参阅下面已接受的答案


这是我第一次编写完成函数,所以我可能遗漏了一些明显的东西。

走上回答我自己问题的道路

COMPREPLY 需要根据 COMP_WORDBREAKS.

中是否存在 @ 进行不同的填充

例如,当用户键入 ssh user@server Tab:

  • 如果 @ 不是 COMP_WORDBREAKS 那么 COMPREPLY 应该是这样的:
COMPREPLY=( "user@server.xyz.com" "user@server-bis.xyz.com" )
  • if @ is in COMP_WORDBREAKS 那么 COMPREPLY 应该是这样的:
COMPREPLY=( "@server.xyz.com" "@server-bis.xyz.com" )

使完成函数兼容这两种情况的解决方案是在找到匹配的主机列表后添加正确的前缀:

更新: 修复了 Solaris bash 4.4 问题

_xyzssh() {
    COMPREPLY=()

    local curr="${COMP_WORDS[COMP_CWORD]}"
    local prev="${COMP_WORDS[COMP_CWORD-1]}"

    [[ $curr == -* ]] && return 0

    [ -f ~/.ssh/known_hosts ] || return 0

    local -a known_hosts
    IFS=$'\n' read -r -d '' -a known_hosts < <(awk '{
      n = split(,arr,",");
        for (i = 1; i <= n; i++)
          if (arr[i] ~ /\.xyz\.com$/)
            print arr[i]
    }' ~/.ssh/known_hosts)

    COMPREPLY=( $(compgen -W "${known_hosts[*]}" -- "${curr#*@}") )

    if [[ $curr == *@* || $prev == @ ]]
    then
        if [[ $COMP_WORDBREAKS == *@* ]]
        then
            COMPREPLY=( "${COMPREPLY[@]/#/@}" )
        else
            COMPREPLY=( "${COMPREPLY[@]/#/${curr%%@*}@}" )
        fi
    fi

    return 0
}
complete -F _xyzssh xyzssh

调试信息

这是我在不同操作系统上键入 xyzssh -X user@server Tab 时在 COMP_WORDS 中得到的内容:

  • redhat 7(bash 4.2,@ COMP_WORDBREAKS
COMP_WORDS=( "xyzssh" "-X" "user@server" )
  • macOS(bash 3.2,@ 出现在 COMP_WORDBREAKS 中)
COMP_WORDS=( "xyzssh" "-X" "user@server" )
  • solaris 11.4(bash 4.4,@ 出现在 COMP_WORDBREAKS 中)
COMP_WORDS=( "xyzssh" "-X" "user" "@" "server" )