# ssh(1) completion -*- shell-script -*- proc _ssh_queries { setglobal COMPREPLY = '( '$( compgen -W \ "cipher cipher-auth mac kex key protocol-version" -- "$cur" ) ) } proc _ssh_query { $(1:-ssh) -Q $2 !2 >/dev/null } proc _ssh_ciphers { var ciphers = ''$( _ssh_query "$1" cipher )'' [[ $ciphers ]] || set ciphers = '"3des-cbc aes128-cbc aes192-cbc aes256-cbc aes128-ctr aes192-ctr aes256-ctr arcfour128 arcfour256 arcfour blowfish-cbc cast128-cbc'" setglobal COMPREPLY = '( '$( compgen -W "$ciphers" -- "$cur" ) ) } proc _ssh_macs { var macs = ''$( _ssh_query "$1" mac )'' [[ $macs ]] || set macs = '"hmac-md5 hmac-sha1 umac-64@openssh.com hmac-ripemd160 hmac-sha1-96 hmac-md5-96'" setglobal COMPREPLY = '( '$( compgen -W "$macs" -- "$cur" ) ) } proc _ssh_options { compopt -o nospace setglobal COMPREPLY = '( '$( compgen -S = -W 'AddressFamily BatchMode BindAddress ChallengeResponseAuthentication CheckHostIP Cipher Ciphers ClearAllForwardings Compression CompressionLevel ConnectionAttempts ConnectTimeout ControlMaster ControlPath ControlPersist DynamicForward EnableSSHKeysign EscapeChar ExitOnForwardFailure ForwardAgent ForwardX11 ForwardX11Timeout ForwardX11Trusted GatewayPorts GlobalKnownHostsFile GSSAPIAuthentication GSSAPIClientIdentity GSSAPIDelegateCredentials GSSAPIKeyExchange GSSAPIRenewalForcesRekey GSSAPIServerIdentity GSSAPITrustDns HashKnownHosts Host HostbasedAuthentication HostKeyAlgorithms HostKeyAlias HostName IdentityFile IdentitiesOnly IPQoS KbdInteractiveDevices KexAlgorithms LocalCommand LocalForward LogLevel MACs NoHostAuthenticationForLocalhost NumberOfPasswordPrompts PasswordAuthentication PermitLocalCommand PKCS11Provider Port PreferredAuthentications Protocol ProxyCommand PubkeyAuthentication RekeyLimit RemoteForward RequestTTY RhostsRSAAuthentication RSAAuthentication SendEnv ServerAliveCountMax ServerAliveInterval SmartcardDevice StrictHostKeyChecking TCPKeepAlive Tunnel TunnelDevice UsePrivilegedPort User UserKnownHostsFile VerifyHostKeyDNS VisualHostKey XAuthLocation' -- "$cur" ) ) } # Complete a ssh suboption (like ForwardAgent=y) # Two parameters: the string to complete including the equal sign, and # the ssh executable to invoke (optional). # Not all suboptions are completed. # Doesn't handle comma-separated lists. proc _ssh_suboption { # Split into subopt and subval var prev = $(1%%=*), cur = $(1#*=) match $prev { with BatchMode|ChallengeResponseAuthentication|CheckHostIP|\ ClearAllForwardings|ControlPersist|Compression|EnableSSHKeysign|\ ExitOnForwardFailure|ForwardAgent|ForwardX11|ForwardX11Trusted|\ GatewayPorts|GSSAPIAuthentication|GSSAPIKeyExchange|\ GSSAPIDelegateCredentials|GSSAPIRenewalForcesRekey|GSSAPITrustDns|\ HashKnownHosts|HostbasedAuthentication|IdentitiesOnly|\ KbdInteractiveAuthentication|KbdInteractiveDevices|\ NoHostAuthenticationForLocalhost|PasswordAuthentication|\ PubkeyAuthentication|RhostsRSAAuthentication|RSAAuthentication|\ StrictHostKeyChecking|TCPKeepAlive|UsePrivilegedPort|\ VerifyHostKeyDNS|VisualHostKey setglobal COMPREPLY = '( '$( compgen -W 'yes no' -- "$cur" ) ) with AddressFamily setglobal COMPREPLY = '( '$( compgen -W 'any inet inet6' -- "$cur" ) ) with BindAddress _ip_addresses with Cipher setglobal COMPREPLY = '( '$( compgen -W 'blowfish des 3des' -- "$cur" ) ) with IPQoS setglobal COMPREPLY = '( '$( compgen -W 'af1{1..4} af2{2..3} af3{1..3} af4{1..3} cs{0..7} ef lowdelay throughput reliability' -- "$cur" ) ) with HostbasedKeyTypes|HostKeyAlgorithms setglobal COMPREPLY = '( '$( compgen -W '$( _ssh_query "$2" key )' -- "$cur" ) ) with KexAlgorithms setglobal COMPREPLY = '( '$( compgen -W '$( _ssh_query "$2" kex )' -- "$cur" ) ) with Protocol setglobal COMPREPLY = '( '$( compgen -W '1 2 1,2 2,1' -- "$cur" ) ) with RequestTTY setglobal COMPREPLY = '( '$( compgen -W 'no yes force auto' -- "$cur" ) ) with Tunnel setglobal COMPREPLY = '( '$( compgen -W 'yes no point-to-point ethernet' \ -- "$cur" ) ) with PreferredAuthentications setglobal COMPREPLY = '( '$( compgen -W 'gssapi-with-mic host-based publickey keyboard-interactive password' -- "$cur" ) ) with MACs _ssh_macs $2 with Ciphers _ssh_ciphers $2 } return 0 } # Try to complete -o SubOptions= # # Returns 0 if the completion was handled or non-zero otherwise. proc _ssh_suboption_check { # Get prev and cur words without splitting on = var cureq = $[_get_cword :=], preveq = $[_get_pword :=] if [[ $cureq == *=* && $preveq == -o ]] { _ssh_suboption $cureq $1 return $? } return 1 } proc _ssh { var cur = '', prev = '', words = '', cword = '' _init_completion -n : || return var configfile = '' var -a config = '' _ssh_suboption_check $1 && return 0 match $prev { with -F|-i|-S _filedir return 0 with -c _ssh_ciphers $1 return 0 with -m _ssh_macs $1 return 0 with -l setglobal COMPREPLY = '( '$( compgen -u -- "$cur" ) ) return 0 with -O setglobal COMPREPLY = '( '$( compgen -W 'check forward exit stop' -- "$cur" ) ) return 0 with -o _ssh_options return 0 with -Q _ssh_queries $1 return 0 with -w _available_interfaces return 0 with -b _ip_addresses return 0 with -D|-e|-I|-L|-p|-R|-W return 0 } if [[ "$cur" == -F* ]] { set cur = $(cur#-F) _filedir # Prefix completions with '-F' setglobal COMPREPLY = '( '"${COMPREPLY[@]/#/-F}" ) set cur = "-F$cur" # Restore cur } elif [[ "$cur" == -* ]] { setglobal COMPREPLY = '( '$( compgen -W '$( _parse_usage "$1" )' -- "$cur" ) ) } else { # Search COMP_WORDS for '-F configfile' or '-Fconfigfile' argument set -- $(words[@]) { if [[ $1 == -F* ]] { if [[ ${#1} -gt 2 ]] { set configfile = $[dequote $(1:2)] } else { shift [[ $1 ]] && set configfile = $[dequote $1] } break } shift } _known_hosts_real -a -F $configfile $cur var args = '' _count_args if [[ $args -gt 1 ]] { compopt -o filenames setglobal COMPREPLY = '( '$( compgen -c -- "$cur" ) ) } } return 0 } && shopt -u hostcomplete && complete -F _ssh ssh slogin autossh # sftp(1) completion # proc _sftp { var cur = '', prev = '', words = '', cword = '' _init_completion || return var configfile = '' _ssh_suboption_check && return 0 match $prev { with -b|-F|-i _filedir return 0 with -o _ssh_options return 0 with -c _ssh_ciphers return 0 with -S _command return 0 with -B|-D|-l|-P|-R|-s return 0 } if [[ "$cur" == -F* ]] { set cur = $(cur#-F) _filedir # Prefix completions with '-F' setglobal COMPREPLY = '( '"${COMPREPLY[@]/#/-F}" ) set cur = "-F$cur" # Restore cur } elif [[ "$cur" == -* ]] { setglobal COMPREPLY = '( '$( compgen -W '$( _parse_usage "$1" )' -- "$cur" ) ) } else { # Search COMP_WORDS for '-F configfile' argument set -- $(words[@]) { if [[ $1 == -F* ]] { if [[ ${#1} -gt 2 ]] { set configfile = $[dequote $(1:2)] } else { shift [[ $1 ]] && set configfile = $[dequote $1] } break } shift } _known_hosts_real -a -F $configfile $cur } return 0 } && shopt -u hostcomplete && complete -F _sftp sftp # things we want to backslash escape in scp paths setglobal _scp_path_esc = ''[][(){}<>",:;^&!$=?`|\\'"'"'[:space:]]'' # Complete remote files with ssh. If the first arg is -d, complete on dirs # only. Returns paths escaped with three backslashes. proc _scp_remote_files { var IFS = '$'\n'' # remove backslash escape from the first colon setglobal cur = $(cur/\\:/:) var userhost = $(cur%%?(\\):*) var path = $(cur#*:) # unescape (3 backslashes to 1 for chars we escaped) set path = $[ sed -e 's/\\\\\\\('$_scp_path_esc'\)/\\\1/g' <<<$path] # default to home dir of specified user on remote host if [[ -z $path ]] { set path = $[ssh -o 'Batchmode yes' $userhost pwd !2 >/dev/null] } var files = '' if [[ $1 == -d ]] { # escape problematic characters; remove non-dirs set files = $[ ssh -o 'Batchmode yes' $userhost \ command ls -aF1dL "$path*" !2 >/dev/null | \ sed -e 's/'$_scp_path_esc'/\\\\\\&/g' -e '/[^\/]$/d] } else { # escape problematic characters; remove executables, aliases, pipes # and sockets; add space at end of file names set files = $[ ssh -o 'Batchmode yes' $userhost \ command ls -aF1dL "$path*" !2 >/dev/null | \ sed -e 's/'$_scp_path_esc'/\\\\\\&/g' -e 's/[*@|=]$//g' \ -e 's/[^\/]$/& /g] } setglobal COMPREPLY = '( '$files ) } # This approach is used instead of _filedir to get a space appended # after local file/dir completions, and -o nospace retained for others. # If first arg is -d, complete on directory names only. The next arg is # an optional prefix to add to returned completions. proc _scp_local_files { var IFS = '$'\n'' var dirsonly = 'false' if [[ $1 == -d ]] { set dirsonly = 'true' shift } if $dirsonly { setglobal COMPREPLY = '( '$( command ls -aF1dL $cur* 2>/dev/null | \ sed -e "s/$_scp_path_esc/\\\\&/g" -e '/[^\/]$/d' -e "s/^/$1/") ) } else { setglobal COMPREPLY = '( '$( command ls -aF1dL $cur* 2>/dev/null | \ sed -e "s/$_scp_path_esc/\\\\&/g" -e 's/[*@|=]$//g' \ -e 's/[^\/]$/& /g' -e "s/^/$1/") ) } } # scp(1) completion # proc _scp { var cur = '', prev = '', words = '', cword = '' _init_completion -n : || return var configfile = '', prefix = '' _ssh_suboption_check && do { setglobal COMPREPLY = '( '"${COMPREPLY[@]/%/ }" ) return 0 } match $prev { with -l|-P return 0 with -F|-i _filedir compopt +o nospace return 0 with -c _ssh_ciphers setglobal COMPREPLY = '( '"${COMPREPLY[@]/%/ }" ) return 0 with -o _ssh_options return 0 with -S _command compopt +o nospace return 0 } _expand || return 0 match $cur { with !(*:*)/*|[.~]* # looks like a path with *:* _scp_remote_files ; return 0 } if [[ "$cur" == -F* ]] { set cur = $(cur#-F) set prefix = '-F' } else { # Search COMP_WORDS for '-F configfile' or '-Fconfigfile' argument set -- $(words[@]) { if [[ $1 == -F* ]] { if [[ ${#1} -gt 2 ]] { set configfile = $[dequote $(1:2)] } else { shift [[ $1 ]] && set configfile = $[dequote $1] } break } shift } match $cur { with -* setglobal COMPREPLY = '( '$( compgen -W '$( _parse_usage "${words[0]}" )' \ -- "$cur" ) ) setglobal COMPREPLY = '( '"${COMPREPLY[@]/%/ }" ) return 0 with */*|[.~]* # not a known host, pass through with * _known_hosts_real -c -a -F $configfile $cur } } _scp_local_files $prefix return 0 } && complete -F _scp -o nospace scp # ex: ts=4 sw=4 et filetype=sh