A conclusão do comando (juntamente com outras coisas) é tratada via bash readline completion . Isso opera em um nível um pouco menor do que a "conclusão programável" usual (que é invocada somente quando o comando é identificado e os dois casos especiais identificados acima).
Atualização: a atual versão dev do bash v5 adiciona complete -I
exatamente para este problema.
complete (TAB) Attempt to perform completion on the text before point. Bash attempts completion treating the text as a variable (if the text begins with $), username (if the text begins with ~), hostname (if the text begins with @), or command (including aliases and functions) in turn. If none of these produces a match, filename completion is attempted. complete-command (M-!) Attempt completion on the text before point, treating it as a command name. Command completion attempts to match the text against aliases, reserved words, shell functions, shell builtins, and finally executable filenames, in that order.
De maneira semelhante ao mais comum complete -F
, parte disso pode ser passado para uma função usando bind -x
.
function _complete0 () {
local -a _cmds
local -A _seen
local _path=$PATH _ii _xx _cc _cmd _short
local _aa=( ${READLINE_LINE} )
if [[ -f ~/.complete.d/"${_aa[0]}" && -x ~/.complete.d/"${_aa[0]}" ]]; then
## user-provided hook
_cmds=( $( ~/.complete.d/"${_aa[0]}" ) )
elif [[ -x ~/.complete.d/DEFAULT ]]; then
_cmds=( $( ~/.complete.d/DEFAULT ) )
else
## compgen -c for default "command" complete
_cmds=( $(PATH=$_path compgen -o bashdefault -o default -c ${_aa[0]}) )
fi
## remove duplicates, cache shortest name
_short="${_cmds[0]}"
_cc=${#_cmds[*]} # NB removing indexes inside loop
for (( _ii=0 ; _ii<$_cc ; _ii++ )); do
_cmd=${_cmds[$_ii]}
[[ -n "${_seen[$_cmd]}" ]] && unset _cmds[$_ii]
_seen[$_cmd]+=1
(( ${#_short} > ${#_cmd} )) && _short="$_cmd"
done
_cmds=( "${_cmds[@]}" ) ## recompute contiguous index
## find common prefix
declare -a _prefix=()
for (( _xx=0; _xx<${#_short}; _xx++ )); do
_prev=${_cmds[0]}
for (( _ii=0 ; _ii<${#_cmds[*]} ; _ii++ )); do
_cmd=${_cmds[$_ii]}
[[ "${_cmd:$_xx:1}" != "${_prev:$_xx:1}" ]] && break
_prev=$_cmd
done
[[ $_ii -eq ${#_cmds[*]} ]] && _prefix[$_xx]="${_cmd:$_xx:1}"
done
printf -v _short "%s" "${_prefix[@]}" # flatten
## emulate completion list of matches
if [[ ${#_cmds[*]} -gt 1 ]]; then
for (( _ii=0 ; _ii<${#_cmds[*]} ; _ii++ )); do
_cmd=${_cmds[$_ii]}
[[ -n "${_seen[$_cmds]}" ]] && printf "%-12s " "$_cmd"
done | sort | fmt -w $((COLUMNS-8)) | column -tx
# fill in shortest match (prefix)
printf -v READLINE_LINE "%s" "$_short"
READLINE_POINT=${#READLINE_LINE}
fi
## exactly one match
if [[ ${#_cmds[*]} -eq 1 ]]; then
_aa[0]="${_cmds[0]}"
printf -v READLINE_LINE "%s " "${_aa[@]}"
READLINE_POINT=${#READLINE_LINE}
else
: # nop
fi
}
bind -x '"\C-i":_complete0'
Isso habilita seus próprios ganchos por string de comando ou prefixo em ~/.complete.d/
. Por exemplo. se você criar um executável ~/.complete.d/loc
com:
#!/bin/bash
echo localc
Isso fará (aproximadamente) o que você espera.
A função acima vai a alguns comprimentos para emular o comportamento normal do completamento do comando bash, embora seja imperfeito (particularmente o duvidoso sort | fmt | column
carry-on para exibir uma lista de correspondências).
No entanto , um problema não trivial com isso, ele só pode usar uma função para substituir a ligação à função complete
principal (chamada com TAB por padrão).
Essa abordagem funcionaria bem com uma vinculação de chave diferente usada apenas para conclusão de comando personalizada, mas ela simplesmente não implementa a lógica de conclusão completa depois disso (por exemplo, palavras posteriores na linha de comando). Fazer isso exigiria analisar a linha de comando, lidar com a posição do cursor e outras coisas complicadas que provavelmente não devem ser consideradas em um script de shell ...