Skip to content

Commit

Permalink
fix: use compgen -P prefix -W '"${arr[@]}"' against patsub_replacement
Browse files Browse the repository at this point in the history
To add a prefix to every element in an array, we have used the
following construct:

  arr=("${arr[@]/#/$prefix}")

However, this can be broken when `shopt -s patsub_replacement` (bash
>= 5.2) is turned on.  In bash >= 5.2, patsub_replacement is enabled
by default.  In particular, the characters `&` contained in $prefix,
if any, will be replaced with the matched string.  To avoid the
unexpected patsub_replacement, one may think about quoting the
replacement as

  arr=("${arr[@]/#/"$prefix"}")

However, this has another problem in bash < 4.3 or when `shopt -s
compat42` is turned on.  In such a case, the inner double quotations
are treated literally so that the `PREFIX` instead of ``"PREFIX"` is
prefixed to elements.  To avoid this situation the outer double
quotations might be removed, but this has even another problem of the
pathname expansions and IFS.  We here instead use _comp_compgen:

  _comp_compgen -Rv arr -- -P "$prefix" -W '"${arr[@]}"'

This patch also contains similar cases for a suffix.
  • Loading branch information
akinomyoga committed Dec 24, 2023
1 parent 6b8f82b commit 45d036a
Show file tree
Hide file tree
Showing 13 changed files with 33 additions and 21 deletions.
9 changes: 4 additions & 5 deletions bash_completion
Original file line number Diff line number Diff line change
Expand Up @@ -1572,8 +1572,8 @@ _comp_compgen_usage()
_comp_compgen_signals()
{
local -a sigs
_comp_compgen -v sigs -c "SIG${cur#"${1-}"}" -- -P "${1-}" -A signal &&
_comp_compgen -U sigs set "${sigs[@]/#${1-}SIG/${1-}}"
_comp_compgen -v sigs -c "SIG${cur#"${1-}"}" -- -A signal &&
_comp_compgen -RU sigs -- -P "${1-}" -W '"${sigs[@]#SIG}"'
}

# This function completes on known mac addresses
Expand Down Expand Up @@ -2047,9 +2047,8 @@ _comp_compgen_usergroups()
if ((${#tmp[@]})); then
local _prefix=${cur%%*([^:])}
_prefix=${_prefix//\\/}
local -a _tmp=("${tmp[@]/#/$_prefix}")
_comp_unlocal tmp
_comp_compgen_set "${_tmp[@]}"
_comp_compgen -Rv tmp -- -P "$_prefix" -W '"${tmp[@]}"'
_comp_compgen -U tmp set "${tmp[@]}"
fi
elif [[ $cur == *:* ]]; then
# Completing group after 'user:gr<TAB>'.
Expand Down
4 changes: 2 additions & 2 deletions completions/_mount.linux
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ _comp_cmd_mount()
ufs umsdos usbfs vfat xfs'
_comp_compgen -a fstypes
[[ $split ]] && ((${#COMPREPLY[@]})) &&
COMPREPLY=(${COMPREPLY[@]/#/$prev,})
_comp_compgen -Rv COMPREPLY -- -P "$prev," -W '"${COMPREPLY[@]}"'
return
;;
--bind | -B | --rbind | -R)
Expand Down Expand Up @@ -204,7 +204,7 @@ _comp_cmd_mount()
# COMP_WORDBREAKS is a real pain in the ass
prev="${prev##*["$COMP_WORDBREAKS"]}"
[[ $split ]] && ((${COMPREPLY[@]})) &&
COMPREPLY=(${COMPREPLY[@]/#/"$prev,"})
_comp_compgen -Rv COMPREPLY -- -P "$prev," -W '"${COMPREPLY[@]}"'
[[ ${COMPREPLY-} == *= ]] && compopt -o nospace
return
;;
Expand Down
2 changes: 1 addition & 1 deletion completions/_umount.linux
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ _comp_cmd_umount()
usbfs vfat xfs'
_comp_compgen -a fstypes
[[ $split ]] && ((${#COMPREPLY[@]})) &&
COMPREPLY=(${COMPREPLY[@]/#/$prev,})
_comp_compgen -Rv COMPREPLY -- -P "$prev," -W '"${COMPREPLY[@]}"'
return
;;
-O)
Expand Down
2 changes: 1 addition & 1 deletion completions/chromium-browser
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ _comp_cmd_chromium_browser()
*://*)
local prefix="${cur%%://*}://"
_comp_compgen_known_hosts -- "${cur#*://}" &&
COMPREPLY=("${COMPREPLY[@]/#/$prefix}")
_comp_compgen -Rv COMPREPLY -- -P "$prefix" -W '"${COMPREPLY[@]}"'
_comp_ltrim_colon_completions "$cur"
;;
*)
Expand Down
2 changes: 1 addition & 1 deletion completions/cppcheck
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ _comp_cmd_cppcheck()
_comp_compgen -- -W 'all warning style performance portability
information unusedFunction missingInclude' &&
[[ $split ]] &&
COMPREPLY=(${COMPREPLY[@]/#/"$prev,"})
_comp_compgen -Rv COMPREPLY -- -P "$prev," -W '"${COMPREPLY[@]}"'
return
;;
--error-exitcode)
Expand Down
2 changes: 1 addition & 1 deletion completions/cvs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ _comp_cmd_cvs__entries()
[[ -e ${prefix-}CVS/Entries ]] || prefix=""
entries=($(cut -d/ -f2 -s "${prefix-}CVS/Entries" 2>/dev/null))
if ((${#entries[@]})); then
entries=("${entries[@]/#/${prefix-}}")
_comp_compgen -Rv entries -- -P "${prefix-}" -W '"${entries[@]}"'
compopt -o filenames
fi
}
Expand Down
2 changes: 1 addition & 1 deletion completions/info
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ _comp_cmd_info()

_comp_split -F : infopath "$infopath"
if ((${#infopath[@]})); then
infopath=("${infopath[@]/%//$cur*}")
_comp_compgen -Rv infopath -- -S "/$cur*" -W '"${infopath[@]}"'
local IFS=
_comp_expand_glob COMPREPLY '${infopath[@]}'
_comp_unlocal IFS
Expand Down
2 changes: 1 addition & 1 deletion completions/kcov
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ _comp_cmd_kcov()
cur="${cur##*,}"
_comp_compgen -- -W "{0..100}"
((${#COMPREPLY[@]} == 1)) &&
COMPREPLY=(${COMPREPLY/#/$prev,})
_comp_compgen -Rv COMPREPLY -- -P "$prev," -W '"$COMPREPLY"'
else
_comp_compgen -- -W "{0..100}"
((${#COMPREPLY[@]} == 1)) && COMPREPLY=("${COMPREPLY/%/,}")
Expand Down
7 changes: 5 additions & 2 deletions completions/man
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,12 @@ _comp_cmd_man()

_comp_split -F : manpath "$manpath"
if ((${#manpath[@]})); then
manpath=("${manpath[@]/%//*man$sect/$cur*}" "${manpath[@]/%//*cat$sect/$cur*}")
local manfiles
_comp_compgen -Rv manfiles -- -S "/*man$sect/$cur*" -W '"${manpath[@]}"'
_comp_compgen -aRv manfiles -- -S "/*cat$sect/$cur*" -W '"${manpath[@]}"'

local IFS=
_comp_expand_glob COMPREPLY '${manpath[@]}'
_comp_expand_glob COMPREPLY '${manfiles[@]}'
_comp_unlocal IFS

if ((${#COMPREPLY[@]} != 0)); then
Expand Down
9 changes: 6 additions & 3 deletions completions/povray
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ _comp_cmd_povray()
cur="${povcur#[-+]I}" # to confuse _comp_compgen_filedir
pfx="${povcur%"$cur"}"
_comp_compgen_filedir pov
((${#COMPREPLY[@]})) && COMPREPLY=("${COMPREPLY[@]/#/$pfx}")
((${#COMPREPLY[@]})) &&
_comp_compgen -Rv COMPREPLY -- -P "$pfx" -W '"${COMPREPLY[@]}"'
return
;;
[-+]O*)
Expand All @@ -40,7 +41,8 @@ _comp_cmd_povray()
cur="${povcur#[-+]O}" # to confuse _comp_compgen_filedir
pfx="${povcur%"$cur"}"
_comp_compgen -a filedir $oext
((${#COMPREPLY[@]})) && COMPREPLY=("${COMPREPLY[@]/#/$pfx}")
((${#COMPREPLY[@]})) &&
_comp_compgen -Rv COMPREPLY -- -P "$pfx" -W '"${COMPREPLY[@]}"'
return
;;
*.ini\[ | *.ini\[*[^]]) # sections in .ini files
Expand All @@ -50,7 +52,8 @@ _comp_cmd_povray()
COMPREPLY=($(command sed -ne \
's/^[[:space:]]*\[\('"$cur"'[^]]*\]\).*$/\1/p' -- "$pfx"))
# to prevent [bar] expand to nothing. can be done more easily?
((${#COMPREPLY[@]})) && COMPREPLY=("${COMPREPLY[@]/#/${pfx}[}")
((${#COMPREPLY[@]})) &&
_comp_compgen -Rv COMPREPLY -- -P "${pfx}[" -W '"${COMPREPLY[@]}"'
return
;;
*)
Expand Down
3 changes: 2 additions & 1 deletion completions/pylint
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ _comp_cmd_pylint()
[[ $cur == *,* ]] && prefix="${cur%,*},"
_comp_compgen -c "${cur##*,}" -- -W "HIGH INFERENCE
INFERENCE_FAILURE UNDEFINED"
((${#COMPREPLY[@]} == 1)) && COMPREPLY=(${COMPREPLY/#/$prefix})
((${#COMPREPLY[@]} == 1)) &&
_comp_compgen -Rv COMPREPLY -- -P "$prefix" -W '"$COMPREPLY"'
return
;;
--format | -${noargopts}f)
Expand Down
4 changes: 2 additions & 2 deletions completions/smartctl
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ _comp_cmd_smartctl__drivedb()
prefix=+
cur="${cur#+}"
fi
_comp_compgen_filedir h &&
[[ $prefix ]] && COMPREPLY=("${COMPREPLY[@]/#/$prefix}")
_comp_compgen_filedir h && [[ $prefix ]] &&
_comp_compgen -Rv COMPREPLY -- -P "$prefix" -W '"${COMPREPLY[@]}"'
}

_comp_cmd_smartctl()
Expand Down
6 changes: 6 additions & 0 deletions test/runLint
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ gitgrep '(?<!command)'"$cmdstart"'(grep|ls|sed|cd)(\s|$)' \
gitgrep '(?<!command)'"$cmdstart"'awk(\s|$)' \
'invoke awk through "_comp_awk"'

#------------------------------------------------------------------------------
# Bash pitfalls/styles/compatibilities (which are not detected by shellcheck)

gitgrep '<<<' 'herestrings use temp files, use some other way'

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

gitgrep "$cmdstart"'((set|shopt)\s+[+-][a-z]+\s+posix\b|(local\s+)?POSIXLY_CORRECT\b)' \
'fiddling with posix mode breaks keybindings with some bash versions'

gitgrep '\$\{([^{}\n]|\{.*\})+/([^{}\n]|\{.*\})+/([^{}"\n]|\{.*\})*\$.*\}' \
'$rep of ${var/pat/$rep} needs to be double-quoted for shopt -s patsub_replacement (bash >= 5.2)'

0 comments on commit 45d036a

Please sign in to comment.