# ssh(1) completion -*- shell-script -*- proc _ssh_queries { setvar 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 ]] || setvar ciphers = ""3des-cbc aes128-cbc aes192-cbc aes256-cbc aes128-ctr aes192-ctr aes256-ctr arcfour128 arcfour256 arcfour blowfish-cbc cast128-cbc"" setvar COMPREPLY = ''( $( compgen -W "$ciphers" -- "$cur" ) ) } proc _ssh_macs { local macs='$( _ssh_query "$1" mac )' [[ $macs ]] || setvar macs = ""hmac-md5 hmac-sha1 umac-64@openssh.com hmac-ripemd160 hmac-sha1-96 hmac-md5-96"" setvar COMPREPLY = ''( $( compgen -W "$macs" -- "$cur" ) ) } proc _ssh_options { compopt -o nospace setvar 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#*=} case (prev) { 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 { setvar COMPREPLY = ''( $( compgen -W 'yes no' -- "$cur" ) ) } AddressFamily { setvar COMPREPLY = ''( $( compgen -W 'any inet inet6' -- "$cur" ) ) } BindAddress { _ip_addresses } Cipher { setvar COMPREPLY = ''( $( compgen -W 'blowfish des 3des' -- "$cur" ) ) } IPQoS { setvar COMPREPLY = ''( $( compgen -W 'af1{1..4} af2{2..3} af3{1..3} af4{1..3} cs{0..7} ef lowdelay throughput reliability' -- "$cur" ) ) } HostbasedKeyTypes|HostKeyAlgorithms { setvar COMPREPLY = ''( $( compgen -W '$( _ssh_query "$2" key )' -- "$cur" ) ) } KexAlgorithms { setvar COMPREPLY = ''( $( compgen -W '$( _ssh_query "$2" kex )' -- "$cur" ) ) } Protocol { setvar COMPREPLY = ''( $( compgen -W '1 2 1,2 2,1' -- "$cur" ) ) } RequestTTY { setvar COMPREPLY = ''( $( compgen -W 'no yes force auto' -- "$cur" ) ) } Tunnel { setvar COMPREPLY = ''( $( compgen -W 'yes no point-to-point ethernet' \ -- "$cur" ) ) } PreferredAuthentications { setvar COMPREPLY = ''( $( compgen -W 'gssapi-with-mic host-based publickey keyboard-interactive password' -- "$cur" ) ) } MACs { _ssh_macs $2 } 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 case (prev) { -F|-i|-S { _filedir return 0 } -c { _ssh_ciphers $1 return 0 } -m { _ssh_macs $1 return 0 } -l { setvar COMPREPLY = ''( $( compgen -u -- "$cur" ) ) return 0 } -O { setvar COMPREPLY = ''( $( compgen -W 'check forward exit stop' -- "$cur" ) ) return 0 } -o { _ssh_options return 0 } -Q { _ssh_queries $1 return 0 } -w { _available_interfaces return 0 } -b { _ip_addresses return 0 } -D|-e|-I|-L|-p|-R|-W { return 0 } } if [[ "$cur" == -F* ]] { setvar cur = ${cur#-F} _filedir # Prefix completions with '-F' setvar COMPREPLY = ''( "${COMPREPLY[@]/#/-F}" ) setvar cur = "-F$cur" # Restore cur } elif [[ "$cur" == -* ]] { setvar 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 ]] { setvar configfile = "$(dequote "${1:2}")" } else { shift [[ $1 ]] && setvar configfile = "$(dequote "$1")" } break } shift } _known_hosts_real -a -F $configfile $cur local args _count_args if [[ $args -gt 1 ]] { compopt -o filenames setvar 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 case (prev) { -b|-F|-i { _filedir return 0 } -o { _ssh_options return 0 } -c { _ssh_ciphers return 0 } -S { _command return 0 } -B|-D|-l|-P|-R|-s { return 0 } } if [[ "$cur" == -F* ]] { setvar cur = ${cur#-F} _filedir # Prefix completions with '-F' setvar COMPREPLY = ''( "${COMPREPLY[@]/#/-F}" ) setvar cur = "-F$cur" # Restore cur } elif [[ "$cur" == -* ]] { setvar COMPREPLY = ''( $( compgen -W '$( _parse_usage "$1" )' -- "$cur" ) ) } else { # Search COMP_WORDS for '-F configfile' argument set -- ${words[@]} { if [[ $1 == -F* ]] { if [[ ${#1} -gt 2 ]] { setvar configfile = "$(dequote "${1:2}")" } else { shift [[ $1 ]] && setvar 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 setvar _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 setvar cur = ${cur/\\:/:} local userhost=${cur%%?(\\):*} local path=${cur#*:} # unescape (3 backslashes to 1 for chars we escaped) setvar path = $( sed -e 's/\\\\\\\('$_scp_path_esc'\)/\\\1/g' <<<"$path" ) # default to home dir of specified user on remote host if [[ -z $path ]] { setvar path = $(ssh -o 'Batchmode yes' $userhost pwd 2>/dev/null) } local files if [[ $1 == -d ]] { # escape problematic characters; remove non-dirs setvar 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 setvar 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' ) } setvar 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 ]] { setvar dirsonly = 'true' shift } if $dirsonly { setvar COMPREPLY = ''( $( command ls -aF1dL $cur* 2>/dev/null | \ sed -e "s/$_scp_path_esc/\\\\&/g" -e '/[^\/]$/d' -e "s/^/$1/") ) } else { setvar 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 { setvar COMPREPLY = ''( "${COMPREPLY[@]/%/ }" ) return 0 } case (prev) { -l|-P { return 0 } -F|-i { _filedir compopt +o nospace return 0 } -c { _ssh_ciphers setvar COMPREPLY = ''( "${COMPREPLY[@]/%/ }" ) return 0 } -o { _ssh_options return 0 } -S { _command compopt +o nospace return 0 } } _expand || return 0 case (cur) { !(*:*)/*|[.~]* { } # looks like a path *:* { _scp_remote_files ; return 0 } } if [[ "$cur" == -F* ]] { setvar cur = ${cur#-F} setvar prefix = '-F' } else { # Search COMP_WORDS for '-F configfile' or '-Fconfigfile' argument set -- ${words[@]} { if [[ $1 == -F* ]] { if [[ ${#1} -gt 2 ]] { setvar configfile = "$(dequote "${1:2}")" } else { shift [[ $1 ]] && setvar configfile = "$(dequote "$1")" } break } shift } case (cur) { -* { setvar COMPREPLY = ''( $( compgen -W '$( _parse_usage "${words[0]}" )' \ -- "$cur" ) ) setvar COMPREPLY = ''( "${COMPREPLY[@]/%/ }" ) return 0 } */*|[.~]* { # not a known host, pass through } * { _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