Skip to content

fix(_comp_compgen_*): avoid conflicts with "-v var" #1028

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Aug 26, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 92 additions & 69 deletions bash_completion

Large diffs are not rendered by default.

16 changes: 8 additions & 8 deletions completions/bts
Original file line number Diff line number Diff line change
@@ -9,14 +9,14 @@ _comp_cmd_bts__compgen_cached_bugs()
-name "${cur}[0-9]*.html" \
-printf "%f\n" | cut -d'.' -f1
)
_comp_compgen -aR -- -W '$bugs'
_comp_compgen -RU bugs -- -W '$bugs'
}

# Generate APT source packages prefixed with "src:"
_comp_cmd_bts__compgen_src_packages_with_prefix()
{
local ppn=${cur:4} # partial package name, after stripping "src:"
_comp_compgen -ac "$ppn" split -P "src:" -- \
_comp_compgen -c "$ppn" -U ppn split -P "src:" -- \
"$(_comp_xfunc apt-cache sources "$ppn")"
}

@@ -28,8 +28,8 @@ _comp_cmd_bts()
case $prev in
show | bugs)
_comp_compgen -- -W 'release-critical RC from: tag: usertag:'
_comp_cmd_bts__compgen_cached_bugs
_comp_cmd_bts__compgen_src_packages_with_prefix
_comp_compgen -ai bts cached_bugs
_comp_compgen -ai bts src_packages_with_prefix
return
;;
select)
@@ -40,7 +40,7 @@ _comp_cmd_bts()
;;
status)
_comp_compgen -- -W 'file: fields: verbose'
_comp_cmd_bts__compgen_cached_bugs
_comp_compgen -ai bts cached_bugs
return
;;
block | unblock)
@@ -58,7 +58,7 @@ _comp_cmd_bts()
return
;;
clone | "done" | reopen | archive | unarchive | retitle | summary | submitter | found | notfound | fixed | notfixed | merge | forcemerge | unmerge | claim | unclaim | forwarded | notforwarded | owner | noowner | subscribe | unsubscribe | reportspam | spamreport | affects | usertag | usertags | reassign | tag | tags)
_comp_cmd_bts__compgen_cached_bugs
_comp_compgen -i bts cached_bugs
return
;;
package)
@@ -67,13 +67,13 @@ _comp_cmd_bts()
;;
cache)
COMPREPLY=($(_comp_xfunc apt-cache packages))
_comp_cmd_bts__compgen_src_packages_with_prefix
_comp_compgen -ai bts src_packages_with_prefix
_comp_compgen -a -- -W 'from: release-critical RC'
return
;;
cleancache)
COMPREPLY=($(_comp_xfunc apt-cache packages))
_comp_cmd_bts__compgen_src_packages_with_prefix
_comp_compgen -ai bts src_packages_with_prefix
_comp_compgen -a -- -W 'from: tag: usertag: ALL'
return
;;
4 changes: 2 additions & 2 deletions completions/cvs
Original file line number Diff line number Diff line change
@@ -43,9 +43,9 @@ _comp_xfunc_cvs_compgen_roots()
local -a cvsroots=()
[[ -v CVSROOT ]] && cvsroots=("$CVSROOT")
[[ -r ~/.cvspass ]] && cvsroots+=($(awk '{ print $2 }' ~/.cvspass))
[[ -r CVS/Root ]] && mapfile -tO ${#cvsroots[@]} cvsroots <CVS/Root
[[ -r CVS/Root ]] && mapfile -tO "${#cvsroots[@]}" cvsroots <CVS/Root
((${#cvsroots[@]})) &&
_comp_compgen -- -W '"${cvsroots[@]}"'
_comp_compgen -U cvsroots -- -W '"${cvsroots[@]}"'
_comp_ltrim_colon_completions "$cur"
}

14 changes: 7 additions & 7 deletions completions/openssl
Original file line number Diff line number Diff line change
@@ -2,27 +2,27 @@

_comp_cmd_openssl__compgen_sections()
{
local config i f
local config _i _file

# check if a specific configuration file is used
for ((i = 2; i < cword; i++)); do
if [[ ${words[i]} == -config ]]; then
config=${words[i + 1]}
for ((_i = 2; _i < cword; _i++)); do
if [[ ${words[_i]} == -config ]]; then
config=${words[_i + 1]}
break
fi
done

# if no config given, check some usual default locations
if [[ ! $config ]]; then
for f in /etc/ssl/openssl.cnf /etc/pki/tls/openssl.cnf \
for _file in /etc/ssl/openssl.cnf /etc/pki/tls/openssl.cnf \
/usr/share/ssl/openssl.cnf; do
[[ -f $f ]] && config=$f && break
[[ -f $_file ]] && config=$_file && break
done
fi

[[ ! -f $config ]] && return

_comp_compgen_split -- "$(awk '/\[.*\]/ {print $2}' "$config")"
_comp_compgen -U config split -- "$(awk '/\[.*\]/ {print $2}' "$config")"
}

_comp_cmd_openssl__compgen_digests()
6 changes: 3 additions & 3 deletions completions/python
Original file line number Diff line number Diff line change
@@ -3,9 +3,9 @@
# @since 2.12
_comp_xfunc_python_compgen_modules()
{
local python=python
[[ ${comp_args[0]##*/} == *3* ]] && python=python3
_comp_cmd_python__compgen_modules "$python"
local _python=python
[[ ${comp_args[0]##*/} == *3* ]] && _python=python3
_comp_cmd_python__compgen_modules "$_python"
}

# @deprecated 2.12 use `_comp_xfunc_python_compgen_modules` instead
30 changes: 15 additions & 15 deletions completions/ssh
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ _comp_cmd_ssh__compgen_queries()
key-plain key-sig protocol-version compression sig ciphers macs
kexalgorithms pubkeyacceptedkeytypes hostkeyalgorithms
hostbasedkeytypes hostbasedacceptedkeytypes)
_comp_compgen -c "${cur,,}" -- -W '"${ret[@]}" help"'
_comp_compgen -c "${cur,,}" -U ret -- -W '"${ret[@]}" help"'
}

# @since 2.12
@@ -37,7 +37,7 @@ _comp_cmd_ssh__compgen_ciphers()
[[ ${ret-} ]] || ret=(3des-cbc aes128-cbc aes192-cbc aes256-cbc
aes128-ctr aes192-ctr aes256-ctr arcfour128 arcfour256 arcfour
blowfish-cbc cast128-cbc)
_comp_compgen -- -W '"${ret[@]}"'
_comp_compgen -U ret -- -W '"${ret[@]}"'
}

_comp_cmd_ssh__compgen_macs()
@@ -46,7 +46,7 @@ _comp_cmd_ssh__compgen_macs()
_comp_compgen -v ret -i ssh query "$1" mac
[[ ${ret-} ]] || ret=(hmac-md5 hmac-sha1 [email protected]
hmac-ripemd160 hmac-sha1-96 hmac-md5-96)
_comp_compgen -- -W '"${ret[@]}"'
_comp_compgen -U ret -- -W '"${ret[@]}"'
}

# @since 2.12
@@ -283,7 +283,7 @@ _comp_xfunc_ssh_compgen_identityfile()
local cur=$cur tmp
[[ ! $cur && -d ~/.ssh ]] && cur=~/.ssh/id
_comp_compgen -v tmp -c "$cur" filedir &&
_comp_compgen -- -W '"${tmp[@]}"' -X "${1:+!}*.pub"
_comp_compgen -U tmp -- -W '"${tmp[@]}"' -X "${1:+!}*.pub"
}

_comp_deprecate_func 2.12 _ssh_identityfile _comp_xfunc_ssh_compgen_identityfile
@@ -467,35 +467,35 @@ _comp_xfunc_scp_compgen_remote_files()
# remove backslash escape from the first colon
cur=${cur/\\:/:}

local userhost=${cur%%?(\\):*}
local path=${cur#*:}
local _userhost=${cur%%?(\\):*}
local _path=${cur#*:}

# unescape (3 backslashes to 1 for chars we escaped)
# shellcheck disable=SC2090
path=$(command sed -e 's/\\\\\\\('"$_comp_cmd_scp__path_esc"'\)/\\\1/g' <<<"$path")
_path=$(command sed -e 's/\\\\\\\('"$_comp_cmd_scp__path_esc"'\)/\\\1/g' <<<"$_path")

# default to home dir of specified user on remote host
if [[ ! $path ]]; then
path=$(ssh -o 'Batchmode yes' "$userhost" pwd 2>/dev/null)
if [[ ! $_path ]]; then
_path=$(ssh -o 'Batchmode yes' "$_userhost" pwd 2>/dev/null)
fi

local files
local _files
if [[ ${1-} == -d ]]; then
# escape problematic characters; remove non-dirs
# shellcheck disable=SC2090
files=$(ssh -o 'Batchmode yes' "$userhost" \
command ls -aF1dL "$path*" 2>/dev/null |
_files=$(ssh -o 'Batchmode yes' "$_userhost" \
command ls -aF1dL "$_path*" 2>/dev/null |
command sed -e 's/'"$_comp_cmd_scp__path_esc"'/\\\\\\&/g' -e '/[^\/]$/d')
else
# escape problematic characters; remove executables, aliases, pipes
# and sockets; add space at end of file names
# shellcheck disable=SC2090
files=$(ssh -o 'Batchmode yes' "$userhost" \
command ls -aF1dL "$path*" 2>/dev/null |
_files=$(ssh -o 'Batchmode yes' "$_userhost" \
command ls -aF1dL "$_path*" 2>/dev/null |
command sed -e 's/'"$_comp_cmd_scp__path_esc"'/\\\\\\&/g' -e 's/[*@|=]$//g' \
-e 's/[^\/]$/& /g')
fi
_comp_split -l COMPREPLY "$files"
_comp_compgen_split -l -- "$_files"
}

# @deprecated 2.12 use `_comp_compgen -ax ssh remote_files` instead
44 changes: 43 additions & 1 deletion doc/api-and-naming.md
Original file line number Diff line number Diff line change
@@ -150,7 +150,7 @@ calling `_comp_compgen` or other generators.
To avoid conflicts with the options specified to `_comp_compgen`, one should
not directly modify or reference the target variable. When post-filtering is
needed, store them in a local array, filter them, and finally append them by
`_comp_compgen -- -W '"${arr[@]}"'`. To split the output of commands and
`_comp_compgen -- -W '"${_arr[@]}"'`. To split the output of commands and
append the results to the target variable, use `_comp_compgen_split -- "$(cmd
...)"` instead of using `_comp_split COMPREPLY "$(cmd ...)"`.

@@ -180,3 +180,45 @@ Exported generators are defined with the names `_comp_xfunc_CMD_compgen_NAME`
and called by `_comp_compgen [opts] -x CMD NAME args`. Internal generators are
defined with the names `_comp_cmd_CMD__compgen_NAME` and called by
`_comp_compgen [opts] -i CMD NAME args`.

#### Local variables of generator and `_comp_compgen -U var`

A generator should basically define local variables with the names starting
with `_`. However, a generator sometimes needs to use local variable names
that do not start with `_`. When the child generator call with a variable name
(such as `local var; _comp_compgen -v var`) is used within the generator, the
local variable can unexpectedly mask a local variable of the upper call.

For example, the following call fails to obtain the result of generator
`mygen1` because the array `arr` is masked by the same name of a local variable
in `_comp_compgen_mygen1`.

```bash
# generator with a problem
_comp_compgen_mygen1()
{
local -a arr=(1 2 3)
_comp_compgen -av arr -- -W '4 5 6'
_comp_compgen_set "${arr[@]/#p}"
}

_comp_compgen -v arr mygen1 # fails to get the result in array `arr`
```

To avoid this, a generator that defines a local variable with its name not
starting with `_` can use the option `-U var` to unlocalize the variable on
assigning the final result.

```bash
# properly designed generator
_comp_compgen_mygen1()
{
local -a arr=(1 2 3)
_comp_compgen -av arr -- -W '4 5 6'
_comp_compgen -U arr set "${arr[@]/#p}"
}
```

To avoid unexpected unlocalization of previous-scope variables, a generator
should specify `-U var` to a child generator (that attempts to store results to
the current target variable) at most once.
12 changes: 12 additions & 0 deletions test/t/unit/test_unit_compgen.py
Original file line number Diff line number Diff line change
@@ -37,6 +37,12 @@ def functions(self, bash):
"complete -F _comp_cmd_fcd fcd",
)

# test_8_option_U
assert_bash_exec(
bash,
"_comp_compgen_gen8() { local -a arr=(x y z); _comp_compgen -U arr -- -W '\"${arr[@]}\"'; }",
)

def test_1_basic(self, bash, functions):
output = assert_bash_exec(
bash, "_comp__test_words 12 34 56 ''", want_output=True
@@ -146,3 +152,9 @@ def test_7_xcmd(self, bash, functions):

completions = assert_complete(bash, "compgen-cmd2 '")
assert completions == ["012", "123", "234", "5foo", "6bar", "7baz"]

def test_8_option_U(self, bash, functions):
output = assert_bash_exec(
bash, "_comp__test_compgen gen8", want_output=True
)
assert output.strip() == "<x><y><z>"