¿Cómo puedo hacer que bash realice la finalización de tabulación para mis alias?

45

Tengo un montón de scripts de finalización de bash configurados (principalmente usando bash-it y algunos configurados manualmente).

También tengo un montón de alias configurados para tareas comunes como gcofor git checkout. En este momento, puedo escribir git checkout dTaby developse ha completado para mí, pero cuando escribo gco dTabno se completa.

Supongo que esto se debe a que el script de finalización se está completando gity no puede ver gco.

¿Hay alguna manera de obtener genérica / programáticamente todos mis scripts de finalización para que funcionen con mis alias? No poder completar cuando se usa el tipo de alias derrota el propósito del alias.

dstarh
fuente
¿Qué sistema operativo y bash estás usando? Estoy en Ubuntu 11.10 y bash 4.2.10 (1) -release (x86_64-pc-linux-gnu) y tengo esta funcionalidad integrada en mi shell para mis muchos alias. por cierto bash --versionpara obtener esto (no use -v, salida diferente).
Michael Durrant
Lo siento, perdí un poco de información - OSX Lion, GNU bash, versión 3.2.48 (1) -release (x86_64-apple-darwin11)
dstarh
1
@killermist: a menos que esté completamente equivocado, zsh tampoco completa los comandos con alias listos para usar. Sin embargo, implementar una función que agrega alias definidos a la finalización parece ser mucho más fácil que para bash, ya que el sistema de finalización de zhs parece más poderoso y directo que bash.
kopischke
Cross site duplicate: stackoverflow.com/questions/342969/…
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
1
@MichaelDurrant ¿Estás seguro de que esto realmente está integrado para los alias? Estoy en Ubuntu 15.10 con Bash 4.3.42 (1) -release (x86_64-pc-linux-gnu) y no existe tal cosa. También probé algunas versiones anteriores. Entonces, por ejemplo, si escribe ll --[TAB], ¿imprimirá una lista de opciones para ls? Soy bastante escéptico con respecto a esto, pero si está seguro de que tal cosa existió en 11.10, tendría curiosidad por analizarlo y determinar qué se eliminó.
Seis

Respuestas:

42

El siguiente código, adaptado de esta respuesta de desbordamiento de pila y este hilo de discusión de los foros de Ubuntu agregará terminaciones para todos sus alias definidos:

# Automatically add completion for all aliases to commands having completion functions
function alias_completion {
    local namespace="alias_completion"

    # parse function based completion definitions, where capture group 2 => function and 3 => trigger
    local compl_regex='complete( +[^ ]+)* -F ([^ ]+) ("[^"]+"|[^ ]+)'
    # parse alias definitions, where capture group 1 => trigger, 2 => command, 3 => command arguments
    local alias_regex="alias ([^=]+)='(\"[^\"]+\"|[^ ]+)(( +[^ ]+)*)'"

    # create array of function completion triggers, keeping multi-word triggers together
    eval "local completions=($(complete -p | sed -Ene "/$compl_regex/s//'\3'/p"))"
    (( ${#completions[@]} == 0 )) && return 0

    # create temporary file for wrapper functions and completions
    rm -f "/tmp/${namespace}-*.tmp" # preliminary cleanup
    local tmp_file; tmp_file="$(mktemp "/tmp/${namespace}-${RANDOM}XXX.tmp")" || return 1

    local completion_loader; completion_loader="$(complete -p -D 2>/dev/null | sed -Ene 's/.* -F ([^ ]*).*/\1/p')"

    # read in "<alias> '<aliased command>' '<command args>'" lines from defined aliases
    local line; while read line; do
        eval "local alias_tokens; alias_tokens=($line)" 2>/dev/null || continue # some alias arg patterns cause an eval parse error
        local alias_name="${alias_tokens[0]}" alias_cmd="${alias_tokens[1]}" alias_args="${alias_tokens[2]# }"

        # skip aliases to pipes, boolean control structures and other command lists
        # (leveraging that eval errs out if $alias_args contains unquoted shell metacharacters)
        eval "local alias_arg_words; alias_arg_words=($alias_args)" 2>/dev/null || continue
        # avoid expanding wildcards
        read -a alias_arg_words <<< "$alias_args"

        # skip alias if there is no completion function triggered by the aliased command
        if [[ ! " ${completions[*]} " =~ " $alias_cmd " ]]; then
            if [[ -n "$completion_loader" ]]; then
                # force loading of completions for the aliased command
                eval "$completion_loader $alias_cmd"
                # 124 means completion loader was successful
                [[ $? -eq 124 ]] || continue
                completions+=($alias_cmd)
            else
                continue
            fi
        fi
        local new_completion="$(complete -p "$alias_cmd")"

        # create a wrapper inserting the alias arguments if any
        if [[ -n $alias_args ]]; then
            local compl_func="${new_completion/#* -F /}"; compl_func="${compl_func%% *}"
            # avoid recursive call loops by ignoring our own functions
            if [[ "${compl_func#_$namespace::}" == $compl_func ]]; then
                local compl_wrapper="_${namespace}::${alias_name}"
                    echo "function $compl_wrapper {
                        (( COMP_CWORD += ${#alias_arg_words[@]} ))
                        COMP_WORDS=($alias_cmd $alias_args \${COMP_WORDS[@]:1})
                        (( COMP_POINT -= \${#COMP_LINE} ))
                        COMP_LINE=\${COMP_LINE/$alias_name/$alias_cmd $alias_args}
                        (( COMP_POINT += \${#COMP_LINE} ))
                        $compl_func
                    }" >> "$tmp_file"
                    new_completion="${new_completion/ -F $compl_func / -F $compl_wrapper }"
            fi
        fi

        # replace completion trigger by alias
        new_completion="${new_completion% *} $alias_name"
        echo "$new_completion" >> "$tmp_file"
    done < <(alias -p | sed -Ene "s/$alias_regex/\1 '\2' '\3'/p")
    source "$tmp_file" && rm -f "$tmp_file"
}; alias_completion

Para los alias simples (solo comando, sin argumentos) asignará la función de finalización original al alias; para los alias con argumentos, crea una función de contenedor que inserta los argumentos adicionales en la función de finalización original.

A diferencia de los scripts a partir de los cuales ha evolucionado, la función respeta las comillas tanto para el comando de alias como para sus argumentos (pero el primero debe coincidir con el comando de finalización y no puede anidarse), y debe filtrar de manera confiable los alias a las listas de comandos y tuberías (que se omiten, ya que es imposible saber qué completar en ellas sin volver a crear la lógica de análisis de la línea de comandos de shell completa).

Uso

Guarde el código como un archivo de script de shell y fuente que, o copie la función al por mayor, .bashrc(o su archivo de puntos pertinente ). Lo importante es llamar a la función después de que se hayan configurado las definiciones de alias y de finalización de bash (el código anterior llama a la función justo después de su definición, en un espíritu de "fuente y olvido", pero puede mover la llamada a cualquier lugar aguas abajo si eso te queda mejor). Si no desea la función en su entorno después de que se cierre, puede agregarla unset -f alias_completiondespués de llamarla.

Notas

Si está utilizando bash4.1 o superior y usa terminaciones cargadas dinámicamente, el script intentará cargar las terminaciones para todos sus comandos con alias para que pueda construir las funciones de contenedor para sus alias.

kopischke
fuente
1
¿Cómo haría para instalar ese script?
Der Hochstapler
1
@OliverSalzburg: tendrá que procesarlo en uno de sus archivos de perfil de shell, crucialmente después de completar bash, eso probablemente lo haga ~/.bashrc. Almacénelo como un archivo de script de shell y cómprelo ( . /path/to/alias_completion.sh), o copie y pegue el código al por mayor.
kopischke
1
@OliverSalzburg: se agregaron instrucciones de uso (no noté de inmediato que no eres el OP).
kopischke
1
@kopischke Vea esta pregunta , aparentemente para los archivos /usr/share/bash-completion/completions/que se encuentran debajo , solo se cargan la primera vez que el usuario accede [TAB]. Entonces, incluso si la función se carga desde ~/.bashrcella, no generará terminaciones para los alias de los comandos en ella. Después de asegurarme de complete -pque funciona apt-gety apt-cachecopié y pegué su función en el terminal y funciona correctamente.
jamadagni
1
@kopischke Así que no estoy seguro de cómo forzar el abastecimiento de todos los archivos de finalización cargados dinámicamente, o incluso si es aconsejable. Por ahora he copiado el archivo generado a partir de la finalización /tmpde ~/.bash_completiony ha añadido manualmente en sus inicios las correspondientes source /usr/share/bash-completion/completions/entradas (por separado para apt-gety apt-cache- apt-{cache,get}Por qué no funciona).
jamadagni
4

¿Hay alguna manera de obtener genérica / programáticamente todos mis scripts de finalización para que funcionen con mis alias?

Sí, aquí está el proyecto de alias completo que resuelve su problema exactamente. Proporciona la finalización de alias genérico y programático sin usar eval.

Cyker
fuente
2

Esta es la forma manual, para aquellos que buscan esto.

Primero, busque el comando de finalización original. Ejemplo:

$ complete | grep git

complete -o bashdefault -o default -o nospace -F __git_wrap__git_main git

Ahora agréguelos a su script de inicio (por ejemplo, ~ / .bashrc):

# load dynamically loaded completion functions (may not be required)
_completion_loader git

# copy the original statement, but replace the last command (git) with your alias (g)
complete -o bashdefault -o default -o nospace -F __git_wrap__git_main g

fuente: https://superuser.com/a/1004334

wisbucky
fuente