diff options
-rw-r--r-- | shell/hush.c | 84 | ||||
-rw-r--r-- | shell/hush_test/hush-vars/param_expand_bash_substring.right | 29 | ||||
-rwxr-xr-x | shell/hush_test/hush-vars/param_expand_bash_substring.tests | 46 |
3 files changed, 120 insertions, 39 deletions
diff --git a/shell/hush.c b/shell/hush.c index 06e9af21a..0333395f8 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -46,7 +46,6 @@ * brace expansion: one/{two,three,four} * reserved words: function select * advanced test: [[ ]] - * substrings: ${var:1:5} * process substitution: <(list) and >(list) * =~: regex operator * let EXPR [EXPR...] @@ -2586,7 +2585,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char var++; } else { /* maybe handle parameter expansion */ - exp_saveptr = var + strcspn(var, ":-=+?%#"); + exp_saveptr = var + strcspn(var, "%#:-=+?"); exp_save = *exp_saveptr; if (exp_save) { exp_word = exp_saveptr; @@ -2636,9 +2635,38 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char else if (loc) /* % or %% and match was found */ *loc = '\0'; } - } else { /* one of :-=+? */ -//TODO: check validity of exp_op. Currently it can be anything. -//TODO: handle ${VAR:N[:M]} here. N, M can be expressions similar to $((EXPR)): 2+2, 2+var etc + } else if (!strchr("%#:-=+?"+3, exp_op)) { +#if ENABLE_HUSH_BASH_COMPAT + /* exp_op is ':' and next char isn't a subst operator. + * Assuming it's ${var:[N][:M]} bashism. + * TODO: N, M can be expressions similar to $((EXPR)): 2+2, 2+var etc + */ + char *end; + unsigned len = INT_MAX; + unsigned beg = 0; + end = --exp_word; + if (*exp_word != ':') /* not ${var::...} */ + beg = bb_strtou(exp_word, &end, 0); + //bb_error_msg("beg:'%s'=%u end:'%s'", exp_word, beg, end); + if (*end == ':') { + len = bb_strtou(end + 1, &end, 0); + //bb_error_msg("len:%u end:'%s'", len, end); + } + if (*end == '\0' + && end != exp_word /* not "${var:}" */ + ) { + //bb_error_msg("from val:'%s'", val); + if (!val || beg >= strlen(val)) + val = ""; + else + val = dyn_val = xstrndup(val + beg, len); + //bb_error_msg("val:'%s'", val); + } else +#endif + { + die_if_script("malformed ${%s...}", var); + } + } else { /* one of "-=+?" */ /* Standard-mandated substitution ops: * ${var?word} - indicate error if unset * If var is unset, word (or a message indicating it is unset @@ -2693,7 +2721,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char } *exp_saveptr = exp_save; - } + } /* if (exp_op) */ arg[0] = first_ch; #if ENABLE_HUSH_TICK @@ -5926,14 +5954,15 @@ static int handle_dollar(o_string *as_string, goto make_one_char_var; case '{': { bool first_char, all_digits; - int expansion; + bool in_expansion_param; ch = i_getch(input); nommu_addchr(as_string, ch); o_addchr(dest, SPECIAL_VAR_SYMBOL); - /* TODO: maybe someone will try to escape the '}' */ - expansion = 0; +// TODO: need to handle "a=ab}; echo ${a%\}}" +// and "a=abc; c=c; echo ${a%${c}}" + in_expansion_param = false; first_char = true; all_digits = false; while (1) { @@ -5957,45 +5986,22 @@ static int handle_dollar(o_string *as_string, goto char_ok; } - if (expansion < 2 - && ( (all_digits && !isdigit(ch)) - || (!all_digits && !isalnum(ch) && ch != '_') + if (!in_expansion_param + && ( (all_digits && !isdigit(ch)) /* met non-digit: 123w */ + || (!all_digits && !isalnum(ch) && ch != '_') /* met non-name char: abc% */ ) ) { /* handle parameter expansions * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_02 */ - if (first_char) - goto case_default; - switch (ch) { - case ':': /* null modifier */ - if (expansion == 0) { - debug_printf_parse(": null modifier\n"); - ++expansion; - break; - } - goto case_default; - case '#': /* remove prefix */ - case '%': /* remove suffix */ - if (expansion == 0) { - debug_printf_parse(": remove suffix/prefix\n"); - expansion = 2; - break; - } - goto case_default; - case '-': /* default value */ - case '=': /* assign default */ - case '+': /* alternative */ - case '?': /* error indicate */ - debug_printf_parse(": parameter expansion\n"); - expansion = 2; - break; - default: - case_default: + if (first_char /* bad (empty var name): "${%..." */ + || !strchr("%#:-=+?", ch) /* bad: "${var<bad_char>..." */ + ) { syntax_error_unterm_str("${name}"); debug_printf_parse("handle_dollar return 1: unterminated ${name}\n"); return 1; } + in_expansion_param = true; } char_ok: debug_printf_parse(": '%c'\n", ch); diff --git a/shell/hush_test/hush-vars/param_expand_bash_substring.right b/shell/hush_test/hush-vars/param_expand_bash_substring.right new file mode 100644 index 000000000..9cd465938 --- /dev/null +++ b/shell/hush_test/hush-vars/param_expand_bash_substring.right @@ -0,0 +1,29 @@ +hush: syntax error: unterminated ${name} +hush: syntax error: unterminated ${name} +hush: syntax error: unterminated ${name} +hush: syntax error: unterminated ${name} +0123456789 +1 =|| +1:1 =|| +1:1:2=|| +1::2 =|| +1 =|0123| +1:1 =|123| +1:1:2=|12| +1::2 =|01| +f =|| +f:1 =|| +f:1:2=|| +f::2 =|| +f =|| +f:1 =|| +f:1:2=|| +f::2 =|| +f =|a| +f:1 =|| +f:1:2=|| +f::2 =|a| +f =|0123456789| +f:1 =|123456789| +f:1:2=|12| +f::2 =|01| diff --git a/shell/hush_test/hush-vars/param_expand_bash_substring.tests b/shell/hush_test/hush-vars/param_expand_bash_substring.tests new file mode 100755 index 000000000..6a1765559 --- /dev/null +++ b/shell/hush_test/hush-vars/param_expand_bash_substring.tests @@ -0,0 +1,46 @@ +# do all of these in subshells since it's supposed to error out + +export var=0123456789 + +# first try some invalid patterns +"$THIS_SH" -c 'echo ${:}' +"$THIS_SH" -c 'echo ${::}' +"$THIS_SH" -c 'echo ${:1}' +"$THIS_SH" -c 'echo ${::1}' + +#this also is not valid in bash, but we accept it: +"$THIS_SH" -c 'echo ${var:}' + +# then some funky ones +# UNFIXED BUG: this should work: "$THIS_SH" -c 'echo ${?:0}' + +# now some valid ones +"$THIS_SH" -c 'set --; echo "1 =|${1}|"' +"$THIS_SH" -c 'set --; echo "1:1 =|${1:1}|"' +"$THIS_SH" -c 'set --; echo "1:1:2=|${1:1:2}|"' +"$THIS_SH" -c 'set --; echo "1::2 =|${1::2}|"' + +"$THIS_SH" -c 'set -- 0123; echo "1 =|${1}|"' +"$THIS_SH" -c 'set -- 0123; echo "1:1 =|${1:1}|"' +"$THIS_SH" -c 'set -- 0123; echo "1:1:2=|${1:1:2}|"' +"$THIS_SH" -c 'set -- 0123; echo "1::2 =|${1::2}|"' + +"$THIS_SH" -c 'unset f; echo "f =|$f|"' +"$THIS_SH" -c 'unset f; echo "f:1 =|${f:1}|"' +"$THIS_SH" -c 'unset f; echo "f:1:2=|${f:1:2}|"' +"$THIS_SH" -c 'unset f; echo "f::2 =|${f::2}|"' + +"$THIS_SH" -c 'f=; echo "f =|$f|"' +"$THIS_SH" -c 'f=; echo "f:1 =|${f:1}|"' +"$THIS_SH" -c 'f=; echo "f:1:2=|${f:1:2}|"' +"$THIS_SH" -c 'f=; echo "f::2 =|${f::2}|"' + +"$THIS_SH" -c 'f=a; echo "f =|$f|"' +"$THIS_SH" -c 'f=a; echo "f:1 =|${f:1}|"' +"$THIS_SH" -c 'f=a; echo "f:1:2=|${f:1:2}|"' +"$THIS_SH" -c 'f=a; echo "f::2 =|${f::2}|"' + +"$THIS_SH" -c 'f=0123456789; echo "f =|$f|"' +"$THIS_SH" -c 'f=0123456789; echo "f:1 =|${f:1}|"' +"$THIS_SH" -c 'f=0123456789; echo "f:1:2=|${f:1:2}|"' +"$THIS_SH" -c 'f=0123456789; echo "f::2 =|${f::2}|"' |