53 votes

Comment puis-je faire en sorte que bash effectue une complétion par tabulation pour mes alias ?

J'ai mis en place un certain nombre de scripts de complétion bash scripts (la plupart en utilisant bash-it et quelques-uns manuellement).

J'ai également mis en place un certain nombre d'alias pour des tâches courantes telles que gco para git checkout . Pour l'instant, je peux taper git checkout dTab y develop est complété pour moi, mais lorsque je tape gco dTab il ne s'achève pas.

Je suppose que c'est parce que la complétion script se termine sur git et il ne voit pas gco .

Existe-t-il un moyen de faire fonctionner de manière générique/programmatique tous mes scripts avec mes alias ? L'impossibilité de compléter lors de l'utilisation de l'alias va à l'encontre de l'objectif de l'alias.

50voto

Niamh Points 1

Le code suivant, adapté de cette réponse de Stack Overflow et ceci Fil de discussion sur les forums Ubuntu ajoutera des compléments pour tous les alias définis :

# 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

Pour les alias simples (commande uniquement, pas d'arguments), il affecte la fonction de complétion originale à l'alias ; pour les alias avec arguments, il crée une fonction enveloppante qui insère les arguments supplémentaires dans la fonction de complétion originale.

Contrairement aux scripts dont elle est issue, la fonction respecte les guillemets à la fois pour la commande d'alias et pour ses arguments (mais les premiers doivent être pris en compte par la commande de complétion, et ne peuvent pas être imbriqués), et elle devrait filtrer de manière fiable les alias à listes de commandes y tuyaux (qui sont ignorées, car il est impossible de savoir ce qu'il faut y compléter sans recréer toute la logique d'analyse de la ligne de commande Shell).

Utilisation

Soit vous enregistrez le code dans un fichier Shell Shell et source ou copier la fonction en gros dans, .bashrc (ou votre fichier de points pertinent ). L'important est d'appeler la fonction après que la complétion bash et les définitions d'alias ont été mises en place (le code ci-dessus appelle la fonction juste après sa définition, dans un esprit "source et oubli", mais vous pouvez déplacer l'appel n'importe où en aval si cela vous convient mieux). Si vous ne voulez pas que la fonction reste dans votre environnement après sa sortie, vous pouvez ajouter unset -f alias_completion après l'avoir appelé.

Notes

Si vous utilisez bash 4.1 ou supérieur et que vous utilisez des complétions chargées dynamiquement, le script tentera de charger les complétions pour toutes vos commandes aliasées afin de pouvoir construire les fonctions enveloppantes pour vos alias.

6voto

wisbucky Points 2298

Il s'agit de la méthode manuelle, pour ceux qui la recherchent.

Tout d'abord, recherchez la commande d'achèvement originale. Exemple :

$ complete | grep git

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

Ajoutez-les à votre script de démarrage (par exemple ~/.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

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

5voto

Cyker Points 291

Existe-t-il un moyen de faire fonctionner de manière générique/programmatique tous mes scripts avec mes alias ?

Oui, voici le alias complet qui résout exactement votre problème. Il fournit une complétion générique et programmatique des alias sans utiliser de eval .

1voto

roneo.org Points 76

La réponse donnée précédemment par kopischke fonctionne comme un charme sur Debian 10 seulement si vous supprimez le alias_completion à la fin

Voici l'extrait correct à coller dans yo ~/.bashrc :

# 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"
};

0voto

philipp2100 Points 21

Kopischke a fait un excellent travail en déterminant l'emballage d'achèvement (merci !).

Cependant, avec les "bons" alias, le code peut être exécuté de manière non désirée, ce qui peut conduire à perte potentielle de données :

$ unalias -a
$ alias cleantmp='echo Removed `rm -v /tmp/uniquenameasdf* | wc -l` files.'
$ touch /tmp/uniquenameasdf42
$ alias_completion 
$ rm /tmp/uniquenameasdf42
rm: cannot remove '/tmp/uniquenameasdf42': No such file or directory
$

eval est dangereux !

SistemesEz.com

SystemesEZ est une communauté de sysadmins où vous pouvez résoudre vos problèmes et vos doutes. Vous pouvez consulter les questions des autres sysadmins, poser vos propres questions ou résoudre celles des autres.

Powered by:

X