In bash, it’s easy enough to set up customized completion of command arguments using the complete built-in. For example, for a hypothetical command with a synopsis of
foo --a | --b | --c
you could do
complete -W '--a --b --c' foo
You can also customize the completion you get when you press Tab at an empty prompt using complete -E, for example complete -E -W 'foo bar'. Then, pressing tab at the empty prompt would suggest only foo and bar.
How do I customize command completion at a non-empty prompt? For example, if I write f, how do I customize the completion to make it complete to foo?
(The actual case I’d like is locTAB → localc. And my brother, who prompted me to ask this, wants it with mplayer.)
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
Completion of the command (along with other things) is handled via bash readline completion. This operates at a slightly lower level than the usual “programmable completion” (which is invoked only when the command is identified, and the two special cases you identified above).
Update: the new release of bash-5.0 (Jan 2019) adds complete -I for exactly this problem.
The relevant readline commands are:
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.
In a similar way to the more common complete -F, some of this can be handed over to a function by using 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'
This enables your own per-command or prefix string hooks in ~/.complete.d/. E.g. if you create an executable ~/.complete.d/loc with:
#!/bin/bash echo localc
This will do (roughly) what you expect.
The function above goes to some lengths to emulate the normal bash command completion behaviour, though it is imperfect (particularly the dubious sort | fmt | column carry-on to display a list of matches).
However, a non-trivial issue with this it can only use a function to replace the binding to the main complete function (invoked with TAB by default).
This approach would work well with a different key-binding used for just custom command completion, but it simply does not implement the full completion logic after that (e.g. later words in the command line). Doing so would require parsing the command line, dealing with cursor position, and other tricky things that probably should not be considered in a shell script…
Method 2
I don’t know if I unterstood your need for this…
This would imply that your bash only knows one command beginning with f.
A basic idea of completion is: if it’s ambiguous, print the possiblities.
So you could set your PATH to a directory only containing this one command and disable all bash builtins to get this work.
Anyhow, I can give you also a kind of workaround:
alias _='true &&'
complete -W foo _
So if you type _ <Tab> it will complete to _ foo which executes foo.
But nethertheless the alias f='foo' would be much easier.
Method 3
Simple answer for you would be to
$ cd into /etc/bash_completion.d $ ls
just the basic outputs
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
just add your desired program to auto complete to bash completion
Method 4
Run the below command to find where mplayer binary is installed:
which mplayer
OR use the path to the mplayer binary if you aleady know it, in the below command:
ln -s /path/to/mplayer /bin/mplayer
Ideally anything you type is searched in all directories specified in the $PATH variable.
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0