Skip to content

Commit

Permalink
Merge pull request #1074 from akinomyoga/patsub_replacement
Browse files Browse the repository at this point in the history
fix: work around `shopt -s patsub_replacement` newly supported in Bash 5.2
  • Loading branch information
akinomyoga authored Dec 24, 2023
2 parents 7fba87c + 2a11209 commit 652e98f
Show file tree
Hide file tree
Showing 16 changed files with 103 additions and 30 deletions.
13 changes: 7 additions & 6 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 Expand Up @@ -2455,7 +2454,9 @@ _comp__included_ssh_config_files()
# -p PREFIX Use PREFIX
# -4 Filter IPv6 addresses from results
# -6 Filter IPv4 addresses from results
# @return Completions, starting with CWORD, are added to COMPREPLY[]
# @var[out] COMPREPLY Completions, starting with CWORD, are added
# @return True (0) if one or more completions are generated, or otherwise False
# (1).
# @since 2.12
_comp_compgen_known_hosts()
{
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
4 changes: 2 additions & 2 deletions completions/chromium-browser
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ _comp_cmd_chromium_browser()
case $cur in
*://*)
local prefix="${cur%%://*}://"
_comp_compgen_known_hosts -- "${cur#*://}"
COMPREPLY=("${COMPREPLY[@]/#/$prefix}")
_comp_compgen_known_hosts -- "${cur#*://}" &&
_comp_compgen -Rv COMPREPLY -- -P "$prefix" -W '"${COMPREPLY[@]}"'
_comp_ltrim_colon_completions "$cur"
;;
*)
Expand Down
5 changes: 3 additions & 2 deletions completions/cppcheck
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ _comp_cmd_cppcheck()
split="set"
fi
_comp_compgen -- -W 'all warning style performance portability
information unusedFunction missingInclude'
[[ $split ]] && COMPREPLY=(${COMPREPLY[@]/#/"$prev,"})
information unusedFunction missingInclude' &&
[[ $split ]] &&
_comp_compgen -Rv COMPREPLY -- -P "$prev," -W '"${COMPREPLY[@]}"'
return
;;
--error-exitcode)
Expand Down
4 changes: 2 additions & 2 deletions completions/cvs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ _comp_cmd_cvs__entries()
local prefix=${cur%/*}/ IFS=$'\n'
[[ -e ${prefix-}CVS/Entries ]] || prefix=""
entries=($(cut -d/ -f2 -s "${prefix-}CVS/Entries" 2>/dev/null))
if [[ $entries ]]; then
entries=("${entries[@]/#/${prefix-}}")
if ((${#entries[@]})); then
_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
4 changes: 2 additions & 2 deletions completions/kcov
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ _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/%/,})
((${#COMPREPLY[@]} == 1)) && COMPREPLY=("${COMPREPLY/%/,}")
compopt -o nospace
fi
return
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
2 changes: 1 addition & 1 deletion completions/mr
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ _comp_cmd_mr()
done
)"
# Split [online|offline] and remove `action` placeholder.
commands="${commands//@(action|[\[\|\]])/$'\n'}"
commands="${commands//@(action|[\[\|\]])/ }"
# Add standard aliases.
commands="${commands} ci co ls"
_comp_split commands "$commands"
Expand Down
5 changes: 4 additions & 1 deletion completions/mutt
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,10 @@ _comp_cmd_mutt__filedir()
elif [[ $cur == !* ]]; then
spoolfile="$("$muttcmd" -F "$muttrc" -Q spoolfile 2>/dev/null |
command sed -e 's|^spoolfile=\"\(.*\)\"$|\1|')"
[[ $spoolfile ]] && eval cur="${cur/^!/$spoolfile}"
if [[ $spoolfile ]]; then
_comp_dequote "\"$spoolfile\"" && spoolfile=$REPLY
cur=$spoolfile${cur:1}
fi
fi
_comp_compgen -c "$cur" filedir
}
Expand Down
16 changes: 11 additions & 5 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 @@ -35,12 +36,16 @@ _comp_cmd_povray()
IFS=$'\n'
command grep '^[-+]I' <<<"${words[*]}"
))
COMPREPLY=(${COMPREPLY[@]#[-+]I})
COMPREPLY=(${COMPREPLY[@]/%.pov/.$oext})
_comp_compgen -Rv COMPREPLY -- -X '' -W '"${COMPREPLY[@]#[-+]I}"'
local i
for i in "${!COMPREPLY[@]}"; do
COMPREPLY[i]=${COMPREPLY[i]/%.pov/".$oext"}
done
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 +55,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
49 changes: 49 additions & 0 deletions doc/styleguide.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,52 @@ avoid backslash escaping or use the one that minimizes the use of backslash
escaping. When the value contains control characters such as a tab and a
newline, we do not directly include them but we use backslash escape sequences
such as `\t` and `\n` in the escape string `$'...'`.

### `patsub_replacement` for array elements

There is a subtlety in quoting of the array expansions with a pattern
replacement when `shopt -s patsub_replacement` (Bash >= 5.2) is
enabled (which is the default of Bash >= 5.2).

For example, the array expansions with a pattern replacement may be
used to add a prefix to every element in an array:

```bash
# problem in bash >= 5.2
arr=("${arr[@]/#/$prefix}")
```

However, this has the problem. The characters `&` contained in
`$prefix`, if any, will be replaced with the matched string. The
unexpected `patsub_replacement` may be suppressed by quoting the
replacement as

```bash
# problem with bash <= 4.2 or "shopt -s compat42"
arr=("${arr[@]/#/"$prefix"}")
```

However, this has another problem in bash < 4.3 or when `shopt -s
compat42` is turned on. 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`.

Specifically for prefixing and suffixing, we may instead use
`_comp_compgen -- -P prefix` and `_comp_compgen -- -S suffix`.

```bash
# solution for prefixing
_comp_compgen -Rv arr -- -P "$prefix" -W '"${arr[@]}"'
```

In a general case, one needs to modify each array element in a loop,
where only the replacement is quoted.

```bash
# general solution
for i in "${!arr[@]}"; do
arr[i]=${arr[i]//pat/"$rep"}
done
```
9 changes: 9 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,9 @@ 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) [see Sec. of patsub_replacement in doc/styleguide.md]'

gitgrep '"([^"\n]|\\.)*\$\{([^{}\n]|\{.*\})+/([^{}\n]|\{.*\})+/([^{}"\n]|\{.*\})*"([^{}"\n]|\{.*\})*\$.*\}' \
'$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 comments on commit 652e98f

Please sign in to comment.