summaryrefslogtreecommitdiffstats
path: root/bash_completion
diff options
context:
space:
mode:
Diffstat (limited to 'bash_completion')
-rw-r--r--bash_completion3211
1 files changed, 2147 insertions, 1064 deletions
diff --git a/bash_completion b/bash_completion
index 1a7f563..e245cc5 100644
--- a/bash_completion
+++ b/bash_completion
@@ -23,12 +23,16 @@
#
# https://github.com/scop/bash-completion
-BASH_COMPLETION_VERSINFO=(2 11)
+BASH_COMPLETION_VERSINFO=(
+ 2 # x-release-please-major
+ 12 # x-release-please-minor
+ 0 # x-release-please-patch
+)
if [[ $- == *v* ]]; then
- BASH_COMPLETION_ORIGINAL_V_VALUE="-v"
+ _comp__init_original_set_v="-v"
else
- BASH_COMPLETION_ORIGINAL_V_VALUE="+v"
+ _comp__init_original_set_v="+v"
fi
if [[ ${BASH_COMPLETION_DEBUG-} ]]; then
@@ -37,13 +41,60 @@ else
set +v
fi
-# Blacklisted completions, causing problems with our code.
-#
-_blacklist_glob='@(acroread.sh)'
-
# 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
@@ -67,9 +118,6 @@ complete -A setopt set
# shopt completes with shopt options
complete -A shopt shopt
-# helptopics
-complete -A helptopic help
-
# unalias completes with aliases
complete -a unalias
@@ -83,7 +131,8 @@ complete -b builtin
# Check if we're running on the given userland
# @param $1 userland to check for
-_userland()
+# @since 2.12
+_comp_userland()
{
local userland=$(uname -s)
[[ $userland == @(Linux|GNU/*) ]] && userland=GNU
@@ -92,94 +141,135 @@ _userland()
# This function sets correct SysV init directories
#
-_sysvdirs()
+# @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)
- return 0
+ ((${#sysvdirs[@]}))
}
# This function checks whether we have a given program on the system.
#
-_have()
+# @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
-}
-
-# Backwards compatibility for compat completions that use have().
-# @deprecated should no longer be used; generally not needed with dynamically
-# loaded completions, and _have is suitable for runtime use.
-have()
-{
- unset -v have
- _have $1 && have=yes
+ PATH=$PATH:/usr/sbin:/sbin:/usr/local/sbin type "$1" &>/dev/null
}
# This function checks whether a given readline variable
# is `on'.
#
-_rl_enabled()
+# @since 2.12
+_comp_readline_variable_on()
{
- [[ "$(bind -v)" == *$1+([[:space:]])on* ]]
+ [[ $(bind -v) == *$1+([[:space:]])on* ]]
}
# This function shell-quotes the argument
-quote()
+# @param $1 String to be quoted
+# @var[out] REPLY Resulting string
+# @since 2.12
+_comp_quote()
{
- local quoted=${1//\'/\'\\\'\'}
- printf "'%s'" "$quoted"
+ REPLY=\'${1//\'/\'\\\'\'}\'
}
-# @see _quote_readline_by_ref()
-quote_readline()
-{
- local ret
- _quote_readline_by_ref "$1" ret
- printf %s "$ret"
-} # quote_readline()
-
-# This function shell-dequotes the argument
-dequote()
+# shellcheck disable=SC1003
+_comp_dequote__initialize()
{
- eval printf %s "$1" 2>/dev/null
+ unset -f "$FUNCNAME"
+ 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')*$'
+}
+_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
}
-# Assign variable one scope above the caller
-# Usage: local "$1" && _upvar $1 "value(s)"
-# Param: $1 Variable name to assign value to
-# Param: $* Value(s) to assign. If multiple values, an array is
-# assigned, otherwise a single value is assigned.
-# NOTE: For assigning multiple variables, use '_upvars'. Do NOT
-# use multiple '_upvar' calls, since one '_upvar' call might
-# reassign a variable to be used by another '_upvar' call.
-# See: https://fvue.nl/wiki/Bash:_Passing_variables_by_reference
-_upvar()
-{
- echo "bash_completion: $FUNCNAME: deprecated function," \
- "use _upvars instead" >&2
- if unset -v "$1"; then # Unset & validate varname
- if (($# == 2)); then
- eval $1=\"\$2\" # Return single value
- else
- eval $1=\(\"\$"{@:2}"\"\) # Return array
- fi
+# 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 ...] &&
-# _upvars [-v varname value] | [-aN varname [value ...]] ...
+# _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
-_upvars()
+# @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" \
@@ -201,7 +291,8 @@ _upvars()
return 1
}
# Assign array of -aN elements
- [[ "$2" ]] && unset -v "$2" && eval $2=\(\"\$"{@:3:${1#-a}}"\"\) &&
+ # 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)" \
@@ -211,7 +302,8 @@ _upvars()
;;
-v)
# Assign single value
- [[ "$2" ]] && unset -v "$2" && eval $2=\"\$3\" &&
+ # shellcheck disable=SC2015 # TODO
+ [[ $2 ]] && unset -v "$2" && eval "$2"=\"\$3\" &&
shift 3 || {
echo "bash_completion: $FUNCNAME: $1:" \
"missing argument(s)" >&2
@@ -226,6 +318,482 @@ _upvars()
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".
+# @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|_*|*[^_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 may affect the result of pathname expansions.
+ local GLOBIGNORE=
+
+ 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=<sep>; <array_name>=(<text>)` 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
+
+ ((${#_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[@]}" "${@:2}"
+ 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"
+ fi
+
+ # 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
+
+ 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"
+}
+
+# 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
@@ -235,9 +803,9 @@ _upvars()
# @param $2 words Name of variable to return words to
# @param $3 cword Name of variable to return cword to
#
-__reassemble_comp_words_by_ref()
+_comp__reassemble_words()
{
- local exclude i j line ref
+ local exclude="" i j line ref
# Exclude word separator characters?
if [[ $1 ]]; then
# Yes, exclude word separator characters;
@@ -248,7 +816,7 @@ __reassemble_comp_words_by_ref()
# Default to cword unchanged
printf -v "$3" %s "$COMP_CWORD"
# Are characters excluded which were former included?
- if [[ -v exclude ]]; then
+ if [[ $exclude ]]; then
# Yes, list of word completion separators has shrunk;
line=$COMP_LINE
# Re-assemble words to complete
@@ -269,12 +837,16 @@ __reassemble_comp_words_by_ref()
((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++))
- # Indicate next word if available, else end *both* while and
- # for loop
- ((i < ${#COMP_WORDS[@]} - 1)) && ((i++)) || break 2
done
# Append word to current word
ref="$2[$j]"
@@ -291,7 +863,7 @@ __reassemble_comp_words_by_ref()
printf -v "$2[i]" %s "${COMP_WORDS[i]}"
done
fi
-} # __reassemble_comp_words_by_ref()
+}
# @param $1 exclude Characters out of $COMP_WORDBREAKS which should NOT be
# considered word breaks. This is useful for things like scp where
@@ -300,22 +872,22 @@ __reassemble_comp_words_by_ref()
# @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 __reassemble_comp_words_by_ref()
-__get_cword_at_cursor_by_ref()
+# @see _comp__reassemble_words()
+_comp__get_cword_at_cursor()
{
local cword words=()
- __reassemble_comp_words_by_ref "$1" words cword
+ _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 leaded by just space(s)?
+ # 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]} && \
+ while [[ ${#cur} -ge ${#words[i]} &&
${cur:0:${#words[i]}} != "${words[i]-}" ]]; do
# Strip first character
- cur="${cur:1}"
+ cur=${cur:1}
# Decrease cursor position, staying >= 0
((index > 0)) && ((index--))
done
@@ -324,7 +896,7 @@ __get_cword_at_cursor_by_ref()
if ((i < cword)); then
# No, cword lies further;
local old_size=${#cur}
- cur="${cur#"${words[i]}"}"
+ cur=${cur#"${words[i]}"}
local new_size=${#cur}
((index -= old_size - new_size))
fi
@@ -335,8 +907,9 @@ __get_cword_at_cursor_by_ref()
((index < 0)) && index=0
fi
- local "$2" "$3" "$4" && _upvars -a${#words[@]} $2 ${words+"${words[@]}"} \
- -v $3 "$cword" -v $4 "${cur:0:index}"
+ 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.
@@ -345,7 +918,7 @@ __get_cword_at_cursor_by_ref()
# (For example, if the line is "ls foobar",
# and the cursor is here --------> ^
# Also one is able to cross over possible wordbreak characters.
-# Usage: _get_comp_words_by_ref [OPTIONS] [VARNAMES]
+# Usage: _comp_get_words [OPTIONS] [VARNAMES]
# Available VARNAMES:
# cur Return cur via $cur
# prev Return prev via $prev
@@ -364,16 +937,23 @@ __get_cword_at_cursor_by_ref()
#
# Example usage:
#
-# $ _get_comp_words_by_ref -n : cur prev
+# $ _comp_get_words -n : cur prev
#
-_get_comp_words_by_ref()
+# @since 2.12
+_comp_get_words()
{
- local exclude flag i OPTIND=1
+ local exclude="" flag i OPTIND=1
local cur cword words=()
- local upargs=() upvars=() vcur vcword vprev vwords
+ 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 ;;
@@ -400,108 +980,44 @@ _get_comp_words_by_ref()
((OPTIND += 1))
done
- __get_cword_at_cursor_by_ref "${exclude-}" words cword cur
+ _comp__get_cword_at_cursor "${exclude-}" words cword cur
- [[ -v vcur ]] && {
+ [[ $vcur ]] && {
upvars+=("$vcur")
- upargs+=(-v $vcur "$cur")
+ upargs+=(-v "$vcur" "$cur")
}
- [[ -v vcword ]] && {
+ [[ $vcword ]] && {
upvars+=("$vcword")
- upargs+=(-v $vcword "$cword")
+ upargs+=(-v "$vcword" "$cword")
}
- [[ -v vprev && $cword -ge 1 ]] && {
+ [[ $vprev ]] && {
+ local value=""
+ ((cword >= 1)) && value=${words[cword - 1]}
upvars+=("$vprev")
- upargs+=(-v $vprev "${words[cword - 1]}")
+ upargs+=(-v "$vprev" "$value")
}
- [[ -v vwords ]] && {
+ [[ $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[@]}"})
+ upargs+=(-a"${#words[@]}" "$vwords" ${words+"${words[@]}"})
}
- ((${#upvars[@]})) && local "${upvars[@]}" && _upvars "${upargs[@]}"
+ ((${#upvars[@]})) && local "${upvars[@]}" && _comp_upvars "${upargs[@]}"
}
-# Get the word to complete.
-# 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 --------> ^
-# @param $1 string 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 integer Index number of word to return, negatively offset to the
-# current word (default is 0, previous is 1), respecting the exclusions
-# given at $1. For example, `_get_cword "=:" 1' returns the word left of
-# the current word, respecting the exclusions "=:".
-# @deprecated Use `_get_comp_words_by_ref cur' instead
-# @see _get_comp_words_by_ref()
-_get_cword()
-{
- local LC_CTYPE=C
- local cword words
- __reassemble_comp_words_by_ref "${1-}" words cword
-
- # return previous word offset by $2
- if [[ ${2-} && ${2//[^0-9]/} ]]; then
- printf "%s" "${words[cword - $2]}"
- elif ((${#words[cword]} == 0 && COMP_POINT == ${#COMP_LINE})); then
- : # nothing
- else
- local i
- local cur="$COMP_LINE"
- local index="$COMP_POINT"
- 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
-
- if [[ ${words[cword]:0:${#cur}} != "$cur" ]]; then
- # We messed up! At least return the whole word so things
- # keep working
- printf "%s" "${words[cword]}"
- else
- printf "%s" "${cur:0:index}"
- fi
- fi
-} # _get_cword()
-
-# Get word previous to the current word.
-# This is a good alternative to `prev=${COMP_WORDS[COMP_CWORD-1]}' because bash4
-# will properly return the previous word with respect to any given exclusions to
-# COMP_WORDBREAKS.
-# @deprecated Use `_get_comp_words_by_ref cur prev' instead
-# @see _get_comp_words_by_ref()
+# 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.
#
-_get_pword()
-{
- if ((COMP_CWORD >= 1)); then
- _get_cword "${@:-}" 1
- fi
-}
-
-# If the word-to-complete contains a colon (:), left-trim COMPREPLY items with
-# word-to-complete.
-# 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, by removing the colon-containing-prefix
-# from COMPREPLY items.
# The preferred solution is to remove the colon (:) from COMP_WORDBREAKS in
# your .bashrc:
#
@@ -510,24 +1026,37 @@ _get_pword()
#
# 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)
-# @modifies global array $COMPREPLY
+# @var[in,out] COMPREPLY
#
-__ltrim_colon_completions()
+# @since 2.12
+_comp_ltrim_colon_completions()
{
- if [[ $1 == *:* && $COMP_WORDBREAKS == *:* ]]; then
- # Remove colon-word prefix from COMPREPLY items
- local colon_word=${1%"${1##*:}"}
- local i=${#COMPREPLY[*]}
- while ((i-- > 0)); do
- COMPREPLY[i]=${COMPREPLY[i]#"$colon_word"}
- done
- fi
-} # __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:
+# `compgen` which requires its arguments quoted/escaped:
#
# $ ls "a'b/"
# c
@@ -538,87 +1067,90 @@ __ltrim_colon_completions()
# 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
-# @param $2 Name of variable to return result to
-_quote_readline_by_ref()
+# @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
- printf -v $2 %s "${1:1}"
+ REPLY=${1:1}
else
- printf -v $2 %q "$1"
+ 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
-
- # 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
- [[ ${!2} == \$* ]] && eval $2=${!2}
-} # _quote_readline_by_ref()
+}
# 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.
#
-_filedir()
+# @since 2.12
+_comp_compgen_filedir()
{
- local IFS=$'\n'
-
- _tilde "${cur-}" || return
+ _comp_compgen_tilde && return
local -a toks
- local reset arg=${1-}
-
- if [[ $arg == -d ]]; then
- reset=$(shopt -po noglob)
- set -o noglob
- toks=($(compgen -d -- "${cur-}"))
- IFS=' '
- $reset
- IFS=$'\n'
+ local _arg=${1-}
+
+ if [[ $_arg == -d ]]; then
+ _comp_compgen -v toks -- -d
else
- local quoted
- _quote_readline_by_ref "${cur-}" quoted
+ 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=()
+ 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)
- [[ ${COMP_FILEDIR_FALLBACK-} || -z ${plusdirs-} ]] ||
- opts+=("${plusdirs[@]}")
-
- reset=$(shopt -po noglob)
- set -o noglob
- toks+=($(compgen "${opts[@]}" -- $quoted))
- IFS=' '
- $reset
- IFS=$'\n'
+ 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
- [[ -n ${COMP_FILEDIR_FALLBACK-} && -n $arg && ${#toks[@]} -lt 1 ]] && {
- reset=$(shopt -po noglob)
- set -o noglob
- toks+=($(compgen -f ${plusdirs+"${plusdirs[@]}"} -- $quoted))
- IFS=' '
- $reset
- IFS=$'\n'
- }
+ [[ ${BASH_COMPLETION_FILEDIR_FALLBACK-} &&
+ $_arg && ${#toks[@]} -lt 1 ]] &&
+ _comp_compgen -av toks -c "$_quoted" -- \
+ -f ${_plusdirs+"${_plusdirs[@]}"}
fi
if ((${#toks[@]} != 0)); then
- # 2>/dev/null for direct invocation, e.g. in the _filedir unit test
+ # 2>/dev/null for direct invocation, e.g. in the _comp_compgen_filedir
+ # unit test
compopt -o filenames 2>/dev/null
- COMPREPLY+=("${toks[@]}")
fi
-} # _filedir()
+
+ # 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.
@@ -626,13 +1158,13 @@ _filedir()
# this to be useful.
# Returns 0 if current option was split, 1 otherwise.
#
-_split_longopt()
+_comp__split_longopt()
{
if [[ $cur == --?*=* ]]; then
# Cut also backslash before '=' in case it ended up there
# for some reason.
- prev="${cur%%?(\\)=*}"
- cur="${cur#*=}"
+ prev=${cur%%?(\\)=*}
+ cur=${cur#*=}
return 0
fi
@@ -642,51 +1174,140 @@ _split_longopt()
# Complete variables.
# @return True (0) if variables were completed,
# False (> 0) if not.
-_variables()
+# @since 2.12
+_comp_compgen_variables()
{
if [[ $cur =~ ^(\$(\{[!#]?)?)([A-Za-z0-9_]*)$ ]]; then
# Completing $var / ${var / ${!var / ${#var
if [[ $cur == '${'* ]]; then
local arrs vars
- vars=($(compgen -A variable -P ${BASH_REMATCH[1]} -S '}' -- ${BASH_REMATCH[3]}))
- arrs=($(compgen -A arrayvar -P ${BASH_REMATCH[1]} -S '[' -- ${BASH_REMATCH[3]}))
+ _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
- COMPREPLY+=(${arrs[*]})
+ _comp_compgen -U vars -U arrs -R -- -W '"${arrs[@]}"'
else
# Complete ${var with ${variable}
- COMPREPLY+=(${vars[*]})
+ _comp_compgen -U vars -U arrs -R -- -W '"${vars[@]}"'
fi
else
# Complete $var with $variable
- COMPREPLY+=($(compgen -A variable -P '$' -- "${BASH_REMATCH[3]}"))
+ _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 IFS=$'\n'
- COMPREPLY+=($(compgen -W '$(printf %s\\n "${!'${BASH_REMATCH[2]}'[@]}")' \
- -P "${BASH_REMATCH[1]}${BASH_REMATCH[2]}[" -S ']}' -- "${BASH_REMATCH[3]}"))
+ 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
- COMPREPLY+=("${BASH_REMATCH[1]}${BASH_REMATCH[2]}[${BASH_REMATCH[3]}]}")
+ 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
- __ltrim_colon_completions "$cur" # array indexes may have colons
return 0
elif [[ $cur =~ ^\$\{[#!]?[A-Za-z0-9_]*\[.*\]$ ]]; then
# Complete ${array[idx] with ${array[idx]}
- COMPREPLY+=("$cur}")
- __ltrim_colon_completions "$cur"
+ _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
- case ${prev-} in
- TZ)
- cur=/usr/share/zoneinfo/$cur
- _filedir
+ _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 'COMPREPLY[i]'
+ unset -v 'COMPREPLY[i]'
continue
elif [[ -d ${COMPREPLY[i]} ]]; then
COMPREPLY[i]+=/
@@ -694,20 +1315,24 @@ _variables()
fi
COMPREPLY[i]=${COMPREPLY[i]#/usr/share/zoneinfo/}
done
- return 0
- ;;
- TERM)
- _terms
- return 0
- ;;
- LANG | LC_*)
- COMPREPLY=($(compgen -W '$(locale -a 2>/dev/null)' \
- -- "$cur"))
- return 0
- ;;
- esac
- fi
- return 1
+ 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
@@ -717,18 +1342,37 @@ _variables()
# cur, prev, words, and cword are local, ditto split if you use -s.
#
# Options:
-# -n EXCLUDE Passed to _get_comp_words_by_ref -n with redirection chars
-# -e XSPEC Passed to _filedir as first arg for stderr redirections
-# -o XSPEC Passed to _filedir as first arg for other output redirections
-# -i XSPEC Passed to _filedir as first arg for stdin redirections
-# -s Split long options with _split_longopt, implies -n =
+# -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.
#
-_init_completion()
+# @since 2.12
+_comp_initialize()
{
- local exclude="" flag outx errx inx OPTIND=1
+ 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 ;;
@@ -736,8 +1380,9 @@ _init_completion()
o) outx=$OPTARG ;;
i) inx=$OPTARG ;;
s)
- split=false
- exclude+==
+ opt_split="set"
+ was_split=""
+ exclude+="="
;;
*)
echo "bash_completion: $FUNCNAME: usage error" >&2
@@ -745,13 +1390,15 @@ _init_completion()
;;
esac
done
+ shift "$((OPTIND - 1))"
+ (($#)) && comp_args=("$@")
COMPREPLY=()
- local redir="@(?([0-9])<|?([0-9&])>?(>)|>&)"
- _get_comp_words_by_ref -n "$exclude<>&" cur prev words cword
+ local redir='@(?(+([0-9])|{[a-zA-Z_]*([a-zA-Z_0-9])})@(>?([>|&])|<?([>&])|<<?([-<]))|&>?(>))'
+ _comp_get_words -n "$exclude<>&" cur prev words cword
# Complete variable names.
- _variables && return 1
+ _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. ">".
@@ -770,8 +1417,9 @@ _init_completion()
esac
;;
esac
- cur="${cur##$redir}"
- _filedir $xspec
+ # shellcheck disable=SC2295 # redir is a pattern
+ cur=${cur##$redir}
+ _comp_compgen_filedir "$xspec"
return 1
fi
@@ -792,259 +1440,342 @@ _init_completion()
((cword <= 0)) && return 1
prev=${words[cword - 1]}
- [[ ${split-} ]] && _split_longopt && split=true
+ [[ $opt_split ]] && _comp__split_longopt && was_split="set"
return 0
}
-# Helper function for _parse_help and _parse_usage.
-__parse_options()
+# 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 IFS=$' \t\n,/|'
+ local option option2 i
# Take first found long option, or first one (short) if not found.
option=
- local -a array=($1)
- for i in "${array[@]}"; do
- case "$i" in
- ---*) break ;;
- --?*)
- option=$i
- break
- ;;
- -?*) [[ $option ]] || option=$i ;;
- *) break ;;
- esac
- done
- [[ $option ]] || return 0
-
- IFS=$' \t\n' # affects parsing of the regexps below...
+ 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%%[<{().[]*}
- printf '%s\n' "${option2/=*/=}"
+ options+=("${option2/=*/=}")
option=${option/"${BASH_REMATCH[1]}"/"${BASH_REMATCH[2]}"}
fi
- option=${option%%[<{().[]*}
- printf '%s\n' "${option/=*/=}"
+ [[ $option =~ ^([^=<{().[]|\.[A-Za-z0-9])+=? ]] &&
+ options+=("$BASH_REMATCH")
}
-# Parse GNU style help output of the given command.
-# @param $1 command; if "-", read from stdin and ignore rest of args
-# @param $2 command options (default: --help)
+# 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.
#
-_parse_help()
-{
- eval local cmd="$(quote "$1")"
- local line
- {
- case $cmd in
- -) cat ;;
- *) LC_ALL=C "$(dequote "$cmd")" ${2:---help} 2>&1 ;;
- esac
- } |
- while read -r line; 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
- __parse_options "${line// or /, }"
-
+# @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.
-# @param $1 command; if "-", read from stdin and ignore rest of args
-# @param $2 command options (default: --usage)
+# 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.
#
-_parse_usage()
-{
- eval local cmd="$(quote "$1")"
- local line match option i char
- {
- case $cmd in
- -) cat ;;
- *) LC_ALL=C "$(dequote "$cmd")" ${2:---usage} 2>&1 ;;
- esac
- } |
- while read -r line; 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 != '[' ]] && printf '%s\n' -$char
- done
- ;;
- *)
- __parse_options "$option"
- ;;
- esac
- line=${line#*"$match"}
- done
-
+# @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
-_signals()
+#
+# @since 2.12
+_comp_compgen_signals()
{
- local -a sigs=($(compgen -P "${1-}" -A signal "SIG${cur#${1-}}"))
- COMPREPLY+=("${sigs[@]/#${1-}SIG/${1-}}")
+ 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
#
-_mac_addresses()
+# @since 2.12
+_comp_compgen_mac_addresses()
{
- local re='\([A-Fa-f0-9]\{2\}:\)\{5\}[A-Fa-f0-9]\{2\}'
+ 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
- COMPREPLY+=($(
+ _comp_compgen -v addresses split -- "$(
{
- LC_ALL=C ifconfig -a || ip link show
+ 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"
- ))
+ "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
- COMPREPLY+=($({
- arp -an || ip neigh show
- } 2>/dev/null | command sed -ne \
- "s/.*[[:space:]]\($re\)[[:space:]].*/\1/p" -ne \
- "s/.*[[:space:]]\($re\)[[:space:]]*$/\1/p"))
+ _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
- COMPREPLY+=($(command sed -ne \
- "s/^[[:space:]]*\($re\)[[:space:]].*/\1/p" /etc/ethers 2>/dev/null))
+ _comp_compgen -av addresses split -- "$(command sed -ne \
+ "s/^[[:space:]]*\($_re\)[[:space:]].*/\1/p" /etc/ethers 2>/dev/null)"
- COMPREPLY=($(compgen -W '${COMPREPLY[@]}' -- "$cur"))
- __ltrim_colon_completions "$cur"
+ _comp_compgen -U addresses ltrim_colon "${addresses[@]}"
}
# This function completes on configured network interfaces
#
-_configured_interfaces()
+# @since 2.12
+_comp_compgen_configured_interfaces()
{
+ local -a files
if [[ -f /etc/debian_version ]]; then
# Debian system
- COMPREPLY=($(compgen -W "$(command sed -ne 's|^iface \([^ ]\{1,\}\).*$|\1|p' \
- /etc/network/interfaces /etc/network/interfaces.d/* 2>/dev/null)" \
- -- "$cur"))
+ _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
- COMPREPLY=($(compgen -W "$(printf '%s\n' \
- /etc/sysconfig/network/ifcfg-* |
- command sed -ne 's|.*ifcfg-\([^*].*\)$|\1|p')" -- "$cur"))
+ _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
- COMPREPLY=($(compgen -W "$(command ls -B \
- /etc/sysconfig/interfaces |
- command sed -ne 's|.*ifcfg-\([^*].*\)$|\1|p')" -- "$cur"))
+ _comp_compgen -U files split -- "$(command ls -B /etc/sysconfig/interfaces |
+ command sed -ne 's|.*ifcfg-\([^*].*\)$|\1|p')"
else
# Assume Red Hat
- COMPREPLY=($(compgen -W "$(printf '%s\n' \
- /etc/sysconfig/network-scripts/ifcfg-* |
- command sed -ne 's|.*ifcfg-\([^*].*\)$|\1|p')" -- "$cur"))
+ _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
#
-_ip_addresses()
+# @since 2.12
+_comp_compgen_ip_addresses()
{
- local n
+ local _n
case ${1-} in
- -a) n='6\?' ;;
- -6) n='6' ;;
- *) n= ;;
+ -a) _n='6\{0,1\}' ;;
+ -6) _n='6' ;;
+ *) _n= ;;
esac
local PATH=$PATH:/sbin
- local addrs=$({
- LC_ALL=C ifconfig -a || ip addr show
+ 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")
- COMPREPLY+=($(compgen -W "$addrs" -- "${cur-}"))
+ "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 kernels
+# This function completes on available kernel versions
#
-_kernel_versions()
+# @since 2.12
+_comp_compgen_kernel_versions()
{
- COMPREPLY=($(compgen -W '$(command ls /lib/modules)' -- "$cur"))
+ _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
#
-_available_interfaces()
+# @since 2.12
+_comp_compgen_available_interfaces()
{
local PATH=$PATH:/sbin
-
- COMPREPLY=($({
- if [[ ${1:-} == -w ]]; then
+ local generated
+ _comp_compgen -v generated split -- "$({
+ if [[ ${1-} == -w ]]; then
iwconfig
- elif [[ ${1:-} == -a ]]; then
- ifconfig || ip link show up
+ elif [[ ${1-} == -a ]]; then
+ ifconfig || ip -c=never link show up || ip link show up
else
- ifconfig -a || ip link show
+ ifconfig -a || ip -c=never link show || ip link show
fi
- } 2>/dev/null | awk \
- '/^[^ \t]/ { if ($1 ~ /^[0-9]+:/) { print $2 } else { print $1 } }'))
-
- COMPREPLY=($(compgen -W '${COMPREPLY[@]/%[[:punct:]]/}' -- "$cur"))
+ } 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.
-_ncpus()
+# @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* ]] && var=_$var
- local n=$(getconf $var 2>/dev/null)
- printf %s ${n:-1}
+ [[ $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 True (0) if completion needs further processing,
-# False (> 0) if tilde is followed by a valid username, completions
-# are put in COMPREPLY and no further processing is necessary.
-_tilde()
+# @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()
{
- local result=0
- if [[ ${1-} == \~* && $1 != */* ]]; then
+ if [[ ${cur-} == \~* && $cur != */* ]]; then
# Try generate ~username completions
- COMPREPLY=($(compgen -P '~' -u -- "${1#\~}"))
- result=${#COMPREPLY[@]}
- # 2>/dev/null for direct invocation, e.g. in the _tilde unit test
- ((result > 0)) && compopt -o filenames 2>/dev/null
+ 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 $result
+ return 1
}
-# Expand variable starting with tilde (~)
+# 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.
@@ -1053,11 +1784,11 @@ _tilde()
# a dollar sign variable ($) or asterisk (*) is not expanded.
# Example usage:
#
-# $ v="~"; __expand_tilde_by_ref v; echo "$v"
+# $ _comp_expand_tilde "~"; echo "$REPLY"
#
# Example output:
#
-# v output
+# $1 REPLY
# -------- ----------------
# ~ /home/user
# ~foo/bar /home/foo/bar
@@ -1065,17 +1796,22 @@ _tilde()
# ~foo/a b /home/foo/a b
# ~foo/* /home/foo/*
#
-# @param $1 Name of variable (not the value of the variable) to expand
-__expand_tilde_by_ref()
-{
- if [[ ${!1-} == \~* ]]; then
- eval $1="$(printf ~%q "${!1#\~}")"
+# @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
-} # __expand_tilde_by_ref()
+}
# This function expands tildes in pathnames
#
-_expand()
+# @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
@@ -1083,58 +1819,67 @@ _expand()
case ${cur-} in
~*/*)
- __expand_tilde_by_ref cur
+ local REPLY
+ _comp_expand_tilde "$cur"
+ cur=$REPLY
;;
~*)
- _tilde "$cur" ||
- eval COMPREPLY[0]="$(printf ~%q "${COMPREPLY[0]#\~}")"
- return ${#COMPREPLY[@]}
+ _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.
- _pids()
+ _comp_compgen_pids()
{
- COMPREPLY=($(compgen -W '$(command ps -efo pid | command sed 1d)' -- "$cur"))
+ _comp_compgen_split -- "$(command ps -efo pid | command sed 1d)"
}
- _pgids()
+ _comp_compgen_pgids()
{
- COMPREPLY=($(compgen -W '$(command ps -efo pgid | command sed 1d)' -- "$cur"))
+ _comp_compgen_split -- "$(command ps -efo pgid | command sed 1d)"
}
- _pnames()
+ _comp_compgen_pnames()
{
- COMPREPLY=($(compgen -X '<defunct>' -W '$(command ps -efo comm | \
- command sed -e 1d -e "s:.*/::" -e "s/^-//" | sort -u)' -- "$cur"))
+ _comp_compgen_split -X '<defunct>' -- "$(command ps -efo comm |
+ command sed -e 1d -e 's:.*/::' -e 's/^-//' | sort -u)"
}
else
- _pids()
+ _comp_compgen_pids()
{
- COMPREPLY=($(compgen -W '$(command ps axo pid=)' -- "$cur"))
+ _comp_compgen_split -- "$(command ps ax -o pid=)"
}
- _pgids()
+ _comp_compgen_pgids()
{
- COMPREPLY=($(compgen -W '$(command ps axo pgid=)' -- "$cur"))
+ _comp_compgen_split -- "$(command ps ax -o pgid=)"
}
# @param $1 if -s, don't try to avoid truncated command names
- _pnames()
+ _comp_compgen_pnames()
{
- local -a procs
+ local -a procs=()
if [[ ${1-} == -s ]]; then
- procs=($(command ps axo comm | command sed -e 1d))
+ _comp_split procs "$(command ps ax -o comm | command sed -e 1d)"
else
- local line i=-1 ifs=$IFS
- IFS=$'\n'
- local -a psout=($(command ps axo command=))
- IFS=$ifs
+ # 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
- # the busybox ps does that, i.e. doesn't respect axo command=
+ # 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]}
@@ -1146,147 +1891,165 @@ else
#
line=${line:i} # take command starting from found index
line=${line%% *} # trim arguments
- procs+=($line)
+ [[ $line ]] && procs+=("$line")
fi
done
if ((i == -1)); then
- # Regular axo command= parsing
+ # Regular command= parsing
for line in "${psout[@]}"; do
if [[ $line =~ ^[[(](.+)[])]$ ]]; then
- procs+=(${BASH_REMATCH[1]})
+ procs+=("${BASH_REMATCH[1]}")
else
line=${line%% *} # trim arguments
line=${line##@(*/|-)} # trim leading path and -
- procs+=($line)
+ [[ $line ]] && procs+=("$line")
fi
done
fi
fi
- COMPREPLY=($(compgen -X "<defunct>" -W '${procs[@]}' -- "$cur"))
+ ((${#procs[@]})) &&
+ _comp_compgen -U procs -- -X "<defunct>" -W '"${procs[@]}"'
}
fi
# This function completes on user IDs
#
-_uids()
+# @since 2.12
+_comp_compgen_uids()
{
if type getent &>/dev/null; then
- COMPREPLY=($(compgen -W '$(getent passwd | cut -d: -f3)' -- "$cur"))
+ _comp_compgen_split -- "$(getent passwd | cut -d: -f3)"
elif type perl &>/dev/null; then
- COMPREPLY=($(compgen -W '$(perl -e '"'"'while (($uid) = (getpwent)[2]) { print $uid . "\n" }'"'"')' -- "$cur"))
+ _comp_compgen_split -- "$(perl -e 'while (($uid) = (getpwent)[2]) { print $uid . "\n" }')"
else
# make do with /etc/passwd
- COMPREPLY=($(compgen -W '$(cut -d: -f3 /etc/passwd)' -- "$cur"))
+ _comp_compgen_split -- "$(cut -d: -f3 /etc/passwd)"
fi
}
# This function completes on group IDs
#
-_gids()
+# @since 2.12
+_comp_compgen_gids()
{
if type getent &>/dev/null; then
- COMPREPLY=($(compgen -W '$(getent group | cut -d: -f3)' -- "$cur"))
+ _comp_compgen_split -- "$(getent group | cut -d: -f3)"
elif type perl &>/dev/null; then
- COMPREPLY=($(compgen -W '$(perl -e '"'"'while (($gid) = (getgrent)[2]) { print $gid . "\n" }'"'"')' -- "$cur"))
+ _comp_compgen_split -- "$(perl -e 'while (($gid) = (getgrent)[2]) { print $gid . "\n" }')"
else
# make do with /etc/group
- COMPREPLY=($(compgen -W '$(cut -d: -f3 /etc/group)' -- "$cur"))
+ _comp_compgen_split -- "$(cut -d: -f3 /etc/group)"
fi
}
# Glob for matching various backup files.
#
-_backup_glob='@(#*#|*@(~|.@(bak|orig|rej|swp|dpkg*|rpm@(orig|new|save))))'
+_comp_backup_glob='@(#*#|*@(~|.@(bak|orig|rej|swp|@(dpkg|ucf)-*|rpm@(orig|new|save))))'
# Complete on xinetd services
#
-_xinetd_services()
+# @since 2.12
+_comp_compgen_xinetd_services()
{
- local xinetddir=${BASHCOMP_XINETDDIR:-/etc/xinetd.d}
+ local xinetddir=${_comp__test_xinetd_dir:-/etc/xinetd.d}
if [[ -d $xinetddir ]]; then
- local IFS=$' \t\n' reset=$(shopt -p nullglob)
- shopt -s nullglob
- local -a svcs=($(printf '%s\n' $xinetddir/!($_backup_glob)))
- $reset
- ((!${#svcs[@]})) ||
- COMPREPLY+=($(compgen -W '${svcs[@]#$xinetddir/}' -- "${cur-}"))
+ 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
#
-_services()
+# @since 2.12
+_comp_compgen_services()
{
local sysvdirs
- _sysvdirs
+ _comp_sysvdirs || return 1
- local IFS=$' \t\n' reset=$(shopt -p nullglob)
- shopt -s nullglob
- COMPREPLY=(
- $(printf '%s\n' ${sysvdirs[0]}/!($_backup_glob|functions|README)))
- $reset
+ local services
+ _comp_expand_glob services '${sysvdirs[0]}/!($_comp_backup_glob|functions|README)'
- COMPREPLY+=($({
+ local _generated=$({
systemctl list-units --full --all ||
systemctl list-unit-files
} 2>/dev/null |
- awk '$1 ~ /\.service$/ { sub("\\.service$", "", $1); print $1 }'))
+ _comp_awk '$1 ~ /\.service$/ { sub("\\.service$", "", $1); print $1 }')
+ _comp_split -la services "$_generated"
if [[ -x /sbin/upstart-udev-bridge ]]; then
- COMPREPLY+=($(initctl list 2>/dev/null | cut -d' ' -f1))
+ _comp_split -la services "$(initctl list 2>/dev/null | cut -d' ' -f1)"
fi
- COMPREPLY=($(compgen -W '${COMPREPLY[@]#${sysvdirs[0]}/}' -- "$cur"))
+ ((${#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.
#
-_service()
+# @since 2.12
+_comp_complete_service()
{
- local cur prev words cword
- _init_completion || return
+ 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
- _services
- [[ -e /etc/mandrake-release ]] && _xinetd_services
+ _comp_compgen_services
+ [[ -e /etc/mandrake-release ]] && _comp_compgen_xinetd_services
else
local sysvdirs
- _sysvdirs
- COMPREPLY=($(compgen -W '`command sed -e "y/|/ /" \
- -ne "s/^.*\(U\|msg_u\)sage.*{\(.*\)}.*$/\2/p" \
- ${sysvdirs[0]}/${prev##*/} 2>/dev/null` start stop' -- "$cur"))
+ _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 _service service
-_sysvdirs
-for svcdir in "${sysvdirs[@]}"; do
- for svc in $svcdir/!($_backup_glob); do
- [[ -x $svc ]] && complete -F _service $svc
- done
-done
-unset svc svcdir sysvdirs
+ complete -F _comp_complete_service service
-# This function completes on modules
+_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
#
-_modules()
+# @since 2.12
+_comp_compgen_kernel_modules()
{
- local modpath
- modpath=/lib/modules/$1
- COMPREPLY=($(compgen -W "$(command ls -RL $modpath 2>/dev/null |
- command sed -ne 's/^\(.*\)\.k\{0,1\}o\(\.[gx]z\)\{0,1\}$/\1/p')" -- "$cur"))
+ 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 installed modules
+# This function completes on inserted kernel modules
+# @param $1 prefix to filter with, default $cur
#
-_installed_modules()
+# @since 2.12
+_comp_compgen_inserted_kernel_modules()
{
- COMPREPLY=($(compgen -W "$(PATH="$PATH:/sbin" lsmod |
- awk '{if (NR != 1) print $1}')" -- "$1"))
+ _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.
@@ -1296,7 +2059,9 @@ _installed_modules()
#
# @param $1 If -u, only return users/groups the user has access to in
# context of current completion.
-_usergroup()
+#
+# @since 2.12
+_comp_compgen_usergroups()
{
if [[ $cur == *\\\\* || $cur == *:*:* ]]; then
# Give up early on if something seems horribly wrong.
@@ -1305,27 +2070,26 @@ _usergroup()
# Completing group after 'user\:gr<TAB>'.
# Reply with a list of groups prefixed with 'user:', readline will
# escape to the colon.
- local prefix
- prefix=${cur%%*([^:])}
- prefix=${prefix//\\/}
- local mycur="${cur#*[:]}"
+ local tmp
if [[ ${1-} == -u ]]; then
- _allowed_groups "$mycur"
+ _comp_compgen -v tmp -c "${cur#*:}" allowed_groups
else
- local IFS=$'\n'
- COMPREPLY=($(compgen -g -- "$mycur"))
+ _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
- COMPREPLY=($(compgen -P "$prefix" -W "${COMPREPLY[@]}"))
elif [[ $cur == *:* ]]; then
# Completing group after 'user:gr<TAB>'.
# Reply with a list of unprefixed groups since readline with split on :
# and only replace the 'gr' part
- local mycur="${cur#*:}"
if [[ ${1-} == -u ]]; then
- _allowed_groups "$mycur"
+ _comp_compgen -c "${cur#*:}" allowed_groups
else
- local IFS=$'\n'
- COMPREPLY=($(compgen -g -- "$mycur"))
+ _comp_compgen -c "${cur#*:}" -- -g
fi
else
# Completing a partial 'usernam<TAB>'.
@@ -1334,168 +2098,274 @@ _usergroup()
# slash. It's better to complete into 'chown username ' than 'chown
# username\:'.
if [[ ${1-} == -u ]]; then
- _allowed_users "$cur"
+ _comp_compgen_allowed_users
else
- local IFS=$'\n'
- COMPREPLY=($(compgen -u -- "$cur"))
+ _comp_compgen -- -u
fi
fi
}
-_allowed_users()
+# @since 2.12
+_comp_compgen_allowed_users()
{
- if _complete_as_root; then
- local IFS=$'\n'
- COMPREPLY=($(compgen -u -- "${1:-$cur}"))
+ if _comp_as_root; then
+ _comp_compgen -- -u
else
- local IFS=$'\n '
- COMPREPLY=($(compgen -W \
- "$(id -un 2>/dev/null || whoami 2>/dev/null)" -- "${1:-$cur}"))
+ _comp_compgen_split -- "$(id -un 2>/dev/null || whoami 2>/dev/null)"
fi
}
-_allowed_groups()
+# @since 2.12
+_comp_compgen_allowed_groups()
{
- if _complete_as_root; then
- local IFS=$'\n'
- COMPREPLY=($(compgen -g -- "$1"))
+ if _comp_as_root; then
+ _comp_compgen -- -g
else
- local IFS=$'\n '
- COMPREPLY=($(compgen -W \
- "$(id -Gn 2>/dev/null || groups 2>/dev/null)" -- "$1"))
+ _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
#
-_shells()
-{
- local shell rest
- while read -r shell rest; do
- [[ $shell == /* && $shell == "$cur"* ]] && COMPREPLY+=($shell)
- done 2>/dev/null </etc/shells
+# @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
#
-_fstypes()
+# @since 2.12
+_comp_compgen_fstypes()
{
- local fss
+ local _fss
if [[ -e /proc/filesystems ]]; then
# Linux
- fss="$(cut -d$'\t' -f2 /proc/filesystems)
- $(awk '! /\*/ { print $NF }' /etc/filesystems 2>/dev/null)"
+ _fss="$(cut -d$'\t' -f2 /proc/filesystems)
+ $(_comp_awk '! /\*/ { print $NF }' /etc/filesystems 2>/dev/null)"
else
# Generic
- fss="$(awk '/^[ \t]*[^#]/ { print $3 }' /etc/fstab 2>/dev/null)
- $(awk '/^[ \t]*[^#]/ { print $3 }' /etc/mnttab 2>/dev/null)
- $(awk '/^[ \t]*[^#]/ { print $4 }' /etc/vfstab 2>/dev/null)
- $(awk '{ print $1 }' /etc/dfs/fstypes 2>/dev/null)
+ _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
- [[ -n $fss ]] && COMPREPLY+=($(compgen -W "$fss" -- "$cur"))
+ [[ $_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
+ case $REPLY in
+ /*) ;;
+ ../*) REPLY=$PWD/${REPLY:3} ;;
+ *) REPLY=$PWD/$REPLY ;;
+ esac
+ while [[ $REPLY == */./* ]]; do
+ REPLY=${REPLY//\/.\//\/}
+ done
+ REPLY=${REPLY//+(\/)/\/}
}
# Get real command.
-# - arg: $1 Command
-# - stdout: Filename of command in PATH with possible symbolic links resolved.
-# Empty string if command not found.
-# - return: True (0) if command found, False (> 0) if not.
-_realcommand()
-{
- type -P "$1" >/dev/null && {
- if type -p realpath >/dev/null; then
- realpath "$(type -P "$1")"
- elif type -p greadlink >/dev/null; then
- greadlink -f "$(type -P "$1")"
- elif type -p readlink >/dev/null; then
- readlink -f "$(type -P "$1")"
- else
- type -P "$1"
- fi
- }
+# 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 first argument, excluding options
-# @param $1 chars Characters out of $COMP_WORDBREAKS which should
-# NOT be considered word breaks. See __reassemble_comp_words_by_ref.
-_get_first_arg()
-{
- local i
+# 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))"
- arg=
- for ((i = 1; i < COMP_CWORD; i++)); do
- if [[ ${COMP_WORDS[i]} != -* ]]; then
- arg=${COMP_WORDS[i]}
+ 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 counts the number of args, excluding options
-# @param $1 chars Characters out of $COMP_WORDBREAKS which should
-# NOT be considered word breaks. See __reassemble_comp_words_by_ref.
-# @param $2 glob Options whose following argument should not be counted
-# @param $3 glob Options that should be counted as args
-_count_args()
+# 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()
{
- local i cword words
- __reassemble_comp_words_by_ref "${1-}" words cword
+ _comp_locate_first_arg "$@" && REPLY=${words[REPLY]}
+}
- args=1
+# 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 [[ ${words[i]} != -* && ${words[i - 1]} != ${2-} || \
- ${words[i]} == ${3-} ]]; then
- ((args++))
+ 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
#
-_pci_ids()
+# @since 2.12
+_comp_compgen_pci_ids()
{
- COMPREPLY+=($(compgen -W \
- "$(PATH="$PATH:/sbin" lspci -n | awk '{print $3}')" -- "$cur"))
+ _comp_compgen_split -- "$(PATH="$PATH:/sbin" lspci -n | _comp_awk '{print $3}')"
}
# This function completes on USB IDs
#
-_usb_ids()
+# @since 2.12
+_comp_compgen_usb_ids()
{
- COMPREPLY+=($(compgen -W \
- "$(PATH="$PATH:/sbin" lsusb | awk '{print $6}')" -- "$cur"))
+ _comp_compgen_split -- "$(PATH="$PATH:/sbin" lsusb | _comp_awk '{print $6}')"
}
# CD device names
-_cd_devices()
+#
+# @since 2.12
+_comp_compgen_cd_devices()
{
- COMPREPLY+=($(compgen -f -d -X "!*/?([amrs])cd*" -- "${cur:-/dev/}"))
+ _comp_compgen -c "${cur:-/dev/}" -- -f -d -X "!*/?([amrs])cd!(c-*)"
}
# DVD device names
-_dvd_devices()
+#
+# @since 2.12
+_comp_compgen_dvd_devices()
{
- COMPREPLY+=($(compgen -f -d -X "!*/?(r)dvd*" -- "${cur:-/dev/}"))
+ _comp_compgen -c "${cur:-/dev/}" -- -f -d -X "!*/?(r)dvd*"
}
# TERM environment variable values
-_terms()
+#
+# @since 2.12
+_comp_compgen_terms()
{
- COMPREPLY+=($(compgen -W "$({
+ _comp_compgen_split -- "$({
command sed -ne 's/^\([^[:space:]#|]\{2,\}\)|.*/\1/p' /etc/termcap
{
toe -a || toe
- } | awk '{ print $1 }'
- find /{etc,lib,usr/lib,usr/share}/terminfo/? -type f -maxdepth 1 |
- awk -F/ '{ print $NF }'
- } 2>/dev/null)" -- "$cur"))
+ } | _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)"
}
-_bashcomp_try_faketty()
+# @since 2.12
+_comp_try_faketty()
{
if type unbuffer &>/dev/null; then
unbuffer -p "$@"
@@ -1514,95 +2384,122 @@ _bashcomp_try_faketty()
# This function provides simple user@host completion
#
-_user_at_host()
+# @since 2.12
+_comp_complete_user_at_host()
{
- local cur prev words cword
- _init_completion -n : || return
+ local cur prev words cword comp_args
+ _comp_initialize -n : -- "$@" || return
if [[ $cur == *@* ]]; then
- _known_hosts_real "$cur"
+ _comp_compgen_known_hosts "$cur"
else
- COMPREPLY=($(compgen -u -S @ -- "$cur"))
+ _comp_compgen -- -u -S @
compopt -o nospace
fi
}
-shopt -u hostcomplete && complete -F _user_at_host talk ytalk finger
+shopt -u hostcomplete && complete -F _comp_complete_user_at_host talk ytalk finger
# NOTE: Using this function as a helper function is deprecated. Use
-# `_known_hosts_real' instead.
-_known_hosts()
-{
- local cur prev words cword
- _init_completion -n : || return
-
- # NOTE: Using `_known_hosts' as a helper function and passing options
- # to `_known_hosts' is deprecated: Use `_known_hosts_real' instead.
- local options
- [[ ${1-} == -a || ${2-} == -a ]] && options=-a
- [[ ${1-} == -c || ${2-} == -c ]] && options+=" -c"
- _known_hosts_real ${options-} -- "$cur"
-} # _known_hosts()
+# `_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.
-_included_ssh_config_files()
+_comp__included_ssh_config_files()
{
(($# < 1)) &&
echo "bash_completion: $FUNCNAME: missing mandatory argument CONFIG" >&2
- local configfile i f
+ local configfile i files f REPLY
configfile=$1
- local reset=$(shopt -po noglob)
- set -o noglob
- local included=($(command sed -ne 's/^[[:blank:]]*[Ii][Nn][Cc][Ll][Uu][Dd][Ee][[:blank:]]\(.*\)$/\1/p' "${configfile}"))
- $reset
-
- [[ ${included-} ]] || return
- for i in "${included[@]}"; do
- # Check the origin of $configfile to complete relative included paths on included
- # files according to ssh_config(5):
- # "[...] 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.[...]"
- if ! [[ $i =~ ^\~.*|^\/.* ]]; then
- if [[ $configfile =~ ^\/etc\/ssh.* ]]; then
- i="/etc/ssh/$i"
- else
- i="$HOME/.ssh/$i"
+ # 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
- fi
- __expand_tilde_by_ref i
- # In case the expanded variable contains multiple paths
- set +o noglob
- for f in $i; do
- if [[ -r $f ]]; then
- config+=("$f")
- # The Included file is processed to look for Included files in itself
- _included_ssh_config_files $f
+ _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
- $reset
done
-} # _included_ssh_config_files()
+}
-# Helper function for completing _known_hosts.
+# 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
-# COMP_KNOWN_HOSTS_WITH_AVAHI is set to a non-empty value. Also hosts from
-# HOSTFILE (compgen -A hostname) are added, unless
-# COMP_KNOWN_HOSTS_WITH_HOSTFILE is set to an empty value.
-# Usage: _known_hosts_real [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
-# Return: Completions, starting with CWORD, are added to COMPREPLY[]
-_known_hosts_real()
-{
- local configfile flag prefix="" ifs=$IFS
- local cur suffix="" aliases i host ipv4 ipv6
+# 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
@@ -1610,21 +2507,27 @@ _known_hosts_real()
local OPTIND=1
while getopts "ac46F:p:" flag "$@"; do
case $flag in
- a) aliases='yes' ;;
+ a) aliases=set ;;
c) suffix=':' ;;
- F) configfile=$OPTARG ;;
+ 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=1 ;;
- 6) ipv6=1 ;;
+ 4) ipv4=set ;;
+ 6) ipv6=set ;;
*)
echo "bash_completion: $FUNCNAME: usage error" >&2
- return 1
+ return 2
;;
esac
done
if (($# < OPTIND)); then
echo "bash_completion: $FUNCNAME: missing mandatory argument CWORD" >&2
- return 1
+ return 2
fi
cur=${!OPTIND}
((OPTIND += 1))
@@ -1634,68 +2537,63 @@ _known_hosts_real()
printf '%s ' ${!OPTIND}
shift
done)" >&2
- return 1
+ return 2
fi
[[ $cur == *@* ]] && prefix=$prefix${cur%@*}@ && cur=${cur#*@}
kh=()
# ssh config files
- if [[ -v configfile ]]; then
- [[ -r $configfile ]] && config+=("$configfile")
+ if [[ $configfile ]]; then
+ [[ -r $configfile && ! -d $configfile ]] && config+=("$configfile")
else
for i in /etc/ssh/ssh_config ~/.ssh/config ~/.ssh2/config; do
- [[ -r $i ]] && config+=("$i")
+ [[ -r $i && ! -d $i ]] && config+=("$i")
done
fi
- local reset=$(shopt -po noglob)
- set -o noglob
-
# "Include" keyword in ssh config files
if ((${#config[@]} > 0)); then
for i in "${config[@]}"; do
- _included_ssh_config_files "$i"
+ _comp__included_ssh_config_files "$i"
done
fi
# Known hosts files from configs
if ((${#config[@]} > 0)); then
- local IFS=$'\n'
# 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)
- tmpkh=($(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))
- IFS=$ifs
- fi
- if ((${#tmpkh[@]} != 0)); then
- local j
- for i in "${tmpkh[@]}"; do
- # First deal with quoted entries...
- while [[ $i =~ ^([^\"]*)\"([^\"]*)\"(.*)$ ]]; do
- i=${BASH_REMATCH[1]}${BASH_REMATCH[3]}
- j=${BASH_REMATCH[2]}
- __expand_tilde_by_ref j # Eval/expand possible `~' or `~user'
- [[ -r $j ]] && kh+=("$j")
- done
- # ...and then the rest.
- for j in $i; do
- __expand_tilde_by_ref j # Eval/expand possible `~' or `~user'
- [[ -r $j ]] && kh+=("$j")
+ 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
- done
+ fi
fi
- if [[ ! -v configfile ]]; then
+ 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 ]] && kh+=("$i")
+ [[ -r $i && ! -d $i ]] && kh+=("$i")
done
for i in /etc/ssh2/knownhosts ~/.ssh2/hostkeys; do
- [[ -d $i ]] && khd+=("$i"/*pub)
+ [[ -d $i ]] || continue
+ _comp_expand_glob tmpkh '"$i"/*.pub' && khd+=("${tmpkh[@]}")
done
fi
@@ -1706,27 +2604,27 @@ _known_hosts_real()
for i in "${kh[@]}"; do
while read -ra tmpkh; do
((${#tmpkh[@]} == 0)) && continue
- set -- "${tmpkh[@]}"
# Skip entries starting with | (hashed) and # (comment)
- [[ $1 == [\|\#]* ]] && continue
+ [[ ${tmpkh[0]} == [\|\#]* ]] && continue
# Ignore leading @foo (markers)
- [[ $1 == @* ]] && shift
+ local host_list=${tmpkh[0]}
+ [[ ${tmpkh[0]} == @* ]] && host_list=${tmpkh[1]-}
# Split entry on commas
- local IFS=,
- for host in $1; do
- # Skip hosts containing wildcards
- [[ $host == *[*?]* ]] && continue
- # Remove leading [
- host="${host#[}"
- # Remove trailing ] + optional :port
- host="${host%]?(:+([0-9]))}"
- # Add host to candidates
- COMPREPLY+=($host)
- done
- IFS=$ifs
+ 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
- COMPREPLY=($(compgen -W '${COMPREPLY[@]}' -- "$cur"))
fi
if ((${#khd[@]} > 0)); then
# Needs to look for files called
@@ -1734,213 +2632,233 @@ _known_hosts_real()
# dont fork any processes, because in a cluster environment,
# there can be hundreds of hostkeys
for i in "${khd[@]}"; do
- if [[ $i == *key_22_$cur*.pub && -r $i ]]; then
+ if [[ $i == *key_22_*.pub && -r $i ]]; then
host=${i/#*key_22_/}
host=${host/%.pub/}
- COMPREPLY+=($host)
+ [[ $host ]] && known_hosts+=("$host")
fi
done
fi
# apply suffix and prefix
- for i in ${!COMPREPLY[*]}; do
- COMPREPLY[i]=$prefix${COMPREPLY[i]}$suffix
- done
+ ((${#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 && -v aliases ]]; then
- local -a hosts=($(command sed -ne 's/^[[:blank:]]*[Hh][Oo][Ss][Tt][[:blank:]]\(.*\)$/\1/p' "${config[@]}"))
- if ((${#hosts[@]} != 0)); then
- COMPREPLY+=($(compgen -P "$prefix" \
- -S "$suffix" -W '${hosts[@]%%[*?%]*}' -X '\!*' -- "$cur"))
+ 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 [[ ${COMP_KNOWN_HOSTS_WITH_AVAHI-} ]] &&
+ if [[ ${BASH_COMPLETION_KNOWN_HOSTS_WITH_AVAHI-} ]] &&
type avahi-browse &>/dev/null; then
- # The original call to avahi-browse also had "-k", to avoid lookups
- # into avahi's services DB. We don't need the name of the service, and
- # if it contains ";", it may mistify the result. But on Gentoo (at
- # least), -k wasn't available (even if mentioned in the manpage) some
- # time ago, so...
- COMPREPLY+=($(compgen -P "$prefix" -S "$suffix" -W \
- "$(avahi-browse -cpr _workstation._tcp 2>/dev/null |
- awk -F';' '/^=/ { print $7 }' | sort -u)" -- "$cur"))
+ # 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
- COMPREPLY+=($(compgen -W \
- "$(ruptime 2>/dev/null | awk '!/^ruptime:/ { print $1 }')" \
- -- "$cur"))
+ 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
- # `COMP_KNOWN_HOSTS_WITH_HOSTFILE' is set to an empty value.
- if [[ -n ${COMP_KNOWN_HOSTS_WITH_HOSTFILE-1} ]]; then
- COMPREPLY+=(
- $(compgen -A hostname -P "$prefix" -S "$suffix" -- "$cur"))
+ # `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
- $reset
+ ((${#known_hosts[@]})) || return 1
- if [[ -v ipv4 ]]; then
- COMPREPLY=("${COMPREPLY[@]/*:*$suffix/}")
+ if [[ $ipv4 ]]; then
+ known_hosts=("${known_hosts[@]/*:*$suffix/}")
fi
- if [[ -v ipv6 ]]; then
- COMPREPLY=("${COMPREPLY[@]/+([0-9]).+([0-9]).+([0-9]).+([0-9])$suffix/}")
+ if [[ $ipv6 ]]; then
+ known_hosts=("${known_hosts[@]/+([0-9]).+([0-9]).+([0-9]).+([0-9])$suffix/}")
fi
- if [[ -v ipv4 || -v ipv6 ]]; then
- for i in "${!COMPREPLY[@]}"; do
- [[ ${COMPREPLY[i]} ]] || unset -v "COMPREPLY[i]"
+ if [[ $ipv4 || $ipv6 ]]; then
+ for i in "${!known_hosts[@]}"; do
+ [[ ${known_hosts[i]} ]] || unset -v 'known_hosts[i]'
done
fi
+ ((${#known_hosts[@]})) || return 1
- __ltrim_colon_completions "$prefix$cur"
-
-} # _known_hosts_real()
-complete -F _known_hosts traceroute traceroute6 \
- fping fping6 telnet rsh rlogin ftp dig mtr ssh-installkeys showmount
-
-# This meta-cd function observes the CDPATH variable, so that cd additionally
-# completes on directories under those specified in CDPATH.
+ _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 mtr 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.
#
-_cd()
+_comp__find_original_word()
{
- local cur prev words cword
- _init_completion || return
+ REPLY=$1
- local IFS=$'\n' i j k
+ # If CWORD or WORDS are undefined, we return the first argument without any
+ # processing.
+ [[ -v cword && -v words ]] || return 0
- compopt -o filenames
-
- # Use standard dir completion if no CDPATH or parameter starts with /,
- # ./ or ../
- if [[ -z ${CDPATH:-} || $cur == ?(.)?(.)/* ]]; then
- _filedir -d
- return
- fi
-
- local -r mark_dirs=$(_rl_enabled mark-directories && echo y)
- local -r mark_symdirs=$(_rl_enabled mark-symlinked-directories && echo y)
-
- # we have a CDPATH, so loop on its contents
- for i in ${CDPATH//:/$'\n'}; do
- # create an array of matched subdirs
- k="${#COMPREPLY[@]}"
- for j in $(compgen -d -- $i/$cur); do
- if [[ ($mark_symdirs && -L $j || $mark_dirs && ! -L $j) && ! -d ${j#$i/} ]]; then
- j+="/"
- fi
- COMPREPLY[k++]=${j#$i/}
+ 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
-
- _filedir -d
-
- if ((${#COMPREPLY[@]} == 1)); then
- i=${COMPREPLY[0]}
- if [[ $i == "$cur" && $i != "*/" ]]; then
- COMPREPLY[0]="${i}/"
- fi
- fi
-
- return
+ REPLY=$i
}
-if shopt -q cdable_vars; then
- complete -v -F _cd -o nospace cd pushd
-else
- complete -F _cd -o nospace cd pushd
-fi
-
-# A _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.
-#
-_command()
-{
- 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
- _command_offset $offset
-}
-
# 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.
#
-_command_offset()
+# @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 word_offset=$1 i j
+ local i tail
for ((i = 0; i < word_offset; i++)); do
- for ((j = 0; j <= ${#COMP_LINE}; j++)); do
- [[ $COMP_LINE == "${COMP_WORDS[i]}"* ]] && break
- COMP_LINE=${COMP_LINE:1}
- ((COMP_POINT--))
- done
- COMP_LINE=${COMP_LINE#"${COMP_WORDS[i]}"}
- ((COMP_POINT -= ${#COMP_WORDS[i]}))
+ tail=${COMP_LINE#*"${COMP_WORDS[i]}"}
+ ((COMP_POINT -= ${#COMP_LINE} - ${#tail}))
+ COMP_LINE=$tail
done
# shift COMP_WORDS elements and adjust COMP_CWORD
- for ((i = 0; i <= COMP_CWORD - word_offset; i++)); do
- COMP_WORDS[i]=${COMP_WORDS[i + word_offset]}
- done
- for ((i; i <= COMP_CWORD; i++)); do
- unset 'COMP_WORDS[i]'
- done
+ COMP_WORDS=("${COMP_WORDS[@]:word_offset}")
((COMP_CWORD -= word_offset))
COMPREPLY=()
local cur
- _get_comp_words_by_ref cur
+ _comp_get_words cur
if ((COMP_CWORD == 0)); then
- local IFS=$'\n'
- compopt -o filenames
- COMPREPLY=($(compgen -d -c -- "$cur"))
+ _comp_compgen_commands
else
- local cmd=${COMP_WORDS[0]} compcmd=${COMP_WORDS[0]}
- local cspec=$(complete -p $cmd 2>/dev/null)
+ _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=$(complete -p "${cmd##*/}" 2>/dev/null)
[[ $cspec ]] && compcmd=${cmd##*/}
fi
# If still nothing, just load it for the basename
if [[ ! $cspec ]]; then
compcmd=${cmd##*/}
- _completion_loader $compcmd
- cspec=$(complete -p $compcmd 2>/dev/null)
+ _comp_load -D -- "$compcmd"
+ cspec=$(complete -p "$compcmd" 2>/dev/null)
fi
- if [[ -n $cspec ]]; then
- if [[ ${cspec#* -F } != "$cspec" ]]; then
- # complete -F <function>
+ 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 <function>
+
+ # 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=()
- # get function name
- local func=${cspec#*-F }
- func=${func%% *}
+ cspec=$(complete -p "$compcmd" 2>/dev/null)
- if ((${#COMP_WORDS[@]} >= 2)); then
- $func $cmd "${COMP_WORDS[-1]}" "${COMP_WORDS[-2]}"
+ # 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
- $func $cmd "${COMP_WORDS[-1]}"
+ # complete -C <command>
+
+ # 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
@@ -1949,53 +2867,93 @@ _command_offset()
# FIXME: should we take "+o opt" into account?
cspec=${cspec#*-o }
opt=${cspec%% *}
- compopt -o $opt
- cspec=${cspec#$opt}
+ compopt -o "$opt"
+ cspec=${cspec#"$opt"}
done
else
cspec=${cspec#complete}
- cspec=${cspec%%$compcmd}
- COMPREPLY=($(eval compgen "$cspec" -- '$cur'))
+ cspec=${cspec%%@("$compcmd"|"'${compcmd//\'/\'\\\'\'}'")}
+ eval "_comp_compgen -- $cspec"
fi
- elif ((${#COMPREPLY[@]} == 0)); then
- # XXX will probably never happen as long as completion loader loads
- # *something* for every command thrown at it ($cspec != empty)
- _minimal
- fi
+ break
+ done
fi
}
-complete -F _command aoss command "do" else eval exec ltrace nice nohup padsp \
+
+# 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
-_root_command()
+# @since 2.12
+_comp_root_command()
{
local PATH=$PATH:/sbin:/usr/sbin:/usr/local/sbin
- local root_command=$1
- _command
+ local _comp_root_command=$1
+ _comp_command
}
-complete -F _root_command fakeroot gksu gksudo kdesudo really
+complete -F _comp_root_command fakeroot gksu gksudo kdesudo really
# Return true if the completion should be treated as running as root
-_complete_as_root()
+#
+# @since 2.12
+_comp_as_root()
{
- [[ $EUID -eq 0 || ${root_command:-} ]]
+ [[ $EUID -eq 0 || ${_comp_root_command-} ]]
}
-_longopt()
+# 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()
{
- local cur prev words cword split
- _init_completion -s || return
+ [[ ! ${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*)
- _filedir -d
+ _comp_compgen -a filedir -d
return
;;
--!(no-*)@(file|path)*)
- _filedir
+ _comp_compgen -a filedir
return
;;
--+([-a-z0-9_]))
@@ -2003,60 +2961,65 @@ _longopt()
"s|.*$prev\[\{0,1\}=[<[]\{0,1\}\([-A-Za-z0-9_]\{1,\}\).*|\1|p")
case ${argtype,,} in
*dir*)
- _filedir -d
+ _comp_compgen -a filedir -d
return
;;
*file* | *path*)
- _filedir
+ _comp_compgen -a filedir
return
;;
esac
;;
esac
- $split && return
+ [[ $was_split ]] && return
if [[ $cur == -* ]]; then
- COMPREPLY=($(compgen -W "$(LC_ALL=C $1 --help 2>&1 |
+ _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)" -- "$cur"))
+ printf '%s\n' "${BASH_REMATCH[0]}"
+ done)"
[[ ${COMPREPLY-} == *= ]] && compopt -o nospace
elif [[ $1 == *@(rmdir|chroot) ]]; then
- _filedir -d
+ _comp_compgen -a filedir -d
else
[[ $1 == *mkdir ]] && compopt -o nospace
- _filedir
+ _comp_compgen -a filedir
fi
}
# makeinfo and texi2dvi are defined elsewhere.
-complete -F _longopt a2ps awk base64 bash bc bison cat chroot colordiff cp \
+complete -F _comp_complete_longopt \
+ a2ps awk base64 bash bc bison cat chroot colordiff cp \
csplit cut date df diff dir du enscript env expand fmt fold gperf \
- grep grub head irb ld ldd less ln ls m4 md5sum mkdir mkfifo mknod \
+ 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 sha{,1,224,256,384,512}sum shar sort split strip sum tac tail tee \
+ sed seq shar sort split strip sum tac tail tee \
texindex touch tr uname unexpand uniq units vdir wc who
-declare -Ag _xspecs
+# @since 2.12
+declare -Ag _comp_xspecs
-_filedir_xspec()
+# @since 2.12
+_comp_complete_filedir_xspec()
{
- local cur prev words cword
- _init_completion || return
+ local cur prev words cword comp_args
+ _comp_initialize -- "$@" || return
+ _comp_compgen_filedir_xspec "$1"
+}
- _tilde "$cur" || return
+# @since 2.12
+_comp_compgen_filedir_xspec()
+{
+ _comp_compgen_tilde && return
- local IFS=$'\n' xspec=${_xspecs[${1##*/}]} tmp
- local -a toks
+ local REPLY
+ _comp_quote_compgen "$cur"
+ local quoted=$REPLY
- toks=($(
- compgen -d -- "$(quote_readline "$cur")" | {
- while read -r tmp; do
- printf '%s\n' $tmp
- done
- }
- ))
+ 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
@@ -2069,198 +3032,318 @@ _filedir_xspec()
fi
xspec="$matchop($xspec|${xspec^^})"
- toks+=($(
- eval compgen -f -X "'!$xspec'" -- '$(quote_readline "$cur")' | {
- while read -r tmp; do
- [[ -n $tmp ]] && printf '%s\n' $tmp
- done
- }
- ))
+ _comp_compgen -av toks -c "$quoted" -- -f -X "@(|!($xspec))"
# Try without filter if it failed to produce anything and configured to
- [[ -n ${COMP_FILEDIR_FALLBACK:-} && ${#toks[@]} -lt 1 ]] && {
- local reset=$(shopt -po noglob)
- set -o noglob
- toks+=($(compgen -f -- "$(quote_readline "$cur")"))
- IFS=' '
- $reset
- IFS=$'\n'
- }
+ [[ ${BASH_COMPLETION_FILEDIR_FALLBACK-} && ${#toks[@]} -lt 1 ]] &&
+ _comp_compgen -av toks -c "$quoted" -- -f
- if ((${#toks[@]} != 0)); then
- compopt -o filenames
- COMPREPLY=("${toks[@]}")
- fi
+ ((${#toks[@]})) || return 1
+
+ compopt -o filenames
+ _comp_compgen -RU toks -- -W '"${toks[@]}"'
}
-_install_xspec()
+_comp__init_install_xspec()
{
local xspec=$1 cmd
shift
for cmd in "$@"; do
- _xspecs[$cmd]=$xspec
+ _comp_xspecs[$cmd]=$xspec
done
}
# bzcmp, bzdiff, bz*grep, bzless, bzmore intentionally not here, see Debian: #455510
-_install_xspec '!*.?(t)bz?(2)' bunzip2 bzcat pbunzip2 pbzcat lbunzip2 lbzcat
-_install_xspec '!*.@(zip|[aegjswx]ar|exe|pk3|wsz|zargo|xpi|s[tx][cdiw]|sx[gm]|o[dt][tspgfc]|od[bm]|oxt|epub|apk|aab|ipa|do[ct][xm]|p[op]t[mx]|xl[st][xm]|pyz|whl)' unzip zipinfo
-_install_xspec '*.Z' compress znew
+_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|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
-_install_xspec '!*.@(Z|[gGd]z|t[ag]z)' gunzip zcat
-_install_xspec '!*.@(Z|[gGdz]z|t[ag]z)' unpigz
-_install_xspec '!*.Z' uncompress
+_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
-_install_xspec '!*.@(tlz|lzma)' lzcat lzegrep lzfgrep lzgrep lzless lzmore unlzma
-_install_xspec '!*.@(?(t)xz|tlz|lzma)' unxz xzcat
-_install_xspec '!*.lrz' lrunzip
-_install_xspec '!*.@(gif|jp?(e)g|miff|tif?(f)|pn[gm]|p[bgp]m|bmp|xpm|ico|xwd|tga|pcx)' ee
-_install_xspec '!*.@(gif|jp?(e)g|tif?(f)|png|p[bgp]m|bmp|x[bp]m|rle|rgb|pcx|fits|pm|svg)' qiv
-_install_xspec '!*.@(gif|jp?(e)g?(2)|j2[ck]|jp[2f]|tif?(f)|png|p[bgp]m|bmp|x[bp]m|rle|rgb|pcx|fits|pm|?(e)ps)' xv
-_install_xspec '!*.@(@(?(e)ps|?(E)PS|pdf|PDF)?(.gz|.GZ|.bz2|.BZ2|.Z))' gv ggv kghostview
-_install_xspec '!*.@(dvi|DVI)?(.@(gz|Z|bz2))' xdvi kdvi
-_install_xspec '!*.dvi' dvips dviselect dvitype dvipdf advi dvipdfm dvipdfmx
-_install_xspec '!*.[pf]df' acroread gpdf xpdf
-_install_xspec '!*.@(?(e)ps|pdf)' kpdf
-_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)?(.?(gz|GZ|bz2|BZ2|xz|XZ)))' okular
-_install_xspec '!*.pdf' epdfview pdfunite
-_install_xspec '!*.@(cb[rz7t]|djv?(u)|?(e)ps|pdf)' zathura
-_install_xspec '!*.@(?(e)ps|pdf)' ps2pdf ps2pdf12 ps2pdf13 ps2pdf14 ps2pdfwr
-_install_xspec '!*.texi*' makeinfo texi2html
-_install_xspec '!*.@(?(la)tex|texi|dtx|ins|ltx|dbj)' tex latex slitex jadetex pdfjadetex pdftex pdflatex texi2dvi xetex xelatex luatex lualatex
-_install_xspec '!*.mp3' mpg123 mpg321 madplay
-_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 fbxine
-_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|iso|ISO)|+([0-9]).@(vdr|VDR))?(.@(crdownload|part))' kaffeine dragon totem
-_install_xspec '!*.@(avi|asf|wmv)' aviplay
-_install_xspec '!*.@(rm?(j)|ra?(m)|smi?(l))' realplay
-_install_xspec '!*.@(mpg|mpeg|avi|mov|qt)' xanim
-_install_xspec '!*.@(og[ag]|m3u|flac|spx)' ogg123
-_install_xspec '!*.@(mp3|ogg|pls|m3u)' gqmpeg freeamp
-_install_xspec '!*.fig' xfig
-_install_xspec '!*.@(mid?(i)|cmf)' playmidi
-_install_xspec '!*.@(mid?(i)|rmi|rcp|[gr]36|g18|mod|xm|it|x3m|s[3t]m|kar)' timidity
-_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
-_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
-_install_xspec '!*.@(zip|z|gz|tgz)' bzme
+_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[bgp]m|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
-_install_xspec '!*.@(?([xX]|[sS])[hH][tT][mM]?([lL]))' netscape mozilla lynx galeon dillo elinks amaya epiphany
-_install_xspec '!*.@(sxw|stw|sxg|sgl|doc?([mx])|dot?([mx])|rtf|txt|htm|html|?(f)odt|ott|odm|pdf)' oowriter lowriter
-_install_xspec '!*.@(sxi|sti|pps?(x)|ppt?([mx])|pot?([mx])|?(f)odp|otp)' ooimpress loimpress
-_install_xspec '!*.@(sxc|stc|xls?([bmx])|xlw|xlt?([mx])|[ct]sv|?(f)ods|ots)' oocalc localc
-_install_xspec '!*.@(sxd|std|sda|sdd|?(f)odg|otg)' oodraw lodraw
-_install_xspec '!*.@(sxm|smf|mml|odf)' oomath lomath
-_install_xspec '!*.odb' oobase lobase
-_install_xspec '!*.[rs]pm' rpm2cpio
-_install_xspec '!*.aux' bibtex
-_install_xspec '!*.po' poedit gtranslator kbabel lokalize
-_install_xspec '!*.@([Pp][Rr][Gg]|[Cc][Ll][Pp])' harbour gharbour hbpp
-_install_xspec '!*.[Hh][Rr][Bb]' hbrun
-_install_xspec '!*.ly' lilypond ly2dvi
-_install_xspec '!*.@(dif?(f)|?(d)patch)?(.@([gx]z|bz2|lzma))' cdiff
-_install_xspec '!@(*.@(ks|jks|jceks|p12|pfx|bks|ubr|gkr|cer|crt|cert|p7b|pkipath|pem|p10|csr|crl)|cacerts)' portecle
-_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 _install_xspec
-
-# Minimal completion to use as fallback in _completion_loader.
-_minimal()
-{
- local cur prev words cword split
- _init_completion -s || return
- $split && return
- _filedir
+_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 _minimal ''
+complete -F _comp_complete_minimal ''
-__load_completion()
+# @since 2.12
+_comp_load()
{
- local -a dirs=(${BASH_COMPLETION_USER_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion}/completions)
- local ifs=$IFS IFS=: dir cmd="${1##*/}" compfile
- [[ -n $cmd ]] || return 1
- for dir in ${XDG_DATA_DIRS:-/usr/local/share:/usr/share}; do
- dirs+=($dir/bash-completion/completions)
+ 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
- IFS=$ifs
+ shift "$((OPTIND - 1))"
- if [[ $BASH_SOURCE == */* ]]; then
- dirs+=("${BASH_SOURCE%/*}/completions")
- else
- dirs+=(./completions)
- fi
+ local cmd=$1 cmdname=${1##*/} dir compfile
+ local -a paths
+ [[ $cmdname ]] || return 1
local backslash=
if [[ $cmd == \\* ]]; then
- cmd="${cmd:1}"
+ 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
- for dir in "${dirs[@]}"; do
- [[ -d $dir ]] || continue
- for compfile in "$cmd" "$cmd.bash" "_$cmd"; do
- compfile="$dir/$compfile"
- # Avoid trying to source dirs; https://bugzilla.redhat.com/903540
- if [[ -f $compfile ]] && . "$compfile" &>/dev/null; then
- [[ $backslash ]] && $(complete -p "$cmd") "\\$cmd"
- return 0
+ # 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.)
+ if [[ $BASH_SOURCE == */* ]]; then
+ dirs+=("${BASH_SOURCE%/*}/completions")
+ else
+ dirs+=(./completions)
+ fi
+
+ # 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 _xspecs[$cmd] ]] &&
- complete -F _filedir_xspec "$cmd" "$backslash$cmd" && return 0
+ [[ -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
-_completion_loader()
+# @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_}"
+ local cmd=${1:-_EmptycmD_}
- __load_completion "$cmd" && return 124
-
- # Need to define *something*, otherwise there will be no completion at all.
- complete -F _minimal -- "$cmd" && return 124
+ # Pass -D to define *something*, or otherwise there will be no completion
+ # at all.
+ _comp_load -D -- "$cmd" && return 124
} &&
- complete -D -F _completion_loader
+ 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... function and its arguments
-_xfunc()
-{
- set -- "$@"
- local srcfile=$1
- shift
- declare -F $1 &>/dev/null || __load_completion "$srcfile"
- "$@"
+# @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
+
# source compat completion directory definitions
-compat_dir=${BASH_COMPLETION_COMPAT_DIR:-/etc/bash_completion.d}
-if [[ -d $compat_dir && -r $compat_dir && -x $compat_dir ]]; then
- for i in "$compat_dir"/*; do
- [[ ${i##*/} != @($_backup_glob|Makefile*|$_blacklist_glob) && -f \
- $i && -r $i ]] && . "$i"
- done
+_comp__init_compat_dirs=()
+if [[ ${BASH_COMPLETION_COMPAT_DIR-} ]]; then
+ _comp__init_compat_dirs+=("$BASH_COMPLETION_COMPAT_DIR")
+else
+ _comp__init_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 [[ ${BASH_SOURCE%/*} == */share/bash-completion ]]; then
+ _comp__init_compat_dir=${BASH_SOURCE%/share/bash-completion/*}/etc/bash_completion.d
+ elif [[ $BASH_SOURCE == */* ]]; then
+ _comp__init_compat_dir="${BASH_SOURCE%/*}/bash_completion.d"
+ else
+ _comp__init_compat_dir=./bash_completion.d
+ fi
+ [[ ${_comp__init_compat_dirs[0]} == "$_comp__init_compat_dir" ]] ||
+ _comp__init_compat_dirs+=("$_comp__init_compat_dir")
fi
-unset compat_dir i _blacklist_glob
+for _comp__init_compat_dir in "${_comp__init_compat_dirs[@]}"; do
+ [[ -d $_comp__init_compat_dir && -r $_comp__init_compat_dir && -x $_comp__init_compat_dir ]] || continue
+ for _comp__init_file in "$_comp__init_compat_dir"/*; do
+ [[ ${_comp__init_file##*/} != @($_comp_backup_glob|Makefile*|${BASH_COMPLETION_COMPAT_IGNORE-}) &&
+ -f $_comp__init_file && -r $_comp__init_file ]] && . "$_comp__init_file"
+ done
+done
+unset -v _comp__init_compat_dirs _comp__init_compat_dir _comp__init_file
# source user completion file
-user_completion=${BASH_COMPLETION_USER_FILE:-~/.bash_completion}
-[[ ${BASH_SOURCE[0]} != "$user_completion" && -r $user_completion && -f $user_completion ]] &&
- . $user_completion
-unset user_completion
+#
+# 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.
+_comp__init_user_file=${BASH_COMPLETION_USER_FILE:-~/.bash_completion}
+[[ $_comp__init_user_file != "${BASH_SOURCE[0]}" && $_comp__init_user_file != /dev/null && -r $_comp__init_user_file && -f $_comp__init_user_file ]] &&
+ . "$_comp__init_user_file"
+unset -v _comp__init_user_file
unset -f have
-unset have
+unset -v have
-set $BASH_COMPLETION_ORIGINAL_V_VALUE
-unset BASH_COMPLETION_ORIGINAL_V_VALUE
+set $_comp__init_original_set_v
+unset -v _comp__init_original_set_v
# ex: filetype=sh