diff options
Diffstat (limited to '')
-rw-r--r-- | bash_completion | 2266 | ||||
-rw-r--r-- | bash_completion.sh.in | 16 |
2 files changed, 2282 insertions, 0 deletions
diff --git a/bash_completion b/bash_completion new file mode 100644 index 0000000..1a7f563 --- /dev/null +++ b/bash_completion @@ -0,0 +1,2266 @@ +# -*- shell-script -*- +# +# bash_completion - programmable completion functions for bash 4.2+ +# +# Copyright © 2006-2008, Ian Macdonald <ian@caliban.org> +# © 2009-2020, Bash Completion Maintainers +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# The latest version of this software can be obtained here: +# +# https://github.com/scop/bash-completion + +BASH_COMPLETION_VERSINFO=(2 11) + +if [[ $- == *v* ]]; then + BASH_COMPLETION_ORIGINAL_V_VALUE="-v" +else + BASH_COMPLETION_ORIGINAL_V_VALUE="+v" +fi + +if [[ ${BASH_COMPLETION_DEBUG-} ]]; then + set -v +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 + +# A lot of the following one-liners were taken directly from the +# completion examples provided with the bash 2.04 source distribution + +# start of section containing compspecs that can be handled within bash + +# user commands see only users +complete -u groups slay w sux + +# bg completes with stopped jobs +complete -A stopped -P '"%' -S '"' bg + +# other job commands +complete -j -P '"%' -S '"' fg jobs disown + +# readonly and unset complete with shell variables +complete -v readonly unset + +# set completes with set options +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 + +# type and which complete on commands +complete -c command type which + +# builtin completes on builtins +complete -b builtin + +# start of section containing completion functions called by other functions + +# Check if we're running on the given userland +# @param $1 userland to check for +_userland() +{ + local userland=$(uname -s) + [[ $userland == @(Linux|GNU/*) ]] && userland=GNU + [[ $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) + # Slackware uses /etc/rc.d + [[ -f /etc/slackware-version ]] && sysvdirs=(/etc/rc.d) + return 0 +} + +# This function checks whether we have a given program on the system. +# +_have() +{ + # 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 +} + +# This function checks whether a given readline variable +# is `on'. +# +_rl_enabled() +{ + [[ "$(bind -v)" == *$1+([[:space:]])on* ]] +} + +# This function shell-quotes the argument +quote() +{ + local quoted=${1//\'/\'\\\'\'} + printf "'%s'" "$quoted" +} + +# @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() +{ + 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 +# 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 + fi +} + +# Assign variables one scope above the caller +# Usage: local varname [varname ...] && +# _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() +{ + if ! (($#)); then + echo "bash_completion: $FUNCNAME: usage: $FUNCNAME" \ + "[-v varname value] | [-aN varname [value ...]] ..." >&2 + return 2 + fi + while (($#)); do + case $1 in + -a*) + # Error checking + [[ ${1#-a} ]] || { + echo "bash_completion: $FUNCNAME:" \ + "\`$1': missing number specifier" >&2 + return 1 + } + printf %d "${1#-a}" &>/dev/null || { + echo bash_completion: \ + "$FUNCNAME: \`$1': invalid number specifier" >&2 + return 1 + } + # Assign array of -aN elements + [[ "$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 + } + ;; + *) + echo "bash_completion: $FUNCNAME: $1: invalid option" >&2 + 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 +# NOT be considered word breaks. This is useful for things like scp where +# we want to return host:path and not only path, so we would pass the +# colon (:) as $1 here. +# @param $2 words Name of variable to return words to +# @param $3 cword Name of variable to return cword to +# +__reassemble_comp_words_by_ref() +{ + local exclude i j line ref + # Exclude word separator characters? + if [[ $1 ]]; then + # Yes, exclude word separator characters; + # Exclude only those characters, which were really included + exclude="[${1//[^$COMP_WORDBREAKS]/}]" + fi + + # Default to cword unchanged + printf -v "$3" %s "$COMP_CWORD" + # Are characters excluded which were former included? + if [[ -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 + # Is current word not word 0 (the command itself) and is word not + # empty and is word made up of just word separator characters to + # be excluded and is current word not preceded by whitespace in + # original line? + while [[ $i -gt 0 && ${COMP_WORDS[i]} == +($exclude) ]]; do + # Is word separator not preceded by whitespace in original line + # and are we not going to append to word 0 (the command + # itself), then append to current word. + [[ $line != [[:blank:]]* ]] && ((j >= 2)) && ((j--)) + # Append word separator to current or new word + ref="$2[$j]" + printf -v "$ref" %s "${!ref-}${COMP_WORDS[i]}" + # Indicate new cword + ((i == COMP_CWORD)) && printf -v "$3" %s "$j" + # Remove optional whitespace + word separator from line copy + line=${line#*"${COMP_WORDS[i]}"} + # 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]" + printf -v "$ref" %s "${!ref-}${COMP_WORDS[i]}" + # Remove optional whitespace + word from line copy + line=${line#*"${COMP_WORDS[i]}"} + # Indicate new cword + ((i == COMP_CWORD)) && printf -v "$3" %s "$j" + done + ((i == COMP_CWORD)) && printf -v "$3" %s "$j" + else + # No, list of word completions separators hasn't changed; + for i in "${!COMP_WORDS[@]}"; do + printf -v "$2[i]" %s "${COMP_WORDS[i]}" + done + fi +} # __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 +# colon (:) as $1 in this case. +# @param $2 words Name of variable to return words to +# @param $3 cword Name of variable to return cword to +# @param $4 cur Name of variable to return current word to complete to +# @see __reassemble_comp_words_by_ref() +__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} + # Cursor not at position 0 and not leaded by just space(s)? + if [[ $index -gt 0 && ($lead && ${lead//[[:space:]]/}) ]]; then + cur=$COMP_LINE + for ((i = 0; i <= cword; ++i)); do + # Current word fits in $cur, and $cur doesn't match cword? + while [[ ${#cur} -ge ${#words[i]} && \ + ${cur:0:${#words[i]}} != "${words[i]-}" ]]; do + # Strip first character + cur="${cur:1}" + # Decrease cursor position, staying >= 0 + ((index > 0)) && ((index--)) + done + + # Does found word match cword? + if ((i < cword)); then + # No, cword lies further; + local old_size=${#cur} + cur="${cur#"${words[i]}"}" + local new_size=${#cur} + ((index -= old_size - new_size)) + fi + done + # Clear $cur if just space(s) + [[ $cur && ! ${cur//[[:space:]]/} ]] && cur= + # Zero $index if negative + ((index < 0)) && index=0 + fi + + local "$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 +# where the user is completing in the middle of a word. +# (For example, if the line is "ls foobar", +# and the cursor is here --------> ^ +# Also one is able to cross over possible wordbreak characters. +# Usage: _get_comp_words_by_ref [OPTIONS] [VARNAMES] +# Available VARNAMES: +# cur Return cur via $cur +# prev Return prev via $prev +# words Return words via $words +# cword Return cword via $cword +# +# Available OPTIONS: +# -n EXCLUDE Characters out of $COMP_WORDBREAKS which should NOT be +# considered word breaks. This is useful for things like scp +# where we want to return host:path and not only path, so we +# would pass the colon (:) as -n option in this case. +# -c VARNAME Return cur via $VARNAME +# -p VARNAME Return prev via $VARNAME +# -w VARNAME Return words via $VARNAME +# -i VARNAME Return cword via $VARNAME +# +# Example usage: +# +# $ _get_comp_words_by_ref -n : cur prev +# +_get_comp_words_by_ref() +{ + local exclude flag i OPTIND=1 + local cur cword words=() + local upargs=() upvars=() vcur vcword vprev vwords + + while getopts "c:i:n:p:w:" flag "$@"; do + case $flag in + c) vcur=$OPTARG ;; + i) vcword=$OPTARG ;; + n) exclude=$OPTARG ;; + p) vprev=$OPTARG ;; + w) vwords=$OPTARG ;; + *) + echo "bash_completion: $FUNCNAME: usage error" >&2 + return 1 + ;; + esac + done + while [[ $# -ge $OPTIND ]]; do + case ${!OPTIND} in + cur) vcur=cur ;; + prev) vprev=prev ;; + cword) vcword=cword ;; + words) vwords=words ;; + *) + echo "bash_completion: $FUNCNAME: \`${!OPTIND}':" \ + "unknown argument" >&2 + return 1 + ;; + esac + ((OPTIND += 1)) + done + + __get_cword_at_cursor_by_ref "${exclude-}" words cword cur + + [[ -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[@]}" +} + +# 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() +# +_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: +# +# # Remove colon (:) from list of word completion separators +# COMP_WORDBREAKS=${COMP_WORDBREAKS//:} +# +# See also: Bash FAQ - E13) Why does filename completion misbehave if a colon +# appears in the filename? - https://tiswww.case.edu/php/chet/bash/FAQ +# @param $1 current word to complete (cur) +# @modifies global array $COMPREPLY +# +__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() + +# This function quotes the argument in a way so that readline dequoting +# results in the original argument. This is necessary for at least +# `compgen' which requires its arguments quoted/escaped: +# +# $ ls "a'b/" +# c +# $ compgen -f "a'b/" # Wrong, doesn't return output +# $ compgen -f "a\'b/" # Good +# a\'b/c +# +# See also: +# - https://lists.gnu.org/archive/html/bug-bash/2009-03/msg00155.html +# - https://www.mail-archive.com/bash-completion-devel@lists.alioth.debian.org/msg01944.html +# @param $1 Argument to quote +# @param $2 Name of variable to return result to +_quote_readline_by_ref() +{ + if [[ $1 == \'* ]]; then + # Leave out first character + printf -v $2 %s "${1:1}" + else + printf -v $2 %q "$1" + 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. +# +_filedir() +{ + local IFS=$'\n' + + _tilde "${cur-}" || 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' + else + local quoted + _quote_readline_by_ref "${cur-}" quoted + + # Munge xspec to contain uppercase version too + # https://lists.gnu.org/archive/html/bug-bash/2010-09/msg00036.html + # news://news.gmane.io/4C940E1C.1010304@case.edu + local xspec=${arg:+"!*.@($arg|${arg^^})"} plusdirs=() + + # Use plusdirs to get dir completions if we have a xspec; if we don't, + # there's no need, dirs come along with other completions. Don't use + # plusdirs quite yet if fallback is in use though, in order to not ruin + # the fallback condition with the "plus" dirs. + local opts=(-f -X "$xspec") + [[ $xspec ]] && plusdirs=(-o plusdirs) + [[ ${COMP_FILEDIR_FALLBACK-} || -z ${plusdirs-} ]] || + opts+=("${plusdirs[@]}") + + 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 $arg && ${#toks[@]} -lt 1 ]] && { + reset=$(shopt -po noglob) + set -o noglob + toks+=($(compgen -f ${plusdirs+"${plusdirs[@]}"} -- $quoted)) + IFS=' ' + $reset + IFS=$'\n' + } + fi + + 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[@]}") + 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 +# this to be useful. +# Returns 0 if current option was split, 1 otherwise. +# +_split_longopt() +{ + if [[ $cur == --?*=* ]]; then + # Cut also backslash before '=' in case it ended up there + # for some reason. + prev="${cur%%?(\\)=*}" + cur="${cur#*=}" + return 0 + fi + + return 1 +} + +# Complete variables. +# @return True (0) if variables were completed, +# False (> 0) if not. +_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]})) + 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[*]}) + else + # Complete ${var with ${variable} + COMPREPLY+=(${vars[*]}) + fi + else + # Complete $var with $variable + 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]}")) + # Complete ${arr[@ and ${arr[* + if [[ ${BASH_REMATCH[3]} == [@*] ]]; then + COMPREPLY+=("${BASH_REMATCH[1]}${BASH_REMATCH[2]}[${BASH_REMATCH[3]}]}") + 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" + return 0 + else + case ${prev-} in + TZ) + cur=/usr/share/zoneinfo/$cur + _filedir + for i in "${!COMPREPLY[@]}"; do + if [[ ${COMPREPLY[i]} == *.tab ]]; then + unset 'COMPREPLY[i]' + continue + elif [[ -d ${COMPREPLY[i]} ]]; then + COMPREPLY[i]+=/ + compopt -o nospace + 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 +} + +# Initialize completion and deal with various general things: do file +# and variable completion where appropriate, and adjust prev, words, +# and cword as if no redirections exist so that completions do not +# need to deal with them. Before calling this function, make sure +# cur, prev, words, and cword are local, ditto split if you use -s. +# +# Options: +# -n EXCLUDE Passed to _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 = +# @return True (0) if completion needs further processing, +# False (> 0) no further processing is necessary. +# +_init_completion() +{ + local exclude="" flag outx errx inx OPTIND=1 + + while getopts "n:e:o:i:s" flag "$@"; do + case $flag in + n) exclude+=$OPTARG ;; + e) errx=$OPTARG ;; + o) outx=$OPTARG ;; + i) inx=$OPTARG ;; + s) + split=false + exclude+== + ;; + *) + echo "bash_completion: $FUNCNAME: usage error" >&2 + return 1 + ;; + esac + done + + COMPREPLY=() + local redir="@(?([0-9])<|?([0-9&])>?(>)|>&)" + _get_comp_words_by_ref -n "$exclude<>&" cur prev words cword + + # Complete variable names. + _variables && return 1 + + # Complete on files if current is a redirect possibly followed by a + # filename, e.g. ">foo", or previous is a "bare" redirect, e.g. ">". + # shellcheck disable=SC2053 + if [[ $cur == $redir* || ${prev-} == $redir ]]; then + local xspec + case $cur in + 2'>'*) xspec=${errx-} ;; + *'>'*) xspec=${outx-} ;; + *'<'*) xspec=${inx-} ;; + *) + case $prev in + 2'>'*) xspec=${errx-} ;; + *'>'*) xspec=${outx-} ;; + *'<'*) xspec=${inx-} ;; + esac + ;; + esac + cur="${cur##$redir}" + _filedir $xspec + return 1 + fi + + # Remove all redirections so completions don't have to deal with them. + local i skip + for ((i = 1; i < ${#words[@]}; )); do + if [[ ${words[i]} == $redir* ]]; then + # If "bare" redirect, remove also the next word (skip=2). + # shellcheck disable=SC2053 + [[ ${words[i]} == $redir ]] && skip=2 || skip=1 + words=("${words[@]:0:i}" "${words[@]:i+skip}") + ((i <= cword)) && ((cword -= skip)) + else + ((i++)) + fi + done + + ((cword <= 0)) && return 1 + prev=${words[cword - 1]} + + [[ ${split-} ]] && _split_longopt && split=true + + return 0 +} + +# Helper function for _parse_help and _parse_usage. +__parse_options() +{ + local option option2 i IFS=$' \t\n,/|' + + # 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... + + # Expand --[no]foo to --foo and --nofoo etc + if [[ $option =~ (\[((no|dont)-?)\]). ]]; then + option2=${option/"${BASH_REMATCH[1]}"/} + option2=${option2%%[<{().[]*} + printf '%s\n' "${option2/=*/=}" + option=${option/"${BASH_REMATCH[1]}"/"${BASH_REMATCH[2]}"} + fi + + option=${option%%[<{().[]*} + printf '%s\n' "${option/=*/=}" +} + +# 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_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 /, }" + + done +} + +# 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_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 + + 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-}}") +} + +# This function completes on known mac addresses +# +_mac_addresses() +{ + local re='\([A-Fa-f0-9]\{2\}:\)\{5\}[A-Fa-f0-9]\{2\}' + local PATH="$PATH:/sbin:/usr/sbin" + + # Local interfaces + # - 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" + )) + + # 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")) + + # /etc/ethers + COMPREPLY+=($(command sed -ne \ + "s/^[[:space:]]*\($re\)[[:space:]].*/\1/p" /etc/ethers 2>/dev/null)) + + COMPREPLY=($(compgen -W '${COMPREPLY[@]}' -- "$cur")) + __ltrim_colon_completions "$cur" +} + +# This function completes on configured network interfaces +# +_configured_interfaces() +{ + 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")) + 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")) + 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")) + else + # Assume Red Hat + COMPREPLY=($(compgen -W "$(printf '%s\n' \ + /etc/sysconfig/network-scripts/ifcfg-* | + command sed -ne 's|.*ifcfg-\([^*].*\)$|\1|p')" -- "$cur")) + fi +} + +# Local IP addresses. +# -4: IPv4 addresses only (default) +# -6: IPv6 addresses only +# -a: All addresses +# +_ip_addresses() +{ + local n + 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 | + command sed -e 's/[[:space:]]addr:/ /' -ne \ + "s|.*inet${n}[[:space:]]\{1,\}\([^[:space:]/]*\).*|\1|p") + COMPREPLY+=($(compgen -W "$addrs" -- "${cur-}")) +} + +# This function completes on available kernels +# +_kernel_versions() +{ + COMPREPLY=($(compgen -W '$(command ls /lib/modules)' -- "$cur")) +} + +# This function completes on all available network interfaces +# -a: restrict to active interfaces only +# -w: restrict to wireless interfaces only +# +_available_interfaces() +{ + local PATH=$PATH:/sbin + + COMPREPLY=($({ + if [[ ${1:-} == -w ]]; then + iwconfig + elif [[ ${1:-} == -a ]]; then + ifconfig || ip link show up + else + ifconfig -a || ip link show + fi + } 2>/dev/null | awk \ + '/^[^ \t]/ { if ($1 ~ /^[0-9]+:/) { print $2 } else { print $1 } }')) + + COMPREPLY=($(compgen -W '${COMPREPLY[@]/%[[:punct:]]/}' -- "$cur")) +} + +# Echo number of CPUs, falling back to 1 on failure. +_ncpus() +{ + local var=NPROCESSORS_ONLN + [[ $OSTYPE == *linux* ]] && var=_$var + local n=$(getconf $var 2>/dev/null) + printf %s ${n:-1} +} + +# 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() +{ + local result=0 + if [[ ${1-} == \~* && $1 != */* ]]; 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 + 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 +# quoted instead of expanded. +# Only the first portion of the variable from the tilde up to the first slash +# (~../) is expanded. The remainder of the variable, containing for example +# a dollar sign variable ($) or asterisk (*) is not expanded. +# Example usage: +# +# $ v="~"; __expand_tilde_by_ref v; echo "$v" +# +# Example output: +# +# v output +# -------- ---------------- +# ~ /home/user +# ~foo/bar /home/foo/bar +# ~foo/$HOME /home/foo/$HOME +# ~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#\~}")" + fi +} # __expand_tilde_by_ref() + +# This function expands tildes in pathnames +# +_expand() +{ + # Expand ~username type directory specifications. We want to expand + # ~foo/... to /home/foo/... to avoid problems when $cur starting with + # a tilde is fed to commands and ending up quoted instead of expanded. + + case ${cur-} in + ~*/*) + __expand_tilde_by_ref cur + ;; + ~*) + _tilde "$cur" || + eval COMPREPLY[0]="$(printf ~%q "${COMPREPLY[0]#\~}")" + return ${#COMPREPLY[@]} + ;; + esac +} + +# 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")) + } + + _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 ((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: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 + 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")) + elif type perl &>/dev/null; then + 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")) + fi +} + +# This function completes on group IDs +# +_gids() +{ + if type getent &>/dev/null; then + 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")) + else + # make do with /etc/group + COMPREPLY=($(compgen -W '$(cut -d: -f3 /etc/group)' -- "$cur")) + fi +} + +# Glob for matching various backup files. +# +_backup_glob='@(#*#|*@(~|.@(bak|orig|rej|swp|dpkg*|rpm@(orig|new|save))))' + +# Complete on xinetd services +# +_xinetd_services() +{ + 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))) + $reset + ((!${#svcs[@]})) || + COMPREPLY+=($(compgen -W '${svcs[@]#$xinetddir/}' -- "${cur-}")) + fi +} + +# This function completes on services +# +_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))) + $reset + + 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)) + fi + + COMPREPLY=($(compgen -W '${COMPREPLY[@]#${sysvdirs[0]}/}' -- "$cur")) +} + +# 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 +# +_service() +{ + local cur prev words cword + _init_completion || return + + # don't complete past 2nd token + ((cword > 2)) && return + + if [[ $cword -eq 1 && $prev == ?(*/)service ]]; then + _services + [[ -e /etc/mandrake-release ]] && _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")) + 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 + +# This function completes on modules +# +_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")) +} + +# This function completes on installed modules +# +_installed_modules() +{ + 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. +# +# The : must be added manually; it will only complete usernames initially. +# The legacy user.group format is not supported. +# +# @param $1 If -u, only return users/groups the user has access to in +# context of current completion. +_usergroup() +{ + if [[ $cur == *\\\\* || $cur == *:*:* ]]; then + # Give up early on if something seems horribly wrong. + return + elif [[ $cur == *\\:* ]]; then + # 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#*[:]}" + if [[ ${1-} == -u ]]; then + _allowed_groups "$mycur" + else + local IFS=$'\n' + COMPREPLY=($(compgen -g -- "$mycur")) + 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" + else + local IFS=$'\n' + COMPREPLY=($(compgen -g -- "$mycur")) + fi + else + # Completing a partial 'usernam<TAB>'. + # + # 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 + _allowed_users "$cur" + else + local IFS=$'\n' + COMPREPLY=($(compgen -u -- "$cur")) + fi + fi +} + +_allowed_users() +{ + if _complete_as_root; then + local IFS=$'\n' + COMPREPLY=($(compgen -u -- "${1:-$cur}")) + else + local IFS=$'\n ' + COMPREPLY=($(compgen -W \ + "$(id -un 2>/dev/null || whoami 2>/dev/null)" -- "${1:-$cur}")) + fi +} + +_allowed_groups() +{ + if _complete_as_root; then + local IFS=$'\n' + COMPREPLY=($(compgen -g -- "$1")) + else + local IFS=$'\n ' + COMPREPLY=($(compgen -W \ + "$(id -Gn 2>/dev/null || groups 2>/dev/null)" -- "$1")) + fi +} + +# This function completes on valid shells +# +_shells() +{ + local shell rest + while read -r shell rest; do + [[ $shell == /* && $shell == "$cur"* ]] && COMPREPLY+=($shell) + done 2>/dev/null </etc/shells +} + +# This function completes on valid filesystem types +# +_fstypes() +{ + local fss + + if [[ -e /proc/filesystems ]]; then + # Linux + fss="$(cut -d$'\t' -f2 /proc/filesystems) + $(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) + $([[ -d /etc/fs ]] && command ls /etc/fs)" + fi + + [[ -n $fss ]] && COMPREPLY+=($(compgen -W "$fss" -- "$cur")) +} + +# 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 + } +} + +# 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 + + arg= + 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. +# @param $2 glob Options whose following argument should not be counted +# @param $3 glob Options that should be counted as args +_count_args() +{ + local i cword words + __reassemble_comp_words_by_ref "${1-}" words cword + + args=1 + for ((i = 1; i < cword; i++)); do + # shellcheck disable=SC2053 + if [[ ${words[i]} != -* && ${words[i - 1]} != ${2-} || \ + ${words[i]} == ${3-} ]]; then + ((args++)) + fi + done +} + +# This function completes on PCI IDs +# +_pci_ids() +{ + 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")) +} + +# CD device names +_cd_devices() +{ + COMPREPLY+=($(compgen -f -d -X "!*/?([amrs])cd*" -- "${cur:-/dev/}")) +} + +# DVD device names +_dvd_devices() +{ + 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")) +} + +_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 +[[ $OSTYPE == *freebsd* ]] && complete -W 'index search fetch fetch-list + extract patch configure build install reinstall deinstall clean + clean-depends kernel buildworld' make + +# This function provides simple user@host completion +# +_user_at_host() +{ + local cur prev words cword + _init_completion -n : || return + + if [[ $cur == *@* ]]; then + _known_hosts_real "$cur" + else + COMPREPLY=($(compgen -u -S @ -- "$cur")) + compopt -o nospace + fi +} +shopt -u hostcomplete && complete -F _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() + +# 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() +{ + (($# < 1)) && + echo "bash_completion: $FUNCNAME: missing mandatory argument CONFIG" >&2 + local configfile i f + 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" + 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 + fi + done + $reset + done +} # _included_ssh_config_files() + +# Helper function for completing _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 + local -a kh tmpkh=() khd=() config=() + + # TODO remove trailing %foo from entries + + local OPTIND=1 + while getopts "ac46F:p:" flag "$@"; do + case $flag in + a) aliases='yes' ;; + c) suffix=':' ;; + F) configfile=$OPTARG ;; + p) prefix=$OPTARG ;; + 4) ipv4=1 ;; + 6) ipv6=1 ;; + *) + echo "bash_completion: $FUNCNAME: usage error" >&2 + return 1 + ;; + esac + done + if (($# < OPTIND)); then + echo "bash_completion: $FUNCNAME: missing mandatory argument CWORD" >&2 + return 1 + fi + cur=${!OPTIND} + ((OPTIND += 1)) + if (($# >= OPTIND)); then + echo "bash_completion: $FUNCNAME($*): unprocessed arguments:" \ + "$(while (($# >= OPTIND)); do + printf '%s ' ${!OPTIND} + shift + done)" >&2 + return 1 + fi + + [[ $cur == *@* ]] && prefix=$prefix${cur%@*}@ && cur=${cur#*@} + kh=() + + # ssh config files + 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") + 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" + 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") + done + done + fi + + 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") + done + for i in /etc/ssh2/knownhosts ~/.ssh2/hostkeys; do + [[ -d $i ]] && khd+=("$i"/*pub) + done + fi + + # If we have known_hosts files to use + if ((${#kh[@]} + ${#khd[@]} > 0)); then + if ((${#kh[@]} > 0)); then + # https://man.openbsd.org/sshd.8#SSH_KNOWN_HOSTS_FILE_FORMAT + for i in "${kh[@]}"; do + while read -ra tmpkh; do + ((${#tmpkh[@]} == 0)) && continue + set -- "${tmpkh[@]}" + # Skip entries starting with | (hashed) and # (comment) + [[ $1 == [\|\#]* ]] && continue + # Ignore leading @foo (markers) + [[ $1 == @* ]] && shift + # 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 + done <"$i" + done + COMPREPLY=($(compgen -W '${COMPREPLY[@]}' -- "$cur")) + fi + 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 + host=${i/#*key_22_/} + host=${host/%.pub/} + COMPREPLY+=($host) + fi + done + fi + + # apply suffix and prefix + 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 && -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-} ]] && + 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")) + fi + + # Add hosts reported by ruptime. + 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" -S "$suffix" -- "$cur")) + fi + + $reset + + if [[ -v ipv4 ]]; then + COMPREPLY=("${COMPREPLY[@]/*:*$suffix/}") + fi + if [[ -v ipv6 ]]; then + COMPREPLY=("${COMPREPLY[@]/+([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]" + done + fi + + __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. +# +_cd() +{ + local cur prev words cword + _init_completion || return + + local IFS=$'\n' i j k + + 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/} + done + done + + _filedir -d + + if ((${#COMPREPLY[@]} == 1)); then + i=${COMPREPLY[0]} + if [[ $i == "$cur" && $i != "*/" ]]; then + COMPREPLY[0]="${i}/" + fi + fi + + return +} +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() +{ + # rewrite current completion context before invoking + # actual command completion + + # 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 + COMP_LINE=${COMP_LINE:1} + ((COMP_POINT--)) + done + COMP_LINE=${COMP_LINE#"${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]} + done + for ((i; i <= COMP_CWORD; i++)); do + unset 'COMP_WORDS[i]' + done + ((COMP_CWORD -= word_offset)) + + COMPREPLY=() + local cur + _get_comp_words_by_ref cur + + if ((COMP_CWORD == 0)); then + local IFS=$'\n' + compopt -o filenames + COMPREPLY=($(compgen -d -c -- "$cur")) + else + local cmd=${COMP_WORDS[0]} compcmd=${COMP_WORDS[0]} + local cspec=$(complete -p $cmd 2>/dev/null) + + # If we have no completion for $cmd yet, see if we have for basename + if [[ ! $cspec && $cmd == */* ]]; then + cspec=$(complete -p ${cmd##*/} 2>/dev/null) + [[ $cspec ]] && compcmd=${cmd##*/} + fi + # If still nothing, just load it for the basename + if [[ ! $cspec ]]; then + compcmd=${cmd##*/} + _completion_loader $compcmd + cspec=$(complete -p $compcmd 2>/dev/null) + fi + + if [[ -n $cspec ]]; then + if [[ ${cspec#* -F } != "$cspec" ]]; then + # complete -F <function> + + # get function name + local func=${cspec#*-F } + func=${func%% *} + + if ((${#COMP_WORDS[@]} >= 2)); then + $func $cmd "${COMP_WORDS[-1]}" "${COMP_WORDS[-2]}" + else + $func $cmd "${COMP_WORDS[-1]}" + fi + + # restore initial compopts + local opt + while [[ $cspec == *" -o "* ]]; do + # FIXME: should we take "+o opt" into account? + cspec=${cspec#*-o } + opt=${cspec%% *} + compopt -o $opt + cspec=${cspec#$opt} + done + else + cspec=${cspec#complete} + cspec=${cspec%%$compcmd} + COMPREPLY=($(eval compgen "$cspec" -- '$cur')) + 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 + fi +} +complete -F _command aoss command "do" else eval exec ltrace nice nohup padsp \ + "then" time tsocks vsound xargs + +_root_command() +{ + local PATH=$PATH:/sbin:/usr/sbin:/usr/local/sbin + local root_command=$1 + _command +} +complete -F _root_command fakeroot gksu gksudo kdesudo really + +# Return true if the completion should be treated as running as root +_complete_as_root() +{ + [[ $EUID -eq 0 || ${root_command:-} ]] +} + +_longopt() +{ + local cur prev words cword split + _init_completion -s || return + + case "${prev,,}" in + --help | --usage | --version) + return + ;; + --!(no-*)dir*) + _filedir -d + return + ;; + --!(no-*)@(file|path)*) + _filedir + return + ;; + --+([-a-z0-9_])) + local argtype=$(LC_ALL=C $1 --help 2>&1 | command sed -ne \ + "s|.*$prev\[\{0,1\}=[<[]\{0,1\}\([-A-Za-z0-9_]\{1,\}\).*|\1|p") + case ${argtype,,} in + *dir*) + _filedir -d + return + ;; + *file* | *path*) + _filedir + return + ;; + esac + ;; + esac + + $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]+([-_][A-Za-z0-9]+)*=? ]] && + printf '%s\n' ${BASH_REMATCH[0]} + done)" -- "$cur")) + [[ ${COMPREPLY-} == *= ]] && compopt -o nospace + elif [[ $1 == *@(rmdir|chroot) ]]; then + _filedir -d + else + [[ $1 == *mkdir ]] && compopt -o nospace + _filedir + fi +} +# makeinfo and texi2dvi are defined elsewhere. +complete -F _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 \ + 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 \ + texindex touch tr uname unexpand uniq units vdir wc who + +declare -Ag _xspecs + +_filedir_xspec() +{ + local cur prev words cword + _init_completion || return + + _tilde "$cur" || return + + local IFS=$'\n' xspec=${_xspecs[${1##*/}]} tmp + local -a toks + + toks=($( + compgen -d -- "$(quote_readline "$cur")" | { + while read -r tmp; do + printf '%s\n' $tmp + done + } + )) + + # Munge xspec to contain uppercase version too + # https://lists.gnu.org/archive/html/bug-bash/2010-09/msg00036.html + # news://news.gmane.io/4C940E1C.1010304@case.edu + eval xspec="${xspec}" + local matchop=! + if [[ $xspec == !* ]]; then + xspec=${xspec#!} + matchop=@ + fi + xspec="$matchop($xspec|${xspec^^})" + + 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' + } + + if ((${#toks[@]} != 0)); then + compopt -o filenames + COMPREPLY=("${toks[@]}") + fi +} + +_install_xspec() +{ + local xspec=$1 cmd + shift + for cmd in "$@"; do + _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 +# 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 +# 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 +# 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 +} +# 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 '' + +__load_completion() +{ + 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) + done + IFS=$ifs + + if [[ $BASH_SOURCE == */* ]]; then + dirs+=("${BASH_SOURCE%/*}/completions") + else + 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 + 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 + fi + done + done + + # Look up simple "xspec" completions + [[ -v _xspecs[$cmd] ]] && + complete -F _filedir_xspec "$cmd" "$backslash$cmd" && return 0 + + return 1 +} + +# set up dynamic completion loading +_completion_loader() +{ + # $1=_EmptycmD_ already for empty cmds in bash 4.3, set to it for earlier + 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 +} && + complete -D -F _completion_loader + +# 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" + "$@" +} + +# 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 +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 && -f $user_completion ]] && + . $user_completion +unset user_completion + +unset -f have +unset have + +set $BASH_COMPLETION_ORIGINAL_V_VALUE +unset BASH_COMPLETION_ORIGINAL_V_VALUE + +# ex: filetype=sh diff --git a/bash_completion.sh.in b/bash_completion.sh.in new file mode 100644 index 0000000..b2a527e --- /dev/null +++ b/bash_completion.sh.in @@ -0,0 +1,16 @@ +# shellcheck shell=sh disable=SC1091,SC2039,SC2166 +# Check for interactive bash and that we haven't already been sourced. +if [ "x${BASH_VERSION-}" != x -a "x${PS1-}" != x -a "x${BASH_COMPLETION_VERSINFO-}" = x ]; then + + # Check for recent enough version of bash. + if [ "${BASH_VERSINFO[0]}" -gt 4 ] || + [ "${BASH_VERSINFO[0]}" -eq 4 -a "${BASH_VERSINFO[1]}" -ge 2 ]; then + [ -r "${XDG_CONFIG_HOME:-$HOME/.config}/bash_completion" ] && + . "${XDG_CONFIG_HOME:-$HOME/.config}/bash_completion" + if shopt -q progcomp && [ -r @datadir@/@PACKAGE@/bash_completion ]; then + # Source completion code. + . @datadir@/@PACKAGE@/bash_completion + fi + fi + +fi |