> --- a/CHANGES
> +++ b/CHANGES
> @@ -1,3 +1,326 @@
> +This document details the changes between this version, bash-5.2-alpha, and
> +the previous version, bash-5.1-release.
>
> [...]
>
> +x. New shell option: patsub_replacement. When enabled, a `&' in the
> replacement
> + string of the pattern substitution expansion is replaced by the portion of
> + the string that matched the pattern. Backslash will escape the `&' and
> + insert a literal `&'.
>
> [...]
I haven't received replies to my previous reply, but let me write a
related discussion again. The current behavior is as follows:
$ bash-dev --norc
$ shopt | grep patsub
patsub_replacement on
$ var=ABCDE value='(K&R)'
$ echo "${var//C/$value}"
AB(KCR)DE
$ echo "${var//C/"$value"}" # (My suggestion of "quoting &")
AB(KCR)DE # (the current patsub_replacement isn't designed to work for this)
$ echo "${var//C/(K\\&R)}"
AB(K&R)DE
$ echo "${var//C/\\\\\\\\}"
AB\\\\DE
$ echo "${var//C/\\\\\\\\&}"
AB\\CDE
Today I tried to modify my script so that it works with `shopt -s
patsub_replacement', and now I become more skeptical about making
patsub_replacement default and also about its current design.
I needed to modify more places than I initially expected, and also it
is not simple to simply perform the replacement that contains
arbitrary strings. Then, I checked other existing Bash programs and
found that most of the large Bash programs/frameworks are affected,
which include "bashdb", "bash-completion", "bash-it",
"bash-oo-framework", "neofetch", and "bashtop" (plus mine, "ble.sh").
The programs that seemed to be fine among the ones I have checked were
only "oh-my-bash", "romkatv/gitstatus" and "git-prompt.bash". So I
feel it is better to make patsub_replacement off by default. I attach
"affected.txt", the list of lines from these programs that can be
broken by "patsub_replacement" depending on the user inputs,
configurations and the system status:
Also, it is not simple to correctly write the codes so that it works
for both sides of patsub_replacement on/off. If it was a standalone
Bash program, we can just set `shopt -u patsub_replacement' at the
beginning of the script file, but debuggers (bashdb), shell
configurations (bash-completion, bash-it, ble.sh, etc.) and shell
libraries (shell libraries, ...) cannot assume one specific side of
the settings of `patsub_replacement' because that option is under the
control of the main program or the user of the interactive shell. I
have thought about how we can correctly write, e.g., «
result=${result// /$string} » as simply as possible (see the attached
"workaround.sh"), but I still feel they are all too non-trivial to
replace the simple « result=${result// /$string} ». I don't think
everyone can write it correctly.
Sorry, but I'd like to still push treating the quoted replacement (as
$string in « result=${result// /$string} ») literally just like the
glob operators in ${var//"$pat"} or ${var#"$pat"} (as recently
explained in e.g.
https://lists.gnu.org/archive/html/help-bash/2022-01/msg00022.html).
In Chet's previous reply, it is explained that
> > > * It is consistent with the treatment of the glob special characters
> > > and anchors # and % in $pat of ${var/$pat}.
> >
> > Yeah, doing that was probably a mistake, but we have to live with it now.
> > Those are really part of the pattern operator itself, not properties of
> > the pattern. But nevertheless.
but I don't feel it is a mistake but rather natural than introducing
an extra level of unescaping/unquoting. It might be just because I got
used to it, but
> However, if I understand it correctly, similar treatment is
> already standardized in POSIX for ``quoting characters within the
> braces'' (as ${var#"$xxx"} and ${var%"$xxx"}):
>
> > quoted from POSIX XCU 2.6.2
> > https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/utilities/V3_chap02.html#tag_18_06_02
> > [...]
which means most shell implementations agreed with its behavior for
glob operators in ${var#pat} and ${var%pat}.
./bashdb-5.0-1.1.2/lib/fns.sh:33: result=${result// /$string}
./bash-completion/bash_completion:741: ((${#COMPREPLY[@]} == 1)) &&
COMPREPLY=(${COMPREPLY/#/$prefix})
./bash-completion/bash_completion:920:
option=${option/"${BASH_REMATCH[1]}"/"${BASH_REMATCH[2]}"}
./bash-completion/bash_completion:955:
line=${line/"${BASH_REMATCH[0]}"/"${BASH_REMATCH[1]}"}
./bash-completion/bash_completion:1020:
COMPREPLY+=("${sigs[@]/#${1-}SIG/${1-}}")
./bash-completion/completions/_mount.linux:27: $split &&
COMPREPLY=(${COMPREPLY[@]/#/$prev,})
./bash-completion/completions/_mount.linux:209: $split &&
COMPREPLY=(${COMPREPLY[@]/#/"$prev,"})
./bash-completion/completions/_umount.linux:119: $split &&
COMPREPLY=(${COMPREPLY[@]/#/$prev,})
./bash-completion/completions/chromium-browser:21:
COMPREPLY=("${COMPREPLY[@]/#/$prefix}")
./bash-completion/completions/cppcheck:29: $split &&
COMPREPLY=(${COMPREPLY[@]/#/"$prev,"})
./bash-completion/completions/cvs:9:
entries=("${entries[@]/#/${prefix:-}}")
./bash-completion/completions/info:52: infopath="${infopath//://$cur* }"
./bash-completion/completions/kcov:38:
COMPREPLY=(${COMPREPLY/#/$prev,})
./bash-completion/completions/man:80:
manpath="${manpath//://*man$sect/$cur* } ${manpath//://*cat$sect/$cur* }"
./bash-completion/completions/man:82: manpath="${manpath//://*man$sect/
} ${manpath//://*cat$sect/ }"
./bash-completion/completions/mutt:121: [[ -n $spoolfile ]] && eval
cur="${cur/^!/$spoolfile}"
./bash-completion/completions/povray:19:
COMPREPLY=(${COMPREPLY[@]/#/$pfx})
./bash-completion/completions/povray:39:
COMPREPLY=(${COMPREPLY[@]/%.pov/.$oext})
./bash-completion/completions/povray:43:
COMPREPLY=(${COMPREPLY[@]/#/$pfx})
./bash-completion/completions/povray:55:
COMPREPLY=("${COMPREPLY[@]/#/${pfx}[}")
./bash-completion/completions/pylint:87: ((${#COMPREPLY[@]} == 1))
&& COMPREPLY=(${COMPREPLY/#/$prefix})
./bash-completion/completions/smartctl:91: [[ -n $prefix ]] &&
COMPREPLY=("${COMPREPLY[@]/#/$prefix}")
./bash-it/completion/available/sdkman.completion.bash:15:
CANDIDATES="${SDKMAN_CANDIDATES_CSV//,/${IFS:0:1}}"
./bash-it/plugins/available/alias-completion.plugin.bash:87:
COMP_LINE=\${COMP_LINE/$alias_name/$alias_cmd $alias_args}
./bash-oo-framework/lib/util/exception.sh:157: local
underlinedObjectInLine="${errLine/$stringToMarkWithoutSlash/$underlinedObject}"
./neofetch/neofetch:3012: song="${song_format/\%artist\%/$artist}"
./neofetch/neofetch:3013: song="${song/\%album\%/$album}"
./neofetch/neofetch:3014: song="${song/\%title\%/$title}"
./neofetch/neofetch:4087: ascii_data="${ascii_data//\$\{c1\}/$c1}"
./neofetch/neofetch:4088: ascii_data="${ascii_data//\$\{c2\}/$c2}"
./neofetch/neofetch:4089: ascii_data="${ascii_data//\$\{c3\}/$c3}"
./neofetch/neofetch:4090: ascii_data="${ascii_data//\$\{c4\}/$c4}"
./neofetch/neofetch:4091: ascii_data="${ascii_data//\$\{c5\}/$c5}"
./neofetch/neofetch:4092: ascii_data="${ascii_data//\$\{c6\}/$c6}"
./neofetch/neofetch:4611:
string="${string/:/${reset}${colon_color}${separator:=:}${info_color}}"
./neofetch/neofetch:4628: "${underline//
/$underline_char}${reset} "
./neofetch/neofetch:4808: bar+="${bar_color_elapsed}${prog//
/${bar_char_elapsed}}"
./neofetch/neofetch:4809: bar+="${bar_color_total}${total//
/${bar_char_total}}"
./bashtop/bashtop:953:
line_array=(${input_line/${key}/${key// /}})
./bashtop/bashtop:994: math="${math//x/$found}"
./bashtop/bashtop:1933: text="${r_tmp// /$text}"
./bashtop/bashtop:2356: proc_array[0]="${proc_array[0]/
${selected}/${symbol}${selected}}"
./bashtop/bashtop:2624: proc_array[0]="${proc_array[0]/
${selected}/${symbol}${selected}}"
./sentaku/sentaku:829:
_s_show="${_s_show//$w/\\e[31${negative}m$w\\e[${negative}m}"
./sbp/segments/git.bash:55: outgoing_filled="${upstream_stripped/ahead
/${outgoing_icon}}"
./sbp/segments/git.bash:56: upstream_status="${outgoing_filled/behind
/${incoming_icon}}"
./ble.sh/keymap/vi.sh:6974: ins=${ins//[!$'\n']/"$s"}
./ble.sh/lib/core-syntax.sh:976: a=${a//@h/$histc1}
./ble.sh/lib/core-syntax.sh:977: a=${a//@q/$histc2}
./ble.sh/lib/core-syntax.sh:982: a=${a//@h/$histc1}
./ble.sh/lib/core-syntax.sh:983: a=${a//@q/$histc2}
./ble.sh/lib/core-syntax.sh:2255: rex_event=${rex_event//@A/$A}
./ble.sh/lib/core-syntax.sh:2266: rex_quicksub=${rex_quicksub//@A/[$histc2]}
./ble.sh/lib/core-syntax.sh:2267: rex_quicksub=${rex_quicksub//@C/$histc2}
./ble.sh/lib/core-syntax.sh:4453: a=\\ ; b="\\$a"; ret="${ret//"$a"/$b}"
./ble.sh/src/edit.sh:604: a='\' b='\\' text=${text//"$a"/$b}
./ble.sh/src/util.sh:118: ble/array#push specs "${var[@]/%/=$value}" #
#D1570 WA checked
./ble.sh/src/util.sh:777: ARR=("${ARR[@]::$2}" "${sARR[@]/#/$4}"
"${ARR[@]:$3}")' # WA #D1570 checked
./ble.sh/src/util.sh:806: ret="${ret// /$1}"
./ble.sh/src/util.sh:1083: a=${chars1:i:1} b=\\${chars2:i:1}
ret=${ret//"$a"/$b}
./ble.sh/src/util.sh:5305: ble/util/put
"${_ble_term_visible_bell_show//'%message%'/$sgr$message}" >&2
./ble.sh/src/util.sh:5622: local ret=${_ble_term_Ss//@1/$state}
#!/bin/bash
function fn_broken {
result=${result// /$string}
}
# solution 1 (need to write the same parameter expansions twice)
function fn_v1 {
if shopt -q patsub_replacement 2>/dev/null; then
shopt -u patsub_replacement
result=${result// /$string}
shopt -s patsub_replacement
else
result=${result// /$string}
fi
}
# solution 2
function fn_v2 {
if shopt -q patsub_replacement 2>/dev/null; then
shopt -u patsub_replacement
"$FUNCNAME" "$@"
local status=$?
shopt -s patsub_replacement
return "$status"
else
result=${result// /$string}
fi
}
# solution 3 (there is a fork cost)
function fn_v3 {
local reset=$(shopt -p patsub_replacement 2>/dev/null)
shopt -u patsub_replacement 2>/dev/null
result=${result// /$string}
eval -- "$reset"
}
# solution 4
function fn_v4 {
local shopt=$BASHOPTS
shopt -u patsub_replacement 2>/dev/null
result=${result// /$string}
[[ :$shopt: != *:patsub_replacement:* ]] || shopt -s patsub_replacement
}
# solution 5
function fn_v5 {
local reset=
if shopt -q patsub_replacement &>/dev/null; then
shopt -u patsub_replacement
reset='shopt -s patsub_replacement'
fi
result=${result// /$string}
eval "$reset"
}