# -*- shell-script -*- # # bash_completion - programmable completion functions for bash 4.2+ # # Copyright © 2006-2008, Ian Macdonald # © 2009-2020, Bash Completion Maintainers # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # The latest version of this software can be obtained here: # # https://github.com/scop/bash-completion BASH_COMPLETION_VERSINFO=( 2 # x-release-please-major 14 # x-release-please-minor 0 # x-release-please-patch ) if [[ $- == *v* ]]; then _comp__init_original_set_v="-v" else _comp__init_original_set_v="+v" fi if [[ ${BASH_COMPLETION_DEBUG-} ]]; then set -v else set +v fi # Turn on extended globbing and programmable completion shopt -s extglob progcomp # Declare a compatibility function name # @param $1 Version of bash-completion where the deprecation occurred # @param $2 Old function name # @param $3 New function name # @since 2.12 _comp_deprecate_func() { if (($# != 3)); then printf 'bash_completion: %s: usage: %s DEPRECATION_VERSION OLD_NAME NEW_NAME\n' "$FUNCNAME" "$FUNCNAME" return 2 fi if [[ $2 != [a-zA-Z_]*([a-zA-Z_0-9]) ]]; then printf 'bash_completion: %s: %s\n' "$FUNCNAME" "\$2: invalid function name '$1'" >&2 return 2 elif [[ $3 != [a-zA-Z_]*([a-zA-Z_0-9]) ]]; then printf 'bash_completion: %s: %s\n' "$FUNCNAME" "\$3: invalid function name '$2'" >&2 return 2 fi eval -- "$2() { $3 \"\$@\"; }" } # Declare a compatibility variable name. # For bash 4.3+, a real name alias is created, allowing value changes to # "apply through" when the variables are set later. For bash versions earlier # than that, the operation is once-only; the value of the new variable # (if it's unset) is set to that of the old (if set) at call time. # # @param $1 Version of bash-completion where the deprecation occurred # @param $2 Old variable name # @param $3 New variable name # @since 2.12 _comp_deprecate_var() { if (($# != 3)); then printf 'bash_completion: %s: usage: %s DEPRECATION_VERSION OLD_NAME NEW_NAME\n' "$FUNCNAME" "$FUNCNAME" return 2 fi if [[ $2 != [a-zA-Z_]*([a-zA-Z_0-9]) ]]; then printf 'bash_completion: %s: %s\n' "$FUNCNAME" "\$2: invalid variable name '$1'" >&2 return 2 elif [[ $3 != [a-zA-Z_]*([a-zA-Z_0-9]) ]]; then printf 'bash_completion: %s: %s\n' "$FUNCNAME" "\$3: invalid variable name '$2'" >&2 return 2 fi if ((BASH_VERSINFO[0] >= 5 || BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] >= 3)); then eval "declare -gn $2=$3" elif [[ -v $2 && ! -v $3 ]]; then printf -v "$3" %s "$2" fi } # A lot of the following one-liners were taken directly from the # completion examples provided with the bash 2.04 source distribution # start of section containing compspecs that can be handled within bash # user commands see only users complete -u groups slay w sux # bg completes with stopped jobs complete -A stopped -P '"%' -S '"' bg # other job commands complete -j -P '"%' -S '"' fg jobs disown # readonly and unset complete with shell variables complete -v readonly unset # shopt completes with shopt options complete -A shopt shopt # unalias completes with aliases complete -a unalias # type and which complete on commands complete -c command type which # builtin completes on builtins complete -b builtin # start of section containing completion functions called by other functions # Check if we're running on the given userland # @param $1 userland to check for # @since 2.12 _comp_userland() { local userland=$(uname -s) [[ $userland == @(Linux|GNU/*) ]] && userland=GNU [[ $userland == "$1" ]] } # This function sets correct SysV init directories # # @since 2.12 _comp_sysvdirs() { sysvdirs=() [[ -d /etc/rc.d/init.d ]] && sysvdirs+=(/etc/rc.d/init.d) [[ -d /etc/init.d ]] && sysvdirs+=(/etc/init.d) # Slackware uses /etc/rc.d [[ -f /etc/slackware-version ]] && sysvdirs=(/etc/rc.d) ((${#sysvdirs[@]})) } # This function checks whether we have a given program on the system. # # @since 2.12 _comp_have_command() { # Completions for system administrator commands are installed as well in # case completion is attempted via `sudo command ...'. PATH=$PATH:/usr/sbin:/sbin:/usr/local/sbin type "$1" &>/dev/null } # This function checks whether a given readline variable # is `on'. # # @since 2.12 _comp_readline_variable_on() { [[ $(bind -v) == *$1+([[:space:]])on* ]] } # This function shell-quotes the argument # @param $1 String to be quoted # @var[out] REPLY Resulting string # @since 2.12 _comp_quote() { REPLY=\'${1//\'/\'\\\'\'}\' } # shellcheck disable=SC1003 _comp_dequote__initialize() { local regex_param='\$([_a-zA-Z][_a-zA-Z0-9]*|[-*@#?$!0-9_])|\$\{[!#]?([_a-zA-Z][_a-zA-Z0-9]*(\[([0-9]+|[*@])\])?|[-*@#?$!0-9_])\}' local regex_quoted='\\.|'\''[^'\'']*'\''|\$?"([^\"$`!]|'$regex_param'|\\.)*"|\$'\''([^\'\'']|\\.)*'\''' _comp_dequote__regex_safe_word='^([^\'\''"$`;&|<>()!]|'$regex_quoted'|'$regex_param')*$' unset -f "$FUNCNAME" } _comp_dequote__initialize # This function expands a word using `eval` in a safe way. This function can # be typically used to get the expanded value of `${word[i]}` as # `_comp_dequote "${word[i]}"`. When the word contains unquoted shell special # characters, command substitutions, and other unsafe strings, the function # call fails before applying `eval`. Otherwise, `eval` is applied to the # string to generate the result. # # @param $1 String to be expanded. A safe word consists of the following # sequence of substrings: # # - Shell non-special characters: [^\'"$`;&|<>()!]. # - Parameter expansions of the forms $PARAM, ${!PARAM}, # ${#PARAM}, ${NAME[INDEX]}, ${!NAME[INDEX]}, ${#NAME[INDEX]} # where INDEX is an integer, `*` or `@`, NAME is a valid # variable name [_a-zA-Z][_a-zA-Z0-9]*, and PARAM is NAME or a # parameter [-*@#?$!0-9_]. # - Quotes \?, '...', "...", $'...', and $"...". In the double # quotations, parameter expansions are allowed. # # @var[out] REPLY Array that contains the expanded results. Multiple words or # no words may be generated through pathname expansions. # # Note: This function allows parameter expansions as safe strings, which might # cause unexpected results: # # * This allows execution of arbitrary commands through extra expansions of # array subscripts in name references. For example, # # declare -n v='dummy[$(echo xxx >/dev/tty)]' # echo "$v" # This line executes the command 'echo xxx'. # _comp_dequote '"$v"' # This line also executes it. # # * This may change the internal state of the variable that has side effects. # For example, the state of the random number generator of RANDOM can change: # # RANDOM=1234 # Set seed # echo "$RANDOM" # This produces 30658. # RANDOM=1234 # Reset seed # _comp_dequote '"$RANDOM"' # This line changes the internal state. # echo "$RANDOM" # This fails to reproduce 30658. # # We allow these parameter expansions as a part of safe strings assuming the # referential transparency of the simple parameter expansions and the sane # setup of the variables by the user or other frameworks that the user loads. # @since 2.12 _comp_dequote() { REPLY=() # fallback value for unsafe word and failglob [[ $1 =~ $_comp_dequote__regex_safe_word ]] || return 1 eval "REPLY=($1)" 2>/dev/null # may produce failglob } # Unset the given variables across a scope boundary. Useful for unshadowing # global scoped variables. Note that simply calling unset on a local variable # will not unshadow the global variable. Rather, the result will be a local # variable in an unset state. # Usage: local IFS='|'; _comp_unlocal IFS # @param $* Variable names to be unset # @since 2.12 _comp_unlocal() { if ((BASH_VERSINFO[0] >= 5)) && shopt -q localvar_unset; then shopt -u localvar_unset unset -v "$@" shopt -s localvar_unset else unset -v "$@" fi } # Assign variables one scope above the caller # Usage: local varname [varname ...] && # _comp_upvars [-v varname value] | [-aN varname [value ...]] ... # Available OPTIONS: # -aN Assign next N values to varname as array # -v Assign single value to varname # @return 1 if error occurs # @see https://fvue.nl/wiki/Bash:_Passing_variables_by_reference # @since 2.12 _comp_upvars() { if ! (($#)); then echo "bash_completion: $FUNCNAME: usage: $FUNCNAME" \ "[-v varname value] | [-aN varname [value ...]] ..." >&2 return 2 fi while (($#)); do case $1 in -a*) # Error checking [[ ${1#-a} ]] || { echo "bash_completion: $FUNCNAME:" \ "\`$1': missing number specifier" >&2 return 1 } printf %d "${1#-a}" &>/dev/null || { echo bash_completion: \ "$FUNCNAME: \`$1': invalid number specifier" >&2 return 1 } # Assign array of -aN elements # shellcheck disable=SC2015,SC2140 # TODO [[ $2 ]] && unset -v "$2" && eval "$2"=\(\"\$"{@:3:${1#-a}}"\"\) && shift $((${1#-a} + 2)) || { echo bash_completion: \ "$FUNCNAME: \`$1${2+ }$2': missing argument(s)" \ >&2 return 1 } ;; -v) # Assign single value # shellcheck disable=SC2015 # TODO [[ $2 ]] && unset -v "$2" && eval "$2"=\"\$3\" && shift 3 || { echo "bash_completion: $FUNCNAME: $1:" \ "missing argument(s)" >&2 return 1 } ;; *) echo "bash_completion: $FUNCNAME: $1: invalid option" >&2 return 1 ;; esac done } # Get the list of filenames that match with the specified glob pattern. # This function does the globbing in a controlled environment, avoiding # interference from user's shell options/settings or environment variables. # @param $1 array_name Array name # The array name should not start with an underscore "_", which is internally # used. The array name should not be "GLOBIGNORE" or "GLOBSORT". # @param $2 pattern Pattern string to be evaluated. # This pattern string will be evaluated using "eval", so brace expansions, # parameter expansions, command substitutions, and other expansions will be # processed. The user-provided strings should not be directly specified to # this argument. # @return 0 if at least one path is generated, 1 if no path is generated, or 2 # if the usage is incorrect. # @since 2.12 _comp_expand_glob() { if (($# != 2)); then printf 'bash-completion: %s: unexpected number of arguments\n' "$FUNCNAME" >&2 printf 'usage: %s ARRAY_NAME PATTERN\n' "$FUNCNAME" >&2 return 2 elif [[ $1 == @(GLOBIGNORE|GLOBSORT|_*|*[^_a-zA-Z0-9]*|[0-9]*|'') ]]; then printf 'bash-completion: %s: invalid array name "%s"\n' "$FUNCNAME" "$1" >&2 return 2 fi # Save and adjust the settings. local _original_opts=$SHELLOPTS:$BASHOPTS set +o noglob shopt -s nullglob shopt -u failglob dotglob # Also the user's GLOBIGNORE and GLOBSORT (bash >= 5.3) may affect the # result of pathname expansions. local GLOBIGNORE="" GLOBSORT=name # To canonicalize the sorting order of the generated paths, we set # LC_COLLATE=C and unset LC_ALL while preserving LC_CTYPE. local LC_COLLATE=C LC_CTYPE=${LC_ALL:-${LC_CTYPE:-${LANG-}}} LC_ALL= eval -- "$1=()" # a fallback in case that the next line fails. eval -- "$1=($2)" # Restore the settings. Note: Changing GLOBIGNORE affects the state of # "shopt -q dotglob", so we need to explicitly restore the original state # of "shopt -q dotglob". _comp_unlocal GLOBIGNORE if [[ :$_original_opts: == *:dotglob:* ]]; then shopt -s dotglob else shopt -u dotglob fi [[ :$_original_opts: == *:nullglob:* ]] || shopt -u nullglob [[ :$_original_opts: == *:failglob:* ]] && shopt -s failglob [[ :$_original_opts: == *:noglob:* ]] && set -o noglob eval "((\${#$1[@]}))" } # Split a string and assign to an array. This function basically performs # `IFS=; =()` but properly handles saving/restoring the # state of `IFS` and the shell option `noglob`. A naive splitting by # `arr=(...)` suffers from unexpected IFS and pathname expansions, so one # should prefer this function to such naive splitting. # OPTIONS # -a Append to the array # -F sep Set a set of separator characters (used as IFS). The default # separator is $' \t\n' # -l The same as -F $'\n' # @param $1 array_name The array name # The array name should not start with an underscores "_", which is # internally used. The array name should not be either "IFS" or # "OPT{IND,ARG,ERR}". # @param $2 text The string to split # @return 2 when the usage is wrong, 0 when one or more completions are # generated, or 1 when the execution succeeds but no candidates are # generated. # @since 2.12 _comp_split() { local _append="" IFS=$' \t\n' local OPTIND=1 OPTARG="" OPTERR=0 _opt while getopts ':alF:' _opt "$@"; do case $_opt in a) _append=set ;; l) IFS=$'\n' ;; F) IFS=$OPTARG ;; *) echo "bash_completion: $FUNCNAME: usage error" >&2 return 2 ;; esac done shift "$((OPTIND - 1))" if (($# != 2)); then printf '%s\n' "bash_completion: $FUNCNAME: unexpected number of arguments" >&2 printf '%s\n' "usage: $FUNCNAME [-al] [-F SEP] ARRAY_NAME TEXT" >&2 return 2 elif [[ $1 == @(*[^_a-zA-Z0-9]*|[0-9]*|''|_*|IFS|OPTIND|OPTARG|OPTERR) ]]; then printf '%s\n' "bash_completion: $FUNCNAME: invalid array name '$1'" >&2 return 2 fi local _original_opts=$SHELLOPTS set -o noglob local _old_size _new_size if [[ $_append ]]; then eval "$1+=()" # in case $1 is unset eval "_old_size=\${#$1[@]}" eval "$1+=(\$2)" else _old_size=0 eval "$1=(\$2)" fi eval "_new_size=\${#$1[@]}" [[ :$_original_opts: == *:noglob:* ]] || set +o noglob ((_new_size > _old_size)) } # Helper function for _comp_compgen # @var[in] $? # @var[in] _var # @var[in] _append # @return original $? _comp_compgen__error_fallback() { local _status=$? if [[ $_append ]]; then # make sure existence of variable eval -- "$_var+=()" else eval -- "$_var=()" fi return "$_status" } # Provide a common interface to generate completion candidates in COMPREPLY or # in a specified array. # OPTIONS # -a Append to the array # -v arr Store the results to the array ARR. The default is `COMPREPLY`. # The array name should not start with an underscores "_", which is # internally used. The array name should not be any of "cur", "IFS" # or "OPT{IND,ARG,ERR}". # -U var Unlocalize VAR before performing the assignments. This option can # be specified multiple times to register multiple variables. This # option is supposed to be used in implementing a generator (G1) when # G1 defines a local variable name that does not start with `_`. In # such a case, when the target variable specified to G1 by `-v VAR1` # conflicts with the local variable, the assignment to the target # variable fails to propagate outside G1. To avoid such a situation, # G1 can call `_comp_compgen` with `-U VAR` to unlocalize `VAR` # before accessing the target variable. For a builtin compgen call # (i.e., _comp_compgen [options] -- options), VAR is unlocalized # after calling the builtin `compgen` but before assigning results to # the target array. For a generator call (i.e., _comp_compgen # [options] G2 ...), VAR is unlocalized before calling the child # generator function `_comp_compgen_G2`. # -c cur Set a word used as a prefix to filter the completions. The default # is ${cur-}. # -R The same as -c ''. Use raw outputs without filtering. # -C dir Evaluate compgen/generator in the specified directory. # @var[in,opt] cur Used as the default value of a prefix to filter the # completions. # # Usage #1: _comp_compgen [-alR|-F sep|-v arr|-c cur|-C dir] -- options... # Call `compgen` with the specified arguments and store the results in the # specified array. This function essentially performs arr=($(compgen args...)) # but properly handles shell options, IFS, etc. using _comp_split. This # function is equivalent to `_comp_split [-a] -l arr "$(IFS=sep; compgen # args... -- cur)"`, but this pattern is frequent in the codebase and is good # to separate out as a function for the possible future implementation change. # OPTIONS # -F sep Set a set of separator characters (used as IFS in evaluating # `compgen'). The default separator is $' \t\n'. Note that this is # not the set of separators to delimit output of `compgen', but the # separators in evaluating the expansions of `-W '...'`, etc. The # delimiter of the output of `compgen` is always a newline. # -l The same as -F $'\n'. Use lines as words in evaluating compgen. # @param $1... options Arguments that are passed to compgen (if $1 starts with # a hyphen `-`). # # Note: References to positional parameters $1, $2, ... (such as -W '$1') # will not work as expected because these reference the arguments of # `_comp_compgen' instead of those of the caller function. When there are # needs to reference them, save the arguments to an array and reference the # array instead. # # Note: The array option `-V arr` in bash >= 5.3 should be instead specified # as `-v arr` as a part of the `_comp_compgen` options. # @return True (0) if at least one completion is generated, False (1) if no # completion is generated, or 2 with an incorrect usage. # # Usage #2: _comp_compgen [-aR|-v arr|-c cur|-C dir|-i cmd|-x cmd] name args... # Call the generator `_comp_compgen_NAME ARGS...` with the specified options. # This provides a common interface to call the functions `_comp_compgen_NAME`, # which produce completion candidates, with custom options [-alR|-v arr|-c # cur]. The option `-F sep` is not used with this usage. # OPTIONS # -x cmd Call exported generator `_comp_xfunc_CMD_compgen_NAME` # -i cmd Call internal generator `_comp_cmd_CMD__compgen_NAME` # @param $1... name args Calls the function _comp_compgen_NAME with the # specified ARGS (if $1 does not start with a hyphen `-`). The options # [-alR|-v arr|-c cur] are inherited by the child calls of `_comp_compgen` # inside `_comp_compgen_NAME` unless the child call `_comp_compgen` receives # overriding options. # @var[in,opt,internal] _comp_compgen__append # @var[in,opt,internal] _comp_compgen__var # @var[in,opt,internal] _comp_compgen__cur # These variables are internally used to pass the effect of the options # [-alR|-v arr|-c cur] to the child calls of `_comp_compgen` in # `_comp_compgen_NAME`. # @return Exit status of the generator. # # @remarks When no options are supplied to _comp_compgen, `_comp_compgen NAME # args` is equivalent to the direct call `_comp_compgen_NAME args`. As the # direct call is slightly more efficient, the direct call is preferred over # calling it through `_comp_compgen`. # # @remarks Design `_comp_compgen_NAME`: a function that produce completions can # be defined with the name _comp_compgen_NAME. The function is supposed to # generate completions by calling `_comp_compgen`. To reflect the options # specified to the outer calls of `_comp_compgen`, the function should not # directly modify `COMPREPLY`. To add words, one can call # # _comp_compgen -- -W '"${words[@]}"' # # To directly add words without filtering by `cur`, one can call # # _comp_compgen -R -- -W '"${words[@]}"' # # or use the utility `_comp_compgen_set`: # # _comp_compgen_set "${words[@]}" # # Other nested calls of _comp_compgen can also be used. The function is # supposed to replace the existing content of the array by default to allow the # caller control whether to replace or append by the option `-a`. # # @since 2.12 _comp_compgen() { local _append= local _var= local _cur=${_comp_compgen__cur-${cur-}} local _dir="" local _ifs=$' \t\n' _has_ifs="" local _icmd="" _xcmd="" local -a _upvars=() local _old_nocasematch="" if shopt -q nocasematch; then _old_nocasematch=set shopt -u nocasematch fi local OPTIND=1 OPTARG="" OPTERR=0 _opt while getopts ':av:U:Rc:C:lF:i:x:' _opt "$@"; do case $_opt in a) _append=set ;; v) if [[ $OPTARG == @(*[^_a-zA-Z0-9]*|[0-9]*|''|_*|IFS|OPTIND|OPTARG|OPTERR|cur) ]]; then printf 'bash_completion: %s: -v: invalid array name `%s'\''\n' "$FUNCNAME" "$OPTARG" >&2 return 2 fi _var=$OPTARG ;; U) if [[ $OPTARG == @(*[^_a-zA-Z0-9]*|[0-9]*|'') ]]; then printf 'bash_completion: %s: -U: invalid variable name `%s'\''\n' "$FUNCNAME" "$OPTARG" >&2 return 2 elif [[ $OPTARG == @(_*|IFS|OPTIND|OPTARG|OPTERR|cur) ]]; then printf 'bash_completion: %s: -U: unnecessary to mark `%s'\'' as upvar\n' "$FUNCNAME" "$OPTARG" >&2 return 2 fi _upvars+=("$OPTARG") ;; c) _cur=$OPTARG ;; R) _cur="" ;; C) if [[ ! $OPTARG ]]; then printf 'bash_completion: %s: -C: invalid directory name `%s'\''\n' "$FUNCNAME" "$OPTARG" >&2 return 2 fi _dir=$OPTARG ;; l) _has_ifs=set _ifs=$'\n' ;; F) _has_ifs=set _ifs=$OPTARG ;; [ix]) if [[ ! $OPTARG ]]; then printf 'bash_completion: %s: -%s: invalid command name `%s'\''\n' "$FUNCNAME" "$_opt" "$OPTARG" >&2 return 2 elif [[ $_icmd ]]; then printf 'bash_completion: %s: -%s: `-i %s'\'' is already specified\n' "$FUNCNAME" "$_opt" "$_icmd" >&2 return 2 elif [[ $_xcmd ]]; then printf 'bash_completion: %s: -%s: `-x %s'\'' is already specified\n' "$FUNCNAME" "$_opt" "$_xcmd" >&2 return 2 fi ;;& i) _icmd=$OPTARG ;; x) _xcmd=$OPTARG ;; *) printf 'bash_completion: %s: usage error\n' "$FUNCNAME" >&2 return 2 ;; esac done [[ $_old_nocasematch ]] && shopt -s nocasematch shift "$((OPTIND - 1))" if (($# == 0)); then printf 'bash_completion: %s: unexpected number of arguments\n' "$FUNCNAME" >&2 printf 'usage: %s [-alR|-F SEP|-v ARR|-c CUR] -- ARGS...' "$FUNCNAME" >&2 return 2 fi if [[ ! $_var ]]; then # Inherit _append and _var only when -v var is unspecified. _var=${_comp_compgen__var-COMPREPLY} [[ $_append ]] || _append=${_comp_compgen__append-} fi if [[ $1 != -* ]]; then # usage: _comp_compgen [options] NAME args if [[ $_has_ifs ]]; then printf 'bash_completion: %s: `-l'\'' and `-F sep'\'' are not supported for generators\n' "$FUNCNAME" >&2 return 2 fi local -a _generator if [[ $_icmd ]]; then _generator=("_comp_cmd_${_icmd//[^a-zA-Z0-9_]/_}__compgen_$1") elif [[ $_xcmd ]]; then _generator=(_comp_xfunc "$_xcmd" "compgen_$1") else _generator=("_comp_compgen_$1") fi if ! declare -F -- "${_generator[0]}" &>/dev/null; then printf 'bash_completion: %s: unrecognized generator `%s'\'' (function %s not found)\n' "$FUNCNAME" "$1" "${_generator[0]}" >&2 return 2 fi shift _comp_compgen__call_generator "$@" else # usage: _comp_compgen [options] -- [compgen_options] if [[ $_icmd || $_xcmd ]]; then printf 'bash_completion: %s: generator name is unspecified for `%s'\''\n' "$FUNCNAME" "${_icmd:+-i $_icmd}${_xcmd:+x $_xcmd}" >&2 return 2 fi # Note: $* in the below checks would be affected by uncontrolled IFS in # bash >= 5.0, so we need to set IFS to the normal value. The behavior # in bash < 5.0, where unquoted $* in conditional command did not honor # IFS, was a bug. # Note: Also, ${_cur:+-- "$_cur"} and ${_append:+-a} would be affected # by uncontrolled IFS. local IFS=$' \t\n' # Note: extglob *\$?(\{)[0-9]* can be extremely slow when the string # "${*:2:_nopt}" becomes longer, so we test \$[0-9] and \$\{[0-9] # separately. if [[ $* == *\$[0-9]* || $* == *\$\{[0-9]* ]]; then printf 'bash_completion: %s: positional parameter $1, $2, ... do not work inside this function\n' "$FUNCNAME" >&2 return 2 fi _comp_compgen__call_builtin "$@" fi } # Helper function for _comp_compgen. This function calls a generator. # @param $1... generator_args # @var[in] _dir # @var[in] _cur # @arr[in] _generator # @arr[in] _upvars # @var[in] _append # @var[in] _var _comp_compgen__call_generator() { ((${#_upvars[@]})) && _comp_unlocal "${_upvars[@]}" if [[ $_dir ]]; then local _original_pwd=$PWD local PWD=${PWD-} OLDPWD=${OLDPWD-} # Note: We also redirect stdout because `cd` may output the target # directory to stdout when CDPATH is set. command cd -- "$_dir" &>/dev/null || { _comp_compgen__error_fallback return } fi local _comp_compgen__append=$_append local _comp_compgen__var=$_var local _comp_compgen__cur=$_cur cur=$_cur # Note: we use $1 as a part of a function name, and we use $2... as # arguments to the function if any. # shellcheck disable=SC2145 "${_generator[@]}" "$@" local _status=$? # Go back to the original directory. # Note: Failure of this line results in the change of the current # directory visible to the user. We intentionally do not redirect # stderr so that the error message appear in the terminal. # shellcheck disable=SC2164 [[ $_dir ]] && command cd -- "$_original_pwd" return "$_status" } # Helper function for _comp_compgen. This function calls the builtin compgen. # @param $1... compgen_args # @var[in] _dir # @var[in] _ifs # @var[in] _cur # @arr[in] _upvars # @var[in] _append # @var[in] _var if ((BASH_VERSINFO[0] > 5 || BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >= 3)); then # bash >= 5.3 has `compgen -V array_name` _comp_compgen__call_builtin() { if [[ $_dir ]]; then local _original_pwd=$PWD local PWD=${PWD-} OLDPWD=${OLDPWD-} # Note: We also redirect stdout because `cd` may output the target # directory to stdout when CDPATH is set. command cd -- "$_dir" &>/dev/null || { _comp_compgen__error_fallback return } fi local -a _result=() # Note: We specify -X '' to exclude empty completions to make the # behavior consistent with the implementation for Bash < 5.3 where # `_comp_split -l` removes empty lines. If the caller specifies -X # pat, the effect of -X '' is overwritten by the specified one. IFS=$_ifs compgen -V _result -X '' "$@" ${_cur:+-- "$_cur"} || { _comp_compgen__error_fallback return } # Go back to the original directory. # Note: Failure of this line results in the change of the current # directory visible to the user. We intentionally do not redirect # stderr so that the error message appear in the terminal. # shellcheck disable=SC2164 [[ $_dir ]] && command cd -- "$_original_pwd" ((${#_upvars[@]})) && _comp_unlocal "${_upvars[@]}" ((${#_result[@]})) || return if [[ $_append ]]; then eval -- "$_var+=(\"\${_result[@]}\")" else eval -- "$_var=(\"\${_result[@]}\")" fi return } else _comp_compgen__call_builtin() { local _result _result=$( if [[ $_dir ]]; then # Note: We also redirect stdout because `cd` may output the target # directory to stdout when CDPATH is set. command cd -- "$_dir" &>/dev/null || return fi IFS=$_ifs compgen "$@" ${_cur:+-- "$_cur"} ) || { _comp_compgen__error_fallback return } ((${#_upvars[@]})) && _comp_unlocal "${_upvars[@]}" _comp_split -l ${_append:+-a} "$_var" "$_result" } fi # usage: _comp_compgen_set [words...] # Reset COMPREPLY with the specified WORDS. If no arguments are specified, the # array is cleared. # # When an array name is specified by `-v VAR` in a caller _comp_compgen, the # array is reset instead of COMPREPLY. When the `-a` flag is specified in a # caller _comp_compgen, the words are appended to the existing elements of the # array instead of replacing the existing elements. This function ignores # ${cur-} or the prefix specified by `-v CUR`. # @return 0 if at least one completion is generated, or 1 otherwise. # @since 2.12 _comp_compgen_set() { local _append=${_comp_compgen__append-} local _var=${_comp_compgen__var-COMPREPLY} eval -- "$_var${_append:++}=(\"\$@\")" (($#)) } # Simply split the text and generate completions. This function should be used # instead of `_comp_compgen -- -W "$(command)"`, which is vulnerable because # option -W evaluates the shell expansions included in the option argument. # Options: # -F sep Specify the separators. The default is $' \t\n' # -l The same as -F $'\n' # -X arg The same as the compgen option -X. # -S arg The same as the compgen option -S. # -P arg The same as the compgen option -P. # -o arg The same as the compgen option -o. # @param $1 String to split # @return 0 if at least one completion is generated, or 1 otherwise. # @since 2.12 _comp_compgen_split() { local _ifs=$' \t\n' local -a _compgen_options=() local OPTIND=1 OPTARG="" OPTERR=0 _opt while getopts ':lF:X:S:P:o:' _opt "$@"; do case $_opt in l) _ifs=$'\n' ;; F) _ifs=$OPTARG ;; [XSPo]) _compgen_options+=("-$_opt" "$OPTARG") ;; *) printf 'bash_completion: usage: %s [-l|-F sep] [--] str\n' "$FUNCNAME" >&2 return 2 ;; esac done shift "$((OPTIND - 1))" if (($# != 1)); then printf 'bash_completion: %s: unexpected number of arguments.\n' "$FUNCNAME" >&2 printf 'usage: %s [-l|-F sep] [--] str' "$FUNCNAME" >&2 return 2 fi local input=$1 IFS=$' \t\n' _comp_compgen -F "$_ifs" -U input -- ${_compgen_options[@]+"${_compgen_options[@]}"} -W '$input' } # Check if the argument looks like a path. # @param $1 thing to check # @return True (0) if it does, False (> 0) otherwise # @since 2.12 _comp_looks_like_path() { [[ ${1-} == @(*/|[.~])* ]] } # Reassemble command line words, excluding specified characters from the # list of word completion separators (COMP_WORDBREAKS). # @param $1 chars Characters out of $COMP_WORDBREAKS which should # NOT be considered word breaks. This is useful for things like scp where # we want to return host:path and not only path, so we would pass the # colon (:) as $1 here. # @param $2 words Name of variable to return words to # @param $3 cword Name of variable to return cword to # _comp__reassemble_words() { local exclude="" i j line ref # Exclude word separator characters? if [[ $1 ]]; then # Yes, exclude word separator characters; # Exclude only those characters, which were really included exclude="[${1//[^$COMP_WORDBREAKS]/}]" fi # Default to cword unchanged printf -v "$3" %s "$COMP_CWORD" # Are characters excluded which were former included? if [[ $exclude ]]; then # Yes, list of word completion separators has shrunk; line=$COMP_LINE # Re-assemble words to complete for ((i = 0, j = 0; i < ${#COMP_WORDS[@]}; i++, j++)); do # Is current word not word 0 (the command itself) and is word not # empty and is word made up of just word separator characters to # be excluded and is current word not preceded by whitespace in # original line? while [[ $i -gt 0 && ${COMP_WORDS[i]} == +($exclude) ]]; do # Is word separator not preceded by whitespace in original line # and are we not going to append to word 0 (the command # itself), then append to current word. [[ $line != [[:blank:]]* ]] && ((j >= 2)) && ((j--)) # Append word separator to current or new word ref="$2[$j]" printf -v "$ref" %s "${!ref-}${COMP_WORDS[i]}" # Indicate new cword ((i == COMP_CWORD)) && printf -v "$3" %s "$j" # Remove optional whitespace + word separator from line copy line=${line#*"${COMP_WORDS[i]}"} # Indicate next word if available, else end *both* while and # for loop if ((i < ${#COMP_WORDS[@]} - 1)); then ((i++)) else break 2 fi # Start new word if word separator in original line is # followed by whitespace. [[ $line == [[:blank:]]* ]] && ((j++)) done # Append word to current word ref="$2[$j]" printf -v "$ref" %s "${!ref-}${COMP_WORDS[i]}" # Remove optional whitespace + word from line copy line=${line#*"${COMP_WORDS[i]}"} # Indicate new cword ((i == COMP_CWORD)) && printf -v "$3" %s "$j" done ((i == COMP_CWORD)) && printf -v "$3" %s "$j" else # No, list of word completions separators hasn't changed; for i in "${!COMP_WORDS[@]}"; do printf -v "$2[i]" %s "${COMP_WORDS[i]}" done fi } # @param $1 exclude Characters out of $COMP_WORDBREAKS which should NOT be # considered word breaks. This is useful for things like scp where # we want to return host:path and not only path, so we would pass the # colon (:) as $1 in this case. # @param $2 words Name of variable to return words to # @param $3 cword Name of variable to return cword to # @param $4 cur Name of variable to return current word to complete to # @see _comp__reassemble_words() _comp__get_cword_at_cursor() { local cword words=() _comp__reassemble_words "$1" words cword local i cur="" index=$COMP_POINT lead=${COMP_LINE:0:COMP_POINT} # Cursor not at position 0 and not led by just space(s)? if [[ $index -gt 0 && ($lead && ${lead//[[:space:]]/}) ]]; then cur=$COMP_LINE for ((i = 0; i <= cword; ++i)); do # Current word fits in $cur, and $cur doesn't match cword? while [[ ${#cur} -ge ${#words[i]} && ${cur:0:${#words[i]}} != "${words[i]-}" ]]; do # Strip first character cur=${cur:1} # Decrease cursor position, staying >= 0 ((index > 0)) && ((index--)) done # Does found word match cword? if ((i < cword)); then # No, cword lies further; local old_size=${#cur} cur=${cur#"${words[i]}"} local new_size=${#cur} ((index -= old_size - new_size)) fi done # Clear $cur if just space(s) [[ $cur && ! ${cur//[[:space:]]/} ]] && cur= # Zero $index if negative ((index < 0)) && index=0 fi local IFS=$' \t\n' local "$2" "$3" "$4" && _comp_upvars -a"${#words[@]}" "$2" ${words[@]+"${words[@]}"} \ -v "$3" "$cword" -v "$4" "${cur:0:index}" } # Get the word to complete and optional previous words. # This is nicer than ${COMP_WORDS[COMP_CWORD]}, since it handles cases # where the user is completing in the middle of a word. # (For example, if the line is "ls foobar", # and the cursor is here --------> ^ # Also one is able to cross over possible wordbreak characters. # Usage: _comp_get_words [OPTIONS] [VARNAMES] # Available VARNAMES: # cur Return cur via $cur # prev Return prev via $prev # words Return words via $words # cword Return cword via $cword # # Available OPTIONS: # -n EXCLUDE Characters out of $COMP_WORDBREAKS which should NOT be # considered word breaks. This is useful for things like scp # where we want to return host:path and not only path, so we # would pass the colon (:) as -n option in this case. # -c VARNAME Return cur via $VARNAME # -p VARNAME Return prev via $VARNAME # -w VARNAME Return words via $VARNAME # -i VARNAME Return cword via $VARNAME # # Example usage: # # $ _comp_get_words -n : cur prev # # @since 2.12 _comp_get_words() { local exclude="" flag i OPTIND=1 local cur cword words=() local upargs=() upvars=() vcur="" vcword="" vprev="" vwords="" while getopts "c:i:n:p:w:" flag "$@"; do case $flag in [cipw]) if [[ $OPTARG != [a-zA-Z_]*([a-zA-Z_0-9])?(\[*\]) ]]; then echo "bash_completion: $FUNCNAME: -$flag: invalid variable name \`$OPTARG'" >&2 return 1 fi ;;& c) vcur=$OPTARG ;; i) vcword=$OPTARG ;; n) exclude=$OPTARG ;; p) vprev=$OPTARG ;; w) vwords=$OPTARG ;; *) echo "bash_completion: $FUNCNAME: usage error" >&2 return 1 ;; esac done while [[ $# -ge $OPTIND ]]; do case ${!OPTIND} in cur) vcur=cur ;; prev) vprev=prev ;; cword) vcword=cword ;; words) vwords=words ;; *) echo "bash_completion: $FUNCNAME: \`${!OPTIND}':" \ "unknown argument" >&2 return 1 ;; esac ((OPTIND += 1)) done _comp__get_cword_at_cursor "${exclude-}" words cword cur [[ $vcur ]] && { upvars+=("$vcur") upargs+=(-v "$vcur" "$cur") } [[ $vcword ]] && { upvars+=("$vcword") upargs+=(-v "$vcword" "$cword") } [[ $vprev ]] && { local value="" ((cword >= 1)) && value=${words[cword - 1]} upvars+=("$vprev") upargs+=(-v "$vprev" "$value") } [[ $vwords ]] && { # Note: bash < 4.4 has a bug that all the elements are connected with # ${v+"$@"} when IFS does not contain whitespace. local IFS=$' \t\n' upvars+=("$vwords") upargs+=(-a"${#words[@]}" "$vwords" ${words+"${words[@]}"}) } ((${#upvars[@]})) && local "${upvars[@]}" && _comp_upvars "${upargs[@]}" } # Generate the specified items after left-trimming with the word-to-complete # containing a colon (:). If the word-to-complete does not contain a colon, # this generates the specified items without modifications. # @param $@ items to generate # @var[in] cur current word to complete # # @remarks In Bash, with a colon in COMP_WORDBREAKS, words containing colons # are always completed as entire words if the word to complete contains a # colon. This function fixes this behavior by removing the # colon-containing-prefix from the items. # # The preferred solution is to remove the colon (:) from COMP_WORDBREAKS in # your .bashrc: # # # Remove colon (:) from list of word completion separators # COMP_WORDBREAKS=${COMP_WORDBREAKS//:} # # See also: Bash FAQ - E13) Why does filename completion misbehave if a colon # appears in the filename? - https://tiswww.case.edu/php/chet/bash/FAQ # # @since 2.12 _comp_compgen_ltrim_colon() { (($#)) || return 0 local -a _tmp _tmp=("$@") if [[ $cur == *:* && $COMP_WORDBREAKS == *:* ]]; then # Remove colon-word prefix from items local _colon_word=${cur%"${cur##*:}"} _tmp=("${_tmp[@]#"$_colon_word"}") fi _comp_compgen_set "${_tmp[@]}" } # If the word-to-complete contains a colon (:), left-trim COMPREPLY items with # word-to-complete. # # @param $1 current word to complete (cur) # @var[in,out] COMPREPLY # # @since 2.12 _comp_ltrim_colon_completions() { ((${#COMPREPLY[@]})) || return 0 _comp_compgen -c "$1" ltrim_colon "${COMPREPLY[@]}" } # This function quotes the argument in a way so that readline dequoting # results in the original argument. This is necessary for at least # `compgen` which requires its arguments quoted/escaped: # # $ ls "a'b/" # c # $ compgen -f "a'b/" # Wrong, doesn't return output # $ compgen -f "a\'b/" # Good # a\'b/c # # See also: # - https://lists.gnu.org/archive/html/bug-bash/2009-03/msg00155.html # - https://www.mail-archive.com/bash-completion-devel@lists.alioth.debian.org/msg01944.html # @param $1 Argument to quote # @var[out] REPLY Quoted result is stored in this variable # @since 2.12 # shellcheck disable=SC2178 # The assignment is not intended for the global "REPLY" _comp_quote_compgen() { if [[ $1 == \'* ]]; then # Leave out first character REPLY=${1:1} else printf -v REPLY %q "$1" # If result becomes quoted like this: $'string', re-evaluate in order # to drop the additional quoting. See also: # https://www.mail-archive.com/bash-completion-devel@lists.alioth.debian.org/msg01942.html if [[ $REPLY == \$\'*\' ]]; then local value=${REPLY:2:-1} # Strip beginning $' and ending '. value=${value//'%'/%%} # Escape % for printf format. # shellcheck disable=SC2059 printf -v REPLY "$value" # Decode escape sequences of \.... fi fi } # This function performs file and directory completion. It's better than # simply using 'compgen -f', because it honours spaces in filenames. # @param $1 If `-d', complete only on directories. Otherwise filter/pick only # completions with `.$1' and the uppercase version of it as file # extension. # @return 0 if at least one completion is generated, or 1 otherwise. # # @since 2.12 _comp_compgen_filedir() { _comp_compgen_tilde && return local -a toks local _arg=${1-} if [[ $_arg == -d ]]; then _comp_compgen -v toks -- -d else local REPLY _comp_quote_compgen "${cur-}" local _quoted=$REPLY _comp_unlocal REPLY # work around bash-4.2 where compgen -f "''" produces nothing. [[ $_quoted == "''" ]] && _quoted="" # Munge xspec to contain uppercase version too # https://lists.gnu.org/archive/html/bug-bash/2010-09/msg00036.html # news://news.gmane.io/4C940E1C.1010304@case.edu local _xspec=${_arg:+"!*.@($_arg|${_arg^^})"} _plusdirs=() # Use plusdirs to get dir completions if we have a xspec; if we don't, # there's no need, dirs come along with other completions. Don't use # plusdirs quite yet if fallback is in use though, in order to not ruin # the fallback condition with the "plus" dirs. local _opts=(-f -X "$_xspec") [[ $_xspec ]] && _plusdirs=(-o plusdirs) [[ ${BASH_COMPLETION_FILEDIR_FALLBACK-} || ! ${_plusdirs-} ]] || _opts+=("${_plusdirs[@]}") _comp_compgen -v toks -c "$_quoted" -- "${_opts[@]}" # Try without filter if it failed to produce anything and configured to [[ ${BASH_COMPLETION_FILEDIR_FALLBACK-} && $_arg && ${#toks[@]} -lt 1 ]] && _comp_compgen -av toks -c "$_quoted" -- \ -f ${_plusdirs+"${_plusdirs[@]}"} fi if ((${#toks[@]} != 0)); then # Remove . and .. (as well as */. and */..) from suggestions, unless # .. or */.. was typed explicitly by the user (for users who use # tab-completion to append a slash after '..') if [[ $cur != ?(*/).. ]]; then _comp_compgen -Rv toks -- -X '?(*/)@(.|..)' -W '"${toks[@]}"' fi fi if ((${#toks[@]} != 0)); then # 2>/dev/null for direct invocation, e.g. in the _comp_compgen_filedir # unit test compopt -o filenames 2>/dev/null fi # Note: bash < 4.4 has a bug that all the elements are connected with # ${v+"${a[@]}"} when IFS does not contain whitespace. local IFS=$' \t\n' _comp_compgen -U toks set ${toks[@]+"${toks[@]}"} } # This function splits $cur=--foo=bar into $prev=--foo, $cur=bar, making it # easier to support both "--foo bar" and "--foo=bar" style completions. # `=' should have been removed from COMP_WORDBREAKS when setting $cur for # this to be useful. # Returns 0 if current option was split, 1 otherwise. # _comp__split_longopt() { if [[ $cur == --?*=* ]]; then # Cut also backslash before '=' in case it ended up there # for some reason. prev=${cur%%?(\\)=*} cur=${cur#*=} return 0 fi return 1 } # Complete variables. # @return True (0) if variables were completed, # False (> 0) if not. # @since 2.12 _comp_compgen_variables() { if [[ $cur =~ ^(\$(\{[!#]?)?)([A-Za-z0-9_]*)$ ]]; then # Completing $var / ${var / ${!var / ${#var if [[ $cur == '${'* ]]; then local arrs vars _comp_compgen -v vars -c "${BASH_REMATCH[3]}" -- -A variable -P "${BASH_REMATCH[1]}" -S '}' _comp_compgen -v arrs -c "${BASH_REMATCH[3]}" -- -A arrayvar -P "${BASH_REMATCH[1]}" -S '[' if ((${#vars[@]} == 1 && ${#arrs[@]} != 0)); then # Complete ${arr with ${array[ if there is only one match, and that match is an array variable compopt -o nospace _comp_compgen -U vars -U arrs -R -- -W '"${arrs[@]}"' else # Complete ${var with ${variable} _comp_compgen -U vars -U arrs -R -- -W '"${vars[@]}"' fi else # Complete $var with $variable _comp_compgen -ac "${BASH_REMATCH[3]}" -- -A variable -P '$' fi return 0 elif [[ $cur =~ ^(\$\{[#!]?)([A-Za-z0-9_]*)\[([^]]*)$ ]]; then # Complete ${array[i with ${array[idx]} local vars _comp_compgen -v vars -c "${BASH_REMATCH[3]}" -- -W '"${!'"${BASH_REMATCH[2]}"'[@]}"' \ -P "${BASH_REMATCH[1]}${BASH_REMATCH[2]}[" -S ']}' # Complete ${arr[@ and ${arr[* if [[ ${BASH_REMATCH[3]} == [@*] ]]; then vars+=("${BASH_REMATCH[1]}${BASH_REMATCH[2]}[${BASH_REMATCH[3]}]}") fi # array indexes may have colons if ((${#vars[@]})); then _comp_compgen -U vars -c "$cur" ltrim_colon "${vars[@]}" else _comp_compgen_set fi return 0 elif [[ $cur =~ ^\$\{[#!]?[A-Za-z0-9_]*\[.*\]$ ]]; then # Complete ${array[idx] with ${array[idx]} _comp_compgen -c "$cur" ltrim_colon "$cur}" return 0 fi return 1 } # Complete a delimited value. # # Usage: [-k] DELIMITER COMPGEN_ARG... # -k: do not filter out already present tokens in value # @since 2.12 _comp_delimited() { local prefix="" delimiter=$1 deduplicate=set shift if [[ $delimiter == -k ]]; then deduplicate="" delimiter=$1 shift fi [[ $cur == *"$delimiter"* ]] && prefix=${cur%"$delimiter"*}$delimiter if [[ $deduplicate ]]; then # We could construct a -X pattern to feed to compgen, but that'd # conflict with possibly already set -X in $@, as well as have # glob char escaping issues to deal with. Do removals by hand instead. _comp_compgen -R -- "$@" local -a existing _comp_split -F "$delimiter" existing "$cur" # Do not remove the last from existing if it's not followed by the # delimiter so we get space appended. [[ ! $cur || $cur == *"$delimiter" ]] || unset -v "existing[${#existing[@]}-1]" if ((${#COMPREPLY[@]})); then local x i for x in ${existing+"${existing[@]}"}; do for i in "${!COMPREPLY[@]}"; do if [[ $x == "${COMPREPLY[i]}" ]]; then unset -v 'COMPREPLY[i]' continue 2 # assume no dupes in COMPREPLY fi done done ((${#COMPREPLY[@]})) && _comp_compgen -c "${cur##*"$delimiter"}" -- -W '"${COMPREPLY[@]}"' fi else _comp_compgen -c "${cur##*"$delimiter"}" -- "$@" fi # It would seem that in some specific cases we could avoid adding the # prefix to all completions, thereby making the list of suggestions # cleaner, and only adding it when there's exactly one completion. # The cases where this opportunity has been observed involve having # `show-all-if-ambiguous` on, but even that has cases where it fails # and the last separator including everything before it is lost. # https://github.com/scop/bash-completion/pull/913#issuecomment-1490140309 local i for i in "${!COMPREPLY[@]}"; do COMPREPLY[i]="$prefix${COMPREPLY[i]}" done [[ $delimiter != : ]] || _comp_ltrim_colon_completions "$cur" } # Complete assignment of various known environment variables. # # The word to be completed is expected to contain the entire assignment, # including the variable name and the "=". Some known variables are completed # with colon separated values; for those to work, colon should not have been # used to split words. See related parameters to _comp_initialize. # # @param $1 variable assignment to be completed # @return True (0) if variable value completion was attempted, # False (> 0) if not. # @since 2.12 _comp_variable_assignments() { local cur=${1-} i if [[ $cur =~ ^([A-Za-z_][A-Za-z0-9_]*)=(.*)$ ]]; then prev=${BASH_REMATCH[1]} cur=${BASH_REMATCH[2]} else return 1 fi case $prev in TZ) cur=/usr/share/zoneinfo/$cur _comp_compgen_filedir if ((${#COMPREPLY[@]})); then for i in "${!COMPREPLY[@]}"; do if [[ ${COMPREPLY[i]} == *.tab ]]; then unset -v 'COMPREPLY[i]' continue elif [[ -d ${COMPREPLY[i]} ]]; then COMPREPLY[i]+=/ compopt -o nospace fi COMPREPLY[i]=${COMPREPLY[i]#/usr/share/zoneinfo/} done fi ;; TERM) _comp_compgen_terms ;; LANG | LC_*) _comp_compgen_split -- "$(locale -a 2>/dev/null)" ;; LANGUAGE) _comp_delimited : -W '$(locale -a 2>/dev/null)' ;; *) _comp_compgen_variables && return 0 _comp_compgen -a filedir ;; esac return 0 } # Initialize completion and deal with various general things: do file # and variable completion where appropriate, and adjust prev, words, # and cword as if no redirections exist so that completions do not # need to deal with them. Before calling this function, make sure # cur, prev, words, and cword are local, ditto split if you use -s. # # Options: # -n EXCLUDE Passed to _comp_get_words -n with redirection chars # -e XSPEC Passed to _comp_compgen_filedir as first arg for stderr # redirections # -o XSPEC Passed to _comp_compgen_filedir as first arg for other output # redirections # -i XSPEC Passed to _comp_compgen_filedir as first arg for stdin # redirections # -s Split long options with _comp__split_longopt, implies -n = # @param $1...$3 args Original arguments specified to the completion function. # The first argument $1 is command name. The second # argument $2 is the string before the cursor in the # current word. The third argument $3 is the previous # word. # @var[out] cur Reconstructed current word # @var[out] prev Reconstructed previous word # @var[out] words Reconstructed words # @var[out] cword Current word index in `words` # @var[out] comp_args Original arguments specified to the completion # function are saved in this array, if the arguments # $1...$3 is specified. # @var[out,opt] was_split When "-s" is specified, `"set"/""` is set depending # on whether the split happened. # @return True (0) if completion needs further processing, # False (> 0) no further processing is necessary. # # @since 2.12 _comp_initialize() { local exclude="" opt_split="" outx="" errx="" inx="" local flag OPTIND=1 OPTARG="" OPTERR=0 while getopts "n:e:o:i:s" flag "$@"; do case $flag in n) exclude+=$OPTARG ;; e) errx=$OPTARG ;; o) outx=$OPTARG ;; i) inx=$OPTARG ;; s) opt_split="set" was_split="" exclude+="=" ;; *) echo "bash_completion: $FUNCNAME: usage error" >&2 return 1 ;; esac done shift "$((OPTIND - 1))" (($#)) && comp_args=("$@") COMPREPLY=() local redir='@(?(+([0-9])|{[a-zA-Z_]*([a-zA-Z_0-9])})@(>?([>|&])|&])|<?(>))' _comp_get_words -n "$exclude<>&" cur prev words cword # Complete variable names. _comp_compgen_variables && return 1 # Complete on files if current is a redirect possibly followed by a # filename, e.g. ">foo", or previous is a "bare" redirect, e.g. ">". # shellcheck disable=SC2053 if [[ $cur == $redir* || ${prev-} == $redir ]]; then local xspec case $cur in 2'>'*) xspec=${errx-} ;; *'>'*) xspec=${outx-} ;; *'<'*) xspec=${inx-} ;; *) case $prev in 2'>'*) xspec=${errx-} ;; *'>'*) xspec=${outx-} ;; *'<'*) xspec=${inx-} ;; esac ;; esac # shellcheck disable=SC2295 # redir is a pattern cur=${cur##$redir} _comp_compgen_filedir "$xspec" return 1 fi # Remove all redirections so completions don't have to deal with them. local i skip for ((i = 1; i < ${#words[@]}; )); do if [[ ${words[i]} == $redir* ]]; then # If "bare" redirect, remove also the next word (skip=2). # shellcheck disable=SC2053 [[ ${words[i]} == $redir ]] && skip=2 || skip=1 words=("${words[@]:0:i}" "${words[@]:i+skip}") ((i <= cword)) && ((cword -= skip)) else ((i++)) fi done ((cword <= 0)) && return 1 prev=${words[cword - 1]} [[ $opt_split ]] && _comp__split_longopt && was_split="set" return 0 } # Helper function for _comp_compgen_help and _comp_compgen_usage. # Obtain the help output based on the arguments. # @param $@ args Arguments specified to the caller. # @var[out] _lines # @return 2 if the usage is wrong, 1 if no output is obtained, or otherwise 0. _comp_compgen_help__get_help_lines() { local -a help_cmd case ${1-} in -) if (($# > 1)); then printf 'bash_completion: %s -: extra arguments for -\n' "${FUNCNAME[1]}" >&2 printf 'usage: %s -\n' "${FUNCNAME[1]}" >&2 printf 'usage: %s -c cmd args...\n' "${FUNCNAME[1]}" >&2 printf 'usage: %s [-- args...]\n' "${FUNCNAME[1]}" >&2 return 2 fi help_cmd=(exec cat) ;; -c) if (($# < 2)); then printf 'bash_completion: %s -c: no command is specified\n' "${FUNCNAME[1]}" >&2 printf 'usage: %s -\n' "${FUNCNAME[1]}" >&2 printf 'usage: %s -c cmd args...\n' "${FUNCNAME[1]}" >&2 printf 'usage: %s [-- args...]\n' "${FUNCNAME[1]}" >&2 return 2 fi help_cmd=("${@:2}") ;; --) shift 1 ;& *) local REPLY _comp_dequote "${comp_args[0]-}" || REPLY=${comp_args[0]-} help_cmd=("${REPLY:-false}" "$@") ;; esac local REPLY _comp_split -l REPLY "$(LC_ALL=C "${help_cmd[@]}" 2>&1)" && _lines=("${REPLY[@]}") } # Helper function for _comp_compgen_help and _comp_compgen_usage. # @var[in,out] options Add options # @return True (0) if an option was found, False (> 0) otherwise _comp_compgen_help__parse() { local option option2 i # Take first found long option, or first one (short) if not found. option= local -a array if _comp_split -F $' \t\n,/|' array "$1"; then for i in "${array[@]}"; do case "$i" in ---*) break ;; --?*) option=$i break ;; -?*) [[ $option ]] || option=$i ;; *) break ;; esac done fi [[ $option ]] || return 1 # Expand --[no]foo to --foo and --nofoo etc if [[ $option =~ (\[((no|dont)-?)\]). ]]; then option2=${option/"${BASH_REMATCH[1]}"/} option2=${option2%%[<{().[]*} options+=("${option2/=*/=}") option=${option/"${BASH_REMATCH[1]}"/"${BASH_REMATCH[2]}"} fi [[ $option =~ ^([^=<{().[]|\.[A-Za-z0-9])+=? ]] && options+=("$BASH_REMATCH") } # Parse GNU style help output of the given command and generate and store # completions in an array. The help output is produced in the way depending on # the usage: # usage: _comp_compgen_help - # read from stdin # usage: _comp_compgen_help -c cmd args... # run "cmd args..." # usage: _comp_compgen_help [[--] args...] # run "${comp_args[0]} args..." # When no arguments are specified, `--help` is assumed. # # @var[in] comp_args[0] # @since 2.12 _comp_compgen_help() { (($#)) || set -- -- --help local -a _lines _comp_compgen_help__get_help_lines "$@" || return "$?" local -a options=() local _line for _line in "${_lines[@]}"; do [[ $_line == *([[:blank:]])-* ]] || continue # transform "-f FOO, --foo=FOO" to "-f , --foo=FOO" etc while [[ $_line =~ ((^|[^-])-[A-Za-z0-9?][[:space:]]+)\[?[A-Z0-9]+([,_-]+[A-Z0-9]+)?(\.\.+)?\]? ]]; do _line=${_line/"${BASH_REMATCH[0]}"/"${BASH_REMATCH[1]}"} done _comp_compgen_help__parse "${_line// or /, }" done ((${#options[@]})) || return 1 _comp_compgen -U options -- -W '"${options[@]}"' return 0 } # Parse BSD style usage output (options in brackets) of the given command. The # help output is produced in the way depending on the usage: # usage: _comp_compgen_usage - # read from stdin # usage: _comp_compgen_usage -c cmd args... # run "cmd args..." # usage: _comp_compgen_usage [[--] args...] # run "${comp_args[0]} args..." # When no arguments are specified, `--usage` is assumed. # # @var[in] comp_args[0] # @since 2.12 _comp_compgen_usage() { (($#)) || set -- -- --usage local -a _lines _comp_compgen_help__get_help_lines "$@" || return "$?" local -a options=() local _line _match _option _i _char for _line in "${_lines[@]}"; do while [[ $_line =~ \[[[:space:]]*(-[^]]+)[[:space:]]*\] ]]; do _match=${BASH_REMATCH[0]} _option=${BASH_REMATCH[1]} case $_option in -?(\[)+([a-zA-Z0-9?])) # Treat as bundled short options for ((_i = 1; _i < ${#_option}; _i++)); do _char=${_option:_i:1} [[ $_char != '[' ]] && options+=("-$_char") done ;; *) _comp_compgen_help__parse "$_option" ;; esac _line=${_line#*"$_match"} done done ((${#options[@]})) || return 1 _comp_compgen -U options -- -W '"${options[@]}"' return 0 } # This function completes on signal names (minus the SIG prefix) # @param $1 prefix # # @since 2.12 _comp_compgen_signals() { local -a sigs _comp_compgen -v sigs -c "SIG${cur#"${1-}"}" -- -A signal && _comp_compgen -RU sigs -- -P "${1-}" -W '"${sigs[@]#SIG}"' } # This function completes on known mac addresses # # @since 2.12 _comp_compgen_mac_addresses() { local _re='\([A-Fa-f0-9]\{2\}:\)\{5\}[A-Fa-f0-9]\{2\}' local PATH="$PATH:/sbin:/usr/sbin" local -a addresses # Local interfaces # - ifconfig on Linux: HWaddr or ether # - ifconfig on FreeBSD: ether # - ip link: link/ether _comp_compgen -v addresses split -- "$( { LC_ALL=C ifconfig -a || ip -c=never link show || ip link show } 2>/dev/null | command sed -ne \ "s/.*[[:space:]]HWaddr[[:space:]]\{1,\}\($_re\)[[:space:]].*/\1/p" -ne \ "s/.*[[:space:]]HWaddr[[:space:]]\{1,\}\($_re\)[[:space:]]*$/\1/p" -ne \ "s|.*[[:space:]]\(link/\)\{0,1\}ether[[:space:]]\{1,\}\($_re\)[[:space:]].*|\2|p" -ne \ "s|.*[[:space:]]\(link/\)\{0,1\}ether[[:space:]]\{1,\}\($_re\)[[:space:]]*$|\2|p" )" # ARP cache _comp_compgen -av addresses split -- "$( { arp -an || ip -c=never neigh show || ip neigh show } 2>/dev/null | command sed -ne \ "s/.*[[:space:]]\($_re\)[[:space:]].*/\1/p" -ne \ "s/.*[[:space:]]\($_re\)[[:space:]]*$/\1/p" )" # /etc/ethers _comp_compgen -av addresses split -- "$(command sed -ne \ "s/^[[:space:]]*\($_re\)[[:space:]].*/\1/p" /etc/ethers 2>/dev/null)" _comp_compgen -U addresses ltrim_colon "${addresses[@]}" } # This function completes on configured network interfaces # # @since 2.12 _comp_compgen_configured_interfaces() { local -a files if [[ -f /etc/debian_version ]]; then # Debian system _comp_expand_glob files '/etc/network/interfaces /etc/network/interfaces.d/*' || return 0 _comp_compgen -U files split -- "$(command sed -ne \ 's|^iface \([^ ]\{1,\}\).*$|\1|p' "${files[@]}" 2>/dev/null)" elif [[ -f /etc/SuSE-release ]]; then # SuSE system _comp_expand_glob files '/etc/sysconfig/network/ifcfg-*' || return 0 _comp_compgen -U files split -- "$(printf '%s\n' "${files[@]}" | command sed -ne 's|.*ifcfg-\([^*].*\)$|\1|p')" elif [[ -f /etc/pld-release ]]; then # PLD Linux _comp_compgen -U files split -- "$(command ls -B /etc/sysconfig/interfaces | command sed -ne 's|.*ifcfg-\([^*].*\)$|\1|p')" else # Assume Red Hat _comp_expand_glob files '/etc/sysconfig/network-scripts/ifcfg-*' || return 0 _comp_compgen -U files split -- "$(printf '%s\n' "${files[@]}" | command sed -ne 's|.*ifcfg-\([^*].*\)$|\1|p')" fi } # Local IP addresses. # If producing IPv6 completions, `_comp_initialize` with `-n :`. # # -4: IPv4 addresses only (default) # -6: IPv6 addresses only # -a: All addresses # # @since 2.12 _comp_compgen_ip_addresses() { local _n case ${1-} in -a) _n='6\{0,1\}' ;; -6) _n='6' ;; *) _n= ;; esac local PATH=$PATH:/sbin local addrs _comp_compgen -v addrs split -- "$({ LC_ALL=C ifconfig -a || ip -c=never addr show || ip addr show } 2>/dev/null | command sed -e 's/[[:space:]]addr:/ /' -ne \ "s|.*inet${_n}[[:space:]]\{1,\}\([^[:space:]/]*\).*|\1|p")" || return if [[ ! $_n ]]; then _comp_compgen -U addrs set "${addrs[@]}" else _comp_compgen -U addrs ltrim_colon "${addrs[@]}" fi } # This function completes on available kernel versions # # @since 2.12 _comp_compgen_kernel_versions() { _comp_compgen_split -- "$(command ls /lib/modules)" } # This function completes on all available network interfaces # -a: restrict to active interfaces only # -w: restrict to wireless interfaces only # # @since 2.12 _comp_compgen_available_interfaces() { local PATH=$PATH:/sbin local generated _comp_compgen -v generated split -- "$({ if [[ ${1-} == -w ]]; then iwconfig elif [[ ${1-} == -a ]]; then ifconfig || ip -c=never link show up || ip link show up else ifconfig -a || ip -c=never link show || ip link show fi } 2>/dev/null | _comp_awk \ '/^[^ \t]/ { if ($1 ~ /^[0-9]+:/) { print $2 } else { print $1 } }')" && _comp_compgen -U generated set "${generated[@]%:}" } # Echo number of CPUs, falling back to 1 on failure. # @var[out] REPLY # @return 0 if it successfully obtained the number of CPUs, or otherwise 1 # @since 2.12 _comp_get_ncpus() { local var=NPROCESSORS_ONLN [[ $OSTYPE == *@(linux|msys|cygwin)* ]] && var=_$var if REPLY=$(getconf $var 2>/dev/null) && ((REPLY >= 1)); then return 0 else REPLY=1 return 1 fi } # Perform tilde (~) completion # @return False (1) if completion needs further processing, # True (0) if tilde is followed by a valid username, completions are # put in COMPREPLY and no further processing is necessary. # @since 2.12 _comp_compgen_tilde() { if [[ ${cur-} == \~* && $cur != */* ]]; then # Try generate ~username completions if _comp_compgen -c "${cur#\~}" -- -P '~' -u; then # 2>/dev/null for direct invocation, e.g. in the # _comp_compgen_tilde unit test compopt -o filenames 2>/dev/null return 0 fi fi return 1 } # Expand string starting with tilde (~) # We want to expand ~foo/... to /home/foo/... to avoid problems when # word-to-complete starting with a tilde is fed to commands and ending up # quoted instead of expanded. # Only the first portion of the variable from the tilde up to the first slash # (~../) is expanded. The remainder of the variable, containing for example # a dollar sign variable ($) or asterisk (*) is not expanded. # Example usage: # # $ _comp_expand_tilde "~"; echo "$REPLY" # # Example output: # # $1 REPLY # -------- ---------------- # ~ /home/user # ~foo/bar /home/foo/bar # ~foo/$HOME /home/foo/$HOME # ~foo/a b /home/foo/a b # ~foo/* /home/foo/* # # @param $1 Value to expand # @var[out] REPLY Expanded result # @since 2.12 _comp_expand_tilde() { REPLY=$1 if [[ $1 == \~* ]]; then printf -v REPLY '~%q' "${1#\~}" eval "REPLY=$REPLY" fi } # This function expands tildes in pathnames # # @since 2.12 _comp_expand() { # Expand ~username type directory specifications. We want to expand # ~foo/... to /home/foo/... to avoid problems when $cur starting with # a tilde is fed to commands and ending up quoted instead of expanded. case ${cur-} in ~*/*) local REPLY _comp_expand_tilde "$cur" cur=$REPLY ;; ~*) _comp_compgen -v COMPREPLY tilde && eval "COMPREPLY[0]=$(printf ~%q "${COMPREPLY[0]#\~}")" && return 1 ;; esac return 0 } # Process ID related functions. # for AIX and Solaris we use X/Open syntax, BSD for others. # # @since 2.12 if [[ $OSTYPE == *@(solaris|aix)* ]]; then # This function completes on process IDs. _comp_compgen_pids() { _comp_compgen_split -- "$(command ps -efo pid | command sed 1d)" } _comp_compgen_pgids() { _comp_compgen_split -- "$(command ps -efo pgid | command sed 1d)" } _comp_compgen_pnames() { _comp_compgen_split -X '' -- "$(command ps -efo comm | command sed -e 1d -e 's:.*/::' -e 's/^-//' | sort -u)" } else _comp_compgen_pids() { _comp_compgen_split -- "$(command ps ax -o pid=)" } _comp_compgen_pgids() { _comp_compgen_split -- "$(command ps ax -o pgid=)" } # @param $1 if -s, don't try to avoid truncated command names _comp_compgen_pnames() { local -a procs=() if [[ ${1-} == -s ]]; then _comp_split procs "$(command ps ax -o comm | command sed -e 1d)" else # Some versions of ps don't support "command", but do "comm", e.g. # some busybox ones. Fall back local -a psout _comp_split -l psout "$({ command ps ax -o command= || command ps ax -o comm= } 2>/dev/null)" local line i=-1 for line in "${psout[@]}"; do if ((i == -1)); then # First line, see if it has COMMAND column header. For # example some busybox ps versions do that, i.e. don't # respect command= if [[ $line =~ ^(.*[[:space:]])COMMAND([[:space:]]|$) ]]; then # It does; store its index. i=${#BASH_REMATCH[1]} else # Nope, fall through to "regular axo command=" parsing. break fi else # line=${line:i} # take command starting from found index line=${line%% *} # trim arguments [[ $line ]] && procs+=("$line") fi done if ((i == -1)); then # Regular command= parsing for line in "${psout[@]}"; do if [[ $line =~ ^[[(](.+)[])]$ ]]; then procs+=("${BASH_REMATCH[1]}") else line=${line%% *} # trim arguments line=${line##@(*/|-)} # trim leading path and - [[ $line ]] && procs+=("$line") fi done fi fi ((${#procs[@]})) && _comp_compgen -U procs -- -X "" -W '"${procs[@]}"' } fi # This function completes on user IDs # # @since 2.12 _comp_compgen_uids() { if type getent &>/dev/null; then _comp_compgen_split -- "$(getent passwd | cut -d: -f3)" elif type perl &>/dev/null; then _comp_compgen_split -- "$(perl -e 'while (($uid) = (getpwent)[2]) { print $uid . "\n" }')" else # make do with /etc/passwd _comp_compgen_split -- "$(cut -d: -f3 /etc/passwd)" fi } # This function completes on group IDs # # @since 2.12 _comp_compgen_gids() { if type getent &>/dev/null; then _comp_compgen_split -- "$(getent group | cut -d: -f3)" elif type perl &>/dev/null; then _comp_compgen_split -- "$(perl -e 'while (($gid) = (getgrent)[2]) { print $gid . "\n" }')" else # make do with /etc/group _comp_compgen_split -- "$(cut -d: -f3 /etc/group)" fi } # Glob for matching various backup files. # _comp_backup_glob='@(#*#|*@(~|.@(bak|orig|rej|swp|@(dpkg|ucf)-*|rpm@(orig|new|save))))' # Complete on xinetd services # # @since 2.12 _comp_compgen_xinetd_services() { local xinetddir=${_comp__test_xinetd_dir:-/etc/xinetd.d} if [[ -d $xinetddir ]]; then local -a svcs if _comp_expand_glob svcs '$xinetddir/!($_comp_backup_glob)'; then _comp_compgen -U svcs -U xinetddir -- -W '"${svcs[@]#$xinetddir/}"' fi fi } # This function completes on services # # @since 2.12 _comp_compgen_services() { local sysvdirs _comp_sysvdirs || return 1 local services _comp_expand_glob services '${sysvdirs[0]}/!($_comp_backup_glob|functions|README)' local _generated=$({ systemctl list-units --full --all || systemctl list-unit-files } 2>/dev/null | _comp_awk '$1 ~ /\.service$/ { sub("\\.service$", "", $1); print $1 }') _comp_split -la services "$_generated" if [[ -x /sbin/upstart-udev-bridge ]]; then _comp_split -la services "$(initctl list 2>/dev/null | cut -d' ' -f1)" fi ((${#services[@]})) || return 1 _comp_compgen -U services -U sysvdirs -- -W '"${services[@]#${sysvdirs[0]}/}"' } # This completes on a list of all available service scripts for the # 'service' command and/or the SysV init.d directory, followed by # that script's available commands # This function is in the main bash_completion file rather than in a separate # one, because we set it up eagerly as completer for scripts in sysv init dirs # below. # # @since 2.12 _comp_complete_service() { local cur prev words cword comp_args _comp_initialize -- "$@" || return # don't complete past 2nd token ((cword > 2)) && return if [[ $cword -eq 1 && $prev == ?(*/)service ]]; then _comp_compgen_services [[ -e /etc/mandrake-release ]] && _comp_compgen_xinetd_services else local sysvdirs _comp_sysvdirs || return 1 _comp_compgen_split -l -- "$(command sed -e 'y/|/ /' \ -ne 's/^.*\(U\|msg_u\)sage.*{\(.*\)}.*$/\2/p' \ "${sysvdirs[0]}/${prev##*/}" 2>/dev/null) start stop" fi } && complete -F _comp_complete_service service _comp__init_set_up_service_completions() { local sysvdirs svc svcdir svcs _comp_sysvdirs && for svcdir in "${sysvdirs[@]}"; do if _comp_expand_glob svcs '"$svcdir"/!($_comp_backup_glob)'; then for svc in "${svcs[@]}"; do [[ -x $svc ]] && complete -F _comp_complete_service "$svc" done fi done unset -f "$FUNCNAME" } _comp__init_set_up_service_completions # This function completes on kernel modules # @param $1 kernel version # # @since 2.12 _comp_compgen_kernel_modules() { local _modpath=/lib/modules/$1 _comp_compgen_split -- "$(command ls -RL "$_modpath" 2>/dev/null | command sed -ne 's/^\(.*\)\.k\{0,1\}o\(\.[gx]z\)\{0,1\}$/\1/p' \ -e 's/^\(.*\)\.ko\.zst$/\1/p')" } # This function completes on inserted kernel modules # @param $1 prefix to filter with, default $cur # # @since 2.12 _comp_compgen_inserted_kernel_modules() { _comp_compgen -c "${1:-$cur}" split -- "$(PATH="$PATH:/sbin" lsmod | _comp_awk '{if (NR != 1) print $1}')" } # This function completes on user or user:group format; as for chown and cpio. # # The : must be added manually; it will only complete usernames initially. # The legacy user.group format is not supported. # # @param $1 If -u, only return users/groups the user has access to in # context of current completion. # # @since 2.12 _comp_compgen_usergroups() { if [[ $cur == *\\\\* || $cur == *:*:* ]]; then # Give up early on if something seems horribly wrong. return elif [[ $cur == *\\:* ]]; then # Completing group after 'user\:gr'. # Reply with a list of groups prefixed with 'user:', readline will # escape to the colon. local tmp if [[ ${1-} == -u ]]; then _comp_compgen -v tmp -c "${cur#*:}" allowed_groups else _comp_compgen -v tmp -c "${cur#*:}" -- -g fi if ((${#tmp[@]})); then local _prefix=${cur%%*([^:])} _prefix=${_prefix//\\/} _comp_compgen -Rv tmp -- -P "$_prefix" -W '"${tmp[@]}"' _comp_compgen -U tmp set "${tmp[@]}" fi elif [[ $cur == *:* ]]; then # Completing group after 'user:gr'. # Reply with a list of unprefixed groups since readline with split on : # and only replace the 'gr' part if [[ ${1-} == -u ]]; then _comp_compgen -c "${cur#*:}" allowed_groups else _comp_compgen -c "${cur#*:}" -- -g fi else # Completing a partial 'usernam'. # # Don't suffix with a : because readline will escape it and add a # slash. It's better to complete into 'chown username ' than 'chown # username\:'. if [[ ${1-} == -u ]]; then _comp_compgen_allowed_users else _comp_compgen -- -u fi fi } # @since 2.12 _comp_compgen_allowed_users() { if _comp_as_root; then _comp_compgen -- -u else _comp_compgen_split -- "$(id -un 2>/dev/null || whoami 2>/dev/null)" fi } # @since 2.12 _comp_compgen_allowed_groups() { if _comp_as_root; then _comp_compgen -- -g else _comp_compgen_split -- "$(id -Gn 2>/dev/null || groups 2>/dev/null)" fi } # @since 2.12 _comp_compgen_selinux_users() { _comp_compgen_split -- "$(semanage user -nl 2>/dev/null | _comp_awk '{ print $1 }')" } # This function completes on valid shells # @param $1 chroot to search from # # @since 2.12 _comp_compgen_shells() { local -a shells=() local _shell _rest while read -r _shell _rest; do [[ $_shell == /* ]] && shells+=("$_shell") done 2>/dev/null <"${1-}"/etc/shells _comp_compgen -U shells -- -W '"${shells[@]}"' } # This function completes on valid filesystem types # # @since 2.12 _comp_compgen_fstypes() { local _fss if [[ -e /proc/filesystems ]]; then # Linux _fss="$(cut -d$'\t' -f2 /proc/filesystems) $(_comp_awk '! /\*/ { print $NF }' /etc/filesystems 2>/dev/null)" else # Generic _fss="$(_comp_awk '/^[ \t]*[^#]/ { print $3 }' /etc/fstab 2>/dev/null) $(_comp_awk '/^[ \t]*[^#]/ { print $3 }' /etc/mnttab 2>/dev/null) $(_comp_awk '/^[ \t]*[^#]/ { print $4 }' /etc/vfstab 2>/dev/null) $(_comp_awk '{ print $1 }' /etc/dfs/fstypes 2>/dev/null) $(lsvfs 2>/dev/null | _comp_awk '$1 !~ /^(Filesystem|[^a-zA-Z])/ { print $1 }') $([[ -d /etc/fs ]] && command ls /etc/fs)" fi [[ $_fss ]] && _comp_compgen_split -- "$_fss" } # Get absolute path to a file, with rudimentary canonicalization. # No symlink resolution or existence checks are done; # see `_comp_realcommand` for those. # @param $1 The file # @var[out] REPLY The path # @since 2.12 _comp_abspath() { REPLY=$1 [[ $REPLY == /* ]] || REPLY=$PWD/$REPLY REPLY=${REPLY//+(\/)/\/} while true; do # Process "." and "..". To avoid reducing "/../../ => /", we convert # "/*/../" one by one. "/.." at the beginning is ignored. Then, /*/../ # in the middle is processed. Finally, /*/.. at the end is removed. case $REPLY in */./*) REPLY=${REPLY//\/.\//\/} ;; */.) REPLY=${REPLY%/.} ;; /..?(/*)) REPLY=${REPLY#/..} ;; */+([^/])/../*) REPLY=${REPLY/\/+([^\/])\/..\//\/} ;; */+([^/])/..) REPLY=${REPLY%/+([^/])/..} ;; *) break ;; esac done [[ $REPLY ]] || REPLY=/ } # Get real command. # Command is the filename of command in PATH with possible symlinks resolved # (if resolve tooling available), empty string if command not found. # @param $1 Command # @var[out] REPLY Resulting string # @return True (0) if command found, False (> 0) if not. # @since 2.12 _comp_realcommand() { REPLY="" local file file=$(type -P -- "$1") || return $? if type -p realpath >/dev/null; then REPLY=$(realpath "$file") elif type -p greadlink >/dev/null; then REPLY=$(greadlink -f "$file") elif type -p readlink >/dev/null; then REPLY=$(readlink -f "$file") else _comp_abspath "$file" fi } # This function returns the position of the first argument, excluding options # # Options: # -a GLOB Pattern of options that take an option argument # # @var[out] REPLY Position of the first argument before the current one being # completed if any, or otherwise an empty string # @return True (0) if any argument is found, False (> 0) otherwise. # @since 2.12 _comp_locate_first_arg() { local has_optarg="" local OPTIND=1 OPTARG="" OPTERR=0 _opt while getopts ':a:' _opt "$@"; do case $_opt in a) has_optarg=$OPTARG ;; *) echo "bash_completion: $FUNCNAME: usage error" >&2 return 2 ;; esac done shift "$((OPTIND - 1))" local i REPLY= for ((i = 1; i < cword; i++)); do # shellcheck disable=SC2053 if [[ $has_optarg && ${words[i]} == $has_optarg ]]; then ((i++)) elif [[ ${words[i]} != -?* ]]; then REPLY=$i return 0 elif [[ ${words[i]} == -- ]]; then ((i + 1 < cword)) && REPLY=$((i + 1)) && return 0 break fi done return 1 } # This function returns the first argument, excluding options # # Options: # -a GLOB Pattern of options that take an option argument # # @var[out] REPLY First argument before the current one being completed if any, # or otherwise an empty string # @return True (0) if any argument is found, False (> 0) otherwise. # @since 2.12 _comp_get_first_arg() { _comp_locate_first_arg "$@" && REPLY=${words[REPLY]} } # This function counts the number of args, excluding options # # Options: # -n CHARS Characters out of $COMP_WORDBREAKS which should # NOT be considered word breaks. See # _comp__reassemble_words. # -a GLOB Options whose following argument should not be counted # -i GLOB Options that should be counted as args # # @var[out] REPLY Return the number of arguments # @since 2.12 _comp_count_args() { local has_optarg="" has_exclude="" exclude="" glob_include="" local OPTIND=1 OPTARG="" OPTERR=0 _opt while getopts ':a:n:i:' _opt "$@"; do case $_opt in a) has_optarg=$OPTARG ;; n) has_exclude=set exclude+=$OPTARG ;; i) glob_include=$OPTARG ;; *) echo "bash_completion: $FUNCNAME: usage error" >&2 return 2 ;; esac done shift "$((OPTIND - 1))" if [[ $has_exclude ]]; then local cword words _comp__reassemble_words "$exclude<>&" words cword fi local i REPLY=1 for ((i = 1; i < cword; i++)); do # shellcheck disable=SC2053 if [[ $has_optarg && ${words[i]} == $has_optarg ]]; then ((i++)) elif [[ ${words[i]} != -?* || $glob_include && ${words[i]} == $glob_include ]]; then ((REPLY++)) elif [[ ${words[i]} == -- ]]; then ((REPLY += cword - i - 1)) break fi done } # This function completes on PCI IDs # # @since 2.12 _comp_compgen_pci_ids() { _comp_compgen_split -- "$(PATH="$PATH:/sbin" lspci -n | _comp_awk '{print $3}')" } # This function completes on USB IDs # # @since 2.12 _comp_compgen_usb_ids() { _comp_compgen_split -- "$(PATH="$PATH:/sbin" lsusb | _comp_awk '{print $6}')" } # CD device names # # @since 2.12 _comp_compgen_cd_devices() { _comp_compgen -c "${cur:-/dev/}" -- -f -d -X "!*/?([amrs])cd!(c-*)" } # DVD device names # # @since 2.12 _comp_compgen_dvd_devices() { _comp_compgen -c "${cur:-/dev/}" -- -f -d -X "!*/?(r)dvd*" } # TERM environment variable values # # @since 2.12 _comp_compgen_terms() { _comp_compgen_split -- "$({ command sed -ne 's/^\([^[:space:]#|]\{2,\}\)|.*/\1/p' /etc/termcap { toe -a || toe } | _comp_awk '{ print $1 }' _comp_expand_glob dirs '/{etc,lib,usr/lib,usr/share}/terminfo/?' && find "${dirs[@]}" -type f -maxdepth 1 | _comp_awk -F / '{ print $NF }' } 2>/dev/null)" } # @since 2.12 _comp_try_faketty() { if type unbuffer &>/dev/null; then unbuffer -p "$@" elif script --version 2>&1 | command grep -qF util-linux; then # BSD and Solaris "script" do not seem to have required features script -qaefc "$*" /dev/null else "$@" # no can do, fallback fi } # a little help for FreeBSD ports users [[ $OSTYPE == *freebsd* ]] && complete -W 'index search fetch fetch-list extract patch configure build install reinstall deinstall clean clean-depends kernel buildworld' make # This function provides simple user@host completion # # @since 2.12 _comp_complete_user_at_host() { local cur prev words cword comp_args _comp_initialize -n : -- "$@" || return if [[ $cur == *@* ]]; then _comp_compgen_known_hosts "$cur" else _comp_compgen -- -u -S @ compopt -o nospace fi } shopt -u hostcomplete && complete -F _comp_complete_user_at_host talk ytalk finger # NOTE: Using this function as a helper function is deprecated. Use # `_comp_compgen_known_hosts' instead. # @since 2.12 _comp_complete_known_hosts() { local cur prev words cword comp_args _comp_initialize -n : -- "$@" || return # NOTE: Using `_known_hosts' (the old name of `_comp_complete_known_hosts') # as a helper function and passing options to `_known_hosts' is # deprecated: Use `_comp_compgen_known_hosts' instead. local -a options=() [[ ${1-} == -a || ${2-} == -a ]] && options+=(-a) [[ ${1-} == -c || ${2-} == -c ]] && options+=(-c) local IFS=$' \t\n' # Workaround for connected ${v+"$@"} in bash < 4.4 _comp_compgen_known_hosts ${options[@]+"${options[@]}"} -- "$cur" } # Helper function to locate ssh included files in configs # This function looks for the "Include" keyword in ssh config files and # includes them recursively, adding each result to the config variable. _comp__included_ssh_config_files() { (($# < 1)) && echo "bash_completion: $FUNCNAME: missing mandatory argument CONFIG" >&2 local configfile i files f REPLY configfile=$1 # From man ssh_config: # "Files without absolute paths are assumed to be in ~/.ssh if included # in a user configuration file or /etc/ssh if included from the system # configuration file." # This behavior is not affected by the the including file location - # if the system configuration file is included from the user's config, # relative includes are still resolved in the user's ssh config directory. local relative_include_base if [[ $configfile == /etc/ssh* ]]; then relative_include_base="/etc/ssh" else relative_include_base="$HOME/.ssh" fi local depth=1 local -a included local -a include_files included=("$configfile") # Max recursion depth per openssh's READCONF_MAX_DEPTH: # https://github.com/openssh/openssh-portable/blob/5ec5504f1d328d5bfa64280cd617c3efec4f78f3/readconf.c#L2240 local max_depth=16 while ((${#included[@]} > 0 && depth++ < max_depth)); do _comp_split include_files "$(command sed -ne 's/^[[:blank:]]*[Ii][Nn][Cc][Ll][Uu][Dd][Ee][[:blank:]]\(.*\)$/\1/p' "${included[@]}")" || return included=() for i in "${include_files[@]}"; do if [[ $i != [~/]* ]]; then i="${relative_include_base}/${i}" fi _comp_expand_tilde "$i" if _comp_expand_glob files '$REPLY'; then # In case the expanded variable contains multiple paths for f in "${files[@]}"; do if [[ -r $f && ! -d $f ]]; then config+=("$f") included+=("$f") fi done fi done done } # Helper function for completing _comp_complete_known_hosts. # This function performs host completion based on ssh's config and known_hosts # files, as well as hostnames reported by avahi-browse if # BASH_COMPLETION_KNOWN_HOSTS_WITH_AVAHI is set to a non-empty value. # Also hosts from HOSTFILE (compgen -A hostname) are added, unless # BASH_COMPLETION_KNOWN_HOSTS_WITH_HOSTFILE is set to an empty value. # Usage: _comp_compgen_known_hosts [OPTIONS] CWORD # Options: # -a Use aliases from ssh config files # -c Use `:' suffix # -F configfile Use `configfile' for configuration settings # -p PREFIX Use PREFIX # -4 Filter IPv6 addresses from results # -6 Filter IPv4 addresses from results # @var[out] COMPREPLY Completions, starting with CWORD, are added # @return True (0) if one or more completions are generated, or otherwise False # (1). # @since 2.12 _comp_compgen_known_hosts() { local known_hosts _comp_compgen_known_hosts__impl "$@" || return "$?" _comp_compgen -U known_hosts set "${known_hosts[@]}" } _comp_compgen_known_hosts__impl() { known_hosts=() local configfile="" flag prefix="" local cur suffix="" aliases="" i host ipv4="" ipv6="" local -a kh tmpkh=() khd=() config=() # TODO remove trailing %foo from entries local OPTIND=1 while getopts "ac46F:p:" flag "$@"; do case $flag in a) aliases=set ;; c) suffix=':' ;; F) if [[ ! $OPTARG ]]; then echo "bash_completion: $FUNCNAME: -F: an empty filename is specified" >&2 return 2 fi configfile=$OPTARG ;; p) prefix=$OPTARG ;; 4) ipv4=set ;; 6) ipv6=set ;; *) echo "bash_completion: $FUNCNAME: usage error" >&2 return 2 ;; esac done if (($# < OPTIND)); then echo "bash_completion: $FUNCNAME: missing mandatory argument CWORD" >&2 return 2 fi cur=${!OPTIND} ((OPTIND += 1)) if (($# >= OPTIND)); then echo "bash_completion: $FUNCNAME($*): unprocessed arguments:" \ "$(while (($# >= OPTIND)); do printf '%s ' ${!OPTIND} shift done)" >&2 return 2 fi [[ $cur == *@* ]] && prefix=$prefix${cur%@*}@ && cur=${cur#*@} kh=() # ssh config files if [[ $configfile ]]; then [[ -r $configfile && ! -d $configfile ]] && config+=("$configfile") else for i in /etc/ssh/ssh_config ~/.ssh/config ~/.ssh2/config; do [[ -r $i && ! -d $i ]] && config+=("$i") done fi # "Include" keyword in ssh config files if ((${#config[@]} > 0)); then for i in "${config[@]}"; do _comp__included_ssh_config_files "$i" done fi # Known hosts files from configs if ((${#config[@]} > 0)); then # expand paths (if present) to global and user known hosts files # TODO(?): try to make known hosts files with more than one consecutive # spaces in their name work (watch out for ~ expansion # breakage! Alioth#311595) if _comp_split -l tmpkh "$(_comp_awk 'sub("^[ \t]*([Gg][Ll][Oo][Bb][Aa][Ll]|[Uu][Ss][Ee][Rr])[Kk][Nn][Oo][Ww][Nn][Hh][Oo][Ss][Tt][Ss][Ff][Ii][Ll][Ee][ \t=]+", "") { print $0 }' "${config[@]}" | sort -u)"; then local tmpkh2 j REPLY for i in "${tmpkh[@]}"; do # First deal with quoted entries... while [[ $i =~ ^([^\"]*)\"([^\"]*)\"(.*)$ ]]; do i=${BASH_REMATCH[1]}${BASH_REMATCH[3]} _comp_expand_tilde "${BASH_REMATCH[2]}" # Eval/expand possible `~' or `~user' [[ -r $REPLY ]] && kh+=("$REPLY") done # ...and then the rest. _comp_split tmpkh2 "$i" || continue for j in "${tmpkh2[@]}"; do _comp_expand_tilde "$j" # Eval/expand possible `~' or `~user' [[ -r $REPLY ]] && kh+=("$REPLY") done done fi fi if [[ ! $configfile ]]; then # Global and user known_hosts files for i in /etc/ssh/ssh_known_hosts /etc/ssh/ssh_known_hosts2 \ /etc/known_hosts /etc/known_hosts2 ~/.ssh/known_hosts \ ~/.ssh/known_hosts2; do [[ -r $i && ! -d $i ]] && kh+=("$i") done for i in /etc/ssh2/knownhosts ~/.ssh2/hostkeys; do [[ -d $i ]] || continue _comp_expand_glob tmpkh '"$i"/*.pub' && khd+=("${tmpkh[@]}") done fi # If we have known_hosts files to use if ((${#kh[@]} + ${#khd[@]} > 0)); then if ((${#kh[@]} > 0)); then # https://man.openbsd.org/sshd.8#SSH_KNOWN_HOSTS_FILE_FORMAT for i in "${kh[@]}"; do while read -ra tmpkh; do ((${#tmpkh[@]} == 0)) && continue # Skip entries starting with | (hashed) and # (comment) [[ ${tmpkh[0]} == [\|\#]* ]] && continue # Ignore leading @foo (markers) local host_list=${tmpkh[0]} [[ ${tmpkh[0]} == @* ]] && host_list=${tmpkh[1]-} # Split entry on commas local -a hosts if _comp_split -F , hosts "$host_list"; then for host in "${hosts[@]}"; do # Skip hosts containing wildcards [[ $host == *[*?]* ]] && continue # Remove leading [ host=${host#[} # Remove trailing ] + optional :port host=${host%]?(:+([0-9]))} # Add host to candidates [[ $host ]] && known_hosts+=("$host") done fi done <"$i" done fi if ((${#khd[@]} > 0)); then # Needs to look for files called # .../.ssh2/key_22_.pub # dont fork any processes, because in a cluster environment, # there can be hundreds of hostkeys for i in "${khd[@]}"; do if [[ $i == *key_22_*.pub && -r $i ]]; then host=${i/#*key_22_/} host=${host/%.pub/} [[ $host ]] && known_hosts+=("$host") fi done fi # apply suffix and prefix ((${#known_hosts[@]})) && _comp_compgen -v known_hosts -- -W '"${known_hosts[@]}"' -P "$prefix" -S "$suffix" fi # append any available aliases from ssh config files if [[ ${#config[@]} -gt 0 && $aliases ]]; then local -a hosts if _comp_split hosts "$(command sed -ne 's/^[[:blank:]]*[Hh][Oo][Ss][Tt][[:blank:]=]\{1,\}\(.*\)$/\1/p' "${config[@]}")"; then _comp_compgen -av known_hosts -- -P "$prefix" \ -S "$suffix" -W '"${hosts[@]%%[*?%]*}"' -X '@(\!*|)' fi fi # Add hosts reported by avahi-browse, if desired and it's available. if [[ ${BASH_COMPLETION_KNOWN_HOSTS_WITH_AVAHI-} ]] && type avahi-browse &>/dev/null; then # Some old versions of avahi-browse reportedly didn't have -k # (even if mentioned in the manpage); those we do not support any more. local generated=$(avahi-browse -cprak 2>/dev/null | _comp_awk -F ';' \ '/^=/ && $5 ~ /^_(ssh|workstation)\._tcp$/ { print $7 }' | sort -u) _comp_compgen -av known_hosts -- -P "$prefix" -S "$suffix" -W '$generated' fi # Add hosts reported by ruptime. if type ruptime &>/dev/null; then local generated=$(ruptime 2>/dev/null | _comp_awk '!/^ruptime:/ { print $1 }') _comp_compgen -av known_hosts -- -W '$generated' fi # Add results of normal hostname completion, unless # `BASH_COMPLETION_KNOWN_HOSTS_WITH_HOSTFILE' is set to an empty value. if [[ ${BASH_COMPLETION_KNOWN_HOSTS_WITH_HOSTFILE-set} ]]; then _comp_compgen -av known_hosts -- -A hostname -P "$prefix" -S "$suffix" fi ((${#known_hosts[@]})) || return 1 if [[ $ipv4 ]]; then known_hosts=("${known_hosts[@]/*:*$suffix/}") fi if [[ $ipv6 ]]; then known_hosts=("${known_hosts[@]/+([0-9]).+([0-9]).+([0-9]).+([0-9])$suffix/}") fi if [[ $ipv4 || $ipv6 ]]; then for i in "${!known_hosts[@]}"; do [[ ${known_hosts[i]} ]] || unset -v 'known_hosts[i]' done fi ((${#known_hosts[@]})) || return 1 _comp_compgen -v known_hosts -c "$prefix$cur" ltrim_colon "${known_hosts[@]}" } complete -F _comp_complete_known_hosts traceroute traceroute6 \ fping fping6 telnet rsh rlogin ftp dig drill ssh-installkeys showmount # Convert the word index in `words` to the index in `COMP_WORDS`. # @param $1 Index in the array WORDS. # @var[in,opt] words Words that contain reassmbled words. # @var[in,opt] cword Current word index in WORDS. # WORDS and CWORD, if any, are expected to be created by # _comp__reassemble_words. # _comp__find_original_word() { REPLY=$1 # If CWORD or WORDS are undefined, we return the first argument without any # processing. [[ -v cword && -v words ]] || return 0 local reassembled_offset=$1 i=0 j for ((j = 0; j < reassembled_offset; j++)); do local word=${words[j]} while [[ $word && i -lt ${#COMP_WORDS[@]} && $word == *"${COMP_WORDS[i]}"* ]]; do word=${word#*"${COMP_WORDS[i++]}"} done done REPLY=$i } # A meta-command completion function for commands like sudo(8), which need to # first complete on a command, then complete according to that command's own # completion definition. # # @since 2.12 _comp_command_offset() { # rewrite current completion context before invoking # actual command completion # obtain the word index in COMP_WORDS local REPLY _comp__find_original_word "$1" local word_offset=$REPLY # make changes to COMP_* local. Note that bash-4.3..5.0 have a # bug that `local -a arr=("${arr[@]}")` fails. We instead first # assign the values of `COMP_WORDS` to another array `comp_words`. local COMP_LINE=$COMP_LINE COMP_POINT=$COMP_POINT COMP_CWORD=$COMP_CWORD local -a comp_words=("${COMP_WORDS[@]}") local -a COMP_WORDS=("${comp_words[@]}") # find new first word position, then # rewrite COMP_LINE and adjust COMP_POINT local i tail for ((i = 0; i < word_offset; i++)); do tail=${COMP_LINE#*"${COMP_WORDS[i]}"} ((COMP_POINT -= ${#COMP_LINE} - ${#tail})) COMP_LINE=$tail done # shift COMP_WORDS elements and adjust COMP_CWORD COMP_WORDS=("${COMP_WORDS[@]:word_offset}") ((COMP_CWORD -= word_offset)) COMPREPLY=() local cur _comp_get_words cur if ((COMP_CWORD == 0)); then _comp_compgen_commands else _comp_dequote "${COMP_WORDS[0]}" || REPLY=${COMP_WORDS[0]} local cmd=$REPLY compcmd=$REPLY local cspec=$(complete -p -- "$cmd" 2>/dev/null) # If we have no completion for $cmd yet, see if we have for basename if [[ ! $cspec && $cmd == */* ]]; then cspec=$(complete -p -- "${cmd##*/}" 2>/dev/null) [[ $cspec ]] && compcmd=${cmd##*/} fi # If still nothing, just load it for the basename if [[ ! $cspec ]]; then compcmd=${cmd##*/} _comp_load -D -- "$compcmd" cspec=$(complete -p -- "$compcmd" 2>/dev/null) fi local retry_count=0 while true; do # loop for the retry request by status 124 local args original_cur=${comp_args[1]-$cur} if ((${#COMP_WORDS[@]} >= 2)); then args=("$cmd" "$original_cur" "${COMP_WORDS[-2]}") else args=("$cmd" "$original_cur") fi if [[ ! $cspec ]]; then if ((${#COMPREPLY[@]} == 0)); then # XXX will probably never happen as long as completion loader loads # *something* for every command thrown at it ($cspec != empty) _comp_complete_minimal "${args[@]}" fi elif [[ $cspec == *\ -[CF]\ * ]]; then if [[ $cspec == *' -F '* ]]; then # complete -F # get function name local func=${cspec#* -F } func=${func%% *} $func "${args[@]}" # restart completion (once) if function exited with 124 if (($? == 124 && retry_count++ == 0)); then # Note: When the completion function returns 124, the # state of COMPREPLY is discarded. COMPREPLY=() cspec=$(complete -p -- "$compcmd" 2>/dev/null) # Note: When completion spec is removed after 124, we # do not generate any completions including the default # ones. This is the behavior of the original Bash # progcomp. [[ $cspec ]] || break continue fi else # complete -C # get command name local completer=${cspec#* -C \'} # completer commands are always single-quoted if ! _comp_dequote "'$completer"; then _minimal "${args[@]}" break fi completer=${REPLY[0]} local -a suggestions local IFS=$' \t\n' local reset_monitor=$(shopt -po monitor) reset_lastpipe=$(shopt -p lastpipe) reset_noglob=$(shopt -po noglob) set +o monitor shopt -s lastpipe set -o noglob COMP_KEY="$COMP_KEY" COMP_LINE="$COMP_LINE" \ COMP_POINT="$COMP_POINT" COMP_TYPE="$COMP_TYPE" \ $completer "${args[@]}" | mapfile -t suggestions $reset_monitor $reset_lastpipe $reset_noglob _comp_unlocal IFS local suggestion local i=0 COMPREPLY=() for suggestion in "${suggestions[@]}"; do COMPREPLY[i]+=${COMPREPLY[i]+$'\n'}$suggestion if [[ $suggestion != *\\ ]]; then ((i++)) fi done fi # restore initial compopts local opt while [[ $cspec == *" -o "* ]]; do # FIXME: should we take "+o opt" into account? cspec=${cspec#*-o } opt=${cspec%% *} compopt -o "$opt" cspec=${cspec#"$opt"} done else cspec=${cspec#complete} cspec=${cspec%%@("$compcmd"|"'${compcmd//\'/\'\\\'\'}'")} eval "_comp_compgen -- $cspec" fi break done fi } # A _comp_command_offset wrapper function for use when the offset is unknown. # Only intended to be used as a completion function directly associated # with a command, not to be invoked from within other completion functions. # # @since 2.12 _comp_command() { # We unset the shell variable `words` locally to tell # `_comp_command_offset` that the index is intended to be that in # `COMP_WORDS` instead of `words`. local words unset -v words local offset i # find actual offset, as position of the first non-option offset=1 for ((i = 1; i <= COMP_CWORD; i++)); do if [[ ${COMP_WORDS[i]} != -* ]]; then offset=$i break fi done _comp_command_offset $offset } complete -F _comp_command aoss command "do" else eval exec ltrace nice nohup padsp \ "then" time tsocks vsound xargs # @since 2.12 _comp_root_command() { local PATH=$PATH:/sbin:/usr/sbin:/usr/local/sbin local _comp_root_command=$1 _comp_command } complete -F _comp_root_command fakeroot gksu gksudo kdesudo really # Return true if the completion should be treated as running as root # # @since 2.12 _comp_as_root() { [[ $EUID -eq 0 || ${_comp_root_command-} ]] } # Complete on available commands, subject to `no_empty_cmd_completion`. # @return True (0) if one or more completions are generated, or otherwise False # (1). Note that it returns 1 even when the completion generation is canceled # by `shopt -s no_empty_cmd_completion`. # # @since 2.12 _comp_compgen_commands() { [[ ! ${cur-} ]] && shopt -q no_empty_cmd_completion && return 1 # -o filenames for e.g. spaces in paths to and in command names _comp_compgen -- -c -o plusdirs && compopt -o filenames } # @since 2.12 _comp_complete_longopt() { local cur prev words cword was_split comp_args _comp_initialize -s -- "$@" || return case "${prev,,}" in --help | --usage | --version) return ;; --!(no-*)dir*) _comp_compgen -a filedir -d return ;; --!(no-*)@(file|path)*) _comp_compgen -a filedir return ;; --+([-a-z0-9_])) local argtype=$(LC_ALL=C $1 --help 2>&1 | command sed -ne \ "s|.*$prev\[\{0,1\}=[<[]\{0,1\}\([-A-Za-z0-9_]\{1,\}\).*|\1|p") case ${argtype,,} in *dir*) _comp_compgen -a filedir -d return ;; *file* | *path*) _comp_compgen -a filedir return ;; esac ;; esac [[ $was_split ]] && return if [[ $cur == -* ]]; then _comp_compgen_split -- "$(LC_ALL=C $1 --help 2>&1 | while read -r line; do [[ $line =~ --[A-Za-z0-9]+([-_][A-Za-z0-9]+)*=? ]] && printf '%s\n' "${BASH_REMATCH[0]}" done)" [[ ${COMPREPLY-} == *= ]] && compopt -o nospace elif [[ $1 == *@(rmdir|chroot) ]]; then _comp_compgen -a filedir -d else [[ $1 == *mkdir ]] && compopt -o nospace _comp_compgen -a filedir fi } # makeinfo and texi2dvi are defined elsewhere. complete -F _comp_complete_longopt \ a2ps awk base64 bash bc bison cat chroot colordiff cp \ csplit cut date df diff dir du enscript expand fmt fold gperf \ grep grub head irb ld ldd less ln ls m4 mkdir mkfifo mknod \ mv netstat nl nm objcopy objdump od paste pr ptx readelf rm rmdir \ sed seq shar sort split strip sum tac tail tee \ texindex touch tr uname unexpand uniq units vdir wc who # @since 2.12 declare -Ag _comp_xspecs # @since 2.12 _comp_complete_filedir_xspec() { local cur prev words cword comp_args _comp_initialize -- "$@" || return _comp_compgen_filedir_xspec "$1" } # @since 2.12 _comp_compgen_filedir_xspec() { _comp_compgen_tilde && return local REPLY _comp_quote_compgen "$cur" local quoted=$REPLY local xspec=${_comp_xspecs[${1##*/}]-${_xspecs[${1##*/}]-}} local -a toks _comp_compgen -v toks -c "$quoted" -- -d # Munge xspec to contain uppercase version too # https://lists.gnu.org/archive/html/bug-bash/2010-09/msg00036.html # news://news.gmane.io/4C940E1C.1010304@case.edu eval xspec="${xspec}" local matchop=! if [[ $xspec == !* ]]; then xspec=${xspec#!} matchop=@ fi xspec="$matchop($xspec|${xspec^^})" _comp_compgen -av toks -c "$quoted" -- -f -X "@(|!($xspec))" # Try without filter if it failed to produce anything and configured to [[ ${BASH_COMPLETION_FILEDIR_FALLBACK-} && ${#toks[@]} -lt 1 ]] && _comp_compgen -av toks -c "$quoted" -- -f ((${#toks[@]})) || return 1 # Remove . and .. (as well as */. and */..) from suggestions, unless .. or # */.. was typed explicitly by the user (for users who use tab-completion # to append a slash after '..') if [[ $cur != ?(*/).. ]]; then _comp_compgen -Rv toks -- -X '?(*/)@(.|..)' -W '"${toks[@]}"' || return 1 fi compopt -o filenames _comp_compgen -RU toks -- -W '"${toks[@]}"' } _comp__init_install_xspec() { local xspec=$1 cmd shift for cmd in "$@"; do _comp_xspecs[$cmd]=$xspec done } # bzcmp, bzdiff, bz*grep, bzless, bzmore intentionally not here, see Debian: #455510 _comp__init_install_xspec '!*.?(t)bz?(2)' bunzip2 bzcat pbunzip2 pbzcat lbunzip2 lbzcat _comp__init_install_xspec '!*.@(zip|[aegjkswx]ar|exe|pk3|wsz|zargo|xpi|s[tx][cdiw]|sx[gm]|o[dt][tspgfc]|od[bm]|oxt|?(o)xps|epub|cbz|apk|aab|ipa|do[ct][xm]|p[op]t[mx]|xl[st][xm]|pyz|vsix|whl|[Ff][Cc][Ss]td)' unzip zipinfo _comp__init_install_xspec '*.Z' compress znew # zcmp, zdiff, z*grep, zless, zmore intentionally not here, see Debian: #455510 _comp__init_install_xspec '!*.@(Z|[gGd]z|t[ag]z)' gunzip zcat _comp__init_install_xspec '!*.@(Z|[gGdz]z|t[ag]z)' unpigz _comp__init_install_xspec '!*.Z' uncompress # lzcmp, lzdiff intentionally not here, see Debian: #455510 _comp__init_install_xspec '!*.@(tlz|lzma)' lzcat lzegrep lzfgrep lzgrep lzless lzmore unlzma _comp__init_install_xspec '!*.@(?(t)xz|tlz|lzma)' unxz xzcat _comp__init_install_xspec '!*.lrz' lrunzip _comp__init_install_xspec '!*.@(gif|jp?(e)g|miff|tif?(f)|pn[gm]|p[bgp]m|bmp|xpm|ico|xwd|tga|pcx)' ee _comp__init_install_xspec '!*.@(gif|jp?(e)g|tif?(f)|png|p[bgp]m|bmp|x[bp]m|rle|rgb|pcx|fits|pm|svg)' qiv _comp__init_install_xspec '!*.@(gif|jp?(e)g?(2)|j2[ck]|jp[2f]|tif?(f)|png|p[bgpn]m|webp|bmp|x[bp]m|rle|rgb|pcx|fits|pm|?(e)ps)' xv _comp__init_install_xspec '!*.@(@(?(e)ps|?(E)PS|pdf|PDF)?(.gz|.GZ|.bz2|.BZ2|.Z))' gv ggv kghostview _comp__init_install_xspec '!*.@(dvi|DVI)?(.@(gz|Z|bz2))' xdvi kdvi _comp__init_install_xspec '!*.dvi' dvips dviselect dvitype dvipdf advi dvipdfm dvipdfmx _comp__init_install_xspec '!*.[pf]df' acroread gpdf xpdf _comp__init_install_xspec '!*.@(?(e)ps|pdf)' kpdf _comp__init_install_xspec '!*.@(okular|@(?(e|x)ps|?(E|X)PS|[pf]df|[PF]DF|dvi|DVI|cb[rz]|CB[RZ]|djv?(u)|DJV?(U)|dvi|DVI|gif|jp?(e)g|miff|tif?(f)|pn[gm]|p[bgp]m|bmp|xpm|ico|xwd|tga|pcx|GIF|JP?(E)G|MIFF|TIF?(F)|PN[GM]|P[BGP]M|BMP|XPM|ICO|XWD|TGA|PCX|epub|EPUB|odt|ODT|fb?(2)|FB?(2)|mobi|MOBI|g3|G3|chm|CHM|md|markdown)?(.?(gz|GZ|bz2|BZ2|xz|XZ)))' okular _comp__init_install_xspec '!*.pdf' epdfview pdfunite _comp__init_install_xspec '!*.@(cb[rz7t]|djv?(u)|?(e)ps|pdf)' zathura _comp__init_install_xspec '!*.@(?(e)ps|pdf)' ps2pdf ps2pdf12 ps2pdf13 ps2pdf14 ps2pdfwr _comp__init_install_xspec '!*.texi*' makeinfo texi2html _comp__init_install_xspec '!*.@(?(la)tex|texi|dtx|ins|ltx|dbj)' tex latex slitex jadetex pdfjadetex pdftex pdflatex texi2dvi xetex xelatex luatex lualatex _comp__init_install_xspec '!*.mp3' mpg123 mpg321 madplay _comp__init_install_xspec '!*@(.@(mp?(e)g|MP?(E)G|wm[av]|WM[AV]|avi|AVI|asf|vob|VOB|bin|dat|divx|DIVX|vcd|ps|pes|fli|flv|FLV|fxm|FXM|viv|rm|ram|yuv|mov|MOV|qt|QT|web[am]|WEB[AM]|mp[234]|MP[234]|m?(p)4[av]|M?(P)4[AV]|mkv|MKV|og[agmv]|OG[AGMV]|t[ps]|T[PS]|m2t?(s)|M2T?(S)|mts|MTS|wav|WAV|flac|FLAC|asx|ASX|mng|MNG|srt|m[eo]d|M[EO]D|s[3t]m|S[3T]M|it|IT|xm|XM)|+([0-9]).@(vdr|VDR))?(.@(crdownload|part))' xine aaxine cacaxine fbxine _comp__init_install_xspec '!*@(.@(mp?(e)g|MP?(E)G|wm[av]|WM[AV]|avi|AVI|asf|vob|VOB|bin|dat|divx|DIVX|vcd|ps|pes|fli|flv|FLV|fxm|FXM|viv|rm|ram|yuv|mov|MOV|qt|QT|web[am]|WEB[AM]|mp[234]|MP[234]|m?(p)4[av]|M?(P)4[AV]|mkv|MKV|og[agmv]|OG[AGMV]|opus|OPUS|t[ps]|T[PS]|m2t?(s)|M2T?(S)|mts|MTS|wav|WAV|flac|FLAC|asx|ASX|mng|MNG|srt|m[eo]d|M[EO]D|s[3t]m|S[3T]M|it|IT|xm|XM|iso|ISO)|+([0-9]).@(vdr|VDR))?(.@(crdownload|part))' kaffeine dragon totem _comp__init_install_xspec '!*.@(avi|asf|wmv)' aviplay _comp__init_install_xspec '!*.@(rm?(j)|ra?(m)|smi?(l))' realplay _comp__init_install_xspec '!*.@(mpg|mpeg|avi|mov|qt)' xanim _comp__init_install_xspec '!*.@(og[ag]|m3u|flac|spx)' ogg123 _comp__init_install_xspec '!*.@(mp3|ogg|pls|m3u)' gqmpeg freeamp _comp__init_install_xspec '!*.fig' xfig _comp__init_install_xspec '!*.@(mid?(i)|cmf)' playmidi _comp__init_install_xspec '!*.@(mid?(i)|rmi|rcp|[gr]36|g18|mod|xm|it|x3m|s[3t]m|kar)' timidity _comp__init_install_xspec '!*.@(669|abc|am[fs]|d[bs]m|dmf|far|it|mdl|m[eo]d|mid?(i)|mt[2m]|oct|okt?(a)|p[st]m|s[3t]m|ult|umx|wav|xm)' modplugplay modplug123 _comp__init_install_xspec '*.@([ao]|so|so.!(conf|*/*)|[rs]pm|gif|jp?(e)g|mp3|mp?(e)g|avi|asf|ogg|class)' vi vim gvim rvim view rview rgvim rgview gview emacs xemacs sxemacs kate kwrite _comp__init_install_xspec '!*.@(zip|z|gz|tgz)' bzme # konqueror not here on purpose, it's more than a web/html browser _comp__init_install_xspec '!*.@(?([xX]|[sS])[hH][tT][mM]?([lL]))' netscape mozilla lynx galeon dillo elinks amaya epiphany _comp__init_install_xspec '!*.@(sxw|stw|sxg|sgl|doc?([mx])|dot?([mx])|rtf|txt|htm|html|?(f)odt|ott|odm|pdf)' oowriter lowriter _comp__init_install_xspec '!*.@(sxi|sti|pps?(x)|ppt?([mx])|pot?([mx])|?(f)odp|otp)' ooimpress loimpress _comp__init_install_xspec '!*.@(sxc|stc|xls?([bmx])|xlw|xlt?([mx])|[ct]sv|?(f)ods|ots)' oocalc localc _comp__init_install_xspec '!*.@(sxd|std|sda|sdd|?(f)odg|otg)' oodraw lodraw _comp__init_install_xspec '!*.@(sxm|smf|mml|odf)' oomath lomath _comp__init_install_xspec '!*.odb' oobase lobase _comp__init_install_xspec '!*.[rs]pm' rpm2cpio _comp__init_install_xspec '!*.aux' bibtex _comp__init_install_xspec '!*.po' poedit gtranslator kbabel lokalize _comp__init_install_xspec '!*.@([Pp][Rr][Gg]|[Cc][Ll][Pp])' harbour gharbour hbpp _comp__init_install_xspec '!*.[Hh][Rr][Bb]' hbrun _comp__init_install_xspec '!*.ly' lilypond ly2dvi _comp__init_install_xspec '!*.@(dif?(f)|?(d)patch)?(.@([gx]z|bz2|lzma))' cdiff _comp__init_install_xspec '!@(*.@(ks|jks|jceks|p12|pfx|bks|ubr|gkr|cer|crt|cert|p7b|pkipath|pem|p10|csr|crl)|cacerts)' portecle _comp__init_install_xspec '!*.@(mp[234c]|og[ag]|@(fl|a)ac|m4[abp]|spx|tta|w?(a)v|wma|aif?(f)|asf|ape)' kid3 kid3-qt unset -f _comp__init_install_xspec # Minimal completion to use as fallback in _comp_complete_load. # TODO:API: rename per conventions _comp_complete_minimal() { local cur prev words cword comp_args _comp_initialize -- "$@" || return compopt -o bashdefault -o default } # Complete the empty string to allow completion of '>', '>>', and '<' on < 4.3 # https://lists.gnu.org/archive/html/bug-bash/2012-01/msg00045.html complete -F _comp_complete_minimal '' # Initialize the variable "_comp__base_directory" # @var[out] _comp__base_directory _comp__init_base_directory() { local REPLY _comp_abspath "${BASH_SOURCE[0]-./bash_completion}" _comp__base_directory=${REPLY%/*} [[ $_comp__base_directory ]] || _comp__base_directory=/ unset -f "$FUNCNAME" } _comp__init_base_directory # @since 2.12 _comp_load() { local flag_fallback_default="" IFS=$' \t\n' local OPTIND=1 OPTARG="" OPTERR=0 opt while getopts ':D' opt "$@"; do case $opt in D) flag_fallback_default=set ;; *) echo "bash_completion: $FUNCNAME: usage error" >&2 return 2 ;; esac done shift "$((OPTIND - 1))" local cmd=$1 cmdname=${1##*/} dir compfile local -a paths [[ $cmdname ]] || return 1 local backslash= if [[ $cmd == \\* ]]; then cmd=${cmd:1} # If we already have a completion for the "real" command, use it $(complete -p -- "$cmd" 2>/dev/null || echo false) "\\$cmd" && return 0 backslash=\\ fi # Resolve absolute path to $cmd local REPLY pathcmd origcmd=$cmd if pathcmd=$(type -P -- "$cmd"); then _comp_abspath "$pathcmd" cmd=$REPLY fi local -a dirs=() # Lookup order: # 1) From BASH_COMPLETION_USER_DIR (e.g. ~/.local/share/bash-completion): # User installed completions. if [[ ${BASH_COMPLETION_USER_DIR-} ]]; then _comp_split -F : paths "$BASH_COMPLETION_USER_DIR" && dirs+=("${paths[@]/%//completions}") else dirs=("${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion/completions") fi # 2) From the location of bash_completion: Completions relative to the main # script. This is primarily for run-in-place-from-git-clone setups, where # we want to prefer in-tree completions over ones possibly coming with a # system installed bash-completion. (Due to usual install layouts, this # often hits the correct completions in system installations, too.) dirs+=("$_comp__base_directory/completions") # 3) From bin directories extracted from the specified path to the command, # the real path to the command, and $PATH paths=() [[ $cmd == /* ]] && paths+=("${cmd%/*}") _comp_realcommand "$cmd" && paths+=("${REPLY%/*}") _comp_split -aF : paths "$PATH" for dir in "${paths[@]%/}"; do [[ $dir == ?*/@(bin|sbin) ]] && dirs+=("${dir%/*}/share/bash-completion/completions") done # 4) From XDG_DATA_DIRS or system dirs (e.g. /usr/share, /usr/local/share): # Completions in the system data dirs. _comp_split -F : paths "${XDG_DATA_DIRS:-/usr/local/share:/usr/share}" && dirs+=("${paths[@]/%//bash-completion/completions}") # Set up default $IFS in case loaded completions depend on it, # as well as for $compspec invocation below. local IFS=$' \t\n' # Look up and source shift local i prefix compspec for prefix in "" _; do # Regular from all dirs first, then fallbacks for i in ${!dirs[*]}; do dir=${dirs[i]} if [[ ! -d $dir ]]; then unset -v 'dirs[i]' continue fi for compfile in "$prefix$cmdname" "$prefix$cmdname.bash"; do compfile="$dir/$compfile" # Avoid trying to source dirs as long as we support bash < 4.3 # to avoid an fd leak; https://bugzilla.redhat.com/903540 if [[ -d $compfile ]]; then # Do not warn with . or .. (especially the former is common) [[ $compfile == */.?(.) ]] || echo "bash_completion: $compfile: is a directory" >&2 elif [[ -e $compfile ]] && . "$compfile" "$cmd" "$@"; then # At least $cmd is expected to have a completion set when # we return successfully; see if it already does if compspec=$(complete -p -- "$cmd" 2>/dev/null); then # $cmd is the case in which we do backslash processing [[ $backslash ]] && eval "$compspec \"\$backslash\$cmd\"" # If invoked without path, that one should be set, too # ...but let's not overwrite an existing one, if any [[ $origcmd != */* ]] && ! complete -p -- "$origcmd" &>/dev/null && eval "$compspec \"\$origcmd\"" return 0 fi # If not, see if we got one for $cmdname if [[ $cmdname != "$cmd" ]] && compspec=$(complete -p -- "$cmdname" 2>/dev/null); then # Use that for $cmd too, if we have a full path to it [[ $cmd == /* ]] && eval "$compspec \"\$cmd\"" return 0 fi # Nothing expected was set, continue lookup fi done done done # Look up simple "xspec" completions [[ -v _comp_xspecs[$cmdname] || -v _xspecs[$cmdname] ]] && complete -F _comp_complete_filedir_xspec "$cmdname" "$backslash$cmdname" && return 0 if [[ $flag_fallback_default ]]; then complete -F _comp_complete_minimal -- "$origcmd" && return 0 fi return 1 } # set up dynamic completion loading # @since 2.12 _comp_complete_load() { # $1=_EmptycmD_ already for empty cmds in bash 4.3, set to it for earlier local cmd=${1:-_EmptycmD_} # Pass -D to define *something*, or otherwise there will be no completion # at all. _comp_load -D -- "$cmd" && return 124 } && complete -D -F _comp_complete_load # Function for loading and calling functions from dynamically loaded # completion files that may not have been sourced yet. # @param $1 completion file to load function from in case it is missing # @param $2 the xfunc name. When it does not start with `_', # `_comp_xfunc_${1//[^a-zA-Z0-9_]/_}_$2' is used for the actual name of the # shell function. # @param $3... if any, specifies the arguments that are passed to the xfunc. # @since 2.12 _comp_xfunc() { local xfunc_name=$2 [[ $xfunc_name == _* ]] || xfunc_name=_comp_xfunc_${1//[^a-zA-Z0-9_]/_}_$xfunc_name declare -F -- "$xfunc_name" &>/dev/null || _comp_load -- "$1" "$xfunc_name" "${@:3}" } # Call a POSIX-compatible awk. Solaris awk is not POSIX-compliant, but Solaris # provides a POSIX-compatible version through /usr/xpg4/bin/awk. We switch the # implementation to /usr/xpg4/bin/awk in Solaris if any. # @since 2.12 if [[ $OSTYPE == *solaris* && -x /usr/xpg4/bin/awk ]]; then _comp_awk() { /usr/xpg4/bin/awk "$@" } else _comp_awk() { command awk "$@" } fi # List custom/extra completion files to source on the startup ## @param $1 path Path to "bash_completion" ## @var[out] _comp__init_startup_configs _comp__init_collect_startup_configs() { local base_path=${1:-${BASH_SOURCE[1]}} _comp__init_startup_configs=() # source compat completion directory definitions local -a compat_dirs=() local compat_dir if [[ ${BASH_COMPLETION_COMPAT_DIR-} ]]; then compat_dirs+=("$BASH_COMPLETION_COMPAT_DIR") else compat_dirs+=(/etc/bash_completion.d) # Similarly as for the "completions" dir, look up from relative to # bash_completion, primarily for installed-with-prefix and # run-in-place-from-git-clone setups. Notably we do it after the # system location here, in order to prefer in-tree variables and # functions. if [[ $_comp__base_directory == */share/bash-completion ]]; then compat_dir=${_comp__base_directory%/share/bash-completion}/etc/bash_completion.d else compat_dir=$_comp__base_directory/bash_completion.d fi [[ ${compat_dirs[0]} == "$compat_dir" ]] || compat_dirs+=("$compat_dir") fi for compat_dir in "${compat_dirs[@]}"; do [[ -d $compat_dir && -r $compat_dir && -x $compat_dir ]] || continue local compat_files _comp_expand_glob compat_files '"$compat_dir"/*' local compat_file for compat_file in "${compat_files[@]}"; do [[ ${compat_file##*/} != @($_comp_backup_glob|Makefile*|${BASH_COMPLETION_COMPAT_IGNORE-}) && -f $compat_file && -r $compat_file ]] && _comp__init_startup_configs+=("$compat_file") done done # source user completion file # # Remark: We explicitly check that $user_completion is not '/dev/null' # since /dev/null may be a regular file in broken systems and can contain # arbitrary garbages of suppressed command outputs. local user_file=${BASH_COMPLETION_USER_FILE:-~/.bash_completion} [[ $user_file != "$base_path" && $user_file != /dev/null && -r $user_file && -f $user_file ]] && _comp__init_startup_configs+=("$user_file") unset -f "$FUNCNAME" } _comp__init_collect_startup_configs "$BASH_SOURCE" # shellcheck disable=SC2154 for _comp_init_startup_config in "${_comp__init_startup_configs[@]}"; do . "$_comp_init_startup_config" done unset -v _comp__init_startup_configs _comp_init_startup_config unset -f have unset -v have set $_comp__init_original_set_v unset -v _comp__init_original_set_v # ex: filetype=sh