> --- 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" }