Dotychczas pokazaliśmy jak dodać podstawowe uzupełnianie do poleceń, używając gotowych mechanizmów dostępnych w pakiecie bash. W drugiej części pokażemy jak dodać zupełnie nowe, własne uzupełnianie poleceń.

W części pierwszej zobaczyliśmy dodawanie uzupełniania nazw hostów do dowolnie wybranych poleceń używając:

complete -F _known_hosts xvncviewer

Metoda używa polecenia complete do powiadomienia basha, że funkcja _known_hosts powinna zostać użyta do obsługi uzupełniania argumentów dla xvncviewer.

Jeśli chcemy dodać własne uzupełnianie do polecenia, musimy napisać w jej miejsce swoją własną funkcję, i skojarzyć z poleceniem.

Prosty przykład

Jako podstawowy przykład, spróbujemy dodać proste uzupełnianie do programu foo. To przykładowe polecenie przyjmuje trzy argumenty:

  • --help
    Wyświetla opcje pomocy dla foo, i kończy wykonanie.
  • --version
    Pokazuje wersję polecenia foo, i kończy wykonanie.
  • --verbose
    Uruchamia foo w trybie szczegółowego wyjścia

Do obsługi tych argumentów utworzymy nowy plik /etc/bash_completion.d/foo. Plik ten będzie automatycznie dołączony (lub załadowany), gdy ładowany jest kod uzupełniania basha.

Wewnątrz tego pliku zapisz poniższą treść:

_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

Bo go przetestować, możesz teraz dołączyć plik:

skx@lappy:~$ . /etc/bash_completion.d/foo
skx@lappy:~$ foo --TAB
    --help     --verbose  --version

Eksperymentując, przekonasz się, że argumenty są poprawnie uzupełniane, zgodnie z oczekiwaniami. Wpisanie foo --hTAB powoduje uzupełnienie argumentu --help. Wciśnięcie TAB kilka razy powoduje wyświetlenie listy wszystkich podpowiedzi. (W tym wypadku nie ma nawet znaczenia, że program o nazwie foo nie jest dostępny w Twoim systemie.)

Skoro dysponujemy działającym przykładem, powinniśmy spojrzeć, w jaki sposób działa!

Jak działa uzupełnianie

Poprzedni przykład pokazywał prostą funkcję basha, która była wywoływana do obsługi uzupełniania dla polecenia.

Funkcja na początku definiuje kilka zmiennych, cur – bieżące wpisywane słowo, prev – poprzednie wpisane słowo, oraz opts – nasza lista opcji do uzupełnienia.

Uzupełnianie opcji jest następnie obsługiwane poprzez użycie komendy compgen w następującym wywołaniu:

COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )

Sprawia to ustawienie wartości zmiennej $COMPREPLY na wyjście polecenia:

compgen -W "${opts}" -- ${cur}

Gdyby zastąpić te zmienne ich wartościami, przekonamy się, jak ono działa:

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

Polecenie usiłuje zwrócić dopasowanie bieżącego słowa ${cur} do listy --help --verbose --version. Po wywołaniu go w powłoce, będziesz w stanie przekonać się, jak to działa:

skx@lappy:~$ compgen -W "--help --verbose --version" -- --
--help
--verbose
--version
skx@lappy:~$ compgen -W "--help --verbose --version" -- --h
--help

Na początku widać, co się stanie, gdy użytkownik wpisze jedynie -- - wszystkie trzy opcje pasują, więc są zwracane. Przy drugiej próbie, użytkownik wprowadza --h. I to wystarcza do jednoznacznego dopasowania --help, więc to jest zwracane.

W naszej funkcji, po prostu ustawiamy nasz wynik jako COMPREPLY, i kończymy wywołanie. To pozwala bashowi zastąpić bieżące słowo wyjściem. COMPREPLY jest specjalną zmienną, która ma konkretne znaczenie wewnątrz basha. W procedurach uzupełniania używana jest do oznaczania wyniku próby uzupełniania.

Z dokumentacji basha (ang.) możemy dowiedzieć się opisu COMPREPLY:

COMPREPLY
Zmienna tablicowa, z której Bash czyta możliwe uzupełnienia wygenerowane przez funkcję powłoki wywoływaną przez podsystem programowalnego uzupełniania

Możemy również dowiedzieć się, w jaki sposób znaleźliśmy bieżące słowo, używając tablicy COMP_WORDS do odszukania zarówno bieżącego, jak i poprzedniego słowa:

COMP_WORDS
Zmienna tablicowa składająca się z pojedynczych słów w bieżącym wierszu poleceń. Zmienna ta jest dostępna tylko w funkcjach powłoki wywoływanych przez podsystem programowalnego uzupełniania.
COMP_CWORD
Indeks tablicy ${COMP_WORDS} słowa zawierającego bieżącą pozycję kursora. Zmienna ta jest dostępna tylko w funkcjach powłoki wywoływanych przez podsystem programowalnego uzupełniania

Złożony przykład

Wiele poleceń jest bardziej skomplikowane do uzupełnienia, i posiada liczne opcje zależne od poprzednich.

Jako przykład posłuży dostarczane z Xen polecenie xm, posiadające podstawowe opcje:

  • xm list
    Wypisuje wszystkie uruchomione instancje Xen
  • xm create ConfigName
    Tworzy nową instancję Xen używając pliku konfiguracyjnego z /etc/xen o nazwie ConfigName.
  • xm console Name
    Łączy z konsolą uruchomionej maszyny o nazwie Name.

Przeważnie polecenie wywołuje się jako xm operation args, gdzie args różni się w zależności od wybranego argumentu operation.

Konfiguracja prostego uzupełniania pierwszego słowa – operacji – może zostać obsłużone w ten sam sposób, co w naszym poprzednim przykładzie, przy czym operacje nie zaczynają się od prefiksu --. Uzupełnienie argumentów operacji natomiast wymaga specjalnej obsługi.

Jak pamiętacie, mamy dostęp do poprzedniego słowa wiersza poleceń, i używając go, możemy rozróżnić akcje dla poszczególnych operacji.

Przykładowy kod wygląda następująco:

_xm()
{
    local cur prev opts base
    COMPREPLY=()
    cur="${COMP_WORDS[COMP_CWORD]}"
    prev="${COMP_WORDS[COMP_CWORD-1]}"

    #
    #  The basic options we'll complete.
    #
    opts="console create list"


    #
    #  Complete the arguments to some of the basic commands.
    #
    case "${prev}" in
    console)
        local running=$(for x in `xm list --long | grep \(name | grep -v Domain-0 | awk '{ print $2 }' | tr -d \)`; do echo ${x} ; done )
        COMPREPLY=( $(compgen -W "${running}" -- ${cur}) )
        return 0
    ;;
    create)
        local names=$(for x in `ls -1 /etc/xen/*.cfg`; do echo ${x/\/etc\/xen\//} ; done )
        COMPREPLY=( $(compgen -W "${names}" -- ${cur}) )
        return 0
    ;;
    *)
    ;;
    esac

    COMPREPLY=($(compgen -W "${opts}" -- ${cur}))
    return 0
}

complete -F _xm xm

Skonfigurowaliśmy początkowe uzupełnianie operacji i dodaliśmy specjalną obsługę dwóch operacji: create i console. W obu przypadkach używamy compgen do uzupełnienia wejścia w zależności od tekstu podanego przez użytkownika, porównując je z dynamicznie generowaną listą.

Dla operacji console uzupełniamy na podstawie wyjścia poniższego polecenia:

xm list --long | grep \(name | grep -v Domain-0 | awk '{ print $2 }' | tr -d \)

Zwraca ono listę uruchomionych systemów Xen.

Dla operacji tworzenia, uzupełniamy na podstawie wyjścia poniższego polecenia:

for x in `ls -1 /etc/xen/*.cfg`; do echo ${x/\/etc\/xen\//} ; done

Przekształca ono listing katalogu /etc/xen w wyjście składające się z nazw plików zakończonych ciągiem .cfg. Na przykład:

skx@lappy:~$ for x in `ls -1 /etc/xen/*.cfg`; do echo ${x/\/etc\/xen\//}; done
etch.cfg
root.cfg
sarge.cfg
steve.cfg
x.cfg
skx@lappy:~$

Inne uzupełniania

Używając polecenia compgen, pokazaliśmy, jak dopasować wejście użytkownika do list konkretnych łańcuchów znaków, zarówno używając ustalonej listy możliwości, jak i wyjścia innych poleceń.

Możliwe jest również użycie nazw katalogów, nazw procesów oraz innych rzeczy. Pełen opis można zobaczyć w podręczniku basha, poprzez wywołanie polecenia man bash.

Końcowy przykład pokazuje, jak uzupełniać nazwy plików i hostów w odpowiedzi na dwie pierwsze opcje:

#
#  Completion for foo:
#
#  foo file [filename]
#  foo hostname [hostname]
#
_foo()
{
    local cur prev opts
    COMPREPLY=()
    cur="${COMP_WORDS[COMP_CWORD]}"
    prev="${COMP_WORDS[COMP_CWORD-1]}"
    opts="file hostname"

    case "${prev}" in
    file)
        COMPREPLY=( $(compgen -f ${cur}) )
        return 0
    ;;
    hostname)
        COMPREPLY=( $(compgen -A hostname ${cur}) )
        return 0
    ;;
    *)
    ;;
    esac

    COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
}

complete -F _foo foo

Używając tych przykładów, powinniście móc stworzyć własne funkcje obsługi uzupełniania. W 95% przypadków będziecie potrzebować uzupełniania spośród zbioru dostępnych opcji, w pozostałych przypadkach będziecie mieć do czynienia z dynamicznym generowaniem argumentów, jak pokazaliśmy na przykładzie xm.

Prawdopodobnie najlepszym podejściem jest rozbicie opcji na zbiór potoków wiersza poleceń i przetestowanie ich poza środowiskiem uzupełniania (po prostu, w powłoce), a następnie po prostu wklejenie gotowych poleceń do funkcji uzupełniania.