Comment créer un script avec l'auto-complétion?

120

Quand j'utilise un programme comme svnet que je tape dans Gnome Terminal:

svn upd

et Tabcliquez sur son auto-complétée pour:

svn update

Est-il possible de faire quelque chose comme ça dans mon script bash personnalisé?

UAdapter
la source
expliquer "script bash", vous voulez dire lors de l'édition d'un script? que veux-tu en faire?
Bruno Pereira
3
lors de l'utilisation d'un script dans la console
UAdapter
Pour ce qui est du lieu où mettre vos finitions, voir cette question ainsi que les commentaires pour la réponse acceptée.
Jarno

Réponses:

44

Vous pouvez utiliser l’ achèvement programmable . Regardez /etc/bash_completionet /etc/bash_completion.d/*pour quelques exemples.

Florian Diesch
la source
131
Pourquoi ne pas inclure un exemple simple directement lié à la question?
MountainX
2
Les scripts actuels pour Ubuntu 16 se trouvent dans/usr/share/bash-completion/completions/<program>
peter le
17
Imo, des exemples devraient être inclus dans la réponse, pas dans un lien.
Billynoah
2
Je pense que cette plate-forme est censée être une alternative plus pratique aux documentations complètes pouvant être trouvées avec une simple recherche sur Google. Dumping d'un lien de documentation n'aide pas cela. Le lien contenant une ancre ne fait sûrement pas beaucoup de différence.
Timuçin
4
The provided link has that already- ce pourrait être aujourd'hui, mais pas demain. Ou l'année prochaine. Ou dans une décennie. Quoi que vous suggériez au sujet de la pertinence de la documentation, Stack Overflow décourage les réponses par lien uniquement pour ces raisons.
Liam Dawson le
205

J'ai six mois de retard mais je cherchais la même chose et j'ai trouvé ceci:

Vous devrez créer un nouveau fichier:

/etc/bash_completion.d/foo

Pour une complétion automatique statique ( --help/ --verbosepar exemple), ajoutez ceci:

_foo() 
{
    local cur prev opts
    COMPREPLY=()
    cur="${COMP_WORDS[COMP_CWORD]}"
    prev="${COMP_WORDS[COMP_CWORD-1]}"
    opts="--help --verbose --version"

    if [[ ${cur} == -* ]] ; then
        COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
        return 0
    fi
}
complete -F _foo foo
  • COMP_WORDS est un tableau contenant tous les mots de la ligne de commande actuelle.
  • COMP_CWORD est un index du mot contenant la position actuelle du curseur.
  • COMPREPLY est une variable de tableau à partir de laquelle Bash lit les complétions possibles.

Et la compgencommande retourne le tableau d'éléments de --help, --verboseet --versioncorrespondant le mot courant "${cur}":

compgen -W "--help --verbose --version" -- "<userinput>"

Source: http://www.debian-administration.org/articles/316

Louis Soulez
la source
27
Cela devrait être la réponse acceptée! C'est un exemple complet.
Victor Schröder
5
Conseil: si quelqu'un souhaite des suggestions de mots ne commençant pas par -et leur indique de ne pas commencer à taper le mot cible, supprimez simplement les lignes if [...] thenet fi.
Cedric Reichenbach
1
C’est la réponse exacte que je cherche depuis des heures et il s’est avéré qu’elle s’était noyée dans une documentation compliquée qui ne mentionnait jamais que le fichier devait être placé /etc/bash_completion.d/. Je ne suis venu ici que parce que je voulais poster une réponse quelque part, mais il s'avère que quelqu'un avait trois années d'avance depuis le début :) Exemple clair, concis et complet, merci!
Steen Schütt
1
Tutoriel - Créer un script d'achèvement de bash
Lazarus Lazaridis
34

Toutes les finitions de bash sont stockées dans /etc/bash_completion.d/. Donc, si vous construisez un logiciel avec bash_completion, il serait intéressant de demander à deb / make install de déposer un fichier avec le nom du logiciel dans ce répertoire. Voici un exemple de script d'achèvement de bash pour Rsync:

# bash completion for rsync

have rsync &&
_rsync()
{
    # TODO: _split_longopt

    local cur prev shell i userhost path   

    COMPREPLY=()
    cur=`_get_cword`
    prev=${COMP_WORDS[COMP_CWORD-1]}

    _expand || return 0

    case "$prev" in
    --@(config|password-file|include-from|exclude-from))
        _filedir
        return 0
        ;;
    -@(T|-temp-dir|-compare-dest))
        _filedir -d
        return 0
        ;;
    -@(e|-rsh))
        COMPREPLY=( $( compgen -W 'rsh ssh' -- "$cur" ) )
        return 0
        ;;
    esac

    case "$cur" in
    -*)
        COMPREPLY=( $( compgen -W '-v -q  -c -a -r -R -b -u -l -L -H \
            -p -o -g -D -t -S -n -W -x -B -e -C -I -T -P \
            -z -h -4 -6 --verbose --quiet --checksum \
            --archive --recursive --relative --backup \
            --backup-dir --suffix= --update --links \
            --copy-links --copy-unsafe-links --safe-links \
            --hard-links --perms --owner --group --devices\
            --times --sparse --dry-run --whole-file \
            --no-whole-file --one-file-system \
            --block-size= --rsh= --rsync-path= \
            --cvs-exclude --existing --ignore-existing \
            --delete --delete-excluded --delete-after \
            --ignore-errors --max-delete= --partial \
            --force --numeric-ids --timeout= \
            --ignore-times --size-only --modify-window= \
            --temp-dir= --compare-dest= --compress \
            --exclude= --exclude-from= --include= \
            --include-from= --version --daemon --no-detach\
            --address= --config= --port= --blocking-io \
            --no-blocking-io --stats --progress \
            --log-format= --password-file= --bwlimit= \
            --write-batch= --read-batch= --help' -- "$cur" ))
        ;;
    *:*)
        # find which remote shell is used
        shell=ssh
        for (( i=1; i < COMP_CWORD; i++ )); do
            if [[ "${COMP_WORDS[i]}" == -@(e|-rsh) ]]; then
                shell=${COMP_WORDS[i+1]}
                break
            fi
        done
        if [[ "$shell" == ssh ]]; then
            # remove backslash escape from :
            cur=${cur/\\:/:}
            userhost=${cur%%?(\\):*}
            path=${cur#*:}
            # unescape spaces
            path=${path//\\\\\\\\ / }
            if [ -z "$path" ]; then
                # default to home dir of specified
                # user on remote host
                path=$(ssh -o 'Batchmode yes' $userhost pwd 2>/dev/null)
            fi
            # escape spaces; remove executables, aliases, pipes
            # and sockets; add space at end of file names
            COMPREPLY=( $( ssh -o 'Batchmode yes' $userhost \
                command ls -aF1d "$path*" 2>/dev/null | \
                sed -e 's/ /\\\\\\\ /g' -e 's/[*@|=]$//g' \
                -e 's/[^\/]$/& /g' ) )
        fi
        ;;
    *)  
        _known_hosts_real -c -a "$cur"
        _filedir
        ;;
    esac

    return 0
} &&
complete -F _rsync $nospace $filenames rsync

# Local variables:
# mode: shell-script
# sh-basic-offset: 4
# sh-indent-comment: t
# indent-tabs-mode: nil
# End:
# ex: ts=4 sw=4 et filetype=sh

Il serait probablement intéressant d’examiner l’un des fichiers de fin d’essais correspondant à votre programme. Un des exemples les plus simples est le rrdtoolfichier.

Marco Ceppi
la source
2
Peut-on configurer les achèvements pour charger depuis d'autres emplacements? C'EST À DIRE. ~ / .local
Chris
1
Oui, vous pouvez mettre un fichier comme celui-ci où vous voulez, puis source ~/.local/mycrazycompletiondans~/.bashrc
Stefano Palazzo
@Chris voir les instructions dans la FAQ sur l'achèvement de Bash
vendredi
De nos jours, la plupart des achèvements se trouvent dans le répertoire donné par la commande pkg-config --variable = completionsdir bash-completion` et ce répertoire correspond aux recommandations de la FAQ de complétion de Bash liée ci-dessus.
Jarno
34

Voici un tutoriel complet.

Donnons un exemple de script appelé admin.sh sur lequel vous voudriez que la saisie semi-automatique fonctionne.

#!/bin/bash

while [ $# -gt 0 ]; do
  arg=$1
  case $arg in
    option_1)
     # do_option_1
    ;;
    option_2)
     # do_option_1
    ;;
    shortlist)
      echo option_1 option_2 shortlist
    ;;
    *)
     echo Wrong option
    ;;
  esac
  shift
done

Notez la liste des options. L'appel d'un script avec cette option affichera toutes les options possibles pour ce script.

Et voici le script de saisie semi-automatique:

_script()
{
  _script_commands=$(/path/to/your/script.sh shortlist)

  local cur
  COMPREPLY=()
  cur="${COMP_WORDS[COMP_CWORD]}"
  COMPREPLY=( $(compgen -W "${_script_commands}" -- ${cur}) )

  return 0
}
complete -o nospace -F _script ./admin.sh

Notez que le dernier argument à compléter est le nom du script auquel vous souhaitez ajouter la complétion automatique. Tout ce que vous avez à faire est d’ajouter votre script autocomplete à bashrc as

source /path/to/your/autocomplete.sh

ou le copier dans /etc/bash.completion.d

kokosing
la source
1
A quoi sert la prevvariable? Vous ne semblez pas l'utiliser.
Dimo414
@ dimo414 Il semble que oui, j'ai supprimé cette variable
kokosing
Que fait l' -o nospaceoption?
Andrew Lamarra
12

Si tout ce que vous voulez, c'est une auto-complétion basée sur un mot simple (donc aucune complétion de sous-commande ou quoi que ce soit), la completecommande a une -Woption qui fait juste la bonne chose.

Par exemple, j’ai les lignes suivantes .bashrcpour compléter automatiquement un programme appelé jupyter :

# gleaned from `jupyter --help`
_jupyter_options='console qtconsole notebook' # shortened for this answer
complete -W "${_jupyter_options}" 'jupyter'

Maintenant jupyter <TAB> <TAB>auto-complète pour moi.

Les documents sur gnu.org sont utiles.

Il semble que la IFSvariable soit correctement définie, mais cela ne m'a posé aucun problème.

Pour ajouter l'achèvement du nom de fichier et l'achèvement de BASH par défaut, utilisez l' -ooption:

complete -W "${_jupyter_options}" -o bashdefault -o default 'jupyter'

Pour utiliser cela dans zsh, ajoutez le code suivant avant d'exécuter la completecommande dans votre ~/.zshrc:

# make zsh emulate bash if necessary
if [[ -n "$ZSH_VERSION" ]]; then
    autoload bashcompinit
    bashcompinit
fi
Ben
la source
Comment puis-je faire ce travail avec bash jupyter <TAB><TAB>?
papampi
@papampi, je pense que cela ne fonctionne qu'avec un niveau d'achèvement - je pense que pour le faire avec 2 couches, vous auriez besoin de l'une des réponses les plus compliquées ci-dessus. En outre, j'ai récemment lu un tutoriel assez décent sur l'achèvement de bash. Il ne fait pas exactement ce dont vous avez besoin, mais peut-être que ça vous aidera. Bonne chance!
Ben