Graças à resposta da shellholic, eu consegui fazer (um pouco) funcionar para o sftp. Primeiro, crie o arquivo /etc/bash_completion.d/sftp
com o seguinte conteúdo:
# custom sftp(1) based on scp
# see https://sobrelinux.info/questions/8249/is-it-possible-to-get-tab-completion-with-sftp":"'
_expand || return 0
if [[ "$cur" == *:* ]]; then
local IFS=$'\t\n'
# remove backslash escape from :
cur=${cur/\:/:}
userhost=${cur%%?(\):*}
path=${cur#*:}
# unescape spaces
path=${path//\\\\ / }
if [ -z "$path" ]; then
# default to home dir of specified user on remote host
path=$(ssh -o 'Batchmode yes' $userhost pwd 2>/dev/null)
fi
# escape spaces; remove executables, aliases, pipes and sockets;
# add space at end of file names
COMPREPLY=( $( ssh -o 'Batchmode yes' $userhost \
command ls -aF1d "$path*" 2>/dev/null | \
sed -e "s/[][(){}<>\",:;^&\!$=?\'|\ ']/\\\\\\&/g" \
-e 's/[*@|=]$//g' -e 's/[^\/]$/& /g' ) )
return 0
fi
if [[ "$cur" = -F* ]]; then
cur=${cur#-F}
prefix=-F
else
# Search COMP_WORDS for '-F configfile' or '-Fconfigfile' argument
set -- "${COMP_WORDS[@]}"
while [ $# -gt 0 ]; do
if [ "${1:0:2}" = -F ]; then
if [ ${#1} -gt 2 ]; then
configfile="$(dequote "${1:2}")"
else
shift
[ "" ] && configfile="$(dequote "")"
fi
break
fi
shift
done
[[ "$cur" == */* ]] || _known_hosts_real -c -a -F "$configfile" "$cur"
fi
# This approach is used instead of _filedir to get a space appended
# after local file/dir completions, and $nospace retained for others.
local IFS=$'\t\n'
COMPREPLY=( "${COMPREPLY[@]}" $( command ls -aF1d $cur* 2>/dev/null | sed \
-e "s/[][(){}<>\",:;^&\!$=?\'|\ ']/\\&/g" \
-e 's/[*@|=]$//g' -e 's/[^\/]$/& /g' -e "s/^/$prefix/") )
return 0
}
complete -o nospace -F _sftp sftp
Em seguida, no bash, você precisa executar . /etc/bash_completion.d/sftp
para carregar o script.
Tudo o que eu realmente fiz foi copiar / colar o script de conclusão do scp de /etc/bash_completion.d/ssh
e substituir as ocorrências do scp pelo sftp.