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 locTAB→ localc. A mój brat, który poprosił mnie o to, chce tego z mplayerem).


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 ...


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.


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


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.


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.
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.