ash: improve set -x to quote strings as necessary
authorDenys Vlasenko <vda.linux@googlemail.com>
Fri, 21 Jul 2017 11:20:14 +0000 (13:20 +0200)
committerDenys Vlasenko <vda.linux@googlemail.com>
Fri, 21 Jul 2017 11:20:14 +0000 (13:20 +0200)
Basen on the patch from Martijn Dekker <martijn@inlv.org>

function                                             old     new   delta
evalcommand                                         1161    1302    +141
maybe_single_quote                                     -      60     +60
getoptscmd                                           527     546     +19
readtoken1                                          2819    2823      +4
localcmd                                             366     364      -2
evaltreenr                                           495     479     -16
evaltree                                             495     479     -16
------------------------------------------------------------------------------
(add/remove: 1/0 grow/shrink: 3/3 up/down: 224/-34)           Total: 190 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
shell/ash.c
shell/ash_test/ash-quoting/mode_x.right [new file with mode: 0644]
shell/ash_test/ash-quoting/mode_x.tests [new file with mode: 0755]

index b28731eb1eaf2dfd09e3b0ff986129f98d018aa0..a461bb7df7b4f9f9cf52d284e30e4eebbcab2229 100644 (file)
@@ -1742,7 +1742,7 @@ number(const char *s)
 }
 
 /*
- * Produce a possibly single quoted string suitable as input to the shell.
+ * Produce a single quoted string suitable as input to the shell.
  * The return string is allocated on the stack.
  */
 static char *
@@ -1786,6 +1786,47 @@ single_quote(const char *s)
        return stackblock();
 }
 
+/*
+ * Produce a possibly single quoted string suitable as input to the shell.
+ * 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 const char *
+maybe_single_quote(const char *s)
+{
+       const char *p = s;
+
+       while (*p) {
+               /* Assuming ACSII */
+               /* quote ctrl_chars space !"#$%&'()* */
+               if (*p < '+')
+                       goto need_quoting;
+               /* quote ;<=>? */
+               if (*p >= ';' && *p <= '?')
+                       goto need_quoting;
+               /* quote `[\ */
+               if (*p == '`')
+                       goto need_quoting;
+               if (*p == '[')
+                       goto need_quoting;
+               if (*p == '\\')
+                       goto need_quoting;
+               /* quote {|}~ DEL and high bytes */
+               if (*p > 'z')
+                       goto need_quoting;
+               /* Not quoting these: +,-./ 0-9 :@ A-Z ]^_ a-z */
+               /* TODO: maybe avoid quoting % */
+               p++;
+       }
+       return s;
+
+ need_quoting:
+       return single_quote(s);
+}
+
 
 /* ============ nextopt */
 
@@ -9700,18 +9741,36 @@ evalcommand(union node *cmd, int flags)
 
        /* Print the command if xflag is set. */
        if (xflag) {
-               int n;
-               const char *p = " %s" + 1;
+               const char *pfx = "";
+
+               fdprintf(preverrout_fd, "%s", expandstr(ps4val()));
 
-               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) {
+                       char *varval = sp->text;
+                       char *eq = strchrnul(varval, '=');
+                       if (*eq)
+                               eq++;
+                       fdprintf(preverrout_fd, "%s%.*s%s",
+                               pfx,
+                               (int)(eq - varval), varval,
+                               maybe_single_quote(eq)
+                       );
+                       sp = sp->next;
+                       pfx = " ";
+               }
+
+               sp = arglist.list;
+               while (sp) {
+                       fdprintf(preverrout_fd, "%s%s",
+                               pfx,
+                               /* always quote if matches reserved word: */
+                               findkwd(sp->text)
+                               ? single_quote(sp->text)
+                               : maybe_single_quote(sp->text)
+                       );
+                       sp = sp->next;
+                       pfx = " ";
                }
                safe_write(preverrout_fd, "\n", 1);
        }
diff --git a/shell/ash_test/ash-quoting/mode_x.right b/shell/ash_test/ash-quoting/mode_x.right
new file mode 100644 (file)
index 0000000..c2dd355
--- /dev/null
@@ -0,0 +1,10 @@
++ var1=val
++ var2='one two'
++ true '%s\n' one 'two '"'"'three' four
++ this=command
++ 'this=command'
+./mode_x.tests: line 1: this=command: not found
++ true
++ true
++ 'if' true
+./mode_x.tests: line 1: if: not found
diff --git a/shell/ash_test/ash-quoting/mode_x.tests b/shell/ash_test/ash-quoting/mode_x.tests
new file mode 100755 (executable)
index 0000000..16dae3f
--- /dev/null
@@ -0,0 +1,14 @@
+set -x
+
+var1=val
+var2='one two'
+true %s\\n one "two 'three" four
+
+# assignment:
+this=command
+# NOT assignment, +x code should show it quoted:
+"this=command"
+
+if true; then true; fi
+# +x code should quote 'if' here:
+"if" true