diff options
author | Gabriel F. T. Gomes <gabriel@inconstante.net.br> | 2020-08-03 18:43:13 -0300 |
---|---|---|
committer | Gabriel F. T. Gomes <gabriel@inconstante.net.br> | 2020-08-03 18:43:13 -0300 |
commit | 95623d39d6029ba78ec96ad5ea08e9ac12629b91 (patch) | |
tree | ea0fe36eb5e6f40e0a1f765d44c4b0c0b2bfb089 /bash_completion | |
parent | 019f3cc463db63abc6460f97deb488deec43840b (diff) | |
download | bash-completion-95623d39d6029ba78ec96ad5ea08e9ac12629b91.tar.gz |
New upstream version 2.11upstream/2.11upstream
Diffstat (limited to 'bash_completion')
-rw-r--r-- | bash_completion | 1164 |
1 files changed, 633 insertions, 531 deletions
diff --git a/bash_completion b/bash_completion index 6ec510e5..1a7f5634 100644 --- a/bash_completion +++ b/bash_completion @@ -1,9 +1,9 @@ # -*- shell-script -*- # -# bash_completion - programmable completion functions for bash 4.1+ +# bash_completion - programmable completion functions for bash 4.2+ # # Copyright © 2006-2008, Ian Macdonald <ian@caliban.org> -# © 2009-2019, Bash Completion Maintainers +# © 2009-2020, Bash Completion Maintainers # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -23,7 +23,7 @@ # # https://github.com/scop/bash-completion -BASH_COMPLETION_VERSINFO=(2 10) +BASH_COMPLETION_VERSINFO=(2 11) if [[ $- == *v* ]]; then BASH_COMPLETION_ORIGINAL_V_VALUE="-v" @@ -87,18 +87,18 @@ _userland() { local userland=$(uname -s) [[ $userland == @(Linux|GNU/*) ]] && userland=GNU - [[ $userland == $1 ]] + [[ $userland == "$1" ]] } # This function sets correct SysV init directories # _sysvdirs() { - sysvdirs=( ) - [[ -d /etc/rc.d/init.d ]] && sysvdirs+=( /etc/rc.d/init.d ) - [[ -d /etc/init.d ]] && sysvdirs+=( /etc/init.d ) + 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 ) + [[ -f /etc/slackware-version ]] && sysvdirs=(/etc/rc.d) return 0 } @@ -138,19 +138,17 @@ quote() # @see _quote_readline_by_ref() quote_readline() { - local quoted + local ret _quote_readline_by_ref "$1" ret printf %s "$ret" } # quote_readline() - # This function shell-dequotes the argument dequote() { eval printf %s "$1" 2>/dev/null } - # Assign variable one scope above the caller # Usage: local "$1" && _upvar $1 "value(s)" # Param: $1 Variable name to assign value to @@ -159,21 +157,20 @@ dequote() # 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: http://fvue.nl/wiki/Bash:_Passing_variables_by_reference +# 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 + if unset -v "$1"; then # Unset & validate varname + if (($# == 2)); then + eval $1=\"\$2\" # Return single value else - eval $1=\(\"\${@:2}\"\) # Return array + eval $1=\(\"\$"{@:2}"\"\) # Return array fi fi } - # Assign variables one scope above the caller # Usage: local varname [varname ...] && # _upvars [-v varname value] | [-aN varname [value ...]] ... @@ -181,43 +178,54 @@ _upvar() # -aN Assign next N values to varname as array # -v Assign single value to varname # Return: 1 if error occurs -# See: http://fvue.nl/wiki/Bash:_Passing_variables_by_reference +# See: https://fvue.nl/wiki/Bash:_Passing_variables_by_reference _upvars() { - if ! (( $# )); then + if ! (($#)); then echo "bash_completion: $FUNCNAME: usage: $FUNCNAME" \ "[-v varname value] | [-aN varname [value ...]] ..." >&2 return 2 fi - while (( $# )); do + while (($#)); do case $1 in -a*) # Error checking - [[ ${1#-a} ]] || { echo "bash_completion: $FUNCNAME:" \ - "\`$1': missing number specifier" >&2; return 1; } - printf %d "${1#-a}" &>/dev/null || { echo bash_completion: \ - "$FUNCNAME: \`$1': invalid number specifier" >&2 - return 1; } + [[ ${1#-a} ]] || { + echo "bash_completion: $FUNCNAME:" \ + "\`$1': missing number specifier" >&2 + return 1 + } + printf %d "${1#-a}" &>/dev/null || { + echo bash_completion: \ + "$FUNCNAME: \`$1': invalid number specifier" >&2 + return 1 + } # Assign array of -aN elements - [[ "$2" ]] && unset -v "$2" && eval $2=\(\"\${@:3:${1#-a}}\"\) && - shift $((${1#-a} + 2)) || { echo bash_completion: \ - "$FUNCNAME: \`$1${2+ }$2': missing argument(s)" \ - >&2; return 1; } + [[ "$2" ]] && unset -v "$2" && eval $2=\(\"\$"{@:3:${1#-a}}"\"\) && + shift $((${1#-a} + 2)) || { + echo bash_completion: \ + "$FUNCNAME: \`$1${2+ }$2': missing argument(s)" \ + >&2 + return 1 + } ;; -v) # Assign single value [[ "$2" ]] && unset -v "$2" && eval $2=\"\$3\" && - shift 3 || { echo "bash_completion: $FUNCNAME: $1:" \ - "missing argument(s)" >&2; return 1; } + shift 3 || { + echo "bash_completion: $FUNCNAME: $1:" \ + "missing argument(s)" >&2 + return 1 + } ;; *) echo "bash_completion: $FUNCNAME: $1: invalid option" >&2 - return 1 ;; + return 1 + ;; esac done } - # 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 @@ -234,49 +242,49 @@ __reassemble_comp_words_by_ref() if [[ $1 ]]; then # Yes, exclude word separator characters; # Exclude only those characters, which were really included - exclude="${1//[^$COMP_WORDBREAKS]}" + exclude="[${1//[^$COMP_WORDBREAKS]/}]" fi # Default to cword unchanged printf -v "$3" %s "$COMP_CWORD" # Are characters excluded which were former included? - if [[ $exclude ]]; then + if [[ -v exclude ]]; then # Yes, list of word completion separators has shrunk; line=$COMP_LINE # Re-assemble words to complete - for (( i=0, j=0; i < ${#COMP_WORDS[@]}; i++, j++)); do + for ((i = 0, j = 0; i < ${#COMP_WORDS[@]}; i++, j++)); do # Is current word not word 0 (the command itself) and is word not # empty and is word made up of just word separator characters to # be excluded and is current word not preceded by whitespace in # original line? - while [[ $i -gt 0 && ${COMP_WORDS[$i]} == +([$exclude]) ]]; do + while [[ $i -gt 0 && ${COMP_WORDS[i]} == +($exclude) ]]; do # Is word separator not preceded by whitespace in original line # and are we not going to append to word 0 (the command # itself), then append to current word. - [[ $line != [[:blank:]]* ]] && (( j >= 2 )) && ((j--)) + [[ $line != [[:blank:]]* ]] && ((j >= 2)) && ((j--)) # Append word separator to current or new word ref="$2[$j]" - printf -v "$ref" %s "${!ref}${COMP_WORDS[i]}" + printf -v "$ref" %s "${!ref-}${COMP_WORDS[i]}" # Indicate new cword - [[ $i == $COMP_CWORD ]] && printf -v "$3" %s "$j" + ((i == COMP_CWORD)) && printf -v "$3" %s "$j" # Remove optional whitespace + word separator from line copy - line=${line#*"${COMP_WORDS[$i]}"} + line=${line#*"${COMP_WORDS[i]}"} # 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 + ((i < ${#COMP_WORDS[@]} - 1)) && ((i++)) || break 2 done # Append word to current word ref="$2[$j]" - printf -v "$ref" %s "${!ref}${COMP_WORDS[i]}" + printf -v "$ref" %s "${!ref-}${COMP_WORDS[i]}" # Remove optional whitespace + word from line copy line=${line#*"${COMP_WORDS[i]}"} # Indicate new cword - [[ $i == $COMP_CWORD ]] && printf -v "$3" %s "$j" + ((i == COMP_CWORD)) && printf -v "$3" %s "$j" done - [[ $i == $COMP_CWORD ]] && printf -v "$3" %s "$j" + ((i == COMP_CWORD)) && printf -v "$3" %s "$j" else # No, list of word completions separators hasn't changed; for i in "${!COMP_WORDS[@]}"; do @@ -285,7 +293,6 @@ __reassemble_comp_words_by_ref() 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 # we want to return host:path and not only path, so we would pass the @@ -299,45 +306,41 @@ __get_cword_at_cursor_by_ref() local cword words=() __reassemble_comp_words_by_ref "$1" words cword - local i cur index=$COMP_POINT lead=${COMP_LINE:0:$COMP_POINT} + local i cur="" index=$COMP_POINT lead=${COMP_LINE:0:COMP_POINT} # Cursor not at position 0 and not leaded by just space(s)? - if [[ $index -gt 0 && ( $lead && ${lead//[[:space:]]} ) ]]; then + if [[ $index -gt 0 && ($lead && ${lead//[[:space:]]/}) ]]; then cur=$COMP_LINE - for (( i = 0; i <= cword; ++i )); do - while [[ - # Current word fits in $cur? - ${#cur} -ge ${#words[i]} && - # $cur doesn't match cword? - "${cur:0:${#words[i]}}" != "${words[i]}" - ]]; do + 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 -gt 0 ]] && ((index--)) + ((index > 0)) && ((index--)) done # Does found word match cword? - if [[ $i -lt $cword ]]; then + 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 )) + ((index -= old_size - new_size)) fi done # Clear $cur if just space(s) - [[ $cur && ! ${cur//[[:space:]]} ]] && cur= + [[ $cur && ! ${cur//[[:space:]]/} ]] && cur= # Zero $index if negative - [[ $index -lt 0 ]] && index=0 + ((index < 0)) && index=0 fi - local "$2" "$3" "$4" && _upvars -a${#words[@]} $2 "${words[@]}" \ - -v $3 "$cword" -v $4 "${cur:0:$index}" + local "$2" "$3" "$4" && _upvars -a${#words[@]} $2 ${words+"${words[@]}"} \ + -v $3 "$cword" -v $4 "${cur:0:index}" } - # Get the word to complete and optional previous words. -# This is nicer than ${COMP_WORDS[$COMP_CWORD]}, since it handles cases +# 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 --------> ^ @@ -376,35 +379,51 @@ _get_comp_words_by_ref() n) exclude=$OPTARG ;; p) vprev=$OPTARG ;; w) vwords=$OPTARG ;; + *) + echo "bash_completion: $FUNCNAME: usage error" >&2 + return 1 + ;; esac done while [[ $# -ge $OPTIND ]]; do case ${!OPTIND} in - cur) vcur=cur ;; - prev) vprev=prev ;; + cur) vcur=cur ;; + prev) vprev=prev ;; cword) vcword=cword ;; words) vwords=words ;; - *) echo "bash_completion: $FUNCNAME: \`${!OPTIND}':" \ - "unknown argument" >&2; return 1 ;; + *) + echo "bash_completion: $FUNCNAME: \`${!OPTIND}':" \ + "unknown argument" >&2 + return 1 + ;; esac - (( OPTIND += 1 )) + ((OPTIND += 1)) done - __get_cword_at_cursor_by_ref "$exclude" words cword cur + __get_cword_at_cursor_by_ref "${exclude-}" words cword cur - [[ $vcur ]] && { upvars+=("$vcur" ); upargs+=(-v $vcur "$cur" ); } - [[ $vcword ]] && { upvars+=("$vcword"); upargs+=(-v $vcword "$cword"); } - [[ $vprev && $cword -ge 1 ]] && { upvars+=("$vprev" ); upargs+=(-v $vprev - "${words[cword - 1]}"); } - [[ $vwords ]] && { upvars+=("$vwords"); upargs+=(-a${#words[@]} $vwords - "${words[@]}"); } + [[ -v vcur ]] && { + upvars+=("$vcur") + upargs+=(-v $vcur "$cur") + } + [[ -v vcword ]] && { + upvars+=("$vcword") + upargs+=(-v $vcword "$cword") + } + [[ -v vprev && $cword -ge 1 ]] && { + upvars+=("$vprev") + upargs+=(-v $vprev "${words[cword - 1]}") + } + [[ -v vwords ]] && { + upvars+=("$vwords") + upargs+=(-a${#words[@]} $vwords ${words+"${words[@]}"}) + } - (( ${#upvars[@]} )) && local "${upvars[@]}" && _upvars "${upargs[@]}" + ((${#upvars[@]})) && local "${upvars[@]}" && _upvars "${upargs[@]}" } - # Get the word to complete. -# This is nicer than ${COMP_WORDS[$COMP_CWORD]}, since it handles cases +# 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 --------> ^ @@ -422,51 +441,47 @@ _get_cword() { local LC_CTYPE=C local cword words - __reassemble_comp_words_by_ref "$1" words cword + __reassemble_comp_words_by_ref "${1-}" words cword # return previous word offset by $2 - if [[ ${2//[^0-9]/} ]]; then - printf "%s" "${words[cword-$2]}" - elif [[ "${#words[cword]}" -eq 0 || "$COMP_POINT" == "${#COMP_LINE}" ]]; then - printf "%s" "${words[cword]}" + 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 - while [[ - # Current word fits in $cur? - "${#cur}" -ge ${#words[i]} && - # $cur doesn't match cword? - "${cur:0:${#words[i]}}" != "${words[i]}" - ]]; do + 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 -gt 0 ]] && ((index--)) + ((index > 0)) && ((index--)) done - # Does found word matches cword? - if [[ "$i" -lt "$cword" ]]; then + # 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 )) + ((index -= old_size - new_size)) fi done - if [[ "${words[cword]:0:${#cur}}" != "$cur" ]]; then + 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}" + 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 @@ -476,12 +491,11 @@ _get_cword() # _get_pword() { - if [[ $COMP_CWORD -ge 1 ]]; then + 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 @@ -495,23 +509,22 @@ _get_pword() # COMP_WORDBREAKS=${COMP_WORDBREAKS//:} # # See also: Bash FAQ - E13) Why does filename completion misbehave if a colon -# appears in the filename? - http://tiswww.case.edu/php/chet/bash/FAQ +# appears in the filename? - https://tiswww.case.edu/php/chet/bash/FAQ # @param $1 current word to complete (cur) # @modifies global array $COMPREPLY # __ltrim_colon_completions() { - if [[ "$1" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then + if [[ $1 == *:* && $COMP_WORDBREAKS == *:* ]]; then # Remove colon-word prefix from COMPREPLY items local colon_word=${1%"${1##*:}"} local i=${#COMPREPLY[*]} - while [[ $((--i)) -ge 0 ]]; do - COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"} + while ((i-- > 0)); do + COMPREPLY[i]=${COMPREPLY[i]#"$colon_word"} done fi } # __ltrim_colon_completions() - # 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: @@ -523,9 +536,8 @@ __ltrim_colon_completions() # a\'b/c # # See also: -# - http://lists.gnu.org/archive/html/bug-bash/2009-03/msg00155.html -# - http://www.mail-archive.com/bash-completion-devel@lists.alioth.\ -# debian.org/msg01944.html +# - 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() @@ -538,12 +550,11 @@ _quote_readline_by_ref() fi # If result becomes quoted like this: $'string', re-evaluate in order to - # drop the additional quoting. See also: http://www.mail-archive.com/ - # bash-completion-devel@lists.alioth.debian.org/msg01942.html + # 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 @@ -554,51 +565,61 @@ _filedir() { local IFS=$'\n' - _tilde "$cur" || return + _tilde "${cur-}" || return local -a toks - local reset + local reset arg=${1-} - if [[ "$1" == -d ]]; then - reset=$(shopt -po noglob); set -o noglob - toks=( $(compgen -d -- "$cur") ) - IFS=' '; $reset; IFS=$'\n' + if [[ $arg == -d ]]; then + reset=$(shopt -po noglob) + set -o noglob + toks=($(compgen -d -- "${cur-}")) + IFS=' ' + $reset + IFS=$'\n' else local quoted - _quote_readline_by_ref "$cur" quoted + _quote_readline_by_ref "${cur-}" quoted # Munge xspec to contain uppercase version too - # http://thread.gmane.org/gmane.comp.shells.bash.bugs/15294/focus=15306 - local xspec=${1:+"!*.@($1|${1^^})"} plusdirs=() + # https://lists.gnu.org/archive/html/bug-bash/2010-09/msg00036.html + # news://news.gmane.io/4C940E1C.1010304@case.edu + local xspec=${arg:+"!*.@($arg|${arg^^})"} plusdirs=() # Use plusdirs to get dir completions if we have a xspec; if we don't, # there's no need, dirs come along with other completions. Don't use # plusdirs quite yet if fallback is in use though, in order to not ruin # the fallback condition with the "plus" dirs. - local opts=( -f -X "$xspec" ) + local opts=(-f -X "$xspec") [[ $xspec ]] && plusdirs=(-o plusdirs) - [[ ${COMP_FILEDIR_FALLBACK-} ]] || opts+=( "${plusdirs[@]}" ) + [[ ${COMP_FILEDIR_FALLBACK-} || -z ${plusdirs-} ]] || + opts+=("${plusdirs[@]}") - reset=$(shopt -po noglob); set -o noglob - toks+=( $(compgen "${opts[@]}" -- $quoted) ) - IFS=' '; $reset; IFS=$'\n' + reset=$(shopt -po noglob) + set -o noglob + toks+=($(compgen "${opts[@]}" -- $quoted)) + IFS=' ' + $reset + IFS=$'\n' # Try without filter if it failed to produce anything and configured to - [[ -n ${COMP_FILEDIR_FALLBACK:-} && -n "$1" && ${#toks[@]} -lt 1 ]] && { - reset=$(shopt -po noglob); set -o noglob - toks+=( $(compgen -f "${plusdirs[@]}" -- $quoted) ) - IFS=' '; $reset; IFS=$'\n' + [[ -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' } fi - if [[ ${#toks[@]} -ne 0 ]]; then + if ((${#toks[@]} != 0)); then # 2>/dev/null for direct invocation, e.g. in the _filedir unit test compopt -o filenames 2>/dev/null - COMPREPLY+=( "${toks[@]}" ) + COMPREPLY+=("${toks[@]}") fi } # _filedir() - # This function splits $cur=--foo=bar into $prev=--foo, $cur=bar, making it # easier to support both "--foo bar" and "--foo=bar" style completions. # `=' should have been removed from COMP_WORDBREAKS when setting $cur for @@ -607,7 +628,7 @@ _filedir() # _split_longopt() { - if [[ "$cur" == --?*=* ]]; then + if [[ $cur == --?*=* ]]; then # Cut also backslash before '=' in case it ended up there # for some reason. prev="${cur%%?(\\)=*}" @@ -625,41 +646,41 @@ _variables() { if [[ $cur =~ ^(\$(\{[!#]?)?)([A-Za-z0-9_]*)$ ]]; then # Completing $var / ${var / ${!var / ${#var - if [[ $cur == \${* ]]; then + 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]}) ) - if [[ ${#vars[@]} -eq 1 && $arrs ]]; then + vars=($(compgen -A variable -P ${BASH_REMATCH[1]} -S '}' -- ${BASH_REMATCH[3]})) + arrs=($(compgen -A arrayvar -P ${BASH_REMATCH[1]} -S '[' -- ${BASH_REMATCH[3]})) + 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[*]} ) + COMPREPLY+=(${arrs[*]}) else # Complete ${var with ${variable} - COMPREPLY+=( ${vars[*]} ) + COMPREPLY+=(${vars[*]}) fi else # Complete $var with $variable - COMPREPLY+=( $(compgen -A variable -P '$' -- "${BASH_REMATCH[3]}") ) + COMPREPLY+=($(compgen -A variable -P '$' -- "${BASH_REMATCH[3]}")) 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]}") ) + COMPREPLY+=($(compgen -W '$(printf %s\\n "${!'${BASH_REMATCH[2]}'[@]}")' \ + -P "${BASH_REMATCH[1]}${BASH_REMATCH[2]}[" -S ']}' -- "${BASH_REMATCH[3]}")) # Complete ${arr[@ and ${arr[* if [[ ${BASH_REMATCH[3]} == [@*] ]]; then - COMPREPLY+=( "${BASH_REMATCH[1]}${BASH_REMATCH[2]}[${BASH_REMATCH[3]}]}" ) + COMPREPLY+=("${BASH_REMATCH[1]}${BASH_REMATCH[2]}[${BASH_REMATCH[3]}]}") fi - __ltrim_colon_completions "$cur" # array indexes may have colons + __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}" ) + COMPREPLY+=("$cur}") __ltrim_colon_completions "$cur" return 0 else - case $prev in + case ${prev-} in TZ) cur=/usr/share/zoneinfo/$cur _filedir @@ -679,9 +700,9 @@ _variables() _terms return 0 ;; - LANG|LC_*) - COMPREPLY=( $(compgen -W '$(locale -a 2>/dev/null)' \ - -- "$cur" ) ) + LANG | LC_*) + COMPREPLY=($(compgen -W '$(locale -a 2>/dev/null)' \ + -- "$cur")) return 0 ;; esac @@ -714,16 +735,17 @@ _init_completion() e) errx=$OPTARG ;; o) outx=$OPTARG ;; i) inx=$OPTARG ;; - s) split=false ; exclude+== ;; + s) + split=false + exclude+== + ;; + *) + echo "bash_completion: $FUNCNAME: usage error" >&2 + return 1 + ;; esac done - # For some reason completion functions are not invoked at all by - # bash (at least as of 4.1.7) after the command line contains an - # ampersand so we don't get a chance to deal with redirections - # containing them, but if we did, hopefully the below would also - # do the right thing with them... - COMPREPLY=() local redir="@(?([0-9])<|?([0-9&])>?(>)|>&)" _get_comp_words_by_ref -n "$exclude<>&" cur prev words cword @@ -733,17 +755,18 @@ _init_completion() # Complete on files if current is a redirect possibly followed by a # filename, e.g. ">foo", or previous is a "bare" redirect, e.g. ">". - if [[ $cur == $redir* || $prev == $redir ]]; then + # shellcheck disable=SC2053 + if [[ $cur == $redir* || ${prev-} == $redir ]]; then local xspec case $cur in - 2'>'*) xspec=$errx ;; - *'>'*) xspec=$outx ;; - *'<'*) xspec=$inx ;; + 2'>'*) xspec=${errx-} ;; + *'>'*) xspec=${outx-} ;; + *'<'*) xspec=${inx-} ;; *) case $prev in - 2'>'*) xspec=$errx ;; - *'>'*) xspec=$outx ;; - *'<'*) xspec=$inx ;; + 2'>'*) xspec=${errx-} ;; + *'>'*) xspec=${outx-} ;; + *'<'*) xspec=${inx-} ;; esac ;; esac @@ -754,19 +777,20 @@ _init_completion() # Remove all redirections so completions don't have to deal with them. local i skip - for (( i=1; i < ${#words[@]}; )); do + for ((i = 1; i < ${#words[@]}; )); do if [[ ${words[i]} == $redir* ]]; then # If "bare" redirect, remove also the next word (skip=2). + # shellcheck disable=SC2053 [[ ${words[i]} == $redir ]] && skip=2 || skip=1 - words=( "${words[@]:0:i}" "${words[@]:i+skip}" ) - [[ $i -le $cword ]] && (( cword -= skip )) + words=("${words[@]:0:i}" "${words[@]:i+skip}") + ((i <= cword)) && ((cword -= skip)) else - (( i++ )) + ((i++)) fi done - [[ $cword -le 0 ]] && return 1 - prev=${words[cword-1]} + ((cword <= 0)) && return 1 + prev=${words[cword - 1]} [[ ${split-} ]] && _split_longopt && split=true @@ -780,13 +804,16 @@ __parse_options() # Take first found long option, or first one (short) if not found. option= - local -a array=( $1 ) + local -a array=($1) for i in "${array[@]}"; do case "$i" in ---*) break ;; - --?*) option=$i ; break ;; - -?*) [[ $option ]] || option=$i ;; - *) break ;; + --?*) + option=$i + break + ;; + -?*) [[ $option ]] || option=$i ;; + *) break ;; esac done [[ $option ]] || return 0 @@ -811,23 +838,25 @@ __parse_options() # _parse_help() { - eval local cmd=$(quote "$1") + 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 /, }" + { + 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 /, }" - done + done } # Parse BSD style usage output (options in brackets) of the given command. @@ -836,41 +865,43 @@ _parse_help() # _parse_usage() { - eval local cmd=$(quote "$1") + 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 + { + 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 - done + done } # This function completes on signal names (minus the SIG prefix) # @param $1 prefix _signals() { - local -a sigs=( $(compgen -P "$1" -A signal "SIG${cur#$1}") ) - COMPREPLY+=( "${sigs[@]/#${1}SIG/${1}}" ) + local -a sigs=($(compgen -P "${1-}" -A signal "SIG${cur#${1-}}")) + COMPREPLY+=("${sigs[@]/#${1-}SIG/${1-}}") } # This function completes on known mac addresses @@ -884,24 +915,28 @@ _mac_addresses() # - ifconfig on Linux: HWaddr or ether # - ifconfig on FreeBSD: ether # - ip link: link/ether - COMPREPLY+=( $(\ - { LC_ALL=C ifconfig -a || 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" - ) ) + COMPREPLY+=($( + { + LC_ALL=C ifconfig -a || ip link show + } 2>/dev/null | command sed -ne \ + "s/.*[[:space:]]HWaddr[[:space:]]\{1,\}\($re\)[[:space:]].*/\1/p" -ne \ + "s/.*[[:space:]]HWaddr[[:space:]]\{1,\}\($re\)[[:space:]]*$/\1/p" -ne \ + "s|.*[[:space:]]\(link/\)\{0,1\}ether[[:space:]]\{1,\}\($re\)[[:space:]].*|\2|p" -ne \ + "s|.*[[:space:]]\(link/\)\{0,1\}ether[[:space:]]\{1,\}\($re\)[[:space:]]*$|\2|p" + )) # ARP cache - COMPREPLY+=( $({ arp -an || ip neigh show; } 2>/dev/null | command sed -ne \ + 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") ) + "s/.*[[:space:]]\($re\)[[:space:]]*$/\1/p")) # /etc/ethers - COMPREPLY+=( $(command sed -ne \ - "s/^[[:space:]]*\($re\)[[:space:]].*/\1/p" /etc/ethers 2>/dev/null) ) + COMPREPLY+=($(command sed -ne \ + "s/^[[:space:]]*\($re\)[[:space:]].*/\1/p" /etc/ethers 2>/dev/null)) - COMPREPLY=( $(compgen -W '${COMPREPLY[@]}' -- "$cur") ) + COMPREPLY=($(compgen -W '${COMPREPLY[@]}' -- "$cur")) __ltrim_colon_completions "$cur" } @@ -911,24 +946,24 @@ _configured_interfaces() { if [[ -f /etc/debian_version ]]; then # Debian system - COMPREPLY=( $(compgen -W "$(command sed -ne 's|^iface \([^ ]\{1,\}\).*$|\1|p'\ + COMPREPLY=($(compgen -W "$(command sed -ne 's|^iface \([^ ]\{1,\}\).*$|\1|p' \ /etc/network/interfaces /etc/network/interfaces.d/* 2>/dev/null)" \ - -- "$cur") ) + -- "$cur")) 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") ) + COMPREPLY=($(compgen -W "$(printf '%s\n' \ + /etc/sysconfig/network/ifcfg-* | + command sed -ne 's|.*ifcfg-\([^*].*\)$|\1|p')" -- "$cur")) 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") ) + COMPREPLY=($(compgen -W "$(command ls -B \ + /etc/sysconfig/interfaces | + command sed -ne 's|.*ifcfg-\([^*].*\)$|\1|p')" -- "$cur")) else # Assume Red Hat - COMPREPLY=( $(compgen -W "$(printf '%s\n' \ - /etc/sysconfig/network-scripts/ifcfg-* | \ - command sed -ne 's|.*ifcfg-\([^*].*\)$|\1|p')" -- "$cur") ) + COMPREPLY=($(compgen -W "$(printf '%s\n' \ + /etc/sysconfig/network-scripts/ifcfg-* | + command sed -ne 's|.*ifcfg-\([^*].*\)$|\1|p')" -- "$cur")) fi } @@ -940,22 +975,25 @@ _configured_interfaces() _ip_addresses() { local n - case $1 in + case ${1-} in -a) n='6\?' ;; -6) n='6' ;; + *) n= ;; esac local PATH=$PATH:/sbin - local addrs=$({ LC_ALL=C ifconfig -a || ip addr show; } 2>/dev/null | + local addrs=$({ + LC_ALL=C ifconfig -a || 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") ) + COMPREPLY+=($(compgen -W "$addrs" -- "${cur-}")) } # This function completes on available kernels # _kernel_versions() { - COMPREPLY=( $(compgen -W '$(command ls /lib/modules)' -- "$cur") ) + COMPREPLY=($(compgen -W '$(command ls /lib/modules)' -- "$cur")) } # This function completes on all available network interfaces @@ -966,7 +1004,7 @@ _available_interfaces() { local PATH=$PATH:/sbin - COMPREPLY=( $({ + COMPREPLY=($({ if [[ ${1:-} == -w ]]; then iwconfig elif [[ ${1:-} == -a ]]; then @@ -975,9 +1013,9 @@ _available_interfaces() ifconfig -a || ip link show fi } 2>/dev/null | awk \ - '/^[^ \t]/ { if ($1 ~ /^[0-9]+:/) { print $2 } else { print $1 } }') ) + '/^[^ \t]/ { if ($1 ~ /^[0-9]+:/) { print $2 } else { print $1 } }')) - COMPREPLY=( $(compgen -W '${COMPREPLY[@]/%[[:punct:]]/}' -- "$cur") ) + COMPREPLY=($(compgen -W '${COMPREPLY[@]/%[[:punct:]]/}' -- "$cur")) } # Echo number of CPUs, falling back to 1 on failure. @@ -996,17 +1034,16 @@ _ncpus() _tilde() { local result=0 - if [[ $1 == \~* && $1 != */* ]]; then + if [[ ${1-} == \~* && $1 != */* ]]; then # Try generate ~username completions - COMPREPLY=( $(compgen -P '~' -u -- "${1#\~}") ) + COMPREPLY=($(compgen -P '~' -u -- "${1#\~}")) result=${#COMPREPLY[@]} # 2>/dev/null for direct invocation, e.g. in the _tilde unit test - [[ $result -gt 0 ]] && compopt -o filenames 2>/dev/null + ((result > 0)) && compopt -o filenames 2>/dev/null fi return $result } - # Expand variable 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 @@ -1031,12 +1068,11 @@ _tilde() # @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#\~}") + if [[ ${!1-} == \~* ]]; then + eval $1="$(printf ~%q "${!1#\~}")" fi } # __expand_tilde_by_ref() - # This function expands tildes in pathnames # _expand() @@ -1045,102 +1081,102 @@ _expand() # ~foo/... to /home/foo/... to avoid problems when $cur starting with # a tilde is fed to commands and ending up quoted instead of expanded. - if [[ "$cur" == \~*/* ]]; then - __expand_tilde_by_ref cur - elif [[ "$cur" == \~* ]]; then - _tilde "$cur" || eval COMPREPLY[0]=$(printf ~%q "${COMPREPLY[0]#\~}") - return ${#COMPREPLY[@]} - fi -} - -# This function completes on process IDs. -# AIX and Solaris ps prefers X/Open syntax. -[[ $OSTYPE == *@(solaris|aix)* ]] && -_pids() -{ - COMPREPLY=( $(compgen -W '$(command ps -efo pid | command sed 1d)' -- "$cur") ) -} || -_pids() -{ - COMPREPLY=( $(compgen -W '$(command ps axo pid=)' -- "$cur") ) + case ${cur-} in + ~*/*) + __expand_tilde_by_ref cur + ;; + ~*) + _tilde "$cur" || + eval COMPREPLY[0]="$(printf ~%q "${COMPREPLY[0]#\~}")" + return ${#COMPREPLY[@]} + ;; + esac } -# This function completes on process group IDs. -# AIX and SunOS prefer X/Open, all else should be BSD. -[[ $OSTYPE == *@(solaris|aix)* ]] && -_pgids() -{ - COMPREPLY=( $(compgen -W '$(command ps -efo pgid | command sed 1d)' -- "$cur") ) -} || -_pgids() -{ - COMPREPLY=( $(compgen -W '$(command ps axo pgid=)' -- "$cur") ) -} +# Process ID related functions. +# for AIX and Solaris we use X/Open syntax, BSD for others. +if [[ $OSTYPE == *@(solaris|aix)* ]]; then + # This function completes on process IDs. + _pids() + { + COMPREPLY=($(compgen -W '$(command ps -efo pid | command sed 1d)' -- "$cur")) + } -# This function completes on process names. -# AIX and SunOS prefer X/Open, all else should be BSD. -# @param $1 if -s, don't try to avoid truncated command names -[[ $OSTYPE == *@(solaris|aix)* ]] && -_pnames() -{ - COMPREPLY=( $(compgen -X '<defunct>' -W '$(command ps -efo comm | \ - command sed -e 1d -e "s:.*/::" -e "s/^-//" | sort -u)' -- "$cur") ) -} || -_pnames() -{ - local -a procs - if [[ "$1" == -s ]]; then - procs=( $(command ps axo comm | command sed -e 1d) ) - else - local line i=-1 OIFS=$IFS - IFS=$'\n' - local -a psout=( $(command ps axo command=) ) - IFS=$OIFS - for line in "${psout[@]}"; do - if [[ $i -eq -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= - if [[ $line =~ ^(.*[[:space:]])COMMAND([[:space:]]|$) ]]; then - # It does; store its index. - i=${#BASH_REMATCH[1]} - else - # Nope, fall through to "regular axo command=" parsing. - break - fi - else - # - line=${line:$i} # take command starting from found index - line=${line%% *} # trim arguments - procs+=( $line ) - fi - done - if [[ $i -eq -1 ]]; then - # Regular axo command= parsing + _pgids() + { + COMPREPLY=($(compgen -W '$(command ps -efo pgid | command sed 1d)' -- "$cur")) + } + _pnames() + { + COMPREPLY=($(compgen -X '<defunct>' -W '$(command ps -efo comm | \ + command sed -e 1d -e "s:.*/::" -e "s/^-//" | sort -u)' -- "$cur")) + } +else + _pids() + { + COMPREPLY=($(compgen -W '$(command ps axo pid=)' -- "$cur")) + } + _pgids() + { + COMPREPLY=($(compgen -W '$(command ps axo pgid=)' -- "$cur")) + } + # @param $1 if -s, don't try to avoid truncated command names + _pnames() + { + local -a procs + if [[ ${1-} == -s ]]; then + procs=($(command ps axo comm | command sed -e 1d)) + else + local line i=-1 ifs=$IFS + IFS=$'\n' + local -a psout=($(command ps axo command=)) + IFS=$ifs for line in "${psout[@]}"; do - if [[ $line =~ ^[[(](.+)[])]$ ]]; then - procs+=( ${BASH_REMATCH[1]} ) + 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= + if [[ $line =~ ^(.*[[:space:]])COMMAND([[:space:]]|$) ]]; then + # It does; store its index. + i=${#BASH_REMATCH[1]} + else + # Nope, fall through to "regular axo command=" parsing. + break + fi else - line=${line%% *} # trim arguments - line=${line##@(*/|-)} # trim leading path and - - procs+=( $line ) + # + line=${line:i} # take command starting from found index + line=${line%% *} # trim arguments + procs+=($line) fi done + if ((i == -1)); then + # Regular axo command= parsing + for line in "${psout[@]}"; do + if [[ $line =~ ^[[(](.+)[])]$ ]]; then + procs+=(${BASH_REMATCH[1]}) + else + line=${line%% *} # trim arguments + line=${line##@(*/|-)} # trim leading path and - + procs+=($line) + fi + done + fi fi - fi - COMPREPLY=( $(compgen -X "<defunct>" -W '${procs[@]}' -- "$cur" ) ) -} + COMPREPLY=($(compgen -X "<defunct>" -W '${procs[@]}' -- "$cur")) + } +fi # This function completes on user IDs # _uids() { if type getent &>/dev/null; then - COMPREPLY=( $(compgen -W '$(getent passwd | cut -d: -f3)' -- "$cur") ) + COMPREPLY=($(compgen -W '$(getent passwd | cut -d: -f3)' -- "$cur")) elif type perl &>/dev/null; then - COMPREPLY=( $(compgen -W '$(perl -e '"'"'while (($uid) = (getpwent)[2]) { print $uid . "\n" }'"'"')' -- "$cur") ) + COMPREPLY=($(compgen -W '$(perl -e '"'"'while (($uid) = (getpwent)[2]) { print $uid . "\n" }'"'"')' -- "$cur")) else # make do with /etc/passwd - COMPREPLY=( $(compgen -W '$(cut -d: -f3 /etc/passwd)' -- "$cur") ) + COMPREPLY=($(compgen -W '$(cut -d: -f3 /etc/passwd)' -- "$cur")) fi } @@ -1149,12 +1185,12 @@ _uids() _gids() { if type getent &>/dev/null; then - COMPREPLY=( $(compgen -W '$(getent group | cut -d: -f3)' -- "$cur") ) + COMPREPLY=($(compgen -W '$(getent group | cut -d: -f3)' -- "$cur")) elif type perl &>/dev/null; then - COMPREPLY=( $(compgen -W '$(perl -e '"'"'while (($gid) = (getgrent)[2]) { print $gid . "\n" }'"'"')' -- "$cur") ) + COMPREPLY=($(compgen -W '$(perl -e '"'"'while (($gid) = (getgrent)[2]) { print $gid . "\n" }'"'"')' -- "$cur")) else # make do with /etc/group - COMPREPLY=( $(compgen -W '$(cut -d: -f3 /etc/group)' -- "$cur") ) + COMPREPLY=($(compgen -W '$(cut -d: -f3 /etc/group)' -- "$cur")) fi } @@ -1166,12 +1202,14 @@ _backup_glob='@(#*#|*@(~|.@(bak|orig|rej|swp|dpkg*|rpm@(orig|new|save))))' # _xinetd_services() { - local xinetddir=/etc/xinetd.d + local xinetddir=${BASHCOMP_XINETDDIR:-/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)) ) + local IFS=$' \t\n' reset=$(shopt -p nullglob) + shopt -s nullglob + local -a svcs=($(printf '%s\n' $xinetddir/!($_backup_glob))) $reset - COMPREPLY+=( $(compgen -W '${svcs[@]#$xinetddir/}' -- "$cur") ) + ((!${#svcs[@]})) || + COMPREPLY+=($(compgen -W '${svcs[@]#$xinetddir/}' -- "${cur-}")) fi } @@ -1182,20 +1220,23 @@ _services() local sysvdirs _sysvdirs - local IFS=$' \t\n' reset=$(shopt -p nullglob); shopt -s nullglob - COMPREPLY=( \ - $(printf '%s\n' ${sysvdirs[0]}/!($_backup_glob|functions|README)) ) + local IFS=$' \t\n' reset=$(shopt -p nullglob) + shopt -s nullglob + COMPREPLY=( + $(printf '%s\n' ${sysvdirs[0]}/!($_backup_glob|functions|README))) $reset - COMPREPLY+=( $({ systemctl list-units --full --all || \ - systemctl list-unit-files; } 2>/dev/null | \ - awk '$1 ~ /\.service$/ { sub("\\.service$", "", $1); print $1 }') ) + COMPREPLY+=($({ + systemctl list-units --full --all || + systemctl list-unit-files + } 2>/dev/null | + awk '$1 ~ /\.service$/ { sub("\\.service$", "", $1); print $1 }')) if [[ -x /sbin/upstart-udev-bridge ]]; then - COMPREPLY+=( $(initctl list 2>/dev/null | cut -d' ' -f1) ) + COMPREPLY+=($(initctl list 2>/dev/null | cut -d' ' -f1)) fi - COMPREPLY=( $(compgen -W '${COMPREPLY[@]#${sysvdirs[0]}/}' -- "$cur") ) + COMPREPLY=($(compgen -W '${COMPREPLY[@]#${sysvdirs[0]}/}' -- "$cur")) } # This completes on a list of all available service scripts for the @@ -1208,7 +1249,7 @@ _service() _init_completion || return # don't complete past 2nd token - [[ $cword -gt 2 ]] && return + ((cword > 2)) && return if [[ $cword -eq 1 && $prev == ?(*/)service ]]; then _services @@ -1216,12 +1257,12 @@ _service() else local sysvdirs _sysvdirs - COMPREPLY=( $(compgen -W '`command sed -e "y/|/ /" \ + COMPREPLY=($(compgen -W '`command sed -e "y/|/ /" \ -ne "s/^.*\(U\|msg_u\)sage.*{\(.*\)}.*$/\2/p" \ - ${sysvdirs[0]}/${prev##*/} 2>/dev/null` start stop' -- "$cur") ) + ${sysvdirs[0]}/${prev##*/} 2>/dev/null` start stop' -- "$cur")) fi } && -complete -F _service service + complete -F _service service _sysvdirs for svcdir in "${sysvdirs[@]}"; do for svc in $svcdir/!($_backup_glob); do @@ -1236,16 +1277,16 @@ _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") ) + 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")) } # This function completes on installed modules # _installed_modules() { - COMPREPLY=( $(compgen -W "$(PATH="$PATH:/sbin" lsmod | \ - awk '{if (NR != 1) print $1}')" -- "$1") ) + COMPREPLY=($(compgen -W "$(PATH="$PATH:/sbin" lsmod | + awk '{if (NR != 1) print $1}')" -- "$1")) } # This function completes on user or user:group format; as for chown and cpio. @@ -1266,25 +1307,25 @@ _usergroup() # escape to the colon. local prefix prefix=${cur%%*([^:])} - prefix=${prefix//\\} + prefix=${prefix//\\/} local mycur="${cur#*[:]}" - if [[ $1 == -u ]]; then + if [[ ${1-} == -u ]]; then _allowed_groups "$mycur" else local IFS=$'\n' - COMPREPLY=( $(compgen -g -- "$mycur") ) + COMPREPLY=($(compgen -g -- "$mycur")) fi - COMPREPLY=( $(compgen -P "$prefix" -W "${COMPREPLY[@]}") ) + 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 + if [[ ${1-} == -u ]]; then _allowed_groups "$mycur" else local IFS=$'\n' - COMPREPLY=( $(compgen -g -- "$mycur") ) + COMPREPLY=($(compgen -g -- "$mycur")) fi else # Completing a partial 'usernam<TAB>'. @@ -1292,11 +1333,11 @@ _usergroup() # Don't suffix with a : because readline will escape it and add a # slash. It's better to complete into 'chown username ' than 'chown # username\:'. - if [[ $1 == -u ]]; then + if [[ ${1-} == -u ]]; then _allowed_users "$cur" else local IFS=$'\n' - COMPREPLY=( $(compgen -u -- "$cur") ) + COMPREPLY=($(compgen -u -- "$cur")) fi fi } @@ -1305,11 +1346,11 @@ _allowed_users() { if _complete_as_root; then local IFS=$'\n' - COMPREPLY=( $(compgen -u -- "${1:-$cur}") ) + COMPREPLY=($(compgen -u -- "${1:-$cur}")) else local IFS=$'\n ' - COMPREPLY=( $(compgen -W \ - "$(id -un 2>/dev/null || whoami 2>/dev/null)" -- "${1:-$cur}") ) + COMPREPLY=($(compgen -W \ + "$(id -un 2>/dev/null || whoami 2>/dev/null)" -- "${1:-$cur}")) fi } @@ -1317,11 +1358,11 @@ _allowed_groups() { if _complete_as_root; then local IFS=$'\n' - COMPREPLY=( $(compgen -g -- "$1") ) + COMPREPLY=($(compgen -g -- "$1")) else local IFS=$'\n ' - COMPREPLY=( $(compgen -W \ - "$(id -Gn 2>/dev/null || groups 2>/dev/null)" -- "$1") ) + COMPREPLY=($(compgen -W \ + "$(id -Gn 2>/dev/null || groups 2>/dev/null)" -- "$1")) fi } @@ -1331,7 +1372,7 @@ _shells() { local shell rest while read -r shell rest; do - [[ $shell == /* && $shell == "$cur"* ]] && COMPREPLY+=( $shell ) + [[ $shell == /* && $shell == "$cur"* ]] && COMPREPLY+=($shell) done 2>/dev/null </etc/shells } @@ -1354,7 +1395,7 @@ _fstypes() $([[ -d /etc/fs ]] && command ls /etc/fs)" fi - [[ -n $fss ]] && COMPREPLY+=( $(compgen -W "$fss" -- "$cur") ) + [[ -n $fss ]] && COMPREPLY+=($(compgen -W "$fss" -- "$cur")) } # Get real command. @@ -1385,15 +1426,14 @@ _get_first_arg() local i arg= - for (( i=1; i < COMP_CWORD; i++ )); do - if [[ "${COMP_WORDS[i]}" != -* ]]; then + for ((i = 1; i < COMP_CWORD; i++)); do + if [[ ${COMP_WORDS[i]} != -* ]]; then arg=${COMP_WORDS[i]} break fi done } - # 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. @@ -1402,13 +1442,14 @@ _get_first_arg() _count_args() { local i cword words - __reassemble_comp_words_by_ref "$1" words cword + __reassemble_comp_words_by_ref "${1-}" words cword args=1 - for (( i=1; i < cword; i++ )); do - if [[ ${words[i]} != -* && ${words[i-1]} != $2 || - ${words[i]} == $3 ]]; then - (( args++ )) + for ((i = 1; i < cword; i++)); do + # shellcheck disable=SC2053 + if [[ ${words[i]} != -* && ${words[i - 1]} != ${2-} || \ + ${words[i]} == ${3-} ]]; then + ((args++)) fi done } @@ -1417,39 +1458,53 @@ _count_args() # _pci_ids() { - COMPREPLY+=( $(compgen -W \ - "$(PATH="$PATH:/sbin" lspci -n | awk '{print $3}')" -- "$cur") ) + COMPREPLY+=($(compgen -W \ + "$(PATH="$PATH:/sbin" lspci -n | awk '{print $3}')" -- "$cur")) } # This function completes on USB IDs # _usb_ids() { - COMPREPLY+=( $(compgen -W \ - "$(PATH="$PATH:/sbin" lsusb | awk '{print $6}')" -- "$cur") ) + COMPREPLY+=($(compgen -W \ + "$(PATH="$PATH:/sbin" lsusb | awk '{print $6}')" -- "$cur")) } # CD device names _cd_devices() { - COMPREPLY+=( $(compgen -f -d -X "!*/?([amrs])cd*" -- "${cur:-/dev/}") ) + COMPREPLY+=($(compgen -f -d -X "!*/?([amrs])cd*" -- "${cur:-/dev/}")) } # DVD device names _dvd_devices() { - COMPREPLY+=( $(compgen -f -d -X "!*/?(r)dvd*" -- "${cur:-/dev/}") ) + COMPREPLY+=($(compgen -f -d -X "!*/?(r)dvd*" -- "${cur:-/dev/}")) } # TERM environment variable values _terms() { - COMPREPLY+=( $(compgen -W "$({ \ - 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") ) + COMPREPLY+=($(compgen -W "$({ + 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")) +} + +_bashcomp_try_faketty() +{ + if type unbuffer &>/dev/null; then + unbuffer -p "$@" + elif script --version 2>&1 | command grep -qF util-linux; then + # BSD and Solaris "script" do not seem to have required features + script -qaefc "$*" /dev/null + else + "$@" # no can do, fallback + fi } # a little help for FreeBSD ports users @@ -1467,7 +1522,7 @@ _user_at_host() if [[ $cur == *@* ]]; then _known_hosts_real "$cur" else - COMPREPLY=( $(compgen -u -S @ -- "$cur") ) + COMPREPLY=($(compgen -u -S @ -- "$cur")) compopt -o nospace fi } @@ -1483,9 +1538,9 @@ _known_hosts() # 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" + [[ ${1-} == -a || ${2-} == -a ]] && options=-a + [[ ${1-} == -c || ${2-} == -c ]] && options+=" -c" + _known_hosts_real ${options-} -- "$cur" } # _known_hosts() # Helper function to locate ssh included files in configs @@ -1493,18 +1548,24 @@ _known_hosts() # includes them recursively, adding each result to the config variable. _included_ssh_config_files() { - [[ $# -lt 1 ]] && \ + (($# < 1)) && echo "bash_completion: $FUNCNAME: missing mandatory argument CONFIG" >&2 local configfile i f configfile=$1 - local included=( $(command sed -ne 's/^[[:blank:]]*[Ii][Nn][Cc][Ll][Uu][Dd][Ee][[:blank:]]\{1,\}\([^#%]*\)\(#.*\)\{0,1\}$/\1/p' "${configfile}") ) + + 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 + if ! [[ $i =~ ^\~.*|^\/.* ]]; then + if [[ $configfile =~ ^\/etc\/ssh.* ]]; then i="/etc/ssh/$i" else i="$HOME/.ssh/$i" @@ -1512,13 +1573,15 @@ _included_ssh_config_files() fi __expand_tilde_by_ref i # In case the expanded variable contains multiple paths - for f in ${i}; do - if [ -r $f ]; then - config+=( "$f" ) + 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 fi done + $reset done } # _included_ssh_config_files() @@ -1538,9 +1601,9 @@ _included_ssh_config_files() # Return: Completions, starting with CWORD, are added to COMPREPLY[] _known_hosts_real() { - local configfile flag prefix OIFS=$IFS - local cur user suffix aliases i host ipv4 ipv6 - local -a kh tmpkh khd config + local configfile flag prefix="" ifs=$IFS + local cur suffix="" aliases i host ipv4 ipv6 + local -a kh tmpkh=() khd=() config=() # TODO remove trailing %foo from entries @@ -1553,75 +1616,96 @@ _known_hosts_real() p) prefix=$OPTARG ;; 4) ipv4=1 ;; 6) ipv6=1 ;; + *) + echo "bash_completion: $FUNCNAME: usage error" >&2 + return 1 + ;; esac done - [[ $# -lt $OPTIND ]] && \ + if (($# < OPTIND)); then echo "bash_completion: $FUNCNAME: missing mandatory argument CWORD" >&2 - cur=${!OPTIND}; (( OPTIND += 1 )) - [[ $# -ge $OPTIND ]] && \ + return 1 + fi + cur=${!OPTIND} + ((OPTIND += 1)) + if (($# >= OPTIND)); then echo "bash_completion: $FUNCNAME($*): unprocessed arguments:" \ - $(while [[ $# -ge $OPTIND ]]; do printf '%s\n' ${!OPTIND}; shift; done) >&2 + "$(while (($# >= OPTIND)); do + printf '%s ' ${!OPTIND} + shift + done)" >&2 + return 1 + fi - [[ $cur == *@* ]] && user=${cur%@*}@ && cur=${cur#*@} + [[ $cur == *@* ]] && prefix=$prefix${cur%@*}@ && cur=${cur#*@} kh=() # ssh config files - if [[ -n $configfile ]]; then - [[ -r $configfile ]] && config+=( "$configfile" ) + if [[ -v configfile ]]; then + [[ -r $configfile ]] && config+=("$configfile") else for i in /etc/ssh/ssh_config ~/.ssh/config ~/.ssh2/config; do - [[ -r $i ]] && config+=( "$i" ) + [[ -r $i ]] && config+=("$i") done fi + local reset=$(shopt -po noglob) + set -o noglob + # "Include" keyword in ssh config files - for i in "${config[@]}"; do - _included_ssh_config_files "$i" - done + if ((${#config[@]} > 0)); then + for i in "${config[@]}"; do + _included_ssh_config_files "$i" + done + fi # Known hosts files from configs - if [[ ${#config[@]} -gt 0 ]]; then - local IFS=$'\n' j + 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=$OIFS + 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" ) + [[ -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" ) + [[ -r $j ]] && kh+=("$j") done done fi - if [[ -z $configfile ]]; then + if [[ ! -v 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 ]] && kh+=("$i") done for i in /etc/ssh2/knownhosts ~/.ssh2/hostkeys; do - [[ -d $i ]] && khd+=( "$i"/*pub ) + [[ -d $i ]] && khd+=("$i"/*pub) done fi # If we have known_hosts files to use - if [[ ${#kh[@]} -gt 0 || ${#khd[@]} -gt 0 ]]; then - if [[ ${#kh[@]} -gt 0 ]]; then + if ((${#kh[@]} + ${#khd[@]} > 0)); then + if ((${#kh[@]} > 0)); then # https://man.openbsd.org/sshd.8#SSH_KNOWN_HOSTS_FILE_FORMAT for i in "${kh[@]}"; do while read -ra tmpkh; do + ((${#tmpkh[@]} == 0)) && continue set -- "${tmpkh[@]}" # Skip entries starting with | (hashed) and # (comment) [[ $1 == [\|\#]* ]] && continue @@ -1637,78 +1721,84 @@ _known_hosts_real() # Remove trailing ] + optional :port host="${host%]?(:+([0-9]))}" # Add host to candidates - COMPREPLY+=( $host ) + COMPREPLY+=($host) done - IFS=$OIFS + IFS=$ifs done <"$i" done - COMPREPLY=( $(compgen -W '${COMPREPLY[@]}' -- "$cur") ) + COMPREPLY=($(compgen -W '${COMPREPLY[@]}' -- "$cur")) fi - if [[ ${#khd[@]} -gt 0 ]]; then + if ((${#khd[@]} > 0)); then # Needs to look for files called # .../.ssh2/key_22_<hostname>.pub # dont fork any processes, because in a cluster environment, # there can be hundreds of hostkeys - for i in "${khd[@]}" ; do - if [[ "$i" == *key_22_$cur*.pub && -r "$i" ]]; then + for i in "${khd[@]}"; do + if [[ $i == *key_22_$cur*.pub && -r $i ]]; then host=${i/#*key_22_/} host=${host/%.pub/} - COMPREPLY+=( $host ) + COMPREPLY+=($host) fi done fi # apply suffix and prefix - for (( i=0; i < ${#COMPREPLY[@]}; i++ )); do - COMPREPLY[i]=$prefix$user${COMPREPLY[i]}$suffix + for i in ${!COMPREPLY[*]}; do + COMPREPLY[i]=$prefix${COMPREPLY[i]}$suffix done fi # append any available aliases from ssh config files - if [[ ${#config[@]} -gt 0 && -n "$aliases" ]]; then - local hosts=$(command sed -ne 's/^[[:blank:]]*[Hh][Oo][Ss][Tt][[:blank:]]\{1,\}\([^#*?%]*\)\(#.*\)\{0,1\}$/\1/p' "${config[@]}") - COMPREPLY+=( $(compgen -P "$prefix$user" \ - -S "$suffix" -W "$hosts" -- "$cur") ) + 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")) + fi fi # Add hosts reported by avahi-browse, if desired and it's available. - if [[ ${COMP_KNOWN_HOSTS_WITH_AVAHI:-} ]] && \ + if [[ ${COMP_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$user" -S "$suffix" -W \ - "$(avahi-browse -cpr _workstation._tcp 2>/dev/null | \ - awk -F';' '/^=/ { print $7 }' | sort -u)" -- "$cur") ) + COMPREPLY+=($(compgen -P "$prefix" -S "$suffix" -W \ + "$(avahi-browse -cpr _workstation._tcp 2>/dev/null | + awk -F';' '/^=/ { print $7 }' | sort -u)" -- "$cur")) fi # Add hosts reported by ruptime. - COMPREPLY+=( $(compgen -W \ - "$(ruptime 2>/dev/null | awk '!/^ruptime:/ { print $1 }')" \ - -- "$cur") ) + if type ruptime &>/dev/null; then + COMPREPLY+=($(compgen -W \ + "$(ruptime 2>/dev/null | awk '!/^ruptime:/ { print $1 }')" \ + -- "$cur")) + 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$user" -S "$suffix" -- "$cur") ) + $(compgen -A hostname -P "$prefix" -S "$suffix" -- "$cur")) fi - if [[ $ipv4 ]]; then - COMPREPLY=( "${COMPREPLY[@]/*:*$suffix/}" ) + $reset + + if [[ -v ipv4 ]]; then + COMPREPLY=("${COMPREPLY[@]/*:*$suffix/}") fi - if [[ $ipv6 ]]; then - COMPREPLY=( "${COMPREPLY[@]/+([0-9]).+([0-9]).+([0-9]).+([0-9])$suffix/}" ) + if [[ -v ipv6 ]]; then + COMPREPLY=("${COMPREPLY[@]/+([0-9]).+([0-9]).+([0-9]).+([0-9])$suffix/}") fi - if [[ $ipv4 || $ipv6 ]]; then + if [[ -v ipv4 || -v ipv6 ]]; then for i in "${!COMPREPLY[@]}"; do - [[ ${COMPREPLY[i]} ]] || unset -v COMPREPLY[i] + [[ ${COMPREPLY[i]} ]] || unset -v "COMPREPLY[i]" done fi - __ltrim_colon_completions "$prefix$user$cur" + __ltrim_colon_completions "$prefix$cur" } # _known_hosts_real() complete -F _known_hosts traceroute traceroute6 \ @@ -1728,7 +1818,7 @@ _cd() # Use standard dir completion if no CDPATH or parameter starts with /, # ./ or ../ - if [[ -z "${CDPATH:-}" || "$cur" == ?(.)?(.)/* ]]; then + if [[ -z ${CDPATH:-} || $cur == ?(.)?(.)/* ]]; then _filedir -d return fi @@ -1741,7 +1831,7 @@ _cd() # create an array of matched subdirs k="${#COMPREPLY[@]}" for j in $(compgen -d -- $i/$cur); do - if [[ ( $mark_symdirs && -h $j || $mark_dirs && ! -h $j ) && ! -d ${j#$i/} ]]; then + if [[ ($mark_symdirs && -L $j || $mark_dirs && ! -L $j) && ! -d ${j#$i/} ]]; then j+="/" fi COMPREPLY[k++]=${j#$i/} @@ -1750,9 +1840,9 @@ _cd() _filedir -d - if [[ ${#COMPREPLY[@]} -eq 1 ]]; then + if ((${#COMPREPLY[@]} == 1)); then i=${COMPREPLY[0]} - if [[ "$i" == "$cur" && $i != "*/" ]]; then + if [[ $i == "$cur" && $i != "*/" ]]; then COMPREPLY[0]="${i}/" fi fi @@ -1765,15 +1855,18 @@ else complete -F _cd -o nospace cd pushd fi -# a wrapper method for the next one, when the offset is unknown +# 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 + for ((i = 1; i <= COMP_CWORD; i++)); do + if [[ ${COMP_WORDS[i]} != -* ]]; then offset=$i break fi @@ -1793,33 +1886,33 @@ _command_offset() # find new first word position, then # rewrite COMP_LINE and adjust COMP_POINT local word_offset=$1 i j - for (( i=0; i < $word_offset; i++ )); do - for (( j=0; j <= ${#COMP_LINE}; j++ )); do - [[ "$COMP_LINE" == "${COMP_WORDS[i]}"* ]] && break + 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]})) + ((COMP_POINT -= ${#COMP_WORDS[i]})) 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]} + 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 + for ((i; i <= COMP_CWORD; i++)); do unset 'COMP_WORDS[i]' done - ((COMP_CWORD -= $word_offset)) + ((COMP_CWORD -= word_offset)) COMPREPLY=() local cur _get_comp_words_by_ref cur - if [[ $COMP_CWORD -eq 0 ]]; then + if ((COMP_CWORD == 0)); then local IFS=$'\n' compopt -o filenames - COMPREPLY=( $(compgen -d -c -- "$cur") ) + COMPREPLY=($(compgen -d -c -- "$cur")) else local cmd=${COMP_WORDS[0]} compcmd=${COMP_WORDS[0]} local cspec=$(complete -p $cmd 2>/dev/null) @@ -1837,17 +1930,17 @@ _command_offset() fi if [[ -n $cspec ]]; then - if [[ ${cspec#* -F } != $cspec ]]; then + if [[ ${cspec#* -F } != "$cspec" ]]; then # complete -F <function> # get function name local func=${cspec#*-F } func=${func%% *} - if [[ ${#COMP_WORDS[@]} -ge 2 ]]; then - $func $cmd "${COMP_WORDS[${#COMP_WORDS[@]}-1]}" "${COMP_WORDS[${#COMP_WORDS[@]}-2]}" + if ((${#COMP_WORDS[@]} >= 2)); then + $func $cmd "${COMP_WORDS[-1]}" "${COMP_WORDS[-2]}" else - $func $cmd "${COMP_WORDS[${#COMP_WORDS[@]}-1]}" + $func $cmd "${COMP_WORDS[-1]}" fi # restore initial compopts @@ -1862,9 +1955,9 @@ _command_offset() else cspec=${cspec#complete} cspec=${cspec%%$compcmd} - COMPREPLY=( $(eval compgen "$cspec" -- '$cur') ) + COMPREPLY=($(eval compgen "$cspec" -- '$cur')) fi - elif [[ ${#COMPREPLY[@]} -eq 0 ]]; then + 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 @@ -1894,7 +1987,7 @@ _longopt() _init_completion -s || return case "${prev,,}" in - --help|--usage|--version) + --help | --usage | --version) return ;; --!(no-*)dir*) @@ -1913,7 +2006,7 @@ _longopt() _filedir -d return ;; - *file*|*path*) + *file* | *path*) _filedir return ;; @@ -1923,17 +2016,17 @@ _longopt() $split && return - if [[ "$cur" == -* ]]; then - COMPREPLY=( $(compgen -W "$(LC_ALL=C $1 --help 2>&1 | \ - while read -r line; do \ - [[ $line =~ --[-A-Za-z0-9]+=? ]] && \ + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$(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") ) - [[ $COMPREPLY == *= ]] && compopt -o nospace - elif [[ "$1" == *@(rmdir|chroot) ]]; then + done)" -- "$cur")) + [[ ${COMPREPLY-} == *= ]] && compopt -o nospace + elif [[ $1 == *@(rmdir|chroot) ]]; then _filedir -d else - [[ "$1" == *mkdir ]] && compopt -o nospace + [[ $1 == *mkdir ]] && compopt -o nospace _filedir fi } @@ -1945,12 +2038,8 @@ complete -F _longopt a2ps awk base64 bash bc bison cat chroot colordiff cp \ sed seq sha{,1,224,256,384,512}sum shar sort split strip sum tac tail tee \ texindex touch tr uname unexpand uniq units vdir wc who -# declare only knows -g in bash >= 4.2. -if [[ ${BASH_VERSINFO[0]} -gt 4 || ${BASH_VERSINFO[1]} -ge 2 ]]; then - declare -Ag _xspecs -else - declare -A _xspecs -fi +declare -Ag _xspecs + _filedir_xspec() { local cur prev words cword @@ -1961,16 +2050,17 @@ _filedir_xspec() local IFS=$'\n' xspec=${_xspecs[${1##*/}]} tmp local -a toks - toks=( $( + toks=($( compgen -d -- "$(quote_readline "$cur")" | { - while read -r tmp; do - printf '%s\n' $tmp - done + while read -r tmp; do + printf '%s\n' $tmp + done } - )) + )) # Munge xspec to contain uppercase version too - # http://thread.gmane.org/gmane.comp.shells.bash.bugs/15294/focus=15306 + # https://lists.gnu.org/archive/html/bug-bash/2010-09/msg00036.html + # news://news.gmane.io/4C940E1C.1010304@case.edu eval xspec="${xspec}" local matchop=! if [[ $xspec == !* ]]; then @@ -1979,24 +2069,27 @@ _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 + toks+=($( + eval compgen -f -X "'!$xspec'" -- '$(quote_readline "$cur")' | { + while read -r tmp; do + [[ -n $tmp ]] && printf '%s\n' $tmp + done } - )) + )) # 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' + local reset=$(shopt -po noglob) + set -o noglob + toks+=($(compgen -f -- "$(quote_readline "$cur")")) + IFS=' ' + $reset + IFS=$'\n' } - if [[ ${#toks[@]} -ne 0 ]]; then + if ((${#toks[@]} != 0)); then compopt -o filenames - COMPREPLY=( "${toks[@]}" ) + COMPREPLY=("${toks[@]}") fi } @@ -2010,7 +2103,7 @@ _install_xspec() } # 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|[egjswx]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 '!*.@(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 # zcmp, zdiff, z*grep, zless, zmore intentionally not here, see Debian: #455510 _install_xspec '!*.@(Z|[gGd]z|t[ag]z)' gunzip zcat @@ -2036,7 +2129,7 @@ _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 +_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 @@ -2076,37 +2169,48 @@ _minimal() _filedir } # Complete the empty string to allow completion of '>', '>>', and '<' on < 4.3 -# http://lists.gnu.org/archive/html/bug-bash/2012-01/msg00045.html +# https://lists.gnu.org/archive/html/bug-bash/2012-01/msg00045.html complete -F _minimal '' - __load_completion() { - local -a dirs=( ${BASH_COMPLETION_USER_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion}/completions ) - local OIFS=$IFS IFS=: dir cmd="${1##*/}" compfile + 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 ) + dirs+=($dir/bash-completion/completions) done - IFS=$OIFS + IFS=$ifs if [[ $BASH_SOURCE == */* ]]; then - dirs+=( "${BASH_SOURCE%/*}/completions" ) + dirs+=("${BASH_SOURCE%/*}/completions") else - dirs+=( ./completions ) + dirs+=(./completions) + fi + + local backslash= + if [[ $cmd == \\* ]]; then + cmd="${cmd:1}" + # If we already have a completion for the "real" command, use it + $(complete -p "$cmd" 2>/dev/null || echo false) "\\$cmd" && return 0 + backslash=\\ fi for dir in "${dirs[@]}"; do - [[ -d "$dir" ]] || continue + [[ -d $dir ]] || continue for compfile in "$cmd" "$cmd.bash" "_$cmd"; do compfile="$dir/$compfile" # Avoid trying to source dirs; https://bugzilla.redhat.com/903540 - [[ -f "$compfile" ]] && . "$compfile" &>/dev/null && return 0 + if [[ -f $compfile ]] && . "$compfile" &>/dev/null; then + [[ $backslash ]] && $(complete -p "$cmd") "\\$cmd" + return 0 + fi done done # Look up simple "xspec" completions - [[ "${_xspecs[$cmd]}" ]] && complete -F _filedir_xspec "$cmd" && return 0 + [[ -v _xspecs[$cmd] ]] && + complete -F _filedir_xspec "$cmd" "$backslash$cmd" && return 0 return 1 } @@ -2122,7 +2226,7 @@ _completion_loader() # Need to define *something*, otherwise there will be no completion at all. complete -F _minimal -- "$cmd" && return 124 } && -complete -D -F _completion_loader + complete -D -F _completion_loader # Function for loading and calling functions from dynamically loaded # completion files that may not have been sourced yet. @@ -2133,9 +2237,7 @@ _xfunc() set -- "$@" local srcfile=$1 shift - declare -F $1 &>/dev/null || { - __load_completion "$srcfile" - } + declare -F $1 &>/dev/null || __load_completion "$srcfile" "$@" } @@ -2143,16 +2245,16 @@ _xfunc() 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" + [[ ${i##*/} != @($_backup_glob|Makefile*|$_blacklist_glob) && -f \ + $i && -r $i ]] && . "$i" done fi unset compat_dir i _blacklist_glob # source user completion file user_completion=${BASH_COMPLETION_USER_FILE:-~/.bash_completion} -[[ ${BASH_SOURCE[0]} != $user_completion && -r $user_completion ]] \ - && . $user_completion +[[ ${BASH_SOURCE[0]} != "$user_completion" && -r $user_completion && -f $user_completion ]] && + . $user_completion unset user_completion unset -f have |