Here is another patch that was pending. Bash Version: 5.2 Commit 87a6e89edc7cb1af057e2c15666df189595d305b (the current devel)
Description: When `shopt -s patsub_replacement' is turned on and the pattern string `pat' of ${var/$pat/"&"} is just an anchoring character `#' or `%', the internal escape of `&' (i.e., `\&') remains in the result of the parameter expansion. Also, with the same condition, the unquoted & in ${var/$pat/&} remains a literal & where we expect it to be expanded to an empty string. Repeat-By: When `pat' contains non-anchoring characters, the expected result of the expansions is obtained as $ bash-dev --norc $ shopt -s patsub_replacement $ v=1234 pat=2; echo "${v/$pat/<&>}, ${v/$pat/<\&>}, ${v/$pat/"<&>"}" 1<2>34, 1<&>34, 1<&>34 $ v=1234 pat=#1; echo "${v/$pat/<&>}, ${v/$pat/<\&>}, ${v/$pat/"<&>"}" <1>234, <&>234, <&>234 Here, unquoted & in the replacement is expanded to the matched string as expected while quoted &'s are expanded to the literal `&'. However, only when pat=# or pat=%, the result becomes $ v=1234 pat=#; echo "${v/$pat/<&>}, ${v/$pat/<\&>}, ${v/$pat/"<&>"}" <&>1234, <\&>1234, <\&>1234 where unquoted & does not become an empty string, and quoted &'s become \&. In particular, with ${v/$pat/"<&>"}, a backslash originally not present is inserted in the expanded result. I instead expect the following result for consistency with the other cases: <>1234, <&>1234, <&>1234 Fix: I attach a patch `r0031-fix-patsub.patch.txt'. I guess the original intent of the related code has been to disable patsub_replacement for ${var/#/...} and ${var/%/...} where the pattern `# + <empty string>' or `% + <empty string>' is explicitly specified (not through a parameter expansion $pat), but I would still think we should keep the special treatment of unquoted & for these cases as well when patsub_replacement is turned on for consistency with other cases. -- Koichi
From 3ff6e8b3fcb131676a7c7542b25d48b5768c8ea7 Mon Sep 17 00:00:00 2001 From: Koichi Murase <myoga.mur...@gmail.com> Date: Mon, 24 Jan 2022 22:31:51 +0900 Subject: [PATCH 1/3] fix ${v/#PAT/"REP"}: process patsub_replacement also for the special cases --- subst.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/subst.c b/subst.c index 163f42c1..666127c8 100644 --- a/subst.c +++ b/subst.c @@ -8930,36 +8930,38 @@ pat_subst (string, pat, rep, mflags) * STRING and return the result. * 3. A null STRING with a matching pattern means to append REP to * STRING and return the result. - * These don't understand or process `&' in the replacement string. */ if ((pat == 0 || *pat == 0) && (mtype == MATCH_BEG || mtype == MATCH_END)) { - replen = STRLEN (rep); + if (mflags & MATCH_EXPREP) + rstr = strcreplace (rep, '&', "", 2); + else + rstr = rep; + rslen = STRLEN (rstr); l = STRLEN (string); - ret = (char *)xmalloc (replen + l + 2); - if (replen == 0) + ret = (char *)xmalloc (rslen + l + 2); + if (rslen == 0) strcpy (ret, string); else if (mtype == MATCH_BEG) { - strcpy (ret, rep); - strcpy (ret + replen, string); + strcpy (ret, rstr); + strcpy (ret + rslen, string); } else { strcpy (ret, string); - strcpy (ret + l, rep); + strcpy (ret + l, rstr); } + if (rstr != rep) + free (rstr); return (ret); } else if (*string == 0 && (match_pattern (string, pat, mtype, &s, &e) != 0)) { - replen = STRLEN (rep); - ret = (char *)xmalloc (replen + 1); - if (replen == 0) - ret[0] = '\0'; + if (mflags & MATCH_EXPREP) + return strcreplace (rep, '&', "", 2); else - strcpy (ret, rep); - return (ret); + return savestring (rep); } ret = (char *)xmalloc (rsize = 64); -- 2.36.1