Jak dostosować uzupełnianie poleceń Bash?

24

W bash łatwo jest skonfigurować niestandardowe uzupełnianie argumentów poleceń za pomocą completewbudowanego. Na przykład, jeśli dla hipotetycznego polecenia z streszczeniem foo --a | --b | --c, możesz to zrobićcomplete -W '--a --b --c' foo

Można również dostosować zakończenie uzyskać po naciśnięciu Tabna pusty wiersz za pomocą complete -E, np complete -E -W 'foo bar'. Następnie naciśnięcie klawisza Tab w pustym monicie sugeruje tylko fooi bar.

Jak dostosować uzupełnianie poleceń w niepusty komunikat? Np. Jeśli siedzę w:

anthony@Zia:~$ f

jak dostosować ukończenie, aby naciśnięcie klawisza zawsze się kończyło foo?

(Rzeczywisty przypadek, który chciałbym, to locTABlocalc. A mój brat, który poprosił mnie o to, chce tego z mplayerem).

derobert
źródło
2
Czy rozważałeś po prostu aliasing locdo localc? Proponuję alternatywy, ponieważ po dłuższym czasie kopania i przeszukiwania nie znalazłem sposobu na dostosowanie zakończenia bash w ten sposób. To może nie być możliwe.
jw013,
@ jw013 Tak, szukałem przez chwilę i nic nie znalazłem. Oczekuję, że ktoś też zasugeruje przejście na Zsh. Przynajmniej jeśli zsh to zrobi, mogę spokojnie odpocząć wiedząc, że będzie też następna wersja bash. 😀
derobert,
musisz nacisnąć klawisz TAB dwa razy, aby wykonać polecenia.
Floyd,
1
Czy to nie złamie wszystkich innych ukończeń? To znaczy, nawet jeśli jest to możliwe, to już nie będzie w stanie uzyskać locate, locale, lockfilelub którykolwiek z pozostałych rozwinięć loc. Być może lepszym rozwiązaniem byłoby zmapowanie innego klucza do tego konkretnego zakończenia.
terdon
1
czy możesz podać przykład takiego kontekstu (który prowadziłby do pożądanego efektu loc<TAB>->localc)?
damienfrancois

Odpowiedzi:

9

Zakończenie polecenia (wraz z innymi) jest obsługiwane przez bash ukończenia readline . Działa to na nieco niższym poziomie niż zwykłe „programowalne zakończenie” (które jest wywoływane tylko po zidentyfikowaniu polecenia i dwóch specjalnych przypadkach zidentyfikowanych powyżej).

Aktualizacja: nowa wersja bash-5.0 (styczeń 2019) dodaje complete -Iwłaśnie do tego problemu.

Odpowiednie polecenia readline to:

complete (TAB)
       Attempt to perform completion on the text  before  point.   Bash
       attempts completion treating the text as a variable (if the text
       begins with $), username (if the text begins with  ~),  hostname
       (if  the  text begins with @), or command (including aliases and
       functions) in turn.  If none of these produces a match, filename
       completion is attempted.

complete-command (M-!)
       Attempt  completion  on  the text before point, treating it as a
       command name.  Command completion attempts  to  match  the  text
       against   aliases,   reserved   words,  shell  functions,  shell
       builtins, and finally executable filenames, in that order.

W podobny sposób jak w przypadku bardziej powszechnych complete -Fniektóre z nich można przekazać do funkcji za pomocą bind -x.

function _complete0 () {
    local -a _cmds
    local -A _seen
    local _path=$PATH _ii _xx _cc _cmd _short
    local _aa=( ${READLINE_LINE} )

    if [[ -f ~/.complete.d/"${_aa[0]}" && -x  ~/.complete.d/"${_aa[0]}" ]]; then
        ## user-provided hook
        _cmds=( $( ~/.complete.d/"${_aa[0]}" ) )
    elif [[ -x  ~/.complete.d/DEFAULT ]]; then
        _cmds=( $( ~/.complete.d/DEFAULT ) )
    else 
        ## compgen -c for default "command" complete 
        _cmds=( $(PATH=$_path compgen -o bashdefault -o default -c ${_aa[0]}) )  
    fi

    ## remove duplicates, cache shortest name
    _short="${_cmds[0]}"
    _cc=${#_cmds[*]} # NB removing indexes inside loop
    for (( _ii=0 ; _ii<$_cc ; _ii++ )); do
        _cmd=${_cmds[$_ii]}
        [[ -n "${_seen[$_cmd]}" ]] && unset _cmds[$_ii]
        _seen[$_cmd]+=1
        (( ${#_short} > ${#_cmd} )) && _short="$_cmd"
    done
    _cmds=( "${_cmds[@]}" )  ## recompute contiguous index

    ## find common prefix
    declare -a _prefix=()
    for (( _xx=0; _xx<${#_short}; _xx++ )); do
        _prev=${_cmds[0]}
        for (( _ii=0 ; _ii<${#_cmds[*]} ; _ii++ )); do
            _cmd=${_cmds[$_ii]}
             [[ "${_cmd:$_xx:1}" != "${_prev:$_xx:1}" ]] && break
            _prev=$_cmd
        done
        [[ $_ii -eq ${#_cmds[*]} ]] && _prefix[$_xx]="${_cmd:$_xx:1}"
    done
    printf -v _short "%s" "${_prefix[@]}"  # flatten 

    ## emulate completion list of matches
    if [[ ${#_cmds[*]} -gt 1 ]]; then
        for (( _ii=0 ; _ii<${#_cmds[*]} ; _ii++ )); do
            _cmd=${_cmds[$_ii]}
            [[ -n "${_seen[$_cmds]}" ]] && printf "%-12s " "$_cmd" 
        done | sort | fmt -w $((COLUMNS-8)) | column -tx
        # fill in shortest match (prefix)
        printf -v READLINE_LINE "%s" "$_short"
        READLINE_POINT=${#READLINE_LINE}  
    fi
    ## exactly one match
    if [[ ${#_cmds[*]} -eq 1 ]]; then
        _aa[0]="${_cmds[0]}"
        printf -v READLINE_LINE "%s " "${_aa[@]}"
        READLINE_POINT=${#READLINE_LINE}  
    else
        : # nop
    fi
}

bind -x '"\C-i":_complete0'

Umożliwia to dołączenie własnego ciągu znaków dla każdego polecenia lub prefiksu ~/.complete.d/. Np. Jeśli utworzysz plik wykonywalny za ~/.complete.d/locpomocą:

#!/bin/bash
echo localc

To zrobi (mniej więcej) to, czego oczekujesz.

Powyższa funkcja ma pewną długość, aby emulować normalne zachowanie kończenia polecenia bash, choć jest niedoskonała (szczególnie wątpliwe sort | fmt | columnprzeniesienie, aby wyświetlić listę dopasowań).

Jednak nie jest to trywialny problem z tym, że może on użyć tylko funkcji do zastąpienia powiązania z completefunkcją główną (domyślnie wywoływane za pomocą TAB).

Podejście to działałoby dobrze z innym powiązaniem klawiszy używanym tylko do niestandardowego uzupełniania poleceń, ale po prostu nie implementuje logiki pełnego uzupełniania (np. Późniejszych słów w wierszu poleceń). Takie działanie wymagałoby analizy linii poleceń, zajmowania się pozycją kursora i innych trudnych rzeczy, których prawdopodobnie nie należy brać pod uwagę w skrypcie powłoki ...

pan. spuratic
źródło
1

Nie wiem, czy zrozumiałem twoją potrzebę tego ...
Oznaczałoby to, że twoja bash zna tylko jedno polecenie zaczynające się od f.
Podstawową ideą realizacji jest: jeśli jest niejednoznaczna, wydrukuj możliwości.
Więc możesz ustawić swój PATHkatalog na tylko jedno polecenie i wyłączyć wszystkie wbudowane bash, aby uzyskać tę pracę.

W każdym razie mogę dać ci też obejście:

alias _='true &&'
complete -W foo _

Więc jeśli wpiszesz _ <Tab>, dokończy to, do _ fooktórego wykonania foo.

Niemniej jednak alias f='foo'byłoby to o wiele łatwiejsze.

m13r
źródło
0

Prosta odpowiedź byłaby dla ciebie

$ cd into /etc/bash_completion.d
$ ls

tylko podstawowe wyjścia

autoconf       gpg2               ntpdate           shadow
automake       gzip               open-iscsi        smartctl
bash-builtins  iconv              openssl           sqlite3
bind-utils     iftop              perl              ssh
brctl          ifupdown           pkg-config        strace
bzip2          info               pm-utils          subscription-manager
chkconfig      ipmitool           postfix           tar
configure      iproute2           procps            tcpdump
coreutils      iptables           python            util-linux
cpio           lsof               quota-tools       wireless-tools
crontab        lvm                redefine_filedir  xmllint
cryptsetup     lzma               rfkill            xmlwf
dd             make               rpm               xz
dhclient       man                rsync             yum.bash
e2fsprogs      mdadm              scl.bash          yum-utils.bash
findutils      module-init-tools  service
getent         net-tools          sh

po prostu dodaj żądany program do automatycznego uzupełniania do ukończenia bash

unixmiah
źródło
2
Te pliki mają skrypt powłoki, niektóre dość skomplikowane, jeśli dobrze pamiętam. Ponadto uzupełniają argumenty dopiero po wpisaniu polecenia ... Nie wykonują polecenia.
derobert
Rozumiem co masz na myśli. Kiedy możesz zajrzeć do tego dokumentu: debian-administration.org/article/316/…
unixmiah
-3

Uruchom poniższe polecenie, aby dowiedzieć się, gdzie jest zainstalowany plik binarny mplayer:

which mplayer

LUB użyj ścieżki do pliku binarnego mplayer, jeśli o tym wiesz, w poniższym poleceniu:

ln -s /path/to/mplayer /bin/mplayer

Idealnie wszystko, co wpiszesz, jest wyszukiwane we wszystkich katalogach określonych w $PATHzmiennej.

Mathew Paret
źródło
2
Cześć, Mathew, witamy w systemach Unix i Linux. Myślę, że pytanie PO jest nieco bardziej skomplikowane niż sugerowana odpowiedź. Przypuszczam, że mplayerjest już w nich $PATHi chcą czegoś takiego jak mp<tab>produkować mplayer, zamiast wszystkich plików binarnych, które zaczynają się mp(np. mpage mpcd mpartition mplayerItp.)
dr.