Completions for ssh
, scp
and sftp
.
This file is written in literate programming style, to make it easy to explain. See ssh.elv for the generated file.
Install the elvish-completions
package using epm:
use epm
epm:install github.com/zzamboni/elvish-completions
In your rc.elv
, load this module:
use github.com/zzamboni/elvish-completions/ssh
Hosts for the completions will be read from the files listed in the $config-files
variable. Here is its default value:
config-files = [ ~/.ssh/config /etc/ssh/ssh_config /etc/ssh_config ]
All hosts listed in Host
sections of the config files will be provided for completion. Patterns including any metacharacters (*
, ?
and !
) will not be shown.
[~]─> ssh <tab> COMPLETING argument host1 host2 host3
Completions are also provided for config options. If you type -o<space>
and press Tab
, a list of valid configuration options will be provided. The valid configuration options are automatically extracted from the ssh_config
man page, if it’s available.
[~]─> ssh -o <tab> COMPLETING argument _ AddKeysToAgent= ControlPath= HostKeyAlias= NoHostAuthenticationForLocalhost= ServerAliveCountMax= AddressFamily= ControlPersist= HostName= NumberOfPasswordPrompts= ServerAliveInterval= BatchMode= DynamicForward= HostbasedAuthentication= PKCS11Provider= StreamLocalBindMask= ...
We first load a number of libraries, including comp
, the Elvish completion framework.
use ./comp
use re
use str
List of config files from which to extract hostnames.
var config-files = [ ~/.ssh/config /etc/ssh/ssh_config /etc/ssh_config ]
The -ssh-hosts
function extracts all hostnames from the files listed in $config-files
. Nonexistent files in the list are ignored, and only hostnames which do not include glob characters (*
, ?
, !
) are returned.
fn -ssh-hosts {
var hosts = [&]
all $config-files | each {|file|
set _ = ?(cat $file 2>&-) | re:awk {|_ @f|
if (re:match '^(?i)host$' $f[0]) {
all $f[1..] | each {|p|
if (not (re:match '[*?!]' $p)) {
set hosts[$p] = $true
}}}}}
keys $hosts
}
We store in -ssh-options
all the possible configuration options, by parsing them directly from the ssh_config
man page (if available). These are initialized by the -gen-ssh-options
on first use to reduce load time, and cached so that any delay is only incurred once.
var -ssh-options = []
fn -gen-ssh-options {
if (eq $-ssh-options []) {
set -ssh-options = [(
set _ = ?(cat (man -w ssh_config 2>&-)) |
re:awk {|l @f| if (re:match '^\.It Cm' $l) { put $f[2] } } |
comp:decorate &suffix='='
)]
}
all $-ssh-options
}
The $ssh-opts
array stores the definitions of command-line options. For now we only complete:
-o
(including completions for its argument) generated with-gen-ssh-options
defined above-i/--inventory
generated withcomp:files
defined incomp.elv
var ssh-opts = [
[ &short= o
&arg-required= $true
&arg-completer= $-gen-ssh-options~
]
[ &short= i
&long= inventory
&arg-required= $true
&arg-completer= $comp:files~
]
]
-ssh-host-completions
dynamically generates the completion definition for hostnames for ssh-related commands. The hostnames are extracted from the user’s ssh config files by the -ssh-hosts
function defined above. The completions for ssh
and scp
, for example, are the same except for the suffix that needs to be added to the hostnames in the completion, so we allow the suffix to be specified as an option. We also allow for a username to be specified at the beginning of the hostname (user@
), and still generate the completions correctly, so you can type ssh user@abc<Tab>
and the corresponding hostnames will be completed.
fn -ssh-host-completions {|arg &suffix=''|
var user-given = (str:join '' [(re:find '^(.*@)' $arg)[groups][1][text]])
-ssh-hosts | each {|host| put $user-given$host } | comp:decorate &suffix=$suffix
}
We use -ssh-host-completions
to produce the actual completion definitions for ssh
, sftp
and scp
. For scp
we also complete local filenames.
set edit:completion:arg-completer[ssh] = (comp:sequence &opts=$ssh-opts [$-ssh-host-completions~])
set edit:completion:arg-completer[sftp] = (comp:sequence &opts=$ssh-opts [$-ssh-host-completions~])
set edit:completion:arg-completer[scp] = (comp:sequence &opts=$ssh-opts [
{|arg|
-ssh-host-completions &suffix=":" $arg
edit:complete-filename $arg
}
...
])