From 1e811b12317d0eab4e78d848caa640cca497a0a7 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sat, 22 May 2010 03:12:29 +0200 Subject: [PATCH] hush: support ${var:EXPR:EXPR}! function old new delta handle_dollar 574 681 +107 expand_and_evaluate_arith - 77 +77 expand_vars_to_list 2302 2374 +72 add_till_closing_bracket 359 368 +9 builtin_exit 48 47 -1 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 3/1 up/down: 265/-1) Total: 264 bytes Signed-off-by: Denys Vlasenko --- shell/hush.c | 130 ++++++++++++------ .../param_expand_bash_substring.right | 10 ++ .../param_expand_bash_substring.tests | 90 ++++++------ 3 files changed, 148 insertions(+), 82 deletions(-) diff --git a/shell/hush.c b/shell/hush.c index 0f0151a21..41d5fcab2 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -171,6 +171,7 @@ #define debug_printf_env(...) do {} while (0) #define debug_printf_jobs(...) do {} while (0) #define debug_printf_expand(...) do {} while (0) +#define debug_printf_varexp(...) do {} while (0) #define debug_printf_glob(...) do {} while (0) #define debug_printf_list(...) do {} while (0) #define debug_printf_subst(...) do {} while (0) @@ -743,6 +744,10 @@ static const struct built_in_command bltins2[] = { # define DEBUG_EXPAND 0 #endif +#ifndef debug_printf_varexp +# define debug_printf_varexp(...) (indent(), fprintf(stderr, __VA_ARGS__)) +#endif + #ifndef debug_printf_glob # define debug_printf_glob(...) (indent(), fprintf(stderr, __VA_ARGS__)) # define DEBUG_GLOB 1 @@ -1817,11 +1822,11 @@ static void o_addblock(o_string *o, const char *str, int len) o->data[o->length] = '\0'; } -#if !BB_MMU static void o_addstr(o_string *o, const char *str) { o_addblock(o, str, strlen(str)); } +#if !BB_MMU static void nommu_addchr(o_string *o, int ch) { if (o) @@ -2597,12 +2602,19 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char } else { /* maybe handle parameter expansion */ exp_saveptr = var + strcspn(var, "%#:-=+?"); - exp_save = *exp_saveptr; - if (exp_save) { - exp_word = exp_saveptr; - if (exp_save == ':') - exp_word++; - exp_op = *exp_word++; + exp_op = exp_save = *exp_saveptr; + if (exp_op) { + exp_word = exp_saveptr + 1; + if (exp_op == ':') { + exp_op = *exp_word++; + if (ENABLE_HUSH_BASH_COMPAT + && (exp_op == '\0' || !strchr("%#:-=+?"+3, exp_op)) + ) { + /* oops... it's ${var:N[:M]}, not ${var:?xxx} or some such */ + exp_op = ':'; + exp_word--; + } + } *exp_saveptr = '\0'; } } @@ -2656,39 +2668,42 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char *loc = '\0'; } } - } else if (!strchr("%#:-=+?"+3, exp_op)) { + } else if (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 + /* It's ${var:N[:M]} bashism. + * Note that in encoded form it has TWO parts: + * var:NM */ - 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 == ':') { - if (end[1] != '\0') /* not ${var:NUM:} */ - len = bb_strtou(end + 1, &end, 0); - else { - len = 0; - end++; - } - //bb_error_msg("len:%u end:'%s'", len, end); - } - if (*end == '\0') { - //bb_error_msg("from val:'%s'", val); + arith_t beg, len; + int errcode = 0; + + beg = expand_and_evaluate_arith(exp_word, &errcode); + debug_printf_varexp("beg:'%s'=%lld\n", exp_word, (long long)beg); + *p++ = SPECIAL_VAR_SYMBOL; + exp_word = p; + p = strchr(p, SPECIAL_VAR_SYMBOL); + *p = '\0'; + len = expand_and_evaluate_arith(exp_word, &errcode); + debug_printf_varexp("len:'%s'=%lld\n", exp_word, (long long)len); + + if (errcode >= 0 && len >= 0) { /* bash compat: len < 0 is illegal */ + if (beg < 0) /* bash compat */ + beg = 0; + debug_printf_varexp("from val:'%s'\n", val); if (len == 0 || !val || beg >= strlen(val)) val = ""; - else + else { + /* Paranoia. What if user entered 9999999999999 + * which fits in arith_t but not int? */ + if (len >= INT_MAX) + len = INT_MAX; val = to_be_freed = xstrndup(val + beg, len); - //bb_error_msg("val:'%s'", val); + } + debug_printf_varexp("val:'%s'\n", val); } else #endif { - die_if_script("malformed ${%s...}", var); + die_if_script("malformed ${%s:...}", var); val = ""; } } else { /* one of "-=+?" */ @@ -5891,21 +5906,28 @@ static void add_till_backquote(o_string *dest, struct in_str *input) * echo $(echo 'TEST)' BEST) TEST) BEST * echo $(echo \(\(TEST\) BEST) ((TEST) BEST * - * Also adapted to eat ${var%...} constructs, since ... part + * Also adapted to eat ${var%...} and $((...)) constructs, since ... part * can contain arbitrary constructs, just like $(cmd). + * In bash compat mode, it needs to also be able to stop on '}' or ':' + * for ${var:N[:M]} parsing. */ #define DOUBLE_CLOSE_CHAR_FLAG 0x80 -static void add_till_closing_bracket(o_string *dest, struct in_str *input, char end_ch) +static int add_till_closing_bracket(o_string *dest, struct in_str *input, unsigned end_ch) { + int ch; char dbl = end_ch & DOUBLE_CLOSE_CHAR_FLAG; - end_ch &= (DOUBLE_CLOSE_CHAR_FLAG-1); +#if ENABLE_HUSH_BASH_COMPAT + char end_char2 = end_ch >> 8; +#endif + end_ch &= (DOUBLE_CLOSE_CHAR_FLAG - 1); + while (1) { - int ch = i_getch(input); + ch = i_getch(input); if (ch == EOF) { syntax_error_unterm_ch(end_ch); /*xfunc_die(); - redundant */ } - if (ch == end_ch) { + if (ch == end_ch IF_HUSH_BASH_COMPAT( || ch == end_char2)) { if (!dbl) break; /* we look for closing )) of $((EXPR)) */ @@ -5947,6 +5969,7 @@ static void add_till_closing_bracket(o_string *dest, struct in_str *input, char continue; } } + return ch; } #endif /* ENABLE_HUSH_TICK || ENABLE_SH_MATH_SUPPORT */ @@ -6033,22 +6056,45 @@ static int handle_dollar(o_string *as_string, break; if (!isalnum(ch) && ch != '_') { + unsigned end_ch; + unsigned char last_ch; /* handle parameter expansions * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_02 */ if (!strchr("%#:-=+?", ch)) /* ${var... */ goto bad_dollar_syntax; - /* Eat everything until closing '}' */ o_addchr(dest, ch); + + /* Eat everything until closing '}' (or ':') */ + end_ch = '}'; + if (ENABLE_HUSH_BASH_COMPAT + && ch == ':' + && !strchr("%#:-=+?"+3, i_peek(input)) + ) { + /* It's ${var:N[:M]} thing */ + end_ch = '}' * 0x100 + ':'; + } + again: if (!BB_MMU) pos = dest->length; - add_till_closing_bracket(dest, input, '}'); -#if !BB_MMU + last_ch = add_till_closing_bracket(dest, input, end_ch); if (as_string) { o_addstr(as_string, dest->data + pos); - o_addchr(as_string, '}'); + o_addchr(as_string, last_ch); + } + + if (ENABLE_HUSH_BASH_COMPAT && (end_ch & 0xff00)) { + /* close the first block: */ + o_addchr(dest, SPECIAL_VAR_SYMBOL); + /* while parsing N from ${var:N[:M]}... */ + if ((end_ch & 0xff) == last_ch) { + /* ...got ':' - parse the rest */ + end_ch = '}'; + goto again; + } + /* ...got '}', not ':' - it's ${var:N}! emulate :999999999 */ + o_addstr(dest, "999999999"); } -#endif break; } } diff --git a/shell/hush_test/hush-vars/param_expand_bash_substring.right b/shell/hush_test/hush-vars/param_expand_bash_substring.right index 6e3eb3ba6..53b8836ff 100644 --- a/shell/hush_test/hush-vars/param_expand_bash_substring.right +++ b/shell/hush_test/hush-vars/param_expand_bash_substring.right @@ -39,3 +39,13 @@ f:1:2=|12| f::2 =|01| f:1: =|| f:: =|| +Substrings with expressions +f =|01234567| +f:1+1:2+2 =|2345| +f:-1:2+2 =|01234567| +f:1:f =|1234567| +f:1:$f =|1234567| +f:1:${f} =|1234567| +f:1:${f:3:1} =|123| +f:1:1`echo 1`=|1| +Done diff --git a/shell/hush_test/hush-vars/param_expand_bash_substring.tests b/shell/hush_test/hush-vars/param_expand_bash_substring.tests index eedd435ed..a80523add 100755 --- a/shell/hush_test/hush-vars/param_expand_bash_substring.tests +++ b/shell/hush_test/hush-vars/param_expand_bash_substring.tests @@ -1,8 +1,6 @@ -# do all of these in subshells since it's supposed to error out - -export var=0123456789 - # first try some invalid patterns +# do all of these in subshells since it's supposed to error out +export var=0123456789 "$THIS_SH" -c 'echo ${:}' "$THIS_SH" -c 'echo ${::}' "$THIS_SH" -c 'echo ${:1}' @@ -15,44 +13,56 @@ export var=0123456789 # 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 --; echo "1:1: =|${1:1:}|"' -"$THIS_SH" -c 'set --; echo "1:: =|${1::}|"' +set --; echo "1 =|${1}|" +set --; echo "1:1 =|${1:1}|" +set --; echo "1:1:2=|${1:1:2}|" +set --; echo "1::2 =|${1::2}|" +set --; echo "1:1: =|${1:1:}|" +set --; echo "1:: =|${1::}|" -"$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 'set -- 0123; echo "1:1: =|${1:1:}|"' -"$THIS_SH" -c 'set -- 0123; echo "1:: =|${1::}|"' +set -- 0123; echo "1 =|${1}|" +set -- 0123; echo "1:1 =|${1:1}|" +set -- 0123; echo "1:1:2=|${1:1:2}|" +set -- 0123; echo "1::2 =|${1::2}|" +set -- 0123; echo "1:1: =|${1:1:}|" +set -- 0123; echo "1:: =|${1::}|" -"$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 'unset f; echo "f:1: =|${f:1:}|"' -"$THIS_SH" -c 'unset f; echo "f:: =|${f::}|"' +unset f; echo "f =|$f|" +unset f; echo "f:1 =|${f:1}|" +unset f; echo "f:1:2=|${f:1:2}|" +unset f; echo "f::2 =|${f::2}|" +unset f; echo "f:1: =|${f:1:}|" +unset f; echo "f:: =|${f::}|" -"$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=; echo "f:1: =|${f:1:}|"' -"$THIS_SH" -c 'f=; echo "f:: =|${f::}|"' +f=; echo "f =|$f|" +f=; echo "f:1 =|${f:1}|" +f=; echo "f:1:2=|${f:1:2}|" +f=; echo "f::2 =|${f::2}|" +f=; echo "f:1: =|${f:1:}|" +f=; echo "f:: =|${f::}|" -"$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=a; echo "f:1: =|${f:1:}|"' -"$THIS_SH" -c 'f=a; echo "f:: =|${f::}|"' +f=a; echo "f =|$f|" +f=a; echo "f:1 =|${f:1}|" +f=a; echo "f:1:2=|${f:1:2}|" +f=a; echo "f::2 =|${f::2}|" +f=a; echo "f:1: =|${f:1:}|" +f=a; echo "f:: =|${f::}|" -"$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}|"' -"$THIS_SH" -c 'f=0123456789; echo "f:1: =|${f:1:}|"' -"$THIS_SH" -c 'f=0123456789; echo "f:: =|${f::}|"' +f=0123456789; echo "f =|$f|" +f=0123456789; echo "f:1 =|${f:1}|" +f=0123456789; echo "f:1:2=|${f:1:2}|" +f=0123456789; echo "f::2 =|${f::2}|" +f=0123456789; echo "f:1: =|${f:1:}|" +f=0123456789; echo "f:: =|${f::}|" + +echo "Substrings with expressions" +f=01234567; echo 'f '"=|$f|" +f=01234567; echo 'f:1+1:2+2 '"=|${f:1+1:2+2}|" +f=01234567; echo 'f:-1:2+2 '"=|${f:-1:2+2}|" +f=01234567; echo 'f:1:f '"=|${f:1:f}|" +f=01234567; echo 'f:1:$f '"=|${f:1:$f}|" +f=01234567; echo 'f:1:${f} '"=|${f:1:${f}}|" +f=01234567; echo 'f:1:${f:3:1} '"=|${f:1:${f:3:1}}|" +f=01234567; echo 'f:1:1`echo 1`'"=|${f:1:`echo 1`}|" + +echo Done