The xtrace (set -x) output in ash is a bit of a pain, because arguments
containing whitespace aren't quoted. This can make it impossible to tell
which bit belongs to which argument:

$ ash -x -c 'somevar="one two" printf %s\\n one "two three" four'
+ somevar=one two printf %s\n one two three four
one
two three
four

Another disadvantage is you cannot simply copy and paste the commands
from this xtrace output to repeat them for debugging purposes.

I wrote the attached patch which fixes this. It's based on one I wrote
for dash a few weeks ago. That version has gone through a few
iterations, with input from Harald van Dijk, whose suggestions are
incorporated here as well.

The patch changes the following:

(1) Since we don't want every command name and argument quoted but only
those needed for safe re-input into the shell, single_quote() has
acquired an extra argument indicating whether quoting should be
conditional (1) or unconditional (0). Conditional quoting quotes the
string only if it contains characters that are not shell-safe, or is
identical to a shell keyword (reserved word), or is empty.
Shell-safe characters are defined as this set:
0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz%+,./:@_^-

(2) Printing of variable assignments preceding commands is handled
specially; in order for the output to be suitable for re-entry into the
shell, only the part after the "=" is quoted.

(3) While we're at it, the values in the output of "set" (list of all
variables and their values) are quoted conditionally like in bash.

After the patch:

$ ash -x -c 'somevar="one two" printf %s\\n one "two three" four'
+ somevar='one two' printf '%s\n' one 'two three' four
one
two three
four

For further background, see the thread on the dash list:
https://www.mail-archive.com/dash@vger.kernel.org/#01317

Hope this is useful,

- Martijn

diff --git a/shell/ash.c b/shell/ash.c
index 983f7b1..c952749 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -1742,15 +1742,28 @@ number(const char *s)
 	return atoi(s);
 }
 
+/* Characters that don't need quoting before re-entry into the shell. */
+#define SHELLSAFECHARS "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz%+,./:@_^-"
+
+/* Pre-declare this so single_quote() can use it. */
+static const char *const * findkwd(const char *);
+
 /*
  * Produce a possibly single quoted string suitable as input to the shell.
- * The return string is allocated on the stack.
+ * If 'conditional' is nonzero, quoting is only done if the string contains
+ * non-shellsafe characters, or is identical to a shell keyword (reserved
+ * word); if it is zero, quoting is always done.
+ * If quoting was done, the return string is allocated on the stack,
+ * otherwise a pointer to the original string is returned.
  */
 static char *
-single_quote(const char *s)
+single_quote(const char *s, int conditional)
 {
 	char *p;
 
+	if (conditional && s[0] != '\0' && s[strspn(s, SHELLSAFECHARS)] == '\0' && ! findkwd(s))
+		return (char *)s;
+
 	STARTSTACKSTR(p);
 
 	do {
@@ -3299,7 +3312,7 @@ rmaliases(void)
 static void
 printalias(const struct alias *ap)
 {
-	out1fmt("%s=%s\n", ap->name, single_quote(ap->val));
+	out1fmt("%s=%s\n", ap->name, single_quote(ap->val, 0));
 }
 
 /*
@@ -9663,19 +9676,31 @@ evalcommand(union node *cmd, int flags)
 
 	/* Print the command if xflag is set. */
 	if (xflag) {
-		int n;
 		const char *p = " %s" + 1;
 
 		fdprintf(preverrout_fd, p, expandstr(ps4val()));
+
 		sp = varlist.list;
-		for (n = 0; n < 2; n++) {
-			while (sp) {
-				fdprintf(preverrout_fd, p, sp->text);
-				sp = sp->next;
-				p = " %s";
-			}
-			sp = arglist.list;
+		while (sp) {
+			int i = 0;
+
+			if (p[0] == ' ')
+				fdprintf(preverrout_fd, " ");
+			while (sp->text[i] != '=' && sp->text[i] != '\0')
+				fdprintf(preverrout_fd, "%c", sp->text[i++]);
+			if (sp->text[i] == '=')
+				fdprintf(preverrout_fd, "=%s", single_quote(sp->text+i+1, 1));
+			sp = sp->next;
+			p = " %s";
 		}
+
+		sp = arglist.list;
+		while (sp) {
+			fdprintf(preverrout_fd, p, single_quote(sp->text, 1));
+			sp = sp->next;
+			p = " %s";
+		}
+
 		safe_write(preverrout_fd, "\n", 1);
 	}
 
@@ -10687,7 +10712,7 @@ showvars(const char *sep_prefix, int on, int off)
 		p = strchrnul(*ep, '=');
 		q = nullstr;
 		if (*p)
-			q = single_quote(++p);
+			q = single_quote(++p, 1);
 		out1fmt("%s%s%.*s%s\n", sep_prefix, sep, (int)(p - *ep), *ep, q);
 	}
 	return 0;
@@ -12953,7 +12978,7 @@ trapcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
 				 * then it prints short signal names.
 				 * We are printing short names: */
 				out1fmt("trap -- %s %s\n",
-						single_quote(tr),
+						single_quote(tr, 0),
 						get_signame(signo));
 		/* trap_ptr != trap only if we are in special-cased `trap` code.
 		 * In this case, we will exit very soon, no need to free(). */
_______________________________________________
busybox mailing list
busybox@busybox.net
http://lists.busybox.net/mailman/listinfo/busybox

Reply via email to