Jak stworzyć skrypt z autouzupełnianiem?

119

Kiedy używam programu podobnego svni piszę w Gnome Terminal:

svn upd

i naciśnij, Tabże autouzupełnianie:

svn update

Czy można zrobić coś takiego w niestandardowym skrypcie bash?

UAdapter
źródło
wyjaśnić „skrypt bash”, masz na myśli podczas edytowania skryptu? co chcesz z tym zrobić?
Bruno Pereira
3
podczas korzystania ze skryptu w konsoli
UAdapter
Jeśli chodzi o miejsce, w którym należy umieścić swoje uzupełnienia, zobacz to pytanie oraz komentarze do zaakceptowanej odpowiedzi.
jarno

Odpowiedzi:

44

Możesz użyć programowalnego zakończenia . Mają zapoznać się /etc/bash_completioni /etc/bash_completion.d/*dla niektórych przykładów.

Florian Diesch
źródło
130
Co powiesz na prosty przykład bezpośrednio związany z pytaniem?
MountainX
2
Rzeczywiste skrypty dla Ubuntu 16 znajdują się w/usr/share/bash-completion/completions/<program>
Peter
16
Imo, przykłady powinny być zawarte w odpowiedzi, a nie w linku.
billynoah
2
Wierzę, że ta platforma ma być praktyczną alternatywą dla pełnej dokumentacji, którą można znaleźć za pomocą prostego wyszukiwania w Google. Zrzut linku do dokumentacji nie pomaga. Link zawierający kotwicę z pewnością nie robi dużej różnicy.
timuçin
4
The provided link has that already- może dziś, ale jutro nie. Lub w przyszłym roku. Lub za dekadę. Niezależnie od tego, co sugerujesz o tym, że dokumentacja jest nadal aktualna, przepełnienie stosu zniechęca do odpowiedzi tylko z linkiem z tych powodów.
Liam Dawson,
205

Spóźniłem się sześć miesięcy, ale szukałem tego samego i znalazłem to:

Musisz utworzyć nowy plik:

/etc/bash_completion.d/foo

W przypadku statycznego autouzupełniania ( --help/ --verbosena przykład) dodaj:

_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 to tablica zawierająca wszystkie pojedyncze słowa w bieżącym wierszu poleceń.
  • COMP_CWORD jest indeksem słowa zawierającego bieżącą pozycję kursora.
  • COMPREPLY jest zmienną tablicową, z której Bash czyta możliwe uzupełnienia.

A compgenkomenda zwraca tablicę z elementów --help, --verbosei --versiondopasowanie bieżące słowo "${cur}":

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

Źródło: http://www.debian-administration.org/articles/316

Louis Soulez
źródło
27
To powinna być zaakceptowana odpowiedź! To kompletny przykład.
Victor Schröder
5
Wskazówka: jeśli ktoś chce sugestii dotyczących słów, które nie zaczynają się od -i pokazują je bez konieczności wpisywania słowa docelowego, wystarczy usunąć wiersze if [...] theni fi.
Cedric Reichenbach,
1
To jest dokładna odpowiedź, której szukałem od wielu godzin, i okazuje się, że utopiła się w skomplikowanej dokumentacji, która nigdy nie wspomina, że ​​plik powinien zostać umieszczony /etc/bash_completion.d/. Przybyłem tu tylko dlatego, że chciałem gdzieś opublikować odpowiedź, ale okazuje się, że ktoś był cały czas o trzy lata :) Jasny, zwięzły i kompletny przykład, dziękuję!
Steen Schütt,
1
Samouczek - Tworzenie skryptu zakończenia bashu
Lazarus Lazaridis
34

Wszystkie uzupełnienia bash są przechowywane w /etc/bash_completion.d/. Więc jeśli budujesz oprogramowanie z bash_completion, warto by deb / make install upuścił plik z nazwą oprogramowania w tym katalogu. Oto przykładowy skrypt zakończenia bash dla 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

Warto byłoby przejrzeć jeden z plików uzupełniających bash, które najbardziej pasują do twojego programu. Jednym z najprostszych przykładów jest rrdtoolplik.

Marco Ceppi
źródło
2
Czy możemy skonfigurować zakończenia, aby ładowały się z innych lokalizacji? TO ZNACZY. ~ / .local
Chris
1
Tak, możesz umieścić taki plik gdziekolwiek chcesz, a następnie umieścić source ~/.local/mycrazycompletionswój~/.bashrc
Stefano Palazzo
@Chris patrz instrukcje na stronie Bash Completion FAQ
jarno
Obecnie większość uzupełnień znajduje się w katalogu podanym za pomocą komendy pkg-config --variable = completionsdir bash-uzupełnianie`, a katalog ten jest zaleceniem podanym w Bash Completion FAQ, do którego link znajduje się powyżej.
jarno
33

Oto kompletny samouczek.

Załóżmy przykładowy skrypt o nazwie admin.sh, do którego chcesz, aby autouzupełnianie działało.

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

Uwaga krótka lista opcji. Wywołanie skryptu z tą opcją spowoduje wydrukowanie wszystkich możliwych opcji dla tego skryptu.

A tutaj masz skrypt autouzupełniania:

_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

Zauważ, że ostatnim argumentem do uzupełnienia jest nazwa skryptu, do którego chcesz dodać autouzupełnianie. Wszystko, co musisz zrobić, to dodać swój skrypt autouzupełniania do bashrc as

source /path/to/your/autocomplete.sh

lub skopiuj go do /etc/bash.completion.d

kokosing
źródło
1
Do czego służy prevzmienna? Wygląda na to, że go nie używasz.
dimo414
@ dimo414 Wygląda na to, że usunąłem tę zmienną
kokosing
Co robi -o nospaceopcja?
Andrew Lamarra,
11

Jeśli wszystko, czego chcesz, to proste automatyczne uzupełnianie oparte na słowach (więc nie uzupełniaj podkomendy ani nic takiego), completepolecenie ma -Wopcję, która po prostu robi właściwą rzecz.

Na przykład mam następujące wiersze .bashrcdo autouzupełniania programu o nazwie jupyter :

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

Teraz jupyter <TAB> <TAB>autouzupełnianie dla mnie.

W docs w gnu.org są pomocne.

Wygląda na to, że IFSzmienna jest ustawiona poprawnie, ale nie spowodowało to dla mnie żadnych problemów.

Aby dodać uzupełnienie nazwy pliku i domyślne uzupełnienie BASH, użyj -oopcji:

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

Aby użyć tego w zsh, dodaj następujący kod przed uruchomieniem completepolecenia w swoim ~/.zshrc:

# make zsh emulate bash if necessary
if [[ -n "$ZSH_VERSION" ]]; then
    autoload bashcompinit
    bashcompinit
fi
Ben
źródło
Jak sprawić, by to działało bash jupyter <TAB><TAB>?
papampi
@papampi, myślę, że działa tylko z jednym poziomem ukończenia - myślę, że aby to zrobić z 2 warstwami, potrzebujesz jednej z bardziej skomplikowanych odpowiedzi powyżej. Niedawno przeczytałem całkiem niezły tutorial na temat ukończenia bash. Nie robi dokładnie tego, czego potrzebujesz, ale może ci to pomoże. Powodzenia!
Ben,