# 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 { local ciphers='$( _ssh_query "$1" cipher )' [[ $ciphers ]] || setglobal 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 { local macs='$( _ssh_query "$1" mac )' [[ $macs ]] || setglobal 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 local 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 = local cureq=$[_get_cword :=] preveq=$[_get_pword :=] if [[ $cureq == *=* && $preveq == -o ]] { _ssh_suboption $cureq $1 return $? } return 1 } proc _ssh { local cur prev words cword _init_completion -n : || return local configfile local -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* ]] { setglobal cur = $(cur#-F) _filedir # Prefix completions with '-F' setglobal COMPREPLY = ''( "${COMPREPLY[@]/#/-F}" ) setglobal 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 ]] { setglobal configfile = $[dequote $(1:2)] } else { shift [[ $1 ]] && setglobal configfile = $[dequote $1] } break } shift } _known_hosts_real -a -F $configfile $cur local 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 { local cur prev words cword _init_completion || return local 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* ]] { setglobal cur = $(cur#-F) _filedir # Prefix completions with '-F' setglobal COMPREPLY = ''( "${COMPREPLY[@]/#/-F}" ) setglobal 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 ]] { setglobal configfile = $[dequote $(1:2)] } else { shift [[ $1 ]] && setglobal 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 { local IFS=$'\n' # remove backslash escape from the first colon setglobal cur = $(cur/\\:/:) local userhost=$(cur%%?(\\):*) local path=$(cur#*:) # unescape (3 backslashes to 1 for chars we escaped) setglobal path = $[ sed -e 's/\\\\\\\('$_scp_path_esc'\)/\\\1/g' <<<$path] # default to home dir of specified user on remote host if [[ -z $path ]] { setglobal path = $[ssh -o 'Batchmode yes' $userhost pwd !2 >/dev/null] } local files if [[ $1 == -d ]] { # escape problematic characters; remove non-dirs setglobal 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 setglobal 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 { local IFS=$'\n' local dirsonly=false if [[ $1 == -d ]] { setglobal 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 { local cur prev words cword _init_completion -n : || return local 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* ]] { setglobal cur = $(cur#-F) setglobal prefix = '-F' } else { # Search COMP_WORDS for '-F configfile' or '-Fconfigfile' argument set -- $(words[@]) { if [[ $1 == -F* ]] { if [[ ${#1} -gt 2 ]] { setglobal configfile = $[dequote $(1:2)] } else { shift [[ $1 ]] && setglobal 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