Bagaimana cara menyesuaikan penyelesaian perintah Bash?

24

Dalam bash, cukup mudah untuk mengatur penyelesaian argumen perintah yang disesuaikan menggunakan completebuilt-in. Misalnya, jika, untuk perintah hipotetis dengan sinopsis foo --a | --b | --c, Anda bisa melakukannyacomplete -W '--a --b --c' foo

Anda juga dapat menyesuaikan penyelesaian yang Anda dapatkan ketika Anda menekan Tabpada prompt kosong menggunakan complete -E, misalnya complete -E -W 'foo bar',. Kemudian menekan tab pada prompt kosong hanya akan menyarankan foodan bar.

Bagaimana cara menyesuaikan penyelesaian perintah pada non -empty prompt? Misalnya, jika saya duduk di:

anthony@Zia:~$ f

bagaimana kustomisasi penyelesaian sehingga menekan tab akan selalu selesai foo?

(Kasus aktual yang saya suka adalah locTABlocalc. Dan saudara lelaki saya, yang mendorong saya untuk menanyakan hal ini, menginginkannya dengan mplayer).

derobert
sumber
2
Apakah Anda dianggap hanya aliasing locuntuk localc? Saya menyarankan alternatif karena setelah beberapa waktu menggali dan mencari, saya belum menemukan cara untuk menyesuaikan penyelesaian bash dengan cara ini. Itu mungkin tidak mungkin.
jw013
@ jw013 Ya, saya mencari sebentar dan tidak menemukan apa-apa juga. Saya setengah berharap seseorang menyarankan beralih ke zsh juga. Setidaknya jika zsh melakukannya, saya dapat beristirahat dengan nyaman mengetahui versi bash akan juga. 😀
derobert
Anda harus menekan TAB dua kali dan itu akan menyelesaikan perintah.
Floyd
1
Akankah itu mematahkan semua penyelesaian lainnya? Maksudku, bahkan jika mungkin, Anda tidak lagi bisa mendapatkan locate, locale, lockfileatau salah satu ekspansi lain loc. Mungkin pendekatan yang lebih baik adalah memetakan kunci berbeda untuk penyelesaian spesifik itu.
terdon
1
dapatkah Anda memberikan contoh konteks seperti itu (yang akan mengarah pada efek yang diinginkan loc<TAB>->localc)?
damienfrancois

Jawaban:

9

Penyelesaian perintah (bersama dengan hal-hal lain) ditangani melalui bash readline completion . Ini beroperasi pada tingkat yang sedikit lebih rendah daripada "penyelesaian terprogram" yang biasa (yang dipanggil hanya ketika perintah diidentifikasi, dan dua kasus khusus yang Anda identifikasi di atas).

Pembaruan: rilis baru bash-5.0 (Jan 2019) menambahkan complete -Iuntuk masalah ini persis.

Perintah readline yang relevan adalah:

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.

Dalam cara yang mirip dengan yang lebih umum complete -F, beberapa di antaranya dapat diserahkan ke suatu fungsi dengan menggunakan 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'

Ini memungkinkan kait per-perintah atau awalan Anda sendiri ~/.complete.d/. Misalnya, jika Anda membuat executable ~/.complete.d/locdengan:

#!/bin/bash
echo localc

Ini akan melakukan (secara kasar) apa yang Anda harapkan.

Fungsi di atas bersusah payah untuk meniru perilaku penyelesaian perintah bash normal, meskipun tidak sempurna (khususnya barang yang meragukan sort | fmt | columnuntuk menampilkan daftar kecocokan).

Namun , masalah non-sepele dengan ini hanya dapat menggunakan fungsi untuk menggantikan pengikatan ke completefungsi utama (dipanggil dengan TAB secara default).

Pendekatan ini akan bekerja dengan baik dengan pengikatan kunci yang berbeda yang digunakan hanya untuk penyelesaian perintah kustom, tetapi itu tidak mengimplementasikan logika penyelesaian penuh setelah itu (misalnya kata-kata selanjutnya di baris perintah). Melakukannya akan membutuhkan penguraian baris perintah, berurusan dengan posisi kursor, dan hal-hal rumit lainnya yang mungkin tidak boleh dipertimbangkan dalam skrip shell ...

mr.spuratic
sumber
1

Saya tidak tahu jika saya tidak mengerti kebutuhan Anda untuk ini ...
Ini akan menyiratkan bahwa bash Anda hanya tahu satu perintah yang dimulai dengan f.
Gagasan dasar penyelesaian adalah: jika ambigu, cetak kemungkinan.
Jadi Anda bisa mengatur Anda PATHke direktori yang hanya berisi perintah ini dan menonaktifkan semua bash builtin untuk mendapatkan pekerjaan ini.

Bagaimanapun, saya dapat memberi Anda juga semacam solusi:

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

Jadi jika Anda mengetiknya _ <Tab>akan lengkap _ fooyang dieksekusi foo.

Tetapi meskipun demikian alias f='foo'akan jauh lebih mudah.

m13r
sumber
0

Jawaban sederhana untuk Anda adalah

$ cd into /etc/bash_completion.d
$ ls

hanya output dasar

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

cukup tambahkan program yang Anda inginkan untuk otomatis selesai untuk menyelesaikan bash

unixmiah
sumber
2
File-file itu memiliki skrip shell di dalamnya, beberapa cukup rumit jika saya ingat dengan benar. Juga, mereka hanya menyelesaikan argumen setelah Anda mengetik perintah ... Mereka tidak menyelesaikan perintah.
derobert
Aku mengerti apa yang kamu maksud. Ketika Anda dapat melihat pada dokumen ini: debian-administration.org/article/316/…
unixmiah
-3

Jalankan perintah di bawah ini untuk menemukan di mana mplayer binary diinstal:

which mplayer

ATAU gunakan jalur ke biner mplayer jika Anda tahu, dalam perintah di bawah ini:

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

Idealnya apa pun yang Anda ketikkan dicari di semua direktori yang ditentukan dalam $PATHvariabel.

Mathew Paret
sumber
2
Hai, Mathew, selamat datang di Unix & Linux. Saya pikir pertanyaan OP sedikit lebih rumit daripada jawaban yang Anda sarankan. Saya kira mplayersudah di dalam mereka $PATHdan mereka ingin sesuatu seperti mp<tab>untuk menghasilkan mplayer, bukan semua biner yang dimulai dengan mp(misalnya, mpage mpcd mpartition mplayerdll.)
drs