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 -I
untuk 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/loc
dengan:
#!/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 | column
untuk menampilkan daftar kecocokan).
Namun , masalah non-sepele dengan ini hanya dapat menggunakan fungsi untuk menggantikan pengikatan ke complete
fungsi 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 ...
loc
untuklocalc
? 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.