La finalización del comando (junto con otras cosas) se maneja mediante la finalización de bash readline . Esto funciona a un nivel ligeramente más bajo que la "finalización programable" habitual (que se invoca solo cuando se identifica el comando y los dos casos especiales que identificó anteriormente).
Actualización: la nueva versión de bash-5.0 (enero de 2019) agrega complete -I
exactamente este problema.
Los comandos relevantes de readline son:
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 manera similar a la más común complete -F
, algo de esto se puede transferir a una función mediante el uso 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'
Esto habilita sus propios enganches de cadena por comando o prefijo ~/.complete.d/
. Por ejemplo, si crea un ejecutable ~/.complete.d/loc
con:
#!/bin/bash
echo localc
Esto hará (aproximadamente) lo que espera.
La función anterior va hasta cierto punto para emular el comportamiento normal de finalización del comando bash, aunque es imperfecto (particularmente el dudoso equipaje de sort | fmt | column
mano para mostrar una lista de coincidencias).
Sin embargo , un problema no trivial con esto solo puede usar una función para reemplazar el enlace a la complete
función principal (invocado con TAB por defecto).
Este enfoque funcionaría bien con una combinación de teclas diferente utilizada solo para la finalización de comandos personalizados, pero simplemente no implementa la lógica de finalización completa después de eso (por ejemplo, palabras posteriores en la línea de comandos). Hacerlo requeriría analizar la línea de comando, tratar con la posición del cursor y otras cosas difíciles que probablemente no deberían considerarse en un script de shell ...
loc
alocalc
? Sugiero alternativas porque después de bastante tiempo cavando y buscando, no he encontrado una manera de personalizar la finalización de bash de esta manera. Puede que no sea posible.