Skip to content

Commit 652e98f

Browse files
authored
Merge pull request #1074 from akinomyoga/patsub_replacement
fix: work around `shopt -s patsub_replacement` newly supported in Bash 5.2
2 parents 7fba87c + 2a11209 commit 652e98f

File tree

16 files changed

+103
-30
lines changed

16 files changed

+103
-30
lines changed

bash_completion

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1572,8 +1572,8 @@ _comp_compgen_usage()
15721572
_comp_compgen_signals()
15731573
{
15741574
local -a sigs
1575-
_comp_compgen -v sigs -c "SIG${cur#"${1-}"}" -- -P "${1-}" -A signal &&
1576-
_comp_compgen -U sigs set "${sigs[@]/#${1-}SIG/${1-}}"
1575+
_comp_compgen -v sigs -c "SIG${cur#"${1-}"}" -- -A signal &&
1576+
_comp_compgen -RU sigs -- -P "${1-}" -W '"${sigs[@]#SIG}"'
15771577
}
15781578

15791579
# This function completes on known mac addresses
@@ -2047,9 +2047,8 @@ _comp_compgen_usergroups()
20472047
if ((${#tmp[@]})); then
20482048
local _prefix=${cur%%*([^:])}
20492049
_prefix=${_prefix//\\/}
2050-
local -a _tmp=("${tmp[@]/#/$_prefix}")
2051-
_comp_unlocal tmp
2052-
_comp_compgen_set "${_tmp[@]}"
2050+
_comp_compgen -Rv tmp -- -P "$_prefix" -W '"${tmp[@]}"'
2051+
_comp_compgen -U tmp set "${tmp[@]}"
20532052
fi
20542053
elif [[ $cur == *:* ]]; then
20552054
# Completing group after 'user:gr<TAB>'.
@@ -2455,7 +2454,9 @@ _comp__included_ssh_config_files()
24552454
# -p PREFIX Use PREFIX
24562455
# -4 Filter IPv6 addresses from results
24572456
# -6 Filter IPv4 addresses from results
2458-
# @return Completions, starting with CWORD, are added to COMPREPLY[]
2457+
# @var[out] COMPREPLY Completions, starting with CWORD, are added
2458+
# @return True (0) if one or more completions are generated, or otherwise False
2459+
# (1).
24592460
# @since 2.12
24602461
_comp_compgen_known_hosts()
24612462
{

completions/_mount.linux

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ _comp_cmd_mount()
2525
ufs umsdos usbfs vfat xfs'
2626
_comp_compgen -a fstypes
2727
[[ $split ]] && ((${#COMPREPLY[@]})) &&
28-
COMPREPLY=(${COMPREPLY[@]/#/$prev,})
28+
_comp_compgen -Rv COMPREPLY -- -P "$prev," -W '"${COMPREPLY[@]}"'
2929
return
3030
;;
3131
--bind | -B | --rbind | -R)
@@ -204,7 +204,7 @@ _comp_cmd_mount()
204204
# COMP_WORDBREAKS is a real pain in the ass
205205
prev="${prev##*["$COMP_WORDBREAKS"]}"
206206
[[ $split ]] && ((${COMPREPLY[@]})) &&
207-
COMPREPLY=(${COMPREPLY[@]/#/"$prev,"})
207+
_comp_compgen -Rv COMPREPLY -- -P "$prev," -W '"${COMPREPLY[@]}"'
208208
[[ ${COMPREPLY-} == *= ]] && compopt -o nospace
209209
return
210210
;;

completions/_umount.linux

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ _comp_cmd_umount()
111111
usbfs vfat xfs'
112112
_comp_compgen -a fstypes
113113
[[ $split ]] && ((${#COMPREPLY[@]})) &&
114-
COMPREPLY=(${COMPREPLY[@]/#/$prev,})
114+
_comp_compgen -Rv COMPREPLY -- -P "$prev," -W '"${COMPREPLY[@]}"'
115115
return
116116
;;
117117
-O)

completions/chromium-browser

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ _comp_cmd_chromium_browser()
1717
case $cur in
1818
*://*)
1919
local prefix="${cur%%://*}://"
20-
_comp_compgen_known_hosts -- "${cur#*://}"
21-
COMPREPLY=("${COMPREPLY[@]/#/$prefix}")
20+
_comp_compgen_known_hosts -- "${cur#*://}" &&
21+
_comp_compgen -Rv COMPREPLY -- -P "$prefix" -W '"${COMPREPLY[@]}"'
2222
_comp_ltrim_colon_completions "$cur"
2323
;;
2424
*)

completions/cppcheck

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ _comp_cmd_cppcheck()
2424
split="set"
2525
fi
2626
_comp_compgen -- -W 'all warning style performance portability
27-
information unusedFunction missingInclude'
28-
[[ $split ]] && COMPREPLY=(${COMPREPLY[@]/#/"$prev,"})
27+
information unusedFunction missingInclude' &&
28+
[[ $split ]] &&
29+
_comp_compgen -Rv COMPREPLY -- -P "$prev," -W '"${COMPREPLY[@]}"'
2930
return
3031
;;
3132
--error-exitcode)

completions/cvs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ _comp_cmd_cvs__entries()
55
local prefix=${cur%/*}/ IFS=$'\n'
66
[[ -e ${prefix-}CVS/Entries ]] || prefix=""
77
entries=($(cut -d/ -f2 -s "${prefix-}CVS/Entries" 2>/dev/null))
8-
if [[ $entries ]]; then
9-
entries=("${entries[@]/#/${prefix-}}")
8+
if ((${#entries[@]})); then
9+
_comp_compgen -Rv entries -- -P "${prefix-}" -W '"${entries[@]}"'
1010
compopt -o filenames
1111
fi
1212
}

completions/info

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ _comp_cmd_info()
5151

5252
_comp_split -F : infopath "$infopath"
5353
if ((${#infopath[@]})); then
54-
infopath=("${infopath[@]/%//$cur*}")
54+
_comp_compgen -Rv infopath -- -S "/$cur*" -W '"${infopath[@]}"'
5555
local IFS=
5656
_comp_expand_glob COMPREPLY '${infopath[@]}'
5757
_comp_unlocal IFS

completions/kcov

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ _comp_cmd_kcov()
3333
cur="${cur##*,}"
3434
_comp_compgen -- -W "{0..100}"
3535
((${#COMPREPLY[@]} == 1)) &&
36-
COMPREPLY=(${COMPREPLY/#/$prev,})
36+
_comp_compgen -Rv COMPREPLY -- -P "$prev," -W '"$COMPREPLY"'
3737
else
3838
_comp_compgen -- -W "{0..100}"
39-
((${#COMPREPLY[@]} == 1)) && COMPREPLY=(${COMPREPLY/%/,})
39+
((${#COMPREPLY[@]} == 1)) && COMPREPLY=("${COMPREPLY/%/,}")
4040
compopt -o nospace
4141
fi
4242
return

completions/man

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,12 @@ _comp_cmd_man()
7676

7777
_comp_split -F : manpath "$manpath"
7878
if ((${#manpath[@]})); then
79-
manpath=("${manpath[@]/%//*man$sect/$cur*}" "${manpath[@]/%//*cat$sect/$cur*}")
79+
local manfiles
80+
_comp_compgen -Rv manfiles -- -S "/*man$sect/$cur*" -W '"${manpath[@]}"'
81+
_comp_compgen -aRv manfiles -- -S "/*cat$sect/$cur*" -W '"${manpath[@]}"'
82+
8083
local IFS=
81-
_comp_expand_glob COMPREPLY '${manpath[@]}'
84+
_comp_expand_glob COMPREPLY '${manfiles[@]}'
8285
_comp_unlocal IFS
8386

8487
if ((${#COMPREPLY[@]} != 0)); then

completions/mr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ _comp_cmd_mr()
1616
done
1717
)"
1818
# Split [online|offline] and remove `action` placeholder.
19-
commands="${commands//@(action|[\[\|\]])/$'\n'}"
19+
commands="${commands//@(action|[\[\|\]])/ }"
2020
# Add standard aliases.
2121
commands="${commands} ci co ls"
2222
_comp_split commands "$commands"

completions/mutt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,10 @@ _comp_cmd_mutt__filedir()
136136
elif [[ $cur == !* ]]; then
137137
spoolfile="$("$muttcmd" -F "$muttrc" -Q spoolfile 2>/dev/null |
138138
command sed -e 's|^spoolfile=\"\(.*\)\"$|\1|')"
139-
[[ $spoolfile ]] && eval cur="${cur/^!/$spoolfile}"
139+
if [[ $spoolfile ]]; then
140+
_comp_dequote "\"$spoolfile\"" && spoolfile=$REPLY
141+
cur=$spoolfile${cur:1}
142+
fi
140143
fi
141144
_comp_compgen -c "$cur" filedir
142145
}

completions/povray

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ _comp_cmd_povray()
1616
cur="${povcur#[-+]I}" # to confuse _comp_compgen_filedir
1717
pfx="${povcur%"$cur"}"
1818
_comp_compgen_filedir pov
19-
((${#COMPREPLY[@]})) && COMPREPLY=("${COMPREPLY[@]/#/$pfx}")
19+
((${#COMPREPLY[@]})) &&
20+
_comp_compgen -Rv COMPREPLY -- -P "$pfx" -W '"${COMPREPLY[@]}"'
2021
return
2122
;;
2223
[-+]O*)
@@ -35,12 +36,16 @@ _comp_cmd_povray()
3536
IFS=$'\n'
3637
command grep '^[-+]I' <<<"${words[*]}"
3738
))
38-
COMPREPLY=(${COMPREPLY[@]#[-+]I})
39-
COMPREPLY=(${COMPREPLY[@]/%.pov/.$oext})
39+
_comp_compgen -Rv COMPREPLY -- -X '' -W '"${COMPREPLY[@]#[-+]I}"'
40+
local i
41+
for i in "${!COMPREPLY[@]}"; do
42+
COMPREPLY[i]=${COMPREPLY[i]/%.pov/".$oext"}
43+
done
4044
cur="${povcur#[-+]O}" # to confuse _comp_compgen_filedir
4145
pfx="${povcur%"$cur"}"
4246
_comp_compgen -a filedir $oext
43-
((${#COMPREPLY[@]})) && COMPREPLY=("${COMPREPLY[@]/#/$pfx}")
47+
((${#COMPREPLY[@]})) &&
48+
_comp_compgen -Rv COMPREPLY -- -P "$pfx" -W '"${COMPREPLY[@]}"'
4449
return
4550
;;
4651
*.ini\[ | *.ini\[*[^]]) # sections in .ini files
@@ -50,7 +55,8 @@ _comp_cmd_povray()
5055
COMPREPLY=($(command sed -ne \
5156
's/^[[:space:]]*\[\('"$cur"'[^]]*\]\).*$/\1/p' -- "$pfx"))
5257
# to prevent [bar] expand to nothing. can be done more easily?
53-
((${#COMPREPLY[@]})) && COMPREPLY=("${COMPREPLY[@]/#/${pfx}[}")
58+
((${#COMPREPLY[@]})) &&
59+
_comp_compgen -Rv COMPREPLY -- -P "${pfx}[" -W '"${COMPREPLY[@]}"'
5460
return
5561
;;
5662
*)

completions/pylint

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ _comp_cmd_pylint()
8888
[[ $cur == *,* ]] && prefix="${cur%,*},"
8989
_comp_compgen -c "${cur##*,}" -- -W "HIGH INFERENCE
9090
INFERENCE_FAILURE UNDEFINED"
91-
((${#COMPREPLY[@]} == 1)) && COMPREPLY=(${COMPREPLY/#/$prefix})
91+
((${#COMPREPLY[@]} == 1)) &&
92+
_comp_compgen -Rv COMPREPLY -- -P "$prefix" -W '"$COMPREPLY"'
9293
return
9394
;;
9495
--format | -${noargopts}f)

completions/smartctl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ _comp_cmd_smartctl__drivedb()
3535
prefix=+
3636
cur="${cur#+}"
3737
fi
38-
_comp_compgen_filedir h
39-
[[ $prefix ]] && COMPREPLY=("${COMPREPLY[@]/#/$prefix}")
38+
_comp_compgen_filedir h && [[ $prefix ]] &&
39+
_comp_compgen -Rv COMPREPLY -- -P "$prefix" -W '"${COMPREPLY[@]}"'
4040
}
4141

4242
_comp_cmd_smartctl()

doc/styleguide.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,52 @@ avoid backslash escaping or use the one that minimizes the use of backslash
181181
escaping. When the value contains control characters such as a tab and a
182182
newline, we do not directly include them but we use backslash escape sequences
183183
such as `\t` and `\n` in the escape string `$'...'`.
184+
185+
### `patsub_replacement` for array elements
186+
187+
There is a subtlety in quoting of the array expansions with a pattern
188+
replacement when `shopt -s patsub_replacement` (Bash >= 5.2) is
189+
enabled (which is the default of Bash >= 5.2).
190+
191+
For example, the array expansions with a pattern replacement may be
192+
used to add a prefix to every element in an array:
193+
194+
```bash
195+
# problem in bash >= 5.2
196+
arr=("${arr[@]/#/$prefix}")
197+
```
198+
199+
However, this has the problem. The characters `&` contained in
200+
`$prefix`, if any, will be replaced with the matched string. The
201+
unexpected `patsub_replacement` may be suppressed by quoting the
202+
replacement as
203+
204+
```bash
205+
# problem with bash <= 4.2 or "shopt -s compat42"
206+
arr=("${arr[@]/#/"$prefix"}")
207+
```
208+
209+
However, this has another problem in bash < 4.3 or when `shopt -s
210+
compat42` is turned on. The inner double quotations are treated
211+
literally so that the `PREFIX` instead of ``"PREFIX"` is prefixed to
212+
elements. To avoid this situation, the outer double quotations might
213+
be removed, but this has even another problem of the pathname
214+
expansions and `IFS`.
215+
216+
Specifically for prefixing and suffixing, we may instead use
217+
`_comp_compgen -- -P prefix` and `_comp_compgen -- -S suffix`.
218+
219+
```bash
220+
# solution for prefixing
221+
_comp_compgen -Rv arr -- -P "$prefix" -W '"${arr[@]}"'
222+
```
223+
224+
In a general case, one needs to modify each array element in a loop,
225+
where only the replacement is quoted.
226+
227+
```bash
228+
# general solution
229+
for i in "${!arr[@]}"; do
230+
arr[i]=${arr[i]//pat/"$rep"}
231+
done
232+
```

test/runLint

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ gitgrep '(?<!command)'"$cmdstart"'(grep|ls|sed|cd)(\s|$)' \
5252
gitgrep '(?<!command)'"$cmdstart"'awk(\s|$)' \
5353
'invoke awk through "_comp_awk"'
5454

55+
#------------------------------------------------------------------------------
56+
# Bash pitfalls/styles/compatibilities (which are not detected by shellcheck)
57+
5558
gitgrep '<<<' 'herestrings use temp files, use some other way'
5659

5760
filter_out='^(test/|bash_completion\.sh)' gitgrep ' \[ ' \
@@ -61,3 +64,9 @@ gitgrep "$cmdstart"'unset [^-]' 'Explicitly specify "unset -v/-f"'
6164

6265
gitgrep "$cmdstart"'((set|shopt)\s+[+-][a-z]+\s+posix\b|(local\s+)?POSIXLY_CORRECT\b)' \
6366
'fiddling with posix mode breaks keybindings with some bash versions'
67+
68+
gitgrep '\$\{([^{}\n]|\{.*\})+/([^{}\n]|\{.*\})+/([^{}"\n]|\{.*\})*\$.*\}' \
69+
'$rep of ${var/pat/$rep} needs to be double-quoted for shopt -s patsub_replacement (bash >= 5.2) [see Sec. of patsub_replacement in doc/styleguide.md]'
70+
71+
gitgrep '"([^"\n]|\\.)*\$\{([^{}\n]|\{.*\})+/([^{}\n]|\{.*\})+/([^{}"\n]|\{.*\})*"([^{}"\n]|\{.*\})*\$.*\}' \
72+
'$rep of "${var/pat/"$rep"}" should not be quoted for bash-4.2 or shopt -s compat42 (bash >= 4.3) [see Sec. of patsub_replacement in doc/styleguide.md]'

0 commit comments

Comments
 (0)