hush: make
a=55; echo $(($a + 1)) $((1 + $((2)) + `echo $a`))
work as expected
function old new delta
handle_dollar - 667 +667
parse_stream_dquoted - 316 +316
expand_variables 2124 2272 +148
is_assignment 134 215 +81
parse_stream 2038 1240 -798
------------------------------------------------------------------------------
(add/remove: 2/0 grow/shrink: 2/1 up/down: 1212/-798) Total: 414 bytes
diff --git a/shell/hush.c b/shell/hush.c
index 3725191..64b6e87 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -1510,7 +1510,7 @@
}
if (n) {
const char *p = o->data + (int)list[n - 1] + string_start;
- fprintf(stderr, " total_sz:%ld\n", (p + strlen(p) + 1) - o->data);
+ fprintf(stderr, " total_sz:%ld\n", (long)((p + strlen(p) + 1) - o->data));
}
}
#else
@@ -1644,6 +1644,14 @@
}
+/* Expansion can recurse */
+#if ENABLE_HUSH_TICK
+static int process_command_subs(o_string *dest,
+ struct in_str *input, const char *subst_end);
+#endif
+static char *expand_string_to_string(const char *str);
+static int parse_stream_dquoted(o_string *dest, struct in_str *input, int dquote_end);
+
/* expand_strvec_to_strvec() takes a list of strings, expands
* all variable references within and returns a pointer to
* a list of expanded strings, possibly with larger number
@@ -1678,11 +1686,6 @@
return n;
}
-#if ENABLE_HUSH_TICK
-static int process_command_subs(o_string *dest,
- struct in_str *input, const char *subst_end);
-#endif
-
/* Expand all variable references in given string, adding words to list[]
* at n, n+1,... positions. Return updated n (so that list[n] is next one
* to be filled). This routine is extremely tricky: has to deal with
@@ -1710,6 +1713,9 @@
#if ENABLE_HUSH_TICK
o_string subst_result = NULL_O_STRING;
#endif
+#if ENABLE_SH_MATH_SUPPORT
+ char arith_buf[sizeof(arith_t)*3 + 2];
+#endif
o_addstr(output, arg, p - arg);
debug_print_list("expand_vars_to_list[1]", output, n);
arg = ++p;
@@ -1720,6 +1726,7 @@
* expand to nothing (not even an empty string) */
if ((first_ch & 0x7f) != '@')
ored_ch |= first_ch;
+
val = NULL;
switch (first_ch & 0x7f) {
/* Highest bit in first_ch indicates that var is double-quoted */
@@ -1803,19 +1810,52 @@
}
#endif
#if ENABLE_SH_MATH_SUPPORT
- case '+': { /* <SPECIAL_VAR_SYMBOL>(cmd<SPECIAL_VAR_SYMBOL> */
+ case '+': { /* <SPECIAL_VAR_SYMBOL>+cmd<SPECIAL_VAR_SYMBOL> */
arith_eval_hooks_t hooks;
arith_t res;
- char buf[30];
int errcode;
+ char *exp_str;
- *p = '\0';
- ++arg;
+ arg++; /* skip '+' */
+ *p = '\0'; /* replace trailing <SPECIAL_VAR_SYMBOL> */
debug_printf_subst("ARITH '%s' first_ch %x\n", arg, first_ch);
+
+ /* Optional: skip expansion if expr is simple ("a + 3", "i++" etc) */
+ exp_str = arg;
+ while (1) {
+ unsigned char c = *exp_str++;
+ if (c == '\0') {
+ exp_str = NULL;
+ goto skip_expand;
+ }
+ if (isdigit(c))
+ continue;
+ if (strchr(" \t+-*/%_", c) != NULL)
+ continue;
+ c |= 0x20; /* tolower */
+ if (c >= 'a' && c <= 'z')
+ continue;
+ break;
+ }
+ /* We need to expand. Example: "echo $(($a + 1)) $((1 + $((2)) ))" */
+ {
+ struct in_str input;
+ o_string dest = NULL_O_STRING;
+
+ setup_string_in_str(&input, arg);
+ parse_stream_dquoted(&dest, &input, EOF);
+ //bb_error_msg("'%s' -> '%s'", arg, dest.data);
+ exp_str = expand_string_to_string(dest.data);
+ //bb_error_msg("'%s' -> '%s'", dest.data, exp_str);
+ o_free(&dest);
+ }
+ skip_expand:
hooks.lookupvar = lookup_param;
hooks.setvar = arith_set_local_var;
hooks.endofname = endofname;
- res = arith(arg, &errcode, &hooks);
+ res = arith(exp_str ? exp_str : arg, &errcode, &hooks);
+ free(exp_str);
+
if (errcode < 0) {
switch (errcode) {
case -3: maybe_die("arith", "exponent less than 0"); break;
@@ -1824,9 +1864,9 @@
default: maybe_die("arith", "syntax error"); break;
}
}
- sprintf(buf, arith_t_fmt, res);
- o_addstrauto(output, buf);
debug_printf_subst("ARITH RES '"arith_t_fmt"'\n", res);
+ sprintf(arith_buf, arith_t_fmt, res);
+ val = arith_buf;
break;
}
#endif
@@ -1918,13 +1958,14 @@
} else { /* quoted $VAR, val will be appended below */
debug_printf_expand("quoted '%s', output->o_quote:%d\n", val, output->o_quote);
}
- }
- }
+ } /* default: */
+ } /* switch (char after <SPECIAL_VAR_SYMBOL>) */
+
if (val) {
o_addQstr(output, val, strlen(val));
}
/* Do the check to avoid writing to a const string */
- if (p && *p != SPECIAL_VAR_SYMBOL)
+ if (*p != SPECIAL_VAR_SYMBOL)
*p = SPECIAL_VAR_SYMBOL;
#if ENABLE_HUSH_TICK
@@ -3756,11 +3797,11 @@
}
debug_printf("done reading from pipe, pclose()ing\n");
- /* This is the step that waits for the child. Should be pretty
- * safe, since we just read an EOF from its stdout. We could try
- * to do better, by using waitpid, and keeping track of background jobs
- * at the same time. That would be a lot of work, and contrary
- * to the KISS philosophy of this program. */
+ /* Note: we got EOF, and we just close the read end of the pipe.
+ * We do not wait for the `cmd` child to terminate. bash and ash do.
+ * Try this:
+ * echo `echo Hi; exec 1>&-; sleep 2`
+ */
retcode = fclose(p);
free_pipe_list(inner.list_head, /* indent: */ 0);
debug_printf("closed FILE from child, retcode=%d\n", retcode);
@@ -4056,7 +4097,7 @@
if (i_peek(input) == '(') {
i_getch(input);
o_addchr(dest, SPECIAL_VAR_SYMBOL);
- o_addchr(dest, quote_mask | '+');
+ o_addchr(dest, /*quote_mask |*/ '+');
add_till_closing_paren(dest, input, true);
o_addchr(dest, SPECIAL_VAR_SYMBOL);
break;
@@ -4093,6 +4134,86 @@
return 0;
}
+static int parse_stream_dquoted(o_string *dest, struct in_str *input, int dquote_end)
+{
+ int ch, m;
+ int next;
+
+ again:
+ ch = i_getch(input);
+ if (ch == dquote_end) { /* may be only '"' or EOF */
+ dest->nonnull = 1;
+ if (dest->o_assignment == NOT_ASSIGNMENT)
+ dest->o_quote ^= 1;
+ debug_printf_parse("parse_stream_dquoted return 0\n");
+ return 0;
+ }
+ if (ch == EOF) {
+ syntax("unterminated \"");
+ debug_printf_parse("parse_stream_dquoted return 1: unterminated \"\n");
+ return 1;
+ }
+ next = '\0';
+ m = G.charmap[ch];
+ if (ch != '\n') {
+ next = i_peek(input);
+ }
+ debug_printf_parse(": ch=%c (%d) m=%d quote=%d\n",
+ ch, ch, m, dest->o_quote);
+ if (m != CHAR_SPECIAL) {
+ o_addQchr(dest, ch);
+ if ((dest->o_assignment == MAYBE_ASSIGNMENT
+ || dest->o_assignment == WORD_IS_KEYWORD)
+ && ch == '='
+ && is_assignment(dest->data)
+ ) {
+ dest->o_assignment = DEFINITELY_ASSIGNMENT;
+ }
+ goto again;
+ }
+ if (ch == '\\') {
+ if (next == EOF) {
+ syntax("\\<eof>");
+ debug_printf_parse("parse_stream_dquoted return 1: \\<eof>\n");
+ return 1;
+ }
+ /* bash:
+ * "The backslash retains its special meaning [in "..."]
+ * only when followed by one of the following characters:
+ * $, `, ", \, or <newline>. A double quote may be quoted
+ * within double quotes by preceding it with a backslash.
+ * If enabled, history expansion will be performed unless
+ * an ! appearing in double quotes is escaped using
+ * a backslash. The backslash preceding the ! is not removed."
+ */
+ if (strchr("$`\"\\", next) != NULL) {
+ o_addqchr(dest, i_getch(input));
+ } else {
+ o_addqchr(dest, '\\');
+ }
+ goto again;
+ }
+ if (ch == '$') {
+ if (handle_dollar(dest, input) != 0) {
+ debug_printf_parse("parse_stream_dquoted return 1: handle_dollar returned non-0\n");
+ return 1;
+ }
+ goto again;
+ }
+#if ENABLE_HUSH_TICK
+ if (ch == '`') {
+ //int pos = dest->length;
+ o_addchr(dest, SPECIAL_VAR_SYMBOL);
+ o_addchr(dest, 0x80 | '`');
+ add_till_backquote(dest, input);
+ o_addchr(dest, SPECIAL_VAR_SYMBOL);
+ //debug_printf_subst("SUBST RES3 '%s'\n", dest->data + pos);
+ /* fall through */
+ }
+#endif
+ goto again;
+}
+
/* Scan input, call done_word() whenever full IFS delimited word was seen.
* Call done_pipe if '\n' was seen (and end_trigger != NULL).
* Return code is 0 if end_trigger char is met,
@@ -4104,7 +4225,7 @@
int ch, m;
int redir_fd;
redir_type redir_style;
- int shadow_quote = dest->o_quote;
+ int is_in_dquote;
int next;
/* Only double-quote state is handled in the state variable dest->o_quote.
@@ -4113,7 +4234,14 @@
debug_printf_parse("parse_stream entered, end_trigger='%s' dest->o_assignment:%d\n", end_trigger, dest->o_assignment);
+ is_in_dquote = dest->o_quote;
while (1) {
+ if (is_in_dquote) {
+ if (parse_stream_dquoted(dest, input, '"'))
+ return 1; /* propagate parse error */
+ /* If we're here, we reached closing '"' */
+ is_in_dquote = 0;
+ }
m = CHAR_IFS;
next = '\0';
ch = i_getch(input);
@@ -4125,14 +4253,7 @@
}
debug_printf_parse(": ch=%c (%d) m=%d quote=%d\n",
ch, ch, m, dest->o_quote);
- if (m == CHAR_ORDINARY
- || (m != CHAR_SPECIAL && shadow_quote)
- ) {
- if (ch == EOF) {
- syntax("unterminated \"");
- debug_printf_parse("parse_stream return 1: unterminated \"\n");
- return 1;
- }
+ if (m == CHAR_ORDINARY) {
o_addQchr(dest, ch);
if ((dest->o_assignment == MAYBE_ASSIGNMENT
|| dest->o_assignment == WORD_IS_KEYWORD)
@@ -4143,6 +4264,8 @@
}
continue;
}
+ /* m is SPECIAL ($,`), IFS, or ORDINARY_IF_QUOTED (*,#)
+ */
if (m == CHAR_IFS) {
if (done_word(dest, ctx)) {
debug_printf_parse("parse_stream return 1: done_word!=0\n");
@@ -4152,7 +4275,7 @@
break;
/* If we aren't performing a substitution, treat
* a newline as a command separator.
- * [why we don't handle it exactly like ';'? --vda] */
+ * [why don't we handle it exactly like ';'? --vda] */
if (end_trigger && ch == '\n') {
#if ENABLE_HUSH_CASE
/* "case ... in <newline> word) ..." -
@@ -4168,7 +4291,7 @@
}
}
if (end_trigger) {
- if (!shadow_quote && strchr(end_trigger, ch)) {
+ if (strchr(end_trigger, ch)) {
/* Special case: (...word) makes last word terminate,
* as if ';' is seen */
if (ch == ')') {
@@ -4188,15 +4311,17 @@
if (m == CHAR_IFS)
continue;
+ /* m is SPECIAL (e.g. $,`) or ORDINARY_IF_QUOTED (*,#) */
+
if (dest->o_assignment == MAYBE_ASSIGNMENT) {
/* ch is a special char and thus this word
- * cannot be an assignment: */
+ * cannot be an assignment */
dest->o_assignment = NOT_ASSIGNMENT;
}
switch (ch) {
case '#':
- if (dest->length == 0 && !shadow_quote) {
+ if (dest->length == 0) {
while (1) {
ch = i_peek(input);
if (ch == EOF || ch == '\n')
@@ -4213,25 +4338,8 @@
debug_printf_parse("parse_stream return 1: \\<eof>\n");
return 1;
}
- /* bash:
- * "The backslash retains its special meaning [in "..."]
- * only when followed by one of the following characters:
- * $, `, ", \, or <newline>. A double quote may be quoted
- * within double quotes by preceding it with a backslash.
- * If enabled, history expansion will be performed unless
- * an ! appearing in double quotes is escaped using
- * a backslash. The backslash preceding the ! is not removed."
- */
- if (shadow_quote) { //NOT SURE dest->o_quote) {
- if (strchr("$`\"\\", next) != NULL) {
- o_addqchr(dest, i_getch(input));
- } else {
- o_addqchr(dest, '\\');
- }
- } else {
- o_addchr(dest, '\\');
- o_addchr(dest, i_getch(input));
- }
+ o_addchr(dest, '\\');
+ o_addchr(dest, i_getch(input));
break;
case '$':
if (handle_dollar(dest, input) != 0) {
@@ -4258,7 +4366,7 @@
break;
case '"':
dest->nonnull = 1;
- shadow_quote ^= 1; /* invert */
+ is_in_dquote ^= 1; /* invert */
if (dest->o_assignment == NOT_ASSIGNMENT)
dest->o_quote ^= 1;
break;
@@ -4266,7 +4374,7 @@
case '`': {
//int pos = dest->length;
o_addchr(dest, SPECIAL_VAR_SYMBOL);
- o_addchr(dest, shadow_quote /*or dest->o_quote??*/ ? 0x80 | '`' : '`');
+ o_addchr(dest, '`');
add_till_backquote(dest, input);
o_addchr(dest, SPECIAL_VAR_SYMBOL);
//debug_printf_subst("SUBST RES3 '%s'\n", dest->data + pos);