Comment personnaliser l'achèvement de la commande Bash?

24

En bash, il est assez facile de configurer l'achèvement personnalisé des arguments de commande à l'aide de la fonction completeintégrée. Par exemple, si, pour une commande hypothétique avec un synopsis de foo --a | --b | --c, vous pourriez fairecomplete -W '--a --b --c' foo

Vous pouvez également personnaliser l'achèvement que vous obtenez lorsque vous appuyez Tabà un vide rapide en utilisant complete -E, par exemple, complete -E -W 'foo bar'. Ensuite, en appuyant sur tab à l'invite vide suggérerait uniquement fooet bar.

Comment personnaliser l'achèvement de la commande à une invite non vide? Par exemple, si je suis assis à:

anthony@Zia:~$ f

comment personnaliser l'achèvement de sorte que la pression sur l'onglet se termine toujours foo?

(Le cas réel que j'aimerais est locTABlocalc. Et mon frère, qui m'a poussé à le demander, le veut avec mplayer).

derobert
la source
2
Avez - vous envisagé simplement aliasing locà localc? Je suggère des alternatives car après un certain temps à creuser et à chercher, je n'ai pas trouvé un moyen de personnaliser l'achèvement de bash de cette façon. Ce n'est peut-être pas possible.
jw013
@ jw013 Oui, j'ai cherché un moment et je n'ai rien trouvé non plus. Je m'attends à moitié à ce que quelqu'un suggère également de passer à zsh. Au moins si zsh le fait, je peux me reposer confortablement en sachant que la prochaine version de bash le sera également. 😀
derobert
vous devez appuyer deux fois sur TAB et il terminera les commandes.
Floyd
1
Cela ne brisera-t-il pas toutes les autres finitions? Je veux dire, même s'il est possible, vous ne pourrez plus obtenir locate, locale, lockfileou l' une des autres extensions de loc. Une meilleure approche serait peut-être de mapper une clé différente pour cette réalisation spécifique.
terdon
1
pouvez-vous donner un exemple d'un tel contexte (qui conduirait à l'effet souhaité loc<TAB>->localc)?
damienfrancois

Réponses:

9

L'achèvement de la commande (avec d'autres choses) est géré via l' achèvement de la ligne de lecture bash . Cela fonctionne à un niveau légèrement inférieur à la «complétion programmable» habituelle (qui n'est invoquée que lorsque la commande est identifiée et les deux cas spéciaux que vous avez identifiés ci-dessus).

Mise à jour: la nouvelle version de bash-5.0 (janvier 2019) ajoute complete -Iexactement ce problème.

Les commandes readline pertinentes sont:

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 manière similaire à la plus courante complete -F, une partie de cela peut être transférée à une fonction en utilisant 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'

Cela permet à votre propre chaîne de commande ou préfixe de se connecter ~/.complete.d/. Par exemple, si vous créez un exécutable ~/.complete.d/locavec:

#!/bin/bash
echo localc

Cela fera (à peu près) ce que vous attendez.

La fonction ci-dessus va à certaines longueurs pour émuler le comportement normal d'achèvement de la commande bash, bien qu'il soit imparfait (en particulier le bagage à sort | fmt | columnmain douteux pour afficher une liste de correspondances).

Cependant , un problème non trivial avec cela, il ne peut utiliser qu'une fonction pour remplacer la liaison à la completefonction principale (invoquée avec TAB par défaut).

Cette approche fonctionnerait bien avec une liaison de touches différente utilisée uniquement pour la complétion de commande personnalisée, mais elle n'implémente tout simplement pas la logique de complétion complète après cela (par exemple, des mots ultérieurs dans la ligne de commande). Pour ce faire, il faudrait analyser la ligne de commande, gérer la position du curseur et d'autres choses délicates qui ne devraient probablement pas être prises en compte dans un script shell ...

Mr Spuratic
la source
1

Je ne sais pas si je n'ai pas compris votre besoin de cela ...
Cela impliquerait que votre bash ne connaît qu'une seule commande commençant par f.
Une idée de base de l'achèvement est: si elle est ambiguë, imprimez les possibilités.
Vous pouvez donc définir votre PATHdans un répertoire contenant uniquement cette commande et désactiver toutes les commandes bash pour obtenir ce travail.

Quoi qu'il en soit, je peux également vous donner une sorte de solution:

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

Donc, si vous tapez, _ <Tab>il se terminera à _ fooqui s'exécute foo.

Mais ce alias f='foo'serait néanmoins beaucoup plus facile.

m13r
la source
0

La réponse simple pour vous serait de

$ cd into /etc/bash_completion.d
$ ls

juste les sorties de base

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

il suffit d'ajouter le programme souhaité pour terminer automatiquement jusqu'à la fin

unixmiah
la source
2
Ces fichiers contiennent un script shell, certains assez complexes si je me souviens bien. De plus, ils ne complètent les arguments qu'une fois que vous avez déjà tapé la commande ... Ils ne terminent pas la commande.
derobert
Je comprends ce que tu veux dire. Quand vous pouvez jeter un œil à ce document: debian-administration.org/article/316/…
unixmiah
-3

Exécutez la commande ci-dessous pour trouver où le binaire mplayer est installé:

which mplayer

OU utilisez le chemin vers le binaire mplayer si vous le savez déjà, dans la commande ci-dessous:

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

Idéalement, tout ce que vous tapez est recherché dans tous les répertoires spécifiés dans la $PATHvariable.

Mathew Paret
la source
2
Salut, Mathew, bienvenue sur Unix et Linux. Je pense que la question du PO est un peu plus compliquée que la réponse que vous proposez. Je suppose qu'il mplayerest déjà dans leur $PATHet ils veulent quelque chose comme mp<tab>produire mplayer, au lieu de tous les binaires qui commencent mp(par exemple, mpage mpcd mpartition mplayeretc.)
drs