¿Cómo personalizar la finalización del comando Bash?

24

En bash, es bastante fácil configurar la finalización personalizada de argumentos de comando utilizando el completeincorporado. Por ejemplo, si, para un comando hipotético con una sinopsis de foo --a | --b | --c, podría hacercomplete -W '--a --b --c' foo

También puede personalizar la conclusión que se obtiene cuando se pulsa Taben un vacío rápida usando complete -E, por ejemplo, complete -E -W 'foo bar'. Luego, presionando tab en el indicador vacío solo sugeriría fooy bar.

¿Cómo personalizo la finalización de comandos en un indicador no vacío? Por ejemplo, si estoy sentado a las:

anthony@Zia:~$ f

¿Cómo personalizar la finalización para que presionar la pestaña siempre se complete foo?

(El caso real que me gustaría es locTABlocalc. Y mi hermano, que me hizo preguntar esto, lo quiere con mplayer).

derobert
fuente
2
¿Ha considerado simplemente aliasing loca localc? 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.
jw013
@ jw013 Sí, busqué por un tiempo y tampoco pude encontrar nada. Estoy medio esperando que alguien sugiera cambiar a zsh también. Al menos si zsh lo hace, puedo descansar cómodamente sabiendo que la próxima versión de bash también lo hará. 😀
derobert
tienes que presionar TAB dos veces y completará los comandos.
Floyd
1
¿Pero eso no romperá todas las otras terminaciones? Es decir, incluso si es posible, que ya no sería capaz de conseguir locate, locale, lockfileo cualquiera de las otras expansiones de loc. Quizás un mejor enfoque sería asignar una clave diferente a esa finalización específica.
terdon
1
¿Puedes dar un ejemplo de dicho contexto (que llevaría al efecto deseado loc<TAB>->localc)?
damienfrancois

Respuestas:

9

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 -Iexactamente 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/loccon:

#!/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 | columnmano 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 completefunció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 ...

Sr. púrpura
fuente
1

No sé si entendí tu necesidad de esto ...
Esto implicaría que tu bash solo conoce un comando desde el principio f.
Una idea básica de finalización es: si es ambiguo, imprima las posibilidades.
Por lo tanto, puede configurar su PATHen un directorio que solo contenga este comando y deshabilitar todas las funciones internas de bash para que funcione.

De todos modos, puedo darte también una especie de solución alternativa:

alias _='true &&'
complete -W foo _

Entonces, si escribe _ <Tab>, se completará a lo _ fooque se ejecuta foo.

Pero, sin embargo alias f='foo', sería mucho más fácil.

m13r
fuente
0

Una respuesta simple para ti sería

$ cd into /etc/bash_completion.d
$ ls

solo las salidas básicas

autoconf       gpg2               ntpdate           shadow
automake       gzip               open-iscsi        smartctl
bash-builtins  iconv              openssl           sqlite3
bind-utils     iftop              perl              ssh
brctl          ifupdown           pkg-config        strace
bzip2          info               pm-utils          subscription-manager
chkconfig      ipmitool           postfix           tar
configure      iproute2           procps            tcpdump
coreutils      iptables           python            util-linux
cpio           lsof               quota-tools       wireless-tools
crontab        lvm                redefine_filedir  xmllint
cryptsetup     lzma               rfkill            xmlwf
dd             make               rpm               xz
dhclient       man                rsync             yum.bash
e2fsprogs      mdadm              scl.bash          yum-utils.bash
findutils      module-init-tools  service
getent         net-tools          sh

simplemente agregue su programa deseado para completar automáticamente hasta completar bash

unixmiah
fuente
2
Esos archivos tienen script de shell, algunos bastante complejos si no recuerdo mal. Además, solo completan argumentos una vez que ya ha escrito el comando ... No completan el comando.
derobert
Entiendo lo que dices. Cuando pueda ver este documento: debian-administration.org/article/316/…
unixmiah
-3

Ejecute el siguiente comando para encontrar dónde está instalado mplayer binary:

which mplayer

O use la ruta al binario mplayer si ya lo sabe, en el siguiente comando:

ln -s /path/to/mplayer /bin/mplayer

Idealmente, todo lo que escriba se busca en todos los directorios especificados en la $PATHvariable.

Mathew Paret
fuente
2
Hola, Mathew, bienvenido a Unix y Linux. Creo que la pregunta del OP es un poco más complicada que la respuesta que estás sugiriendo. Supongo mplayerque ya está en su $PATHy quieren algo como mp<tab>producir mplayer, en lugar de todos los binarios que comienzan con mp(p. Ej., mpage mpcd mpartition mplayerEtc.)
drs