I'm writing a function that mimics the ssh
command host completion; the main difference being that it uses the hosts of the xyz.com
domain found in ~/.ssh/known_hosts
for the completion.
Here is the function:
_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($1,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
This code works fine in Linux (bash 4.2) but it has a weird behavior in macOS (bash 3.2). For example, when I type:
xyzssh -X user@server
Tab
On Linux it completes with: xyzssh -X user@server.xyz.com
On macOS it "doubles" the username: xyzssh -X useruser@server.xyz.com
I'm guessing that there is something fishy with the @
because when I type:
xyzssh -X user\@server
Tab
It completes correctly on both Linux and macOS.
Is there a way to fix this behavior on macOS? fixed: see accepted answer below
Just found out about COMP_WORDBREAKS
, which contains @
in macOS while in Linux it doesn't.
I can fix the issue when I set COMP_WORDBREAKS="${COMP_WORDBREAKS//@}"
in the shell, but making the change resilient is not that easy in macOS (exporting this setting in my .bash_profile
or .bashrc
doesn't seem to work)...
I got other issues, with Solaris (bash 4.4) this time, in which ${COMP_WORDS[COMP_CWORD]}
doesn't even contain the user@
part when the completion function is called... I' m still investigating how to fix it. fixed: see accepted answer below
It's my first time writing a completion function so I might be missing something obvious.
Taking the path of answering my own question
COMPREPLY
needs to be populated differently depending on the presence of @
in COMP_WORDBREAKS
.
For example, when the user type ssh user@server
Tab:
@
is not in COMP_WORDBREAKS
then COMPREPLY
should look like:COMPREPLY=( "user@server.xyz.com" "user@server-bis.xyz.com" )
@
is in COMP_WORDBREAKS
then COMPREPLY
should look like:COMPREPLY=( "@server.xyz.com" "@server-bis.xyz.com" )
A solution for making the completion function compatible with both cases is to add the right prefix after finding the matching hosts list:
Update: fixed Solaris bash 4.4 issue
_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($1,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
Here is what I get in COMP_WORDS
when I type xyzssh -X user@server
Tab on the different OSs:
@
not present in COMP_WORDBREAKS
)COMP_WORDS=( "xyzssh" "-X" "user@server" )
@
present in COMP_WORDBREAKS
)COMP_WORDS=( "xyzssh" "-X" "user@server" )
@
present in COMP_WORDBREAKS
)COMP_WORDS=( "xyzssh" "-X" "user" "@" "server" )