summaryrefslogtreecommitdiff
path: root/shell
diff options
context:
space:
mode:
authorDenys Vlasenko2018-07-20 16:18:59 +0200
committerDenys Vlasenko2018-07-20 16:29:43 +0200
commit294eb4612cd668521faa48711297196f00af61d9 (patch)
tree15b002f51fdbfc3d32b1c33b8dd886d791f70cbc /shell
parent57235beb696a7dbdb48751b9721c4c025127ae96 (diff)
downloadbusybox-294eb4612cd668521faa48711297196f00af61d9.zip
busybox-294eb4612cd668521faa48711297196f00af61d9.tar.gz
hush: fix word splitting in ${v:+ARG} - dollar_altvalue1 test
ash might be a bit buggy, need to investigate dollar_altvalue9 test function old new delta expand_one_var 1639 2236 +597 expand_variables 112 128 +16 expand_vars_to_list 1117 1097 -20 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 2/1 up/down: 613/-20) Total: 593 bytes Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
Diffstat (limited to 'shell')
-rw-r--r--shell/ash_test/ash-quoting/dollar_altvalue9.right24
-rwxr-xr-xshell/ash_test/ash-quoting/dollar_altvalue9.tests17
-rw-r--r--shell/hush.c263
-rw-r--r--shell/hush_test/hush-quoting/dollar_altvalue9.right24
-rwxr-xr-xshell/hush_test/hush-quoting/dollar_altvalue9.tests17
5 files changed, 284 insertions, 61 deletions
diff --git a/shell/ash_test/ash-quoting/dollar_altvalue9.right b/shell/ash_test/ash-quoting/dollar_altvalue9.right
new file mode 100644
index 0000000..fc6c269
--- /dev/null
+++ b/shell/ash_test/ash-quoting/dollar_altvalue9.right
@@ -0,0 +1,24 @@
+Unquoted 1:
+|a|
+|x y|
+|1|
+|2|
+|1 2|
+|A|
+|B|
+|C D|
+|zb|
+Quoted 1:
+|a 'x y' 1 2 '' 1 2 A B C D zb|
+Unquoted 2:
+|ax y|
+|1|
+|2|
+|1 2|
+|A|
+|B|
+|C D|
+|z|
+|b|
+Quoted 2:
+|a 'x y' 1 2 '' 1 2 A B C D z b|
diff --git a/shell/ash_test/ash-quoting/dollar_altvalue9.tests b/shell/ash_test/ash-quoting/dollar_altvalue9.tests
new file mode 100755
index 0000000..27a6f4f
--- /dev/null
+++ b/shell/ash_test/ash-quoting/dollar_altvalue9.tests
@@ -0,0 +1,17 @@
+f() { for i; do echo "|$i|"; done; }
+
+echo Unquoted 1:
+x='1 2'; f a${x:+ 'x y' $x '' "$x" `echo A B` "`echo C D`" z}b
+echo Quoted 1:
+x='1 2'; f "a${x:+ 'x y' $x '' "$x" `echo A B` "`echo C D`" z}b"
+
+echo Unquoted 2:
+x='1 2'; f a${x:+'x y' $x '' "$x" `echo A B` "`echo C D`" z }b
+echo Quoted 2:
+x='1 2'; f "a${x:+ 'x y' $x '' "$x" `echo A B` "`echo C D`" z }b"
+
+#echo Unquoted 3:
+#e=
+#x='1 2'; f a${x:+'x y' $x '' "$x" $e $e "$e" $e `echo A B` "`echo C D`" z }b
+#echo Quoted 3:
+#x='1 2'; f "a${x:+ 'x y' $x '' "$x" $e $e "$e" $e `echo A B` "`echo C D`" z }b"
diff --git a/shell/hush.c b/shell/hush.c
index b8af1b0..fc77b89 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -5873,6 +5873,138 @@ static char *encode_then_expand_vararg(const char *str, int handle_squotes, int
return exp_str;
}
+static int expand_vars_to_list(o_string *output, int n, char *arg);
+
+static int encode_then_append_var_plusminus(o_string *output, int n,
+ const char *str, int dquoted)
+{
+ struct in_str input;
+ o_string dest = NULL_O_STRING;
+
+#if 0 //todo?
+ const char *cp;
+ cp = str;
+ for (;;) {
+ if (!*cp) return NULL; /* string has no special chars */
+ if (*cp == '$') break;
+ if (*cp == '\\') break;
+ if (*cp == '\'') break;
+ if (*cp == '"') break;
+#if ENABLE_HUSH_TICK
+ if (*cp == '`') break;
+#endif
+ cp++;
+ }
+#endif
+
+ /* Expanding ARG in ${var+ARG}, ${var-ARG} */
+
+ setup_string_in_str(&input, str);
+
+ for (;;) {
+ int ch;
+
+ ch = i_getch(&input);
+ debug_printf_parse("%s: ch=%c (%d) escape=%x\n",
+ __func__, ch, ch, dest.o_expflags);
+
+ if (!dest.o_expflags) {
+ if (ch == EOF)
+ break;
+ if (!dquoted && strchr(G.ifs, ch)) {
+ /* PREFIX${x:d${e}f ...} and we met space: expand "d${e}f" and start new word.
+ * do not assume we are at the start of the word (PREFIX above).
+ */
+ if (dest.data) {
+ n = expand_vars_to_list(output, n, dest.data);
+ o_free(&dest);
+ o_addchr(output, '\0');
+ n = o_save_ptr(output, n); /* create next word */
+ } else
+ if (output->length != o_get_last_ptr(output, n)
+ || output->has_quoted_part
+ ) {
+ /* For these cases:
+ * f() { for i; do echo "|$i|"; done; }; x=x
+ * f a${x:+ }b # 1st condition
+ * |a|
+ * |b|
+ * f ""${x:+ }b # 2nd condition
+ * ||
+ * |b|
+ */
+ o_addchr(output, '\0');
+ n = o_save_ptr(output, n); /* create next word */
+ }
+ continue;
+ }
+ if (!dquoted && ch == '\'') {
+//quoting version of add_till_single_quote() (try to merge?):
+ for (;;) {
+ ch = i_getch(&input);
+ if (ch == EOF) {
+ syntax_error_unterm_ch('\'');
+ goto ret; /* error */
+ }
+ if (ch == '\'')
+ break;
+ o_addqchr(&dest, ch);
+ }
+ continue;
+ }
+ }
+ if (ch == EOF) {
+ syntax_error_unterm_ch('"');
+ goto ret; /* error */
+ }
+ if (ch == '"') {
+ dest.o_expflags ^= EXP_FLAG_ESC_GLOB_CHARS;
+ continue;
+ }
+ if (ch == '\\') {
+ ch = i_getch(&input);
+ if (ch == EOF) {
+//example? error message? syntax_error_unterm_ch('"');
+ debug_printf_parse("%s: error: \\<eof>\n", __func__);
+ goto ret;
+ }
+ o_addqchr(&dest, ch);
+ continue;
+ }
+ if (ch == '$') {
+ if (!parse_dollar(NULL, &dest, &input, /*quote_mask:*/ (dest.o_expflags || dquoted) ? 0x80 : 0)) {
+ debug_printf_parse("%s: error: parse_dollar returned 0 (error)\n", __func__);
+ goto ret;
+ }
+ continue;
+ }
+#if ENABLE_HUSH_TICK
+ if (ch == '`') {
+ //unsigned pos = dest->length;
+ o_addchr(&dest, SPECIAL_VAR_SYMBOL);
+ o_addchr(&dest, (dest.o_expflags || dquoted) ? 0x80 | '`' : '`');
+ if (!add_till_backquote(&dest, &input,
+ /*in_dquote:*/ dest.o_expflags /* nonzero if EXP_FLAG_ESC_GLOB_CHARS set */
+ )
+ ) {
+ goto ret; /* error */
+ }
+ o_addchr(&dest, SPECIAL_VAR_SYMBOL);
+ //debug_printf_subst("SUBST RES3 '%s'\n", dest->data + pos);
+ continue;
+ }
+#endif
+ o_addQchr(&dest, ch);
+ } /* for (;;) */
+
+ if (dest.data) {
+ n = expand_vars_to_list(output, n, dest.data);
+ }
+ ret:
+ o_free_unsafe(&dest);
+ return n;
+}
+
#if ENABLE_FEATURE_SH_MATH
static arith_t expand_and_evaluate_arith(const char *arg, const char **errmsg_p)
{
@@ -6231,78 +6363,86 @@ static NOINLINE int expand_one_var(o_string *output,
*
* Colon forms (${var:-word}, ${var:=word} etc) do the same,
* but also treat null var as if it is unset.
+ *
+ * Word-splitting and single quote behavior:
+ *
+ * $ f() { for i; do echo "|$i|"; done; };
+ *
+ * $ x=; f ${x:?'x y' z}
+ * bash: x: x y z #BUG: does not abort, ${} results in empty expansion
+ * $ x=; f "${x:?'x y' z}"
+ * bash: x: x y z # dash prints: dash: x: 'x y' z #BUG: does not abort, ${} results in ""
+ *
+ * $ x=; f ${x:='x y' z}
+ * |x|
+ * |y|
+ * |z|
+ * $ x=; f "${x:='x y' z}"
+ * |'x y' z|
+ *
+ * $ x=x; f ${x:+'x y' z}|
+ * |x y|
+ * |z|
+ * $ x=x; f "${x:+'x y' z}"
+ * |'x y' z|
+ *
+ * $ x=; f ${x:-'x y' z}
+ * |x y|
+ * |z|
+ * $ x=; f "${x:-'x y' z}"
+ * |'x y' z|
*/
-/*
- * Word-splitting and squote behavior of bash:
- * $ f() { for i; do echo "|$i|"; done; };
- *
- * $ x=; f ${x:?'x y' z}
- * bash: x: x y z #BUG: does not abort, ${} results in empty expansion
- * $ x=; f "${x:?'x y' z}"
- * bash: x: x y z # dash prints: dash: x: 'x y' z #BUG: does not abort, ${} results in ""
- *
- * $ x=; f ${x:='x y' z}
- * |x|
- * |y|
- * |z|
- * $ x=; f "${x:='x y' z}"
- * |'x y' z|
- *
- * $ x=x; f ${x:+'x y' z}
- * |x y|
- * |z|
- * $ x=x; f "${x:+'x y' z}"
- * |'x y' z|
- *
- * $ x=; f ${x:-'x y' z}
- * |x y|
- * |z|
- * $ x=; f "${x:-'x y' z}"
- * |'x y' z|
- */
int use_word = (!val || ((exp_save == ':') && !val[0]));
if (exp_op == '+')
use_word = !use_word;
debug_printf_expand("expand: op:%c (null:%s) test:%i\n", exp_op,
(exp_save == ':') ? "true" : "false", use_word);
if (use_word) {
-//FIXME: unquoted ${x:+"b c" d} and ${x:+'b c' d} should expand to two words
-//currently it expands to three.
- to_be_freed = encode_then_expand_vararg(exp_word,
- /*handle_squotes:*/ !(arg0 & 0x80),
- /*unbackslash:*/ 0
- );
- if (to_be_freed)
- exp_word = to_be_freed;
- if (exp_op == '?') {
- /* mimic bash message */
- msg_and_die_if_script("%s: %s",
- var,
- exp_word[0]
- ? exp_word
- : "parameter null or not set"
- /* ash has more specific messages, a-la: */
- /*: (exp_save == ':' ? "parameter null or not set" : "parameter not set")*/
+ if (exp_op == '+' || exp_op == '-') {
+ /* ${var+word} - use alternative value */
+ /* ${var-word} - use default value */
+ n = encode_then_append_var_plusminus(output, n, exp_word,
+ /*dquoted:*/ (arg0 & 0x80)
);
+ val = NULL;
+ } else {
+ /* ${var?word} - indicate error if unset */
+ /* ${var=word} - assign and use default value */
+ to_be_freed = encode_then_expand_vararg(exp_word,
+ /*handle_squotes:*/ !(arg0 & 0x80),
+ /*unbackslash:*/ 0
+ );
+ if (to_be_freed)
+ exp_word = to_be_freed;
+ if (exp_op == '?') {
+ /* mimic bash message */
+ msg_and_die_if_script("%s: %s",
+ var,
+ exp_word[0]
+ ? exp_word
+ : "parameter null or not set"
+ /* ash has more specific messages, a-la: */
+ /*: (exp_save == ':' ? "parameter null or not set" : "parameter not set")*/
+ );
//TODO: how interactive bash aborts expansion mid-command?
//It aborts the entire line, returns to prompt:
// $ f() { for i; do echo "|$i|"; done; }; x=; f "${x:?'x y' z}"; echo YO
// bash: x: x y z
// $
// ("echo YO" is not executed, neither the f function call)
- } else {
- val = exp_word;
- }
-
- if (exp_op == '=') {
- /* ${var=[word]} or ${var:=[word]} */
- if (isdigit(var[0]) || var[0] == '#') {
- /* mimic bash message */
- msg_and_die_if_script("$%s: cannot assign in this way", var);
- val = NULL;
} else {
- char *new_var = xasprintf("%s=%s", var, val);
- set_local_var(new_var, /*flag:*/ 0);
+ val = exp_word;
+ }
+ if (exp_op == '=') {
+ /* ${var=[word]} or ${var:=[word]} */
+ if (isdigit(var[0]) || var[0] == '#') {
+ /* mimic bash message */
+ msg_and_die_if_script("$%s: cannot assign in this way", var);
+ val = NULL;
+ } else {
+ char *new_var = xasprintf("%s=%s", var, val);
+ set_local_var(new_var, /*flag:*/ 0);
+ }
}
}
}
@@ -6482,8 +6622,9 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg)
}
debug_print_list("expand_vars_to_list[a]", output, n);
/* this part is literal, and it was already pre-quoted
- * if needed (much earlier), do not use o_addQstr here! */
- o_addstr_with_NUL(output, arg);
+ * if needed (much earlier), do not use o_addQstr here!
+ */
+ o_addstr(output, arg);
debug_print_list("expand_vars_to_list[b]", output, n);
} else if (output->length == o_get_last_ptr(output, n) /* expansion is empty */
&& !(cant_be_null & 0x80) /* and all vars were not quoted. */
@@ -6491,8 +6632,6 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg)
n--;
/* allow to reuse list[n] later without re-growth */
output->has_empty_slot = 1;
- } else {
- o_addchr(output, '\0');
}
return n;
@@ -6517,6 +6656,8 @@ static char **expand_variables(char **argv, unsigned expflags)
/* expand argv[i] */
n = expand_vars_to_list(&output, n, *argv++);
+ /* if (!output->has_empty_slot) -- need this?? */
+ o_addchr(&output, '\0');
}
debug_print_list("expand_variables", &output, n);
diff --git a/shell/hush_test/hush-quoting/dollar_altvalue9.right b/shell/hush_test/hush-quoting/dollar_altvalue9.right
new file mode 100644
index 0000000..fc6c269
--- /dev/null
+++ b/shell/hush_test/hush-quoting/dollar_altvalue9.right
@@ -0,0 +1,24 @@
+Unquoted 1:
+|a|
+|x y|
+|1|
+|2|
+|1 2|
+|A|
+|B|
+|C D|
+|zb|
+Quoted 1:
+|a 'x y' 1 2 '' 1 2 A B C D zb|
+Unquoted 2:
+|ax y|
+|1|
+|2|
+|1 2|
+|A|
+|B|
+|C D|
+|z|
+|b|
+Quoted 2:
+|a 'x y' 1 2 '' 1 2 A B C D z b|
diff --git a/shell/hush_test/hush-quoting/dollar_altvalue9.tests b/shell/hush_test/hush-quoting/dollar_altvalue9.tests
new file mode 100755
index 0000000..27a6f4f
--- /dev/null
+++ b/shell/hush_test/hush-quoting/dollar_altvalue9.tests
@@ -0,0 +1,17 @@
+f() { for i; do echo "|$i|"; done; }
+
+echo Unquoted 1:
+x='1 2'; f a${x:+ 'x y' $x '' "$x" `echo A B` "`echo C D`" z}b
+echo Quoted 1:
+x='1 2'; f "a${x:+ 'x y' $x '' "$x" `echo A B` "`echo C D`" z}b"
+
+echo Unquoted 2:
+x='1 2'; f a${x:+'x y' $x '' "$x" `echo A B` "`echo C D`" z }b
+echo Quoted 2:
+x='1 2'; f "a${x:+ 'x y' $x '' "$x" `echo A B` "`echo C D`" z }b"
+
+#echo Unquoted 3:
+#e=
+#x='1 2'; f a${x:+'x y' $x '' "$x" $e $e "$e" $e `echo A B` "`echo C D`" z }b
+#echo Quoted 3:
+#x='1 2'; f "a${x:+ 'x y' $x '' "$x" $e $e "$e" $e `echo A B` "`echo C D`" z }b"