Skip to content

Commit e2e11e6

Browse files
authored
Merge pull request #1028 from akinomyoga/generator-vars
fix(_comp_compgen_*): avoid conflicts with "-v var"
2 parents 2b3f854 + f2df91d commit e2e11e6

File tree

8 files changed

+182
-105
lines changed

8 files changed

+182
-105
lines changed

bash_completion

Lines changed: 92 additions & 69 deletions
Large diffs are not rendered by default.

completions/bts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ _comp_cmd_bts__compgen_cached_bugs()
99
-name "${cur}[0-9]*.html" \
1010
-printf "%f\n" | cut -d'.' -f1
1111
)
12-
_comp_compgen -aR -- -W '$bugs'
12+
_comp_compgen -RU bugs -- -W '$bugs'
1313
}
1414

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

@@ -28,8 +28,8 @@ _comp_cmd_bts()
2828
case $prev in
2929
show | bugs)
3030
_comp_compgen -- -W 'release-critical RC from: tag: usertag:'
31-
_comp_cmd_bts__compgen_cached_bugs
32-
_comp_cmd_bts__compgen_src_packages_with_prefix
31+
_comp_compgen -ai bts cached_bugs
32+
_comp_compgen -ai bts src_packages_with_prefix
3333
return
3434
;;
3535
select)
@@ -40,7 +40,7 @@ _comp_cmd_bts()
4040
;;
4141
status)
4242
_comp_compgen -- -W 'file: fields: verbose'
43-
_comp_cmd_bts__compgen_cached_bugs
43+
_comp_compgen -ai bts cached_bugs
4444
return
4545
;;
4646
block | unblock)
@@ -58,7 +58,7 @@ _comp_cmd_bts()
5858
return
5959
;;
6060
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)
61-
_comp_cmd_bts__compgen_cached_bugs
61+
_comp_compgen -i bts cached_bugs
6262
return
6363
;;
6464
package)
@@ -67,13 +67,13 @@ _comp_cmd_bts()
6767
;;
6868
cache)
6969
COMPREPLY=($(_comp_xfunc apt-cache packages))
70-
_comp_cmd_bts__compgen_src_packages_with_prefix
70+
_comp_compgen -ai bts src_packages_with_prefix
7171
_comp_compgen -a -- -W 'from: release-critical RC'
7272
return
7373
;;
7474
cleancache)
7575
COMPREPLY=($(_comp_xfunc apt-cache packages))
76-
_comp_cmd_bts__compgen_src_packages_with_prefix
76+
_comp_compgen -ai bts src_packages_with_prefix
7777
_comp_compgen -a -- -W 'from: tag: usertag: ALL'
7878
return
7979
;;

completions/cvs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ _comp_xfunc_cvs_compgen_roots()
4343
local -a cvsroots=()
4444
[[ -v CVSROOT ]] && cvsroots=("$CVSROOT")
4545
[[ -r ~/.cvspass ]] && cvsroots+=($(awk '{ print $2 }' ~/.cvspass))
46-
[[ -r CVS/Root ]] && mapfile -tO ${#cvsroots[@]} cvsroots <CVS/Root
46+
[[ -r CVS/Root ]] && mapfile -tO "${#cvsroots[@]}" cvsroots <CVS/Root
4747
((${#cvsroots[@]})) &&
48-
_comp_compgen -- -W '"${cvsroots[@]}"'
48+
_comp_compgen -U cvsroots -- -W '"${cvsroots[@]}"'
4949
_comp_ltrim_colon_completions "$cur"
5050
}
5151

completions/openssl

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,27 @@
22

33
_comp_cmd_openssl__compgen_sections()
44
{
5-
local config i f
5+
local config _i _file
66

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

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

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

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

2828
_comp_cmd_openssl__compgen_digests()

completions/python

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
# @since 2.12
44
_comp_xfunc_python_compgen_modules()
55
{
6-
local python=python
7-
[[ ${comp_args[0]##*/} == *3* ]] && python=python3
8-
_comp_cmd_python__compgen_modules "$python"
6+
local _python=python
7+
[[ ${comp_args[0]##*/} == *3* ]] && _python=python3
8+
_comp_cmd_python__compgen_modules "$_python"
99
}
1010

1111
# @deprecated 2.12 use `_comp_xfunc_python_compgen_modules` instead

completions/ssh

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ _comp_cmd_ssh__compgen_queries()
88
key-plain key-sig protocol-version compression sig ciphers macs
99
kexalgorithms pubkeyacceptedkeytypes hostkeyalgorithms
1010
hostbasedkeytypes hostbasedacceptedkeytypes)
11-
_comp_compgen -c "${cur,,}" -- -W '"${ret[@]}" help"'
11+
_comp_compgen -c "${cur,,}" -U ret -- -W '"${ret[@]}" help"'
1212
}
1313

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

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

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

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

470-
local userhost=${cur%%?(\\):*}
471-
local path=${cur#*:}
470+
local _userhost=${cur%%?(\\):*}
471+
local _path=${cur#*:}
472472

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

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

482-
local files
482+
local _files
483483
if [[ ${1-} == -d ]]; then
484484
# escape problematic characters; remove non-dirs
485485
# shellcheck disable=SC2090
486-
files=$(ssh -o 'Batchmode yes' "$userhost" \
487-
command ls -aF1dL "$path*" 2>/dev/null |
486+
_files=$(ssh -o 'Batchmode yes' "$_userhost" \
487+
command ls -aF1dL "$_path*" 2>/dev/null |
488488
command sed -e 's/'"$_comp_cmd_scp__path_esc"'/\\\\\\&/g' -e '/[^\/]$/d')
489489
else
490490
# escape problematic characters; remove executables, aliases, pipes
491491
# and sockets; add space at end of file names
492492
# shellcheck disable=SC2090
493-
files=$(ssh -o 'Batchmode yes' "$userhost" \
494-
command ls -aF1dL "$path*" 2>/dev/null |
493+
_files=$(ssh -o 'Batchmode yes' "$_userhost" \
494+
command ls -aF1dL "$_path*" 2>/dev/null |
495495
command sed -e 's/'"$_comp_cmd_scp__path_esc"'/\\\\\\&/g' -e 's/[*@|=]$//g' \
496496
-e 's/[^\/]$/& /g')
497497
fi
498-
_comp_split -l COMPREPLY "$files"
498+
_comp_compgen_split -l -- "$_files"
499499
}
500500

501501
# @deprecated 2.12 use `_comp_compgen -ax ssh remote_files` instead

doc/api-and-naming.md

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ calling `_comp_compgen` or other generators.
150150
To avoid conflicts with the options specified to `_comp_compgen`, one should
151151
not directly modify or reference the target variable. When post-filtering is
152152
needed, store them in a local array, filter them, and finally append them by
153-
`_comp_compgen -- -W '"${arr[@]}"'`. To split the output of commands and
153+
`_comp_compgen -- -W '"${_arr[@]}"'`. To split the output of commands and
154154
append the results to the target variable, use `_comp_compgen_split -- "$(cmd
155155
...)"` instead of using `_comp_split COMPREPLY "$(cmd ...)"`.
156156

@@ -180,3 +180,45 @@ Exported generators are defined with the names `_comp_xfunc_CMD_compgen_NAME`
180180
and called by `_comp_compgen [opts] -x CMD NAME args`. Internal generators are
181181
defined with the names `_comp_cmd_CMD__compgen_NAME` and called by
182182
`_comp_compgen [opts] -i CMD NAME args`.
183+
184+
#### Local variables of generator and `_comp_compgen -U var`
185+
186+
A generator should basically define local variables with the names starting
187+
with `_`. However, a generator sometimes needs to use local variable names
188+
that do not start with `_`. When the child generator call with a variable name
189+
(such as `local var; _comp_compgen -v var`) is used within the generator, the
190+
local variable can unexpectedly mask a local variable of the upper call.
191+
192+
For example, the following call fails to obtain the result of generator
193+
`mygen1` because the array `arr` is masked by the same name of a local variable
194+
in `_comp_compgen_mygen1`.
195+
196+
```bash
197+
# generator with a problem
198+
_comp_compgen_mygen1()
199+
{
200+
local -a arr=(1 2 3)
201+
_comp_compgen -av arr -- -W '4 5 6'
202+
_comp_compgen_set "${arr[@]/#p}"
203+
}
204+
205+
_comp_compgen -v arr mygen1 # fails to get the result in array `arr`
206+
```
207+
208+
To avoid this, a generator that defines a local variable with its name not
209+
starting with `_` can use the option `-U var` to unlocalize the variable on
210+
assigning the final result.
211+
212+
```bash
213+
# properly designed generator
214+
_comp_compgen_mygen1()
215+
{
216+
local -a arr=(1 2 3)
217+
_comp_compgen -av arr -- -W '4 5 6'
218+
_comp_compgen -U arr set "${arr[@]/#p}"
219+
}
220+
```
221+
222+
To avoid unexpected unlocalization of previous-scope variables, a generator
223+
should specify `-U var` to a child generator (that attempts to store results to
224+
the current target variable) at most once.

test/t/unit/test_unit_compgen.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ def functions(self, bash):
3737
"complete -F _comp_cmd_fcd fcd",
3838
)
3939

40+
# test_8_option_U
41+
assert_bash_exec(
42+
bash,
43+
"_comp_compgen_gen8() { local -a arr=(x y z); _comp_compgen -U arr -- -W '\"${arr[@]}\"'; }",
44+
)
45+
4046
def test_1_basic(self, bash, functions):
4147
output = assert_bash_exec(
4248
bash, "_comp__test_words 12 34 56 ''", want_output=True
@@ -146,3 +152,9 @@ def test_7_xcmd(self, bash, functions):
146152

147153
completions = assert_complete(bash, "compgen-cmd2 '")
148154
assert completions == ["012", "123", "234", "5foo", "6bar", "7baz"]
155+
156+
def test_8_option_U(self, bash, functions):
157+
output = assert_bash_exec(
158+
bash, "_comp__test_compgen gen8", want_output=True
159+
)
160+
assert output.strip() == "<x><y><z>"

0 commit comments

Comments
 (0)