[PATCH 7/7] ash: fix breakage of ${v/pat/str} and shrink code

Ron Yorston rmy at frippery.org
Fri May 15 10:34:39 UTC 2015


The commit

   ash: move parse-time quote flag detection to run-time

breaks pattern substitution in parameter expansion.  Fix this and
revise the code so that the different handling of the pattern and
the replacement string takes place in rmescapes rather than the
separate function parse_sub_pattern.

function                                             old     new   delta
rmescapes                                            269     315     +46
subevalvar                                          1545    1468     -77
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 1/1 up/down: 46/-77)            Total: -31 bytes

Signed-off-by: Ron Yorston <rmy at pobox.com>
---
 shell/ash.c | 78 +++++++++++++++++++++----------------------------------------
 1 file changed, 26 insertions(+), 52 deletions(-)

diff --git a/shell/ash.c b/shell/ash.c
index e7e7081..34f5510 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -5525,6 +5525,7 @@ ash_arith(const char *s)
 #define RMESCAPE_GLOB   0x2     /* Add backslashes for glob */
 #define RMESCAPE_GROW   0x8     /* Grow strings instead of stalloc */
 #define RMESCAPE_HEAP   0x10    /* Malloc strings instead of stalloc */
+#define RMESCAPE_SLASH  0x20    /* Stop globbing after slash */
 
 /* Add CTLESC when necessary. */
 #define QUOTES_ESC     (EXP_FULL | EXP_CASE | EXP_QPAT | EXP_REDIR)
@@ -5600,6 +5601,7 @@ rmescapes(char *str, int flag)
 	unsigned inquotes;
 	unsigned protect_against_glob;
 	unsigned globbing;
+	IF_ASH_BASH_COMPAT(unsigned slash = flag & RMESCAPE_SLASH;)
 
 	p = strpbrk(str, qchars);
 	if (!p)
@@ -5650,6 +5652,13 @@ rmescapes(char *str, int flag)
 			protect_against_glob = 0;
 			goto copy;
 		}
+#if ENABLE_ASH_BASH_COMPAT
+		else if (*p == '/' && slash) {
+			/* stop handling globbing and mark location of slash */
+			globbing = 0;
+			*p = CTLESC;
+		}
+#endif
 		protect_against_glob = globbing;
  copy:
 		*q++ = *p++;
@@ -6274,50 +6283,6 @@ varunset(const char *end, const char *var, const char *umsg, int varflags)
 	ash_msg_and_raise_error("%.*s: %s%s", (int)(end - var - 1), var, msg, tail);
 }
 
-#if ENABLE_ASH_BASH_COMPAT
-static char *
-parse_sub_pattern(char *arg, int quoted)
-{
-	char *idx, *repl = NULL;
-	unsigned char c;
-
-	//char *org_arg = arg;
-	//bb_error_msg("arg:'%s' quoted:%x", arg, quoted);
-	idx = arg;
-	while (1) {
-		c = *arg;
-		if (!c)
-			break;
-		if (c == '/') {
-			/* Only the first '/' seen is our separator */
-			if (!repl) {
-				repl = idx + 1;
-				c = '\0';
-			}
-		}
-		*idx++ = c;
-		arg++;
-		/*
-		 * Example: v='a\bc'; echo ${v/\\b/_\\_\z_}
-		 * The result is a_\_z_c (not a\_\_z_c)!
-		 *
-		 * Enable debug prints in this function and you'll see:
-		 * ash: arg:'\\b/_\\_z_' varflags:d
-		 * ash: pattern:'\\b' repl:'_\_z_'
-		 * That is, \\b is interpreted as \\b, but \\_ as \_!
-		 * IOW: search pattern and replace string treat backslashes
-		 * differently! That is the reason why we check repl below:
-		 */
-		if (c == '\\' && *arg == '\\' && repl && !quoted)
-			arg++; /* skip both '\', not just first one */
-	}
-	*idx = c; /* NUL */
-	//bb_error_msg("pattern:'%s' repl:'%s'", org_arg, repl);
-
-	return repl;
-}
-#endif /* ENABLE_ASH_BASH_COMPAT */
-
 static const char *
 subevalvar(char *p, char *varname, int strloc, int subtype,
 		int startloc, int varflags, int flag, struct strlist *var_str_list)
@@ -6328,7 +6293,7 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
 	char *loc;
 	char *rmesc, *rmescend;
 	char *str;
-	IF_ASH_BASH_COMPAT(const char *repl = NULL;)
+	IF_ASH_BASH_COMPAT(char *repl;)
 	IF_ASH_BASH_COMPAT(int pos, len, orig_len;)
 	int saveherefd = herefd;
 	int amount, resetloc;
@@ -6453,19 +6418,28 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
 	}
 	rmescend--;
 	str = (char *)stackblock() + strloc;
-	preglob(str, 0);
+	/*
+	 * Example: v='a\bc'; echo ${v/\\b/_\\_\z_}
+	 * The result is a_\_z_c (not a\_\_z_c)!
+	 *
+	 * The search pattern and replace string treat backslashes differently!
+	 * RMESCAPE_SLASH causes preglob to work differently on the pattern
+	 * ans string.
+	 */
+	preglob(str, IF_ASH_BASH_COMPAT(
+		(subtype == VSREPLACE || subtype == VSREPLACEALL) ? RMESCAPE_SLASH :)
+			0);
 
 #if ENABLE_ASH_BASH_COMPAT
 	workloc = expdest - (char *)stackblock();
 	if (subtype == VSREPLACE || subtype == VSREPLACEALL) {
 		char *idx, *end;
 
-		if (!repl) {
-			repl = parse_sub_pattern(str, flag & EXP_QUOTED);
-			//bb_error_msg("repl:'%s'", repl);
-			if (!repl)
-				repl = nullstr;
-		}
+		if ((repl=strchr(str, CTLESC)))
+			*repl++ = '\0';
+		else
+			repl = nullstr;
+		//bb_error_msg("str:'%s' repl:'%s'", str, repl);
 
 		/* If there's no pattern to match, return the expansion unmolested */
 		if (str[0] == '\0')
-- 
2.1.0



More information about the busybox mailing list