diff --git a/bash_completion b/bash_completion index bf42e00885b..38fd4cb4a00 100644 --- a/bash_completion +++ b/bash_completion @@ -655,29 +655,111 @@ _comp_compgen() printf 'bash_completion: %s: unrecognized generator `%s'\'' (function %s not found)\n' "$FUNCNAME" "$1" "${_generator[0]}" >&2 return 2 fi + shift - ((${#_upvars[@]})) && _comp_unlocal "${_upvars[@]}" + _comp_compgen__call_generator "$@" + else + # usage: _comp_compgen [options] -- [compgen_options] + if [[ $_icmd || $_xcmd ]]; then + printf 'bash_completion: %s: generator name is unspecified for `%s'\''\n' "$FUNCNAME" "${_icmd:+-i $_icmd}${_xcmd:+x $_xcmd}" >&2 + return 2 + fi + + # Note: $* in the below checks would be affected by uncontrolled IFS in + # bash >= 5.0, so we need to set IFS to the normal value. The behavior + # in bash < 5.0, where unquoted $* in conditional command did not honor + # IFS, was a bug. + # Note: Also, ${_cur:+-- "$_cur"} and ${_append:+-a} would be affected + # by uncontrolled IFS. + local IFS=$' \t\n' + # Note: extglob *\$?(\{)[0-9]* can be extremely slow when the string + # "${*:2:_nopt}" becomes longer, so we test \$[0-9] and \$\{[0-9] + # separately. + if [[ $* == *\$[0-9]* || $* == *\$\{[0-9]* ]]; then + printf 'bash_completion: %s: positional parameter $1, $2, ... do not work inside this function\n' "$FUNCNAME" >&2 + return 2 + fi + _comp_compgen__call_builtin "$@" + fi +} + +# Helper function for _comp_compgen. This function calls a generator. +# @param $1... generator_args +# @var[in] _dir +# @var[in] _cur +# @arr[in] _generator +# @arr[in] _upvars +# @var[in] _append +# @var[in] _var +_comp_compgen__call_generator() +{ + ((${#_upvars[@]})) && _comp_unlocal "${_upvars[@]}" + + if [[ $_dir ]]; then + local _original_pwd=$PWD + local PWD=${PWD-} OLDPWD=${OLDPWD-} + # Note: We also redirect stdout because `cd` may output the target + # directory to stdout when CDPATH is set. + command cd -- "$_dir" &>/dev/null || + { + _comp_compgen__error_fallback + return + } + fi + + local _comp_compgen__append=$_append + local _comp_compgen__var=$_var + local _comp_compgen__cur=$_cur cur=$_cur + # Note: we use $1 as a part of a function name, and we use $2... as + # arguments to the function if any. + # shellcheck disable=SC2145 + "${_generator[@]}" "$@" + local _status=$? + + # Go back to the original directory. + # Note: Failure of this line results in the change of the current + # directory visible to the user. We intentionally do not redirect + # stderr so that the error message appear in the terminal. + # shellcheck disable=SC2164 + [[ $_dir ]] && command cd -- "$_original_pwd" + + return "$_status" +} + +# Helper function for _comp_compgen. This function calls the builtin compgen. +# @param $1... compgen_args +# @var[in] _dir +# @var[in] _ifs +# @var[in] _cur +# @arr[in] _upvars +# @var[in] _append +# @var[in] _var +if ((BASH_VERSINFO[0] > 5 || BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >= 3)); then + # bash >= 5.3 has `compgen -V array_name` + _comp_compgen__call_builtin() + { if [[ $_dir ]]; then local _original_pwd=$PWD local PWD=${PWD-} OLDPWD=${OLDPWD-} # Note: We also redirect stdout because `cd` may output the target # directory to stdout when CDPATH is set. - command cd -- "$_dir" &>/dev/null || - { - _comp_compgen__error_fallback - return - } + 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=$? + local -a _result=() + + # Note: We specify -X '' to exclude empty completions to make the + # behavior consistent with the implementation for Bash < 5.3 where + # `_comp_split -l` removes empty lines. If the caller specifies -X + # pat, the effect of -X '' is overwritten by the specified one. + IFS=$_ifs compgen -V _result -X '' "$@" ${_cur:+-- "$_cur"} || { + _comp_compgen__error_fallback + return + } # Go back to the original directory. # Note: Failure of this line results in the change of the current @@ -686,46 +768,35 @@ _comp_compgen() # 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 + ((${#_upvars[@]})) && _comp_unlocal "${_upvars[@]}" + ((${#_result[@]})) || return + if [[ $_append ]]; then + eval -- "$_var+=(\"\${_result[@]}\")" + else + eval -- "$_var=(\"\${_result[@]}\")" fi - IFS=$_ifs compgen "$@" ${_cur:+-- "$_cur"} - ) || { - _comp_compgen__error_fallback return } +else + _comp_compgen__call_builtin() + { + local _result + _result=$( + if [[ $_dir ]]; then + # Note: We also redirect stdout because `cd` may output the target + # directory to stdout when CDPATH is set. + command cd -- "$_dir" &>/dev/null || return + fi + IFS=$_ifs compgen "$@" ${_cur:+-- "$_cur"} + ) || { + _comp_compgen__error_fallback + return + } - ((${#_upvars[@]})) && _comp_unlocal "${_upvars[@]}" - _comp_split -l ${_append:+-a} "$_var" "$_result" -} + ((${#_upvars[@]})) && _comp_unlocal "${_upvars[@]}" + _comp_split -l ${_append:+-a} "$_var" "$_result" + } +fi # usage: _comp_compgen_set [words...] # Reset COMPREPLY with the specified WORDS. If no arguments are specified, the