Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
27 changes: 8 additions & 19 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,15 @@ repos:
hooks:
- id: shfmt
types: [text]
files: ^(bash_completion(\.d/[^/]+\.bash)?|completions/.+|test/(config/bashrc|fixtures/.+/bin/.+|fallback/update-fallback-links|runLint|update-test-cmd-list)|.+\.sh(\.in)?)$
exclude: ^completions/(\.gitignore|Makefile.*)$
files: ^(bash_completion(\.d/[^/]+\.bash)?|completions-(core|fallback)/.+\.bash|test/(config/bashrc|fixtures/.+/bin/.+|runLint|update-test-cmd-list)|.+\.sh(\.in)?)$

- repo: https://github.com/shellcheck-py/shellcheck-py
rev: v0.11.0.1
hooks:
- id: shellcheck
args: [-f, gcc]
types: [text]
files: ^(bash_completion(\.d/[^/]+\.bash)?|completions/.+|test/(config/bashrc|fixtures/.+/bin/.+|fallback/update-fallback-links|runLint|update-test-cmd-list)|.+\.sh(\.in)?)$
exclude: ^completions/(\.gitignore|Makefile.*)$
files: ^(bash_completion(\.d/[^/]+\.bash)?|completions-(core|fallback)/.+\.bash|test/(config/bashrc|fixtures/.+/bin/.+|runLint|update-test-cmd-list)|.+\.sh(\.in)?)$
require_serial: false # We disable SC1090 anyway, so parallel is ok

- repo: local
Expand All @@ -40,24 +38,15 @@ repos:
files: ^test/t/.+\.py$
pass_filenames: false

- id: update-test-fallback-links
name: update-test-fallback-links
language: script
entry: test/fallback/update-fallback-links
files: ^completions/_
pass_filenames: false

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.14.3
hooks:
- id: ruff-check
types: [text]
files: ^(helpers/python|.+\.py)$
exclude: ^completions/
files: ^(helpers-core/python|.+\.py)$
- id: ruff-format
types: [text]
files: ^(helpers/python|.+\.py)$
exclude: ^completions/
files: ^(helpers-core/python|.+\.py)$

- repo: local
hooks:
Expand All @@ -69,23 +58,23 @@ repos:
entry: venv-run mypy
args: [--config-file=test/setup.cfg]
types: [python]
# Intentionally not run on helpers/python (we support very old versions)
exclude: ^completions/|^test/fixtures/pytest/
# Intentionally not run on helpers-core/python (we support very old versions)
exclude: ^completions-(core|fallback)/|^test/fixtures/pytest/

- repo: https://github.com/perltidy/perltidy
rev: "20250912"
hooks:
- id: perltidy
types: [text]
files: ^(helpers/perl|.+\.p[ml])$
files: ^(helpers-core/perl|.+\.p[ml])$

- repo: https://github.com/scop/pre-commit-perlcritic
rev: v1.156-1
hooks:
- id: perlcritic
args: [--quiet, --verbose, "5"]
types: [text]
files: ^(helpers/perl|.+\.p[ml])$
files: ^(helpers-core/perl|.+\.p[ml])$

- repo: https://github.com/jackdewinter/pymarkdown
rev: v0.9.33
Expand Down
48 changes: 24 additions & 24 deletions .typos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,66 +3,66 @@
BRE = "BRE"

[default.extend-words]
# completions/abook
# completions-core/abook
informat = "informat"
# completions/ri
# completions-core/ri
ane = "ane"
# completions/reportbug
# completions-core/reportbug
buildd = "buildd"
# completions/tar
# completions-core/tar
caf = "caf"
# completions/chage, test/t/Makefile.am, test/t/test_chage.py,
# completions-core/chage, test/t/Makefile.am, test/t/test_chage.py,
# test/test-cmd-list.txt
chage = "chage"
# test/t/test_ccache.py
clea = "clea"
# test/t/test_pylint_3.py
clien = "clien"
# completions/openssl
# completions-core/openssl
ede = "ede"
# completions/patch
# completions-core/patch
fior = "fior"
# completions/make
# completions-core/make
fo = "fo"
# completions/.gitignore, completions/Makefile.am, completions/_hexdump,
# completions/gnome-screenshot, completions/mii-diag, completions/mii-tool,
# completions/qemu,
# completions-fallback/.gitignore, completions-fallback/Makefile.am,
# completions-fallback/_hexdump, completions-core/gnome-screenshot,
# completions-core/mii-diag, completions-core/mii-tool, completions-core/qemu,
hd = "hd"
# test/t/test_ccache.py
hel = "hel"
# completions/bts
# completions-core/bts
helo = "helo"
# completions/ip
# completions-core/ip
iif = "iif"
# completions/tcpdump
# completions-core/tcpdump
inout = "inout"
# test/t/unit/test_unit_expand_glob.py
ket = "ket"
# completions/isql
# completions-core/isql
Lins = "Lins"
# completions/hcitool, completions/ip
# completions-core/hcitool, completions-core/ip
lst = "lst"
# completions/tshark, test/t/test_screen.py
# completions-core/tshark, test/t/test_screen.py
nd = "nd"
# bash_completion
odf = "odf"
# completions/ip
# completions-core/ip
oif = "oif"
# completions/mplayer
# completions-core/mplayer
oly = "oly"
# test/t/unit/test_unit_find_unique_completion_pair.py
ot = "ot"
# completions/modinfo
# completions-core/modinfo
parm = "parm"
# bash_completion, completions/eog, completions/gnome-screenshot,
# bash_completion, completions-core/eog, completions-core/gnome-screenshot,
# test/t/test_nmap.py
pn = "pn"
# completions/wget
# completions-core/wget
referer = "referer"
# completions/_mount.linux, completions/tune2fs, test/t/test_curl.py,
# completions-core/_mount.linux, completions-core/tune2fs, test/t/test_curl.py,
# test/t/unit/test_unit_find_unique_completion_pair.py
ro = "ro"
# completions/ps
# completions-core/ps
ser = "ser"

[files]
Expand Down
3 changes: 2 additions & 1 deletion Makefile.am
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
SUBDIRS = completions doc helpers test
SUBDIRS = doc completions-core completions-fallback helpers-core \
test completions helpers

pkgdata_DATA = bash_completion

Expand Down
34 changes: 18 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -370,24 +370,26 @@ A. Absolutely not. zsh has an extremely sophisticated completion system
**Q. What is the search order for the completion file of each target command?**

A. The completion files of commands are looked up by the shell function
`__load_completion`. Here, the search order in bash-completion >= 2.12 is
explained.

1. `BASH_COMPLETION_USER_DIR`. The subdirectory `completions` of each paths
in `BASH_COMPLETION_USER_DIR` separated by colons is considered for a
completion directory.
2. The location of the main `bash_completion` file. The subdirectory
`completions` in the same directory as `bash_completion` is considered.
`__load_completion`. Here, the search order in bash-completion >= 2.18 is
explained. We first list up the bash-completion directories:

1. `BASH_COMPLETION_USER_DIR`. Each paths in `BASH_COMPLETION_USER_DIR`
separated by colons is considered for a completion directory.
2. The location of the main `bash_completion` file. The directory
containing `bash_completion` is considered.
3. The location of the target command. When the real location of the command
is in the directory `<prefix>/bin` or `<prefix>/sbin`, the directory
`<prefix>/share/bash-completion/completions` is considered.
`<prefix>/share/bash-completion` is considered.
4. `XDG_DATA_DIRS` (or the system directories `/usr/local/share:/usr/share`
if empty). The subdirectory `bash-completion/completions` of each paths
if empty). The subdirectory `bash-completion` of each paths
in `XDG_DATA_DIRS` separated by colons is considered.

The completion files of the name `<cmd>` or `<cmd>.bash`, where `<cmd>` is
the name of the target command, are searched in the above completion
directories in order. The file that is found first is used. When no
completion file is found in any completion directories in this process, the
completion files of the name `_<cmd>` is next searched in the completion
directories in order.
The completion files of the name `<cmd>.bash` or `<cmd>`, where `<cmd>` is
the name of the target command, are searched in the subdirectory
`completions` in the above bash-completion directories in order. The file
that is found first is used. When no completion file is found in this step,
the completion files of the name `_<cmd>` is next searched in the
`completions` subdirectories in order. When no completion file has not yet
been found in any `completions` directories, the completion files in the
subdirectory `completions-core` and `completions-fallback` at the location of
the main `bash_completion` file are searched in order.
111 changes: 66 additions & 45 deletions bash_completion
Original file line number Diff line number Diff line change
Expand Up @@ -3381,6 +3381,58 @@ _comp__init_base_directory()
}
_comp__init_base_directory

# Attempt to load the specified file and check if the completion setting is
# successfully loaded.
#
# @param $1 compfile ... filename to check.
# @var[in] cmdname
# @var[in] cmd
# @var[in] backslash
# @var[in] origcmd
# @var[in] source_args
# @exit 0 when the file is found, and the completion setting was successfully
# loaded. Otherwise, 1.
_comp_load__visit_file()
{
local compfile=$1
shift
[[ -e $compfile ]] || return 1

# Set up default $IFS in case loaded completions depend on it, as well as
# for $compspec invocation below.
local IFS=$' \t\n' compspec

# Avoid trying to source dirs as long as we support bash < 4.3
# to avoid an fd leak; https://bugzilla.redhat.com/903540
if [[ -d $compfile ]]; then
# Do not warn with . or .. (especially the former is common)
[[ $compfile == */.?(.) ]] ||
echo "bash_completion: $compfile: is a directory" >&2
elif . "$compfile" "$cmd" ${source_args[@]+"${source_args[@]}"}; then

# At least $cmd is expected to have a completion set when we return
# successfully; see if it already does
if compspec=$(complete -p -- "$cmd" 2>/dev/null); then
# $cmd is the case in which we do backslash processing
[[ $backslash ]] && eval "$compspec \"\$backslash\$cmd\""
# If invoked without path, that one should be set, too
# ...but let's not overwrite an existing one, if any
[[ $origcmd != */* ]] &&
! complete -p -- "$origcmd" &>/dev/null &&
eval "$compspec \"\$origcmd\""
return 0
fi
# If not, see if we got one for $cmdname
if [[ $cmdname != "$cmd" ]] && compspec=$(complete -p -- "$cmdname" 2>/dev/null); then
# Use that for $cmd too, if we have a full path to it
[[ $cmd == /* ]] && eval "$compspec \"\$cmd\""
return 0
fi
# Nothing expected was set, continue lookup
fi
return 1
}

# @since 2.12
_comp_load()
{
Expand Down Expand Up @@ -3422,18 +3474,17 @@ _comp_load()
# 1) From BASH_COMPLETION_USER_DIR (e.g. ~/.local/share/bash-completion):
# User installed completions.
if [[ ${BASH_COMPLETION_USER_DIR-} ]]; then
_comp_split -F : paths "$BASH_COMPLETION_USER_DIR" &&
dirs+=("${paths[@]/%//completions}")
_comp_split -F : dirs "$BASH_COMPLETION_USER_DIR"
else
dirs=("${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion/completions")
dirs=("${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion")
fi

# 2) From the location of bash_completion: Completions relative to the main
# script. This is primarily for run-in-place-from-git-clone setups, where
# we want to prefer in-tree completions over ones possibly coming with a
# system installed bash-completion. (Due to usual install layouts, this
# often hits the correct completions in system installations, too.)
dirs+=("$_comp__base_directory/completions")
dirs+=("$_comp__base_directory")

# 3) From bin directories extracted from the specified path to the command,
# the real path to the command, and $PATH
Expand All @@ -3443,60 +3494,30 @@ _comp_load()
_comp_split -aF : paths "$PATH"
for dir in "${paths[@]%/}"; do
[[ $dir == ?*/@(bin|sbin) ]] &&
dirs+=("${dir%/*}/share/bash-completion/completions")
dirs+=("${dir%/*}/share/bash-completion")
done

# 4) From XDG_DATA_DIRS or system dirs (e.g. /usr/share, /usr/local/share):
# Completions in the system data dirs.
_comp_split -F : paths "${XDG_DATA_DIRS:-/usr/local/share:/usr/share}" &&
dirs+=("${paths[@]/%//bash-completion/completions}")

# Set up default $IFS in case loaded completions depend on it,
# as well as for $compspec invocation below.
local IFS=$' \t\n'
dirs+=("${paths[@]/%//bash-completion}")

# Look up and source
shift
local i prefix compspec
local -a source_args=("$@")

local i prefix
for prefix in "" _; do # Regular from all dirs first, then fallbacks
for i in ${!dirs[*]}; do
dir=${dirs[i]}
if [[ ! -d $dir ]]; then
unset -v 'dirs[i]'
continue
fi
for i in "${!dirs[@]}"; do
dir=${dirs[i]}/completions
[[ -d $dir ]] || continue
for compfile in "$prefix$cmdname" "$prefix$cmdname.bash"; do
compfile="$dir/$compfile"
# Avoid trying to source dirs as long as we support bash < 4.3
# to avoid an fd leak; https://bugzilla.redhat.com/903540
if [[ -d $compfile ]]; then
# Do not warn with . or .. (especially the former is common)
[[ $compfile == */.?(.) ]] ||
echo "bash_completion: $compfile: is a directory" >&2
elif [[ -e $compfile ]] && . "$compfile" "$cmd" "$@"; then
# At least $cmd is expected to have a completion set when
# we return successfully; see if it already does
if compspec=$(complete -p -- "$cmd" 2>/dev/null); then
# $cmd is the case in which we do backslash processing
[[ $backslash ]] && eval "$compspec \"\$backslash\$cmd\""
# If invoked without path, that one should be set, too
# ...but let's not overwrite an existing one, if any
[[ $origcmd != */* ]] &&
! complete -p -- "$origcmd" &>/dev/null &&
eval "$compspec \"\$origcmd\""
return 0
fi
# If not, see if we got one for $cmdname
if [[ $cmdname != "$cmd" ]] && compspec=$(complete -p -- "$cmdname" 2>/dev/null); then
# Use that for $cmd too, if we have a full path to it
[[ $cmd == /* ]] && eval "$compspec \"\$cmd\""
return 0
fi
# Nothing expected was set, continue lookup
fi
_comp_load__visit_file "$dir/$compfile" && return 0
done
done
done
_comp_load__visit_file "$_comp__base_directory/completions-core/$cmdname.bash" && return 0
_comp_load__visit_file "$_comp__base_directory/completions-fallback/$cmdname.bash" && return 0

# Look up simple "xspec" completions
[[ -v _comp_xspecs[$cmdname] || -v _xspecs[$cmdname] ]] &&
Expand Down
Loading