zakończenie bash dla wzorców nazw plików lub katalogów

13

Próbuję skonfigurować skrypt kończący bash i mam problemy.

Chciałbym to skonfigurować, aby wymienione uzupełnienia były albo plikami pasującymi do określonego rozszerzenia, albo katalogami (które mogą, ale nie muszą zawierać pliki tego rozszerzenia).

Problem, który mam, polega na tym, że jedynym sposobem, w jaki mogę uzyskać uzupełnienia zawierające pliki i katalogi, jest użycie czegoś podobnego -o plusdirs -f -X '!*.txt', ale kiedy pozwolę bash skompletować jeden z katalogów, po prostu dodaje spację na końcu, a nie ciąć.

_xyz()
{
  local cur=${COMP_WORDS[COMP_CWORD]}
  local prev=${COMP_WORDS[COMP_CWORD-1]}

  #COMPREPLY=( $( compgen -f -X '!*.txt' -- $cur ) )
  #COMPREPLY=( $( compgen -f -G '*.txt' -- $cur ) )
  #COMPREPLY=( $( compgen -o filenames -f -X '!*.txt' -- $cur ) )
  #COMPREPLY=( $( compgen -o dirnames  -f -X '!*.txt' -- $cur ) )
  COMPREPLY=( $( compgen -o plusdirs  -f -X '!*.txt' -- $cur ) )
  return 0
}

complete -F _xyz xyz

Próbowałem też wszystkich skomentowanych wierszy, ale nawet nie rozszerzają katalogów.

Do testowania uruchomiłem to w katalogu z jednym plikiem .txt i jednym katalogiem „dir” (z plikiem .txt w nim, choć to jeszcze nie ma znaczenia). Pisanie za xyz <TAB>pomocą tej funkcji wyświetla katalog i plik .txt, ale pisanie xyz d<TAB>rozwija się do xyz dir(cóż, ze spacją po „dir”).

Rob I
źródło

Odpowiedzi:

10

Jeśli spojrzeć na funkcję _cd()w / etc / bash_completion , zobaczysz, że dołącza się końcowy ukośnik i kompletne dostaje uruchomiony z opcją -o nospacena cd .

Możesz zrobić to samo dla xyz , ale musisz osobno sprawdzić, czy znalezione dopasowanie jest katalogiem (jeśli tak, dodaj ukośnik) lub plikiem (jeśli tak, dodaj spację). Należy to zrobić w pętli for, aby przetworzyć wszystkie znalezione dopasowania.

Ponadto, aby poprawnie obsługiwać ścieżki zawierające spacje, należy ustawić wewnętrzny separator plików tylko na nową linię i uciec ze spacji. Używanie IFS=$'\n'w połączeniu z printf %qsprawia, że ​​uzupełnianie działa z prawie wszystkimi postaciami. 1 Należy zachować szczególną ostrożność, aby nie wydostać się z tylnego miejsca.

Następujące powinny działać:

_xyz ()
{
    local IFS=$'\n'
    local LASTCHAR=' '

    COMPREPLY=($(compgen -o plusdirs -f -X '!*.txt' \
        -- "${COMP_WORDS[COMP_CWORD]}"))

    if [ ${#COMPREPLY[@]} = 1 ]; then
        [ -d "$COMPREPLY" ] && LASTCHAR=/
        COMPREPLY=$(printf %q%s "$COMPREPLY" "$LASTCHAR")
    else
        for ((i=0; i < ${#COMPREPLY[@]}; i++)); do
            [ -d "${COMPREPLY[$i]}" ] && COMPREPLY[$i]=${COMPREPLY[$i]}/
        done
    fi

    return 0
}

complete -o nospace -F _xyz xyz

1 Znak nowej linii jest tutaj oczywistym wyjątkiem, ponieważ jest wewnętrznym separatorem plików.

Dennis
źródło
Działa to świetnie (choć szkoda, że ​​nie jest wbudowane). Dzięki!
Rob I
1
Czy jest jakiś powód, aby nie używać „$ 2” zamiast „$ {COMP_WORDS [COMP_CWORD]}”?
Edward Falk,
3

Myślę, że to proste rozwiązanie działa w ten sposób, że:

  1. Dopasowuje katalogi i pliki, które kończą się na .txt
  2. Obsługuje spacje w nazwach plików
  3. Dodaje ukośnik na końcu uzupełnień folderów bez spacji końcowych
  4. Dodaje miejsce na końcu dopasowania pliku

Klucz był przekazywany -o filenamesdo ukończenia. Zostało to przetestowane na GNU bash 3.2.25 na RHEL 5.3 i GNU bash 4.3.18 na osx

_xyz()
{
  local cur=${COMP_WORDS[COMP_CWORD]}

  local IFS=$'\n'
  COMPREPLY=( $( compgen -o plusdirs  -f -X '!*.txt' -- $cur ) )
}

complete -o filenames -F _xyz xyz
Czad Skeeters
źródło
Tak, to wydaje się działać świetnie. Dużo prostsze, dzięki za złapanie ważnego argumentu!
Rob I