21

I'd like to set up command completion on zsh to display host names after I type

ssh [TAB]

taking the names out of my .ssh/config file (and preferably from known_hosts and /etc/hosts and anywhere else that makes sense) and presenting one single list.

It does some of this currently, but

  1. it doesn't use .ssh/config at all
  2. it requires a username first, even though using .ssh/config makes typing usernames unnecessary
  3. it presents multiple lists (probably one from known_hosts and another from /etc/hosts, but I haven't verified that)

So I want to be include known usernames as well as known hostnames in the (preferably single) list after typing ssh [TAB]

(I'm coming here before Google because 1) it'll result in the answer getting stored here, and 2) it's probably more efficient. If no one else answers, I'll hunt down the answer.)

2
  • This would be a question for superuser, I think May 1, 2012 at 20:19
  • Actually, looking at it now I should have asked it on unix.stackexchange.com. (But I don't recall if that was around 2 years ago when I asked it.) Anyone want to migrate it?
    – iconoclast
    Aug 2, 2012 at 13:22

4 Answers 4

25

Here's the relevant part from my .zshrc. It hasn't changed since 2002, so I might write it differently today, but it still works to complete host names from ~/.ssh/config and ~/.ssh/known_hosts (if HashKnownHosts is off — it didn't exist in those days).

h=()
if [[ -r ~/.ssh/config ]]; then
  h=($h ${${${(@M)${(f)"$(cat ~/.ssh/config)"}:#Host *}#Host }:#*[*?]*})
fi
if [[ -r ~/.ssh/known_hosts ]]; then
  h=($h ${${${(f)"$(cat ~/.ssh/known_hosts{,2} || true)"}%%\ *}%%,*}) 2>/dev/null
fi
if [[ $#h -gt 0 ]]; then
  zstyle ':completion:*:ssh:*' hosts $h
  zstyle ':completion:*:slogin:*' hosts $h
fi
3
  • Okay, I finally figured out why this wasn't working. The problem was not with the code your provided: it works fine, once the other problem was resolved.
    – iconoclast
    Jul 21, 2011 at 5:02
  • 7
    For me, simply adding zstyle ':completion:*:ssh:*' hosts to my .zshrc solved the issue. You can check if this is done or not by running zstyle -b ':completion:*:ssh:*' hosts bool; echo $bool. Dec 8, 2020 at 20:47
  • 1
    Thank you @james-wright for this tip! Solved my host-completion problems with no new plugins and no fiddly config editing. Oct 31, 2022 at 12:25
4

The function that provides ssh completion is located at /usr/share/zsh/functions/Completion/Unix/_ssh on my system.

Also see man zshcompsys for documentation (especially do a search on "host" which appears in multiple places and "ssh" which appears in a couple of places).

It may be possible that adding a zstyle command to your ~/.zshrc would do what you're looking for without having to modify the completion function.

2
  • 1
    Or in /usr/share/zsh/4.3.9/functions/_ssh in Mac OS X (adapt the version)
    – Studer
    Aug 13, 2010 at 21:16
  • I source that file, and type ssh<TAB> and I get filename completion. What' up with that?
    – iconoclast
    Oct 20, 2010 at 3:29
3

I do this using a list of all hosts on a given domain using dig. You can replace the function below with whatever lookup system you want including your hosts file or a static list:

function complete_host_from_zone () {
    reply=(`dig axfr ouraynet.com @ns1.ouraynet.com | grep -e '^[a-z]' | cut -d\. -f1`)
}
compctl -x 'p[1]' -K complete_host_from_zone -- ssh

Note: The code above might not fully replace the complete system for the ssh command in your configuration. If you have problems with it, try changing the "ssh" command to some other random command like "mycompletetest" and see if the completion works for that.

Also note that this does the dns zone transfer on every completion! If you use this a lot or on a fairly static domain it would make sense to do the lookup and save the result, then in your lookup function just set reply=zone_result.

4
  • I can't get that to work at all. Obviously I have to change the reply part, but can you give a working example with static text, so I know what format it should be in? (I tried what I understood the man page to say, and corrected -k to -K, and it still didn't work.)
    – iconoclast
    Oct 20, 2010 at 3:31
  • The reply format is pretty simple, it's just a straight text blob, one possible answer per line. The dns thing is probably the tricky part. Your DNS server has to support zone transfer for this to work. This usually means you have to be talking to the primary dns server for the domain in question, like this dig axfr mydomain.com @ns1.mydomain.com. Make sure you can run that manually and part of the output should be a list of hosts registered on that domain and their A or whatever records. That's what I was greping for, then cuting off only the hostname part not the fully qualified record.
    – Caleb
    Oct 20, 2010 at 12:01
  • Pleasenote that I have rewritten the example code in my answer to be fully working cut and paste solution including a domain name for which the zone transfer lookup works. You can then tailor to suite. I appologize for the original which ended up with two typos from when I ripped out about 20 extra layers of stuff that was in my .zshrc file that isn't relevant to this example.
    – Caleb
    Oct 20, 2010 at 13:03
  • The dig command in backticks works now (thanks for editing it!), but it's not showing any of that command's results when I type ssh<TAB>. Is there something else outside of this that needs to be enabled before it will work?
    – iconoclast
    Nov 1, 2010 at 22:00
0

I like keeping my hashed known_hosts file and would rather not turn HashKnownHosts off. I'd found that seeding what @Gilles has with what is already in my history has been quite effective for my needs.

h=($(echo $(history | awk '{print $4 " " $5 "\n"}' | grep 'ssh ' | awk '{print $2}' | sort -u)))
if [[ -r ~/.ssh/config ]]; then
  h=($h ${${${(@M)${(f)"$(cat ~/.ssh/config)"}:#Host *}#Host }:#*[*?]*})
fi
if [[ -r ~/.ssh/known_hosts ]]; then
   h=($h ${${${(f)"$(cat ~/.ssh/known_hosts{,2} || true)"}%%\ *}%%,*}) 2>/dev/null
fi
if [[ $#h -gt 0 ]]; then
  zstyle ':completion:*:ssh:*' hosts $h
  zstyle ':completion:*:slogin:*' hosts $h
fi

Also, FWIW, this is what I used for Bash:

# SSH Autocompletion
complete -W "
  $(echo $(grep '^\s*ssh ' ~/.bash_history | sort -u | sed 's/^ssh //' | awk '{print $1}'))
  $(echo $(history | awk '{print $2 " " $3}' | grep 'ssh ' | awk '{print $2}' | sort -u))
  $(sed 's/#.*//;' ~/.ssh/config | awk ' /^Host (.+)$/ {$1 = "";print tolower($0)}')
" ssh

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .