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
选项卡
在 Linux 上完成:xyzssh -X user@server.xyz.com
在 macOS 上它“加倍”了用户名:xyzssh -X useruser@server.xyz.com
我猜 @
有问题,因为当我输入时:
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" )
我正在编写一个模仿 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
选项卡
在 Linux 上完成:
xyzssh -X user@server.xyz.com
在 macOS 上它“加倍”了用户名:
xyzssh -X useruser@server.xyz.com
我猜 @
有问题,因为当我输入时:
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 inCOMP_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" )