I'm on Ubuntu 16.04 LTS with bash v4.3.46.
I set an alias "p" to "python3". By writing this in .bashrc
alias p='python3'
Problem:
Completion doesn't work properly when aliased. (It shows in-executable files for python, too)
$p <tab>
a.py a.txt b.py b.txt c.txt hoge/
Compared with normal command: (This only shows executable files)
$python3 <tab>
a.py b.py hoge/
What I tried:
When I checked the completion for python3,
$complete -p python3
complete -F _python python3
Therefore, I added the following in .bashrc
complete -F _python p
And now, I got:
$complete -p p
complete -F _python p
It seems to be working, however, I got the same result as the beginning.
------- Added --------
I found the definition of _python at /usr/share/bash-completion/completions/python3
# bash completion for python -*- shell-script -*-
_python_modules()
{
COMPREPLY+=( $( compgen -W "$( ${1:-python} -c 'import pkgutil
for mod in pkgutil.iter_modules(): print(mod[1])' )" 2>/dev/null -- "$cur" ) )
}
_python()
{
local cur prev words cword
_init_completion || return
case $prev in
-'?'|-h|--help|-V|--version|-c)
return 0
;;
-m)
_python_modules "$1"
return 0
;;
-Q)
COMPREPLY=( $( compgen -W "old new warn warnall" -- "$cur" ) )
return 0
;;
-W)
COMPREPLY=( $( compgen -W "ignore default all module once error" \
-- "$cur" ) )
return 0
;;
!(?(*/)python*([0-9.])|-?))
[[ $cword -lt 2 || ${words[cword-2]} != -#(Q|W) ]] \
&& _filedir
;;
esac
# if '-c' is already given, complete all kind of files.
local i
for (( i=0; i < ${#words[#]}-1; i++ )); do
if [[ ${words[i]} == -c ]]; then
_filedir
fi
done
if [[ "$cur" != -* ]]; then
_filedir 'py?([co])'
else
COMPREPLY=( $( compgen -W '$( _parse_help "$1" -h )' -- "$cur" ) )
fi
return 0
} &&
complete -F _python python python2 python3
# ex: ts=4 sw=4 et filetype=sh
editing this seems to be fine.
Could you tell me how shoud I edit this?
To edit this, I need sudo, is there any other way that doesn't use sudo?
Thank you.
The problem is that this case statement: !(?(*/)python*([0-9.])|-?)) is restricting the tab completion to only aliases/commands that have the word python in them.
If you change that case statement to be: !(*|-h)) or even !(p|-h)) then your autocomplete should work.
I would personally just add the modified code to my .bashrc... something like the following, just renaming the functions and doing the aliasing:
# bash completion for python -*- shell-script -*-
_python_modules_cust()
{
COMPREPLY+=( $( compgen -W "$( ${1:-python} -c 'import pkgutil
for mod in pkgutil.iter_modules(): print(mod[1])' )" 2>/dev/null -- "$cur" ) )
}
_python_cust()
{
local cur prev words cword
_init_completion || return
case $prev in
-'?'|-h|--help|-V|--version|-c)
return 0
;;
-m)
_python_modules_cust "$1"
return 0
;;
-Q)
COMPREPLY=( $( compgen -W "old new warn warnall" -- "$cur" ) )
return 0
;;
-W)
COMPREPLY=( $( compgen -W "ignore default all module once error" \
-- "$cur" ) )
return 0
;;
!(*|-?))
[[ $cword -lt 2 || ${words[cword-2]} != -#(Q|W) ]] \
&& _filedir
;;
esac
# if '-c' is already given, complete all kind of files.
local i
for (( i=0; i < ${#words[#]}-1; i++ )); do
if [[ ${words[i]} == -c ]]; then
_filedir
fi
done
if [[ "$cur" != -* ]]; then
_filedir 'py?([co])'
else
COMPREPLY=( $( compgen -W '$( _parse_help "$1" -h )' -- "$cur" ) )
fi
return 0
} &&
alias p='python3' &&
complete -F _python_cust p
# ex: ts=4 sw=4 et filetype=sh
Related
GNU advises to use --name=value syntax for passing argument for long option. It enables a long option to accept an argument that is itself optional.
Suppose you have a complete set of possible arguments. How do you write a bash completion code for such an option? I want the completion to add space when it completes an unambiguous argument, but not before.
Here is the template code I wrote for completing GNU options given in the code for imaginary command gnu-options.
Options that do not take arguments are defined in array opts. Options that do possibly take argument are defined in associative array args. Note that as with-args0 appears in both it is an option with optional argument.
The script supports even case where $COMP_WORDBREAKS does not include '=', but the shown completions are longer then.
# Hack for given strings $2,$3,... possibly being in $1 and $COMP_WORDBREAKS
# Only the part after each match is listed as a completion.
# Run 'shopt -s extdebug; declare -F __ltrim_colon_completions; shopt -u extdebug'
# to see location for the respective function for colon only.
__ltrim_completions ()
{
local cur=$1; shift
while [[ ${1+x} ]]; do
if [[ "$cur" == *$1* && "$COMP_WORDBREAKS" == *$1* ]]; then
local x_word=${cur%$1*}$1
local i
for i in ${!COMPREPLY[*]}; do
COMPREPLY[$i]=${COMPREPLY[$i]#"$x_word"}
done
fi
shift
done
}
_gnu_options()
{
local IFS=$'\n' # needed for handling trailing space of some options and all arguments
local cur prev words cword split # needed by _init_completion()
local opts i prefix= wordlist
local -A args=()
# Do not treat = as word breaks even if they are in $COMP_WORDBREAKS:
# Split option=value into option in $prev and value in $cur
_init_completion -s || return
# DEFINE OPTIONS THAT DO NOT TAKE AN ARGUMENT HERE:
opts=(with-args0 option0 option1 par param)
# DEFINE THE OPTIONS WITH ARGUMENTS HERE:
args=([with-args0]= [with-args1]=$'arg10\narg11')
args[with-args2]=\
'arg=20
arg=21
var=22
argx'
args[with-args3]=
for i in ${!args[*]}; do
if [[ $prev = --$i ]]; then
local j dobreak=
[[ $split == false ]] && {
# equal sign not used; check, if argument is optional.
for j in ${opts[*]}; do [[ $i == $j ]] && { dobreak=t; break; } done
}
[[ $dobreak ]] && break
[[ "$COMP_WORDBREAKS" != *=* && $split == true ]] && prefix="--$i="
if [[ ${args[$i]} ]]; then
COMPREPLY=( $( compgen -P "$prefix" -W "${args[$i]}" -- "$cur" ) )
__ltrim_completions "$cur" =
else
case $i in
with-args0)
# expand file/directory name.
COMPREPLY=( $( compgen -P "$prefix" -A file -- "$cur" ) )
compopt -o filenames
;;
*)
COMPREPLY=()
;;
esac
fi
return 0
fi
done
wordlist=()
for i in ${opts[*]}; do wordlist+=("--$i "); done
for i in ${!args[*]}; do wordlist+=("--$i="); done
COMPREPLY=( $( compgen -W "${wordlist[*]}" -- "$cur" ) )
compopt -o nospace
} && complete -F _gnu_options gnu-options
i find some strange thing when i use arparse in python3.
#!/usr/bin/env python3
import argparse
def create_parser():
p = argparse.ArgumentParser(add_help=True)
p.add_argument('-i', help='i parameter', required=True)
p.add_argument('-m', help='m parameter', required=True)
return p
if __name__ == '__main__':
p = create_parser()
n = p.parse_args()
print(n)
when i try launch it with
python3 ./script.py -i ./some_folder/some_file -m ./
bash autocomplete work with '-i' parameter, but not work with '-m'. If i rename '-m' to '-me' for example, all works good.
In bash i try launch other commands with '-m' parameter, but it not work only with argparse. Where can there be a mistake here?
What happens here is that the autocompletion for the python3 comand kicks in:
$ complete | grep python
complete -F _python python2
complete -F _python python3
complete -F _python python
The function _python that handles it should look like this:
$ type _python
_python is a function
_python ()
{
local cur prev words cword;
_init_completion || return;
case $prev in
-'?' | -h | --help | -V | --version | -c)
return 0
;;
-m)
_python_modules "$1";
return 0
;;
-Q)
COMPREPLY=($( compgen -W "old new warn warnall" -- "$cur" ));
return 0
;;
-W)
COMPREPLY=($( compgen -W "ignore default all module once error" -- "$cur" ));
return 0
;;
!(?(*/)python*([0-9.])|-?))
[[ $cword -lt 2 || ${words[cword-2]} != -#(Q|W) ]] && _filedir
;;
esac;
local i;
for ((i=0; i < ${#words[#]}-1; i++ ))
do
if [[ ${words[i]} == -c ]]; then
_filedir;
fi;
done;
if [[ "$cur" != -* ]]; then
_filedir 'py?([co])';
else
COMPREPLY=($( compgen -W '$( _parse_help "$1" -h )' -- "$cur" ));
fi;
return 0
}
The completion function treats the -m flag the same wheather it shows up as argument to python or to the script, so it tries to complete with a list of module names.
One way around this would be to use an alias for the python3 command that does not trigger the completion, e.g:
$ alias py3=python3
To make this persistent you can put it in your ~/.bashrc. Then you can use
$ py3 ./script.py -i ./some_folder/some_file -m ./[TAB]
which will use filename completion.
Or rename the -m flag to something else.
The script is:
#!/bin/bash
# Dynamic Menu Function
createmenu () {
select selected_option; do # in "$#" is the default
if [ 1 -le "$REPLY" ] && [ "$REPLY" -le $(($#)) ]; then
break;
else
echo "Please make a vaild selection (1-$#)."
fi
done
}
declare -a drives=();
# Load Menu by Line of Returned Command
mapfile -t drives < <(lsblk --nodeps -o name,serial,size | grep "sd");
# Display Menu and Prompt for Input
echo "Available Drives (Please select one):";
createmenu "${drives[#]}"
# Split Selected Option into Array and Display
drive=($(echo "${selected_option}"));
echo "Drive Id: ${drive[0]}";
echo "Serial Number: ${drive[1]}";
The older system doesn't have mapfile or readarray so I need to convert that line to some alternative that can read each line of the lsblk output into an array.
The line in question that creates the array is:
mapfile -t drives < <(lsblk --nodeps -o name,serial,size | grep "sd");
You can loop over your input and append to the array:
$ while IFS= read -r line; do arr+=("$line"); done < <(printf '%d\n' {0..5})
$ declare -p arr
declare -a arr='([0]="0" [1]="1" [2]="2" [3]="3" [4]="4" [5]="5")'
Or, for your specific case:
while IFS= read -r line; do
drives+=("$line")
done < <(lsblk --nodeps -o name,serial,size | grep "sd")
See the BashFAQ/001 for an excellent explanation why IFS= read -r is a good idea: it makes sure that whitespace is conserved and backslash sequences not interpreted.
Here's the solution I came up with a while back. This is better because it provides a substitute function for older versions of Bash that don't support mapfile/readarray.
if ! type -t readarray >/dev/null; then
# Very minimal readarray implementation using read. Does NOT work with lines that contain double-quotes due to eval()
readarray() {
local cmd opt t v=MAPFILE
while [ -n "$1" ]; do
case "$1" in
-h|--help) echo "minimal substitute readarray for older bash"; exit; ;;
-r) shift; opt="$opt -r"; ;;
-t) shift; t=1; ;;
-u)
shift;
if [ -n "$1" ]; then
opt="$opt -u $1";
shift
fi
;;
*)
if [[ "$1" =~ ^[A-Za-z_]+$ ]]; then
v="$1"
shift
else
echo -en "${C_BOLD}${C_RED}Error: ${C_RESET}Unknown option: '$1'\n" 1>&2
exit
fi
;;
esac
done
cmd="read $opt"
eval "$v=()"
while IFS= eval "$cmd line"; do
line=$(echo "$line" | sed -e "s#\([\"\`]\)#\\\\\1#g" )
eval "${v}+=(\"$line\")"
done
}
fi
You don't have to change your code one bit. It just works!
readarray -t services -u < <(lsblk --nodeps -o name,serial,size | grep "sd")
For those playing along at home, this one aims to provide a mapfile that's feature-compliant with Bash 5, but still runs as far back as Bash 3.x:
#!/usr/bin/env bash
if ! (enable | grep -q 'enable mapfile'); then
function mapfile() {
local DELIM="${DELIM-$'\n'}"; opt_d() { DELIM="$1"; }
local COUNT="${COUNT-"0"}"; opt_n() { COUNT="$1"; }
local ORIGIN="${ORIGIN-"0"}"; opt_O() { ORIGIN="$1"; }
local SKIP="${SKIP-"0"}"; opt_s() { SKIP="$1"; }
local STRIP="${STRIP-"0"}"; opt_t() { STRIP=1; }
local FROM_FD="${FROM_FD-"0"}"; opt_u() { FROM_FD="$1"; }
local CALLBACK="${CALLBACK-}"; opt_C() { CALLBACK="$1"; }
local QUANTUM="${QUANTUM-"5000"}"; opt_c() { QUANTUM="$1"; }
unset OPTIND; local extra_args=()
while getopts ":d:n:O:s:tu:C:c:" opt; do
case "$opt" in
:) echo "${FUNCNAME[0]}: option '-$OPTARG' requires an argument" >&2; exit 1 ;;
\?) echo "${FUNCNAME[0]}: ignoring unknown argument '-$OPTARG'" >&2 ;;
?) "opt_${opt}" "$OPTARG" ;;
esac
done
shift "$((OPTIND - 1))"; set -- ${extra_args[#]+"${extra_args[#]}"} "$#"
local var="${1:-MAPFILE}"
### Bash 3.x doesn't have `declare -g` for "global" scope...
eval "$(printf "%q" "$var")=()" 2>/dev/null || { echo "${FUNCNAME[0]}: '$var': not a valid identifier" >&2; exit 1; }
local __skip="${SKIP:-0}" __counter="${ORIGIN:-0}" __count="${COUNT:-0}" __read="0"
### `while read; do...` has trouble when there's no final newline,
### and we use `$REPLY` rather than providing a variable to preserve
### leading/trailing whitespace...
while true; do
if read -d "$DELIM" -r <&"$FROM_FD"
then [[ ${STRIP:-0} -ge 1 ]] || REPLY="$REPLY$DELIM"
elif [[ -z $REPLY ]]; then break
fi
(( __skip-- <= 0 )) || continue
(( COUNT <= 0 || __count-- > 0 )) || break
### Yes, eval'ing untrusted content is insecure, but `mapfile` allows it...
if [[ -n $CALLBACK ]] && (( QUANTUM > 0 && ++__read % QUANTUM == 0 ))
then eval "$CALLBACK $__counter $(printf "%q" "$REPLY")"; fi
### Bash 3.x doesn't allow `printf -v foo[0]`...
### and `read -r foo[0]` mucks with whitespace
eval "${var}[$((__counter++))]=$(printf "%q" "$REPLY")"
done
}
### Alias `readarray` as well...
readarray() { mapfile "$#"; }
fi
if [[ -z ${PS1+YES} ]]; then
echo "'mapfile' should only be called as a shell function; try \"source ${BASH_SOURCE[0]##*/}\" first..." >&2
exit 1
fi
I'm seeing this every time I open terminal, after a RVM installation:
Last login: Tue Dec 2 10:35:44 on ttys000
rvm_debug () {
(( ${rvm_debug_flag:-0} )) || return 0
if rvm_pretty_print stderr
then
printf "%b" "${rvm_debug_clr:-}$*${rvm_reset_clr:-}\n"
else
printf "%b" "$*\n"
fi >&2
}
This is my bash_profile:
[10:54:13] old_ian :: Ians-MacBook-Pro-2 ➜ ~ ⭑ cat .bash_profile
export PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin
export SSL_CERT_FILE=/usr/local/etc/cacert.pem
export PATH=/usr/local/sbin:$PATH
### Added by the Heroku Toolbelt
export PATH="/usr/local/heroku/bin:$PATH"
### RVM
source $HOME/.rvm/scripts/rvm
PATH="/Applications/Postgres.app/Contents/MacOS/bin:$PATH"
alias bex="bundle exec"
alias grep="grep --color=auto"
alias vi=vim
alias postgres="pg_ctl -D /usr/local/var/postgres -l /usr/local/var/postgres/server.log"
[10:54:19] old_ian :: Ians-MacBook-Pro-2 ➜ ~ ⭑
Maybe something went wrong with it's install ?
I've been using it with no problems.
UPDATES:
Posting cat RVM
#!/usr/bin/env bash
# rvm : Ruby enVironment Manager
# https://rvm.io
# https://github.com/wayneeseguin/rvm
# partial duplication marker dkjnkjvnckbjncvbkjnvkj
# prevent from loading in sh shells
if
\command \test -n "${BASH_VERSION:-}" -o -n "${ZSH_VERSION:-}"
then
case "`uname`" in
(CYGWIN*) __shell_name="`\command \ps -p $$ | \command \awk 'END {print $NF}'` 2>/dev/null" ;;
(*) __shell_name="`\command \ps -p $$ -o comm=`" ;;
esac
case "$__shell_name" in
(""|dash|sh|*/dash|*/sh) return 0 ;; # silently stop in sh shells
esac
unset __shell_name
else
return 0
fi
# also duplicated in scripts/base
__rvm_has_opt()
{
{
# pre-gnu
[[ -n "${ZSH_VERSION}" ]] && setopt | GREP_OPTIONS="" \command \grep "^${1}$" >/dev/null 2>&1
} ||
{
[[ -n "${BASH_VERSION}" ]] && [[ ":$SHELLOPTS:" =~ ":${1}:" ]]
} ||
return 1
}
# Do not allow sourcing RVM in `sh` - it's not supported
# return 0 to exit from sourcing this script without breaking sh
if __rvm_has_opt "posix"
then return 0
fi
# TODO: Alter the variable names to make sense
\export HOME rvm_prefix rvm_user_install_flag rvm_path
HOME="${HOME%%+(\/)}" # Remove trailing slashes if they exist on HOME
[[ -n "${rvm_stored_umask:-}" ]] || export rvm_stored_umask=$(umask)
if (( ${rvm_ignore_rvmrc:=0} == 0 ))
then
rvm_rvmrc_files=("/etc/rvmrc" "$HOME/.rvmrc")
if [[ -n "${rvm_prefix:-}" ]] && ! [[ "$HOME/.rvmrc" -ef "${rvm_prefix}/.rvmrc" ]]
then rvm_rvmrc_files+=( "${rvm_prefix}/.rvmrc" )
fi
for rvmrc in "${rvm_rvmrc_files[#]}"
do
if [[ -f "$rvmrc" ]]
then
# pre-gnu
if GREP_OPTIONS="" \command \grep '^\s*rvm .*$' "$rvmrc" >/dev/null 2>&1
then
printf "%b" "
Error:
$rvmrc is for rvm settings only.
rvm CLI may NOT be called from within $rvmrc.
Skipping the loading of $rvmrc"
return 1
else
source "$rvmrc"
fi
fi
done
unset rvm_rvmrc_files
fi
# duplication marker jdgkjnfnkjdngjkfnd4fd
# detect rvm_path if not set
if [[ -z "${rvm_path:-}" ]]
then
if [[ -n "${BASH_SOURCE:-$_}" && -f "${BASH_SOURCE:-$_}" ]]
then
rvm_path="${BASH_SOURCE:-$_}"
rvm_path="$( \command \cd "${rvm_path%/scripts/rvm}">/dev/null; pwd )"
rvm_prefix=$( dirname $rvm_path )
elif (( UID == 0 ))
then
if (( ${rvm_user_install_flag:-0} == 0 ))
then
rvm_prefix="/usr/local"
rvm_path="${rvm_prefix}/rvm"
else
rvm_prefix="$HOME"
rvm_path="${rvm_prefix}/.rvm"
fi
else
if [[ -d "$HOME/.rvm" && -s "$HOME/.rvm/scripts/rvm" ]]
then
rvm_prefix="$HOME"
rvm_path="${rvm_prefix}/.rvm"
else
rvm_prefix="/usr/local"
rvm_path="${rvm_prefix}/rvm"
fi
fi
else
# remove trailing slashes, btw. %%/ <- does not work as expected
rvm_path="${rvm_path%%+(\/)}"
fi
# guess rvm_prefix if not set
if [[ -z "${rvm_prefix}" ]]
then
rvm_prefix=$( dirname $rvm_path )
fi
# duplication marker kkdfkgnjfndgjkndfjkgnkfjdgn
case "$rvm_path" in
(/usr/local/rvm) rvm_user_install_flag=0 ;;
($HOME/*|/${USER// /_}*) rvm_user_install_flag=1 ;;
(*) rvm_user_install_flag=0 ;;
esac
export rvm_loaded_flag
if [[ -n "${BASH_VERSION:-}" || -n "${ZSH_VERSION:-}" ]] &&
typeset -f rvm >/dev/null 2>&1
then
rvm_loaded_flag=1
else
rvm_loaded_flag=0
fi
if
(( ${rvm_loaded_flag:=0} == 0 )) || (( ${rvm_reload_flag:=0} == 1 ))
then
if
[[ -n "${rvm_path}" && -d "$rvm_path" ]]
then
true ${rvm_scripts_path:="$rvm_path/scripts"}
if
[[ ! -f "$rvm_scripts_path/base" ]]
then
printf "%b" "WARNING:
Could not source '$rvm_scripts_path/base' as file does not exist.
RVM will likely not work as expected.\n"
elif
! source "$rvm_scripts_path/base"
then
printf "%b" "WARNING:
Errors sourcing '$rvm_scripts_path/base'.
RVM will likely not work as expected.\n"
else
__rvm_ensure_is_a_function
__rvm_setup
export rvm_version
rvm_version="$(\command \cat "$rvm_path/VERSION") ($(\command \cat "$rvm_path/RELEASE" 2>/dev/null))"
alias rvm-restart="rvm_reload_flag=1 source '${rvm_scripts_path:-${rvm_path}/scripts}/rvm'"
# Try to load RVM ruby if none loaded yet
__path_to_ruby="$( builtin command -v ruby 2>/dev/null || true )"
if
[[ -z "${__path_to_ruby}" ]] ||
[[ "${__path_to_ruby}" != "${rvm_path}"* ]] ||
[[ "${__path_to_ruby}" == "${rvm_path}/bin/ruby" ]]
then
if [[ -s "$rvm_path/environments/default" ]]
then source "$rvm_path/environments/default"
fi
if
[[ ${rvm_project_rvmrc:-1} -gt 0 ]] &&
! __function_on_stack __rvm_project_rvmrc
then
# Reload the rvmrc, use promptless ensuring shell processes does not
# prompt if .rvmrc trust value is not stored, revert to default on fail
if
rvm_current_rvmrc=""
rvm_project_rvmrc_default=2 rvm_promptless=1 __rvm_project_rvmrc
then
rvm_hook=after_cd
source "${rvm_scripts_path:-${rvm_path}/scripts}/hook"
fi
fi
fi
unset __path_to_ruby
# Makes sure rvm_bin_path is in PATH atleast once.
[[ ":${PATH}:" == *":${rvm_bin_path}:"* ]] || PATH="$PATH:${rvm_bin_path}"
if
(( ${rvm_reload_flag:=0} == 1 ))
then
[[ "${rvm_auto_reload_flag:-0}" == 2 ]] || printf "%b" 'RVM reloaded!\n'
# make sure we clean env on reload
__rvm_env_loaded=1
unset __rvm_project_rvmrc_lock
fi
rvm_loaded_flag=1
__rvm_teardown
fi
else
printf "%b" "\n\$rvm_path ($rvm_path) does not exist."
fi
unset rvm_prefix_needs_trailing_slash rvm_gems_cache_path rvm_gems_path rvm_project_rvmrc_default rvm_gemset_separator rvm_reload_flag
fi
Hope this Helps!
I followed this tutorial to setup auto-completion functions for figlet/toilet.
# bash completion for figlet/toilet
have figlet &&
_figlet()
{
local prev cur opts
_get_comp_words_by_ref cur prev
opts="-f"
COMPREPLY=()
case $prev in
-f)
local running=$(find /usr/share/figlet -name '*.flf' -printf '%P\n' | sed 's/\.flf$//')
COMPREPLY=( $(compgen -W "${running}" -- ${cur}) )
return 0
;;
*)
;;
esac
COMPREPLY=( $( compgen -W "$opts" -- "$cur" ) )
return 0
} &&
complete -F _figlet figlet
###################################################################################################
have toilet &&
_toilet()
{
local prev cur opts
_get_comp_words_by_ref cur prev
opts="-f"
COMPREPLY=()
case $prev in
-f)
local running=$(find /usr/share/figlet -name '*.[tf]lf' -printf '%P\n' | sed 's/\.[tf]lf$//')
COMPREPLY=( $(compgen -W "${running}" -- ${cur}) )
return 0
;;
*)
;;
esac
COMPREPLY=( $( compgen -W "$opts" -- "$cur" ) )
return 0
} &&
complete -F _toilet toilet
_figlet and _toilet are almost identical except the pattern in find/sed commands.
How to extract a function called _figlet_toilet which takes a pattern as argument?
From: http://www.gnu.org/software/bash/manual/html_node/Programmable-Completion.html
After these matches have been generated, any shell function or command specified with the -F and -C options is invoked. When the command or function is invoked, the COMP_LINE, COMP_POINT, COMP_KEY, and COMP_TYPE variables are assigned values as described above (see Bash Variables). If a shell function is being invoked, the COMP_WORDS and COMP_CWORD variables are also set. When the function or command is invoked, the first argument is the name of the command whose arguments are being completed, the second argument is the word being completed, and the third argument is the word preceding the word being completed on the current command line. No filtering of the generated completions against the word being completed is performed; the function or command has complete freedom in generating the matches.
# bash completion for figlet/toilet
{
have figlet || have toilet
} &&
_figlet_toilet()
{
local prev cur opts pat
_get_comp_words_by_ref cur prev
opts="-f"
COMPREPLY=()
case $prev in
-f)
case "${1}" in
figlet)
pat='flf'
;;
toilet)
pat='[tf]lf'
;;
esac
local running=$(find /usr/share/figlet -name "*.${pat}" -printf '%P\n' | sed "s/\.${pat}\$//")
COMPREPLY=( $(compgen -W "${running}" -- ${cur}) )
return 0
;;
*)
;;
esac
# if '-f' is already given, then generate random string
for (( i=0; i < ${#COMP_WORDS[#]}-1; i++ )); do
if [[ ${COMP_WORDS[i]} == -f ]]; then
# COMPREPLY=("'$(fortune -sn42)'")
return 0
fi
done
COMPREPLY=( $( compgen -W "$opts" -- "$cur" ) )
return 0
} &&
complete -F _figlet_toilet figlet toilet