diff --git a/completions/ssh b/completions/ssh index 5f08286569b..189995176b0 100644 --- a/completions/ssh +++ b/completions/ssh @@ -457,6 +457,54 @@ _comp_cmd_sftp() # things we want to backslash escape in scp paths _comp_cmd_scp__path_esc='[][(){}<>"'"'"',:;^&!$=?`\\|[:space:]]' +# Escape shell special characters in filenames by backslash. This also +# suffixes a space or a slash based on the file type. +# +# Note: With a non-empty prefix ($1 of _comp_xfunc_scp_compgen_local_files), +# Bash will not recognize any filenames, so we need to perform the proper +# quoting manually. We also need to manually suffix a space or a slash based +# on the file type because "-o nospace" is specified. One might think of using +# "compopt +o nospace" instead, but it would suffix a space to directory names +# unexpectedly. +# +# Options: +# -d Only directory names are selected. +# @param $1 escape_replacement - If a non-empty value is specified, special +# characters are replaced with the specified value (instead of the default +# '\\&'). +# @stdin List of filenames in the "ls -1F" format, where filenames are +# separated by newlines, and characters /*@|=> are suffixed based on the +# types of the files. +_comp_cmd_scp__escape_path() +{ + local OPTIND=1 OPTARG="" OPTERR=0 opt dirs_only="" + while getopts ':d' _flag "$@"; do + case $_flag in + d) dirs_only=set ;; + *) + echo "bash_completion: $FUNCNAME: usage error: $*" >&2 + return 1 + ;; + esac + done + shift "$((OPTIND - 1))" + local escape_replacement=${1:-'\\&'} + + if [[ $dirs_only ]]; then + # escape problematic characters; remove non-dirs + command sed \ + -e '/[^/]$/d' \ + -e 's/'"$_comp_cmd_scp__path_esc"'/'"$escape_replacement"'/g' + else + # escape problematic characters; remove executable, symlink, pipe, + # socket and door indicators; add space at end of file names + command sed \ + -e 's/[*@|=>]$//g' \ + -e 's/'"$_comp_cmd_scp__path_esc"'/'"$escape_replacement"'/g' \ + -e 's/[^/]$/& /g' + fi +} + # Complete remote files with ssh. Returns paths escaped with three backslashes # (unless -l option is provided). # Options: @@ -501,20 +549,10 @@ _comp_xfunc_scp_compgen_remote_files() fi local _files - if [[ $_dirs_only ]]; then - # escape problematic characters; remove non-dirs - _files=$(ssh -o 'Batchmode yes' "$_userhost" \ - command ls -aF1dL "$_path*" 2>/dev/null | - command sed -e 's/'"$_comp_cmd_scp__path_esc"'/'"$_escape_replacement"'/g' -e '/[^/]$/d') - else - # escape problematic characters; remove executables, aliases, pipes - # and sockets; add space at end of file names - _files=$(ssh -o 'Batchmode yes' "$_userhost" \ - command ls -aF1dL "$_path*" 2>/dev/null | - command sed -e 's/[*@|=]$//g' \ - -e 's/'"$_comp_cmd_scp__path_esc"'/'"$_escape_replacement"'/g' \ - -e 's/[^/]$/& /g') - fi + _files=$(ssh -o 'Batchmode yes' "$_userhost" \ + command ls -aF1dL "$_path*" 2>/dev/null | + _comp_cmd_scp__escape_path ${_dirs_only:+'-d'} -- \ + "$_escape_replacement") _comp_compgen -R split -l -- "$_files" } @@ -539,20 +577,10 @@ _comp_xfunc_scp_compgen_local_files() local files _comp_expand_glob files '"$cur"*' || return 0 - if [[ $_dirs_only ]]; then - _comp_compgen -RU files split -l ${1:+-P "$1"} -- "$( - command ls -aF1dL "${files[@]}" 2>/dev/null | - command sed -e "s/$_comp_cmd_scp__path_esc/\\\\&/g" \ - -e '/[^/]$/d' - )" - else - _comp_compgen -RU files split -l ${1:+-P "$1"} -- "$( - command ls -aF1dL "${files[@]}" 2>/dev/null | - command sed -e 's/[*@|=]$//g' \ - -e "s/$_comp_cmd_scp__path_esc/\\\\&/g" \ - -e 's/[^/]$/& /g' - )" - fi + _comp_compgen -RU files split -l ${1:+-P "$1"} -- "$( + command ls -aF1dL "${files[@]}" 2>/dev/null | + _comp_cmd_scp__escape_path ${_dirs_only:+'-d'} + )" } # @deprecated 2.12