¿Cómo crear un script con autocompletar?

120

Cuando uso el programa like svny escribo en Gnome Terminal:

svn upd

y presiona Tabse autocompleta para:

svn update

¿Es posible hacer algo así en mi script bash personalizado?

Adaptador UA
fuente
explicar "script bash", ¿quieres decir cuando editas un script? ¿Que quieres hacer con eso?
Bruno Pereira
3
cuando se utiliza el script en la consola
UAdapter
En cuanto al lugar donde colocar sus finalizaciones, vea esta pregunta y también los comentarios para la respuesta aceptada allí.
jarno

Respuestas:

44

Puede usar la finalización programable . Mira /etc/bash_completiony /etc/bash_completion.d/*para algunos ejemplos.

Florian Diesch
fuente
131
¿Qué tal si incluimos un ejemplo simple directamente relacionado con la pregunta?
MountainX
2
Las secuencias de comandos reales para Ubuntu 16 se encuentran en/usr/share/bash-completion/completions/<program>
Peter
17
Imo, los ejemplos deben incluirse en la respuesta, no en un enlace.
billynoah
2
Creo que se supone que esta plataforma es una alternativa más práctica a la documentación completa que se puede encontrar con una simple búsqueda en Google. Volcar un enlace de documentación no ayuda a eso. El enlace que contiene un ancla seguramente no hace mucha diferencia.
timuçin
44
The provided link has that already- Podría ser hoy, pero podría no ser mañana. O el año que viene. O en una década. Independientemente de lo que sugiera sobre la documentación que sigue siendo relevante, Stack Overflow desalienta las respuestas de solo enlace por estos motivos.
Liam Dawson
205

Llego seis meses tarde pero estaba buscando lo mismo y encontré esto:

Tendrás que crear un nuevo archivo:

/etc/bash_completion.d/foo

Para un autocompletado estático ( --help/ --verbosepor ejemplo) agregue esto:

_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 es una matriz que contiene todas las palabras individuales en la línea de comando actual.
  • COMP_CWORD es un índice de la palabra que contiene la posición actual del cursor.
  • COMPREPLY es una variable de matriz desde la cual Bash lee las posibles terminaciones.

Y el compgencomando devuelve la matriz de elementos de --help, --verbosey --versioncoinciden con la palabra actual "${cur}":

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

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

Louis Soulez
fuente
27
¡Esta debería ser la respuesta aceptada! Es un ejemplo completo.
Victor Schröder
55
Consejo: si alguien quiere sugerencias de palabras que no comienzan -y mostrarlas sin tener que comenzar a escribir la palabra objetivo, simplemente elimine las líneas if [...] theny fi.
Cedric Reichenbach
1
Esta es la respuesta exacta que he estado buscando durante horas, y resulta que se ahogó en una documentación complicada que nunca menciona que el archivo debe colocarse /etc/bash_completion.d/. Solo vine aquí porque quería publicar una respuesta en alguna parte, pero resultó que alguien tenía tres años de ventaja todo el tiempo :) Ejemplo claro, conciso y completo, ¡gracias!
Steen Schütt
1
Tutorial - Creación de un script de finalización de bash
Lazarus Lazaridis
34

Todas las terminaciones de bash se almacenan en /etc/bash_completion.d/. Entonces, si está creando software con bash_completion, valdría la pena que deb / make install suelte un archivo con el nombre del software en ese directorio. Aquí hay un script de finalización de bash de ejemplo 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

Probablemente valdría la pena revisar uno de los archivos de finalización de bash que más se ajuste a su programa. Uno de los ejemplos más simples es el rrdtoolarchivo.

Marco Ceppi
fuente
2
¿Podemos configurar terminaciones para cargar desde otras ubicaciones? ES DECIR. ~ / .local
Chris
1
Sí, puedes poner un archivo como este donde quieras y luego ponerlo source ~/.local/mycrazycompletionen tu~/.bashrc
Stefano Palazzo
@ Chris vea las instrucciones en Bash Finalización FAQ
jarno
Hoy en día, la mayoría de las terminaciones se encuentran en el directorio dado por el comando pkg-config --variable = completionsdir bash-completar` y ese directorio es la recomendación dada por las preguntas frecuentes de finalización de Bash vinculadas anteriormente.
jarno
34

Aquí hay un tutorial completo.

Tengamos un ejemplo de script llamado admin.sh en el que le gustaría tener el autocompletado funcionando.

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

Opción de nota lista corta. Al llamar al script con esta opción, se imprimirán todas las opciones posibles para este script.

Y aquí tienes el script de autocompletar:

_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

Tenga en cuenta que el último argumento para completar es el nombre del script al que desea agregar autocompletado. Todo lo que necesita hacer es agregar su secuencia de comandos de autocompletar a bashrc como

source /path/to/your/autocomplete.sh

o cópielo a /etc/bash.completion.d

kokosing
fuente
1
¿Para qué es la prevvariable? Parece que no lo usas.
dimo414
@ dimo414 Parece que sí,
eliminé
¿Qué hace la -o nospaceopción?
Andrew Lamarra
12

Si todo lo que desea es un autocompletado basado en palabras (para que no se complete el subcomando ni nada), el completecomando tiene una -Wopción que simplemente hace lo correcto.

Por ejemplo, tengo las siguientes líneas en mi .bashrcpara completar automáticamente un programa llamado jupyter :

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

Ahora se jupyter <TAB> <TAB>autocompleta para mí.

Los documentos en gnu.org son útiles.

Parece depender de que la IFSvariable se configure correctamente, pero eso no me ha causado ningún problema.

Para agregar la finalización del nombre de archivo y la finalización predeterminada de BASH, use la -oopción:

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

Para usar esto en zsh, agregue el siguiente código antes de ejecutar el completecomando en su ~/.zshrc:

# make zsh emulate bash if necessary
if [[ -n "$ZSH_VERSION" ]]; then
    autoload bashcompinit
    bashcompinit
fi
Ben
fuente
¿Cómo hago para que esto funcione bash jupyter <TAB><TAB>?
papampi
@papampi, creo que solo funciona con un nivel de finalización: creo que para hacerlo con 2 capas necesitaría una de las respuestas más complicadas anteriores. Además, recientemente leí un tutorial bastante decente sobre la finalización de bash. No hace exactamente lo que necesita, pero tal vez lo ayude. ¡Buena suerte!
Ben