Como criar um script com preenchimento automático?

120

Quando uso o programa como svne digito no Gnome Terminal:

svn upd

e pressione Tabo preenchimento automático para:

svn update

É possível fazer algo assim no meu script personalizado do bash?

UAdapter
fonte
explicar "script bash", você quer dizer quando editar um script? O que você quer fazer com isso?
Bruno Pereira
3
ao usar script no console
UAdapter 17/10/11
Quanto ao local onde colocar suas conclusões, consulte esta pergunta e também os comentários para a resposta aceita.
jarno 20/01

Respostas:

44

Você pode usar a conclusão programável . Têm olhada /etc/bash_completione /etc/bash_completion.d/*para alguns exemplos.

Florian Diesch
fonte
131
Que tal incluir um exemplo simples diretamente relacionado à pergunta?
MountainX
2
Os scripts reais para o Ubuntu 16 estão localizados em/usr/share/bash-completion/completions/<program>
peter
17
Imo, exemplos devem ser incluídos na resposta, não em um link.
billynoah
2
Acredito que essa plataforma seja uma alternativa mais prática às documentações completas que podem ser encontradas com uma simples pesquisa no Google. Despejar um link de documentação não ajuda nisso. O link que contém uma âncora certamente não faz muita diferença.
timuçin
4
The provided link has that already- pode hoje, mas pode não amanhã. Ou no próximo ano. Ou em uma década. O que você sugerir sobre a documentação ainda ser relevante, o Stack Overflow desencoraja respostas somente de links por esses motivos.
Liam Dawson
205

Estou com seis meses de atraso, mas estava procurando a mesma coisa e achei o seguinte:

Você precisará criar um novo arquivo:

/etc/bash_completion.d/foo

Para um preenchimento automático estático ( --help/ --verbosepor exemplo), adicione este:

_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 é uma matriz que contém todas as palavras individuais na linha de comando atual.
  • COMP_CWORD é um índice da palavra que contém a posição atual do cursor.
  • COMPREPLY é uma variável de matriz da qual o Bash lê as possíveis conclusões.

E o compgencomando retorna a matriz de elementos --help, --verbosee --versioncombinando a palavra atual "${cur}":

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

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

Louis Soulez
fonte
27
Essa deve ser a resposta aceita! É um exemplo completo.
Victor Schröder
5
Dica: se alguém quiser sugestões para palavras que não começam com -e mostrá-las sem precisar digitar a palavra de destino, basta remover as linhas if [...] thene fi.
Cedric Reichenbach
1
Esta é a resposta exata que eu estou procurando há horas, e acontece que ela se afogou em alguma documentação complicada que nunca menciona que o arquivo deve ser colocado /etc/bash_completion.d/. Eu só vim aqui porque queria postar uma resposta em algum lugar, mas acontece que alguém estava três anos à frente o tempo todo :) Exemplo claro, conciso e completo, obrigado!
Steen Schütt
1
Tutorial - Criando um script de conclusão do bash
Lazarus Lazaridis
34

Todas as conclusões do bash são armazenadas em /etc/bash_completion.d/. Portanto, se você estiver construindo um software com bash_completion, vale a pena fazer com que a instalação deb / make solte um arquivo com o nome do software nesse diretório. Aqui está um exemplo de script de conclusão do bash para 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

Provavelmente valeria a pena revisar um dos arquivos de conclusão do bash que mais se aproxima do seu programa. Um dos exemplos mais simples é o rrdtoolarquivo.

Marco Ceppi
fonte
2
Podemos configurar conclusões para carregar de outros locais? IE. ~ / .local
Chris
1
Sim, você pode colocar um arquivo como este onde quiser e depois colocar source ~/.local/mycrazycompletionno seu~/.bashrc
Stefano Palazzo
@ Chris ver instruções em Perguntas frequentes sobre a conclusão do bash
jarno 20/01
Atualmente, a maioria das conclusões está localizada no diretório fornecido pelo comando pkg-config --variable = completionsdir bash-conclusão` e esse diretório é a recomendação dada pelas Perguntas frequentes sobre a conclusão do bash vinculadas acima.
jarno 20/01
34

Aqui está um tutorial completo.

Vamos ter um exemplo de script chamado admin.sh, no qual você gostaria que o preenchimento automático funcionasse.

#!/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

Observe a lista de opções. Chamar script com esta opção imprimirá todas as opções possíveis para esse script.

E aqui você tem o script de preenchimento automático:

_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

Observe que o último argumento a ser concluído é o nome do script ao qual você deseja adicionar o preenchimento automático. Tudo que você precisa fazer é adicionar seu script de preenchimento automático ao bashrc como

source /path/to/your/autocomplete.sh

ou copie-o para /etc/bash.completion.d

kokosing
fonte
1
Para que serve a prevvariável? Você não parece usá-lo.
dimo414
@ dimo414 Parece que sim, tirei essa variável
kokosing
O que a -o nospaceopção faz?
Andrew Lamarra
11

Se tudo o que você deseja é uma conclusão automática baseada em palavras simples (portanto, nenhuma conclusão de subcomando ou qualquer coisa), o completecomando tem uma -Wopção que faz a coisa certa.

Por exemplo, tenho as seguintes linhas no meu .bashrcpara completar automaticamente um programa chamado jupyter :

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

Agora é jupyter <TAB> <TAB>preenchido automaticamente para mim.

Os documentos em gnu.org são úteis.

Parece depender da IFSvariável ser definida corretamente, mas isso não causou nenhum problema para mim.

Para adicionar a conclusão do nome de arquivo e a conclusão padrão do BASH, use a -oopção:

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

Para usar isso no zsh, adicione o seguinte código antes de executar o completecomando no seu ~/.zshrc:

# make zsh emulate bash if necessary
if [[ -n "$ZSH_VERSION" ]]; then
    autoload bashcompinit
    bashcompinit
fi
Ben
fonte
Como eu faço isso funcionar bash jupyter <TAB><TAB>?
21418 papampi
@ pampampi, acho que funciona apenas com um nível de conclusão - acho que para fazê-lo com duas camadas, você precisará de uma das respostas mais complicadas acima. Além disso, recentemente li um tutorial bastante decente sobre a conclusão do bash. Não faz exatamente o que você precisa, mas talvez o ajude. Boa sorte!
Ben