diff options
author | Denys Vlasenko <dvlasenk@redhat.com> | 2010-09-05 14:45:38 +0200 |
---|---|---|
committer | Denys Vlasenko <dvlasenk@redhat.com> | 2010-09-05 14:47:58 +0200 |
commit | 36f774a0cd2bf8dd72b192aab93831c5ac0c58f0 (patch) | |
tree | afa0b3d247e4dd163b89b7b61f4e3e0f3143b102 /shell/hush.c | |
parent | 701e127f7d892909a58c6f3333e23588ccef9e22 (diff) | |
download | busybox-w32-36f774a0cd2bf8dd72b192aab93831c5ac0c58f0.tar.gz busybox-w32-36f774a0cd2bf8dd72b192aab93831c5ac0c58f0.tar.bz2 busybox-w32-36f774a0cd2bf8dd72b192aab93831c5ac0c58f0.zip |
hush: add support for ${var/pattern/repl}, conditional on bash compat
function old new delta
expand_vars_to_list 2386 2833 +447
expand_string_to_string 69 110 +41
parse_dollar 681 721 +40
hush_main 963 945 -18
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 3/1 up/down: 528/-18) Total: 510 bytes
Signed-off-by: Denys Vlasenko <dvlasenk@redhat.com>
Diffstat (limited to 'shell/hush.c')
-rw-r--r-- | shell/hush.c | 179 |
1 files changed, 150 insertions, 29 deletions
diff --git a/shell/hush.c b/shell/hush.c index 4f80b7d83..9a08e90c9 100644 --- a/shell/hush.c +++ b/shell/hush.c | |||
@@ -50,7 +50,6 @@ | |||
50 | * | 50 | * |
51 | * Bash compat TODO: | 51 | * Bash compat TODO: |
52 | * redirection of stdout+stderr: &> and >& | 52 | * redirection of stdout+stderr: &> and >& |
53 | * subst operator: ${var/[/]expr/expr} | ||
54 | * brace expansion: one/{two,three,four} | 53 | * brace expansion: one/{two,three,four} |
55 | * reserved words: function select | 54 | * reserved words: function select |
56 | * advanced test: [[ ]] | 55 | * advanced test: [[ ]] |
@@ -330,6 +329,17 @@ | |||
330 | #define _SPECIAL_VARS_STR "_*@$!?#" | 329 | #define _SPECIAL_VARS_STR "_*@$!?#" |
331 | #define SPECIAL_VARS_STR ("_*@$!?#" + 1) | 330 | #define SPECIAL_VARS_STR ("_*@$!?#" + 1) |
332 | #define NUMERIC_SPECVARS_STR ("_*@$!?#" + 3) | 331 | #define NUMERIC_SPECVARS_STR ("_*@$!?#" + 3) |
332 | #if ENABLE_HUSH_BASH_COMPAT | ||
333 | /* Support / and // replace ops */ | ||
334 | /* Note that // is stored as \ in "encoded" string representation */ | ||
335 | # define VAR_ENCODED_SUBST_OPS "\\/%#:-=+?" | ||
336 | # define VAR_SUBST_OPS ("\\/%#:-=+?" + 1) | ||
337 | # define MINUS_PLUS_EQUAL_QUESTION ("\\/%#:-=+?" + 5) | ||
338 | #else | ||
339 | # define VAR_ENCODED_SUBST_OPS "%#:-=+?" | ||
340 | # define VAR_SUBST_OPS "%#:-=+?" | ||
341 | # define MINUS_PLUS_EQUAL_QUESTION ("%#:-=+?" + 3) | ||
342 | #endif | ||
333 | 343 | ||
334 | #define SPECIAL_VAR_SYMBOL 3 | 344 | #define SPECIAL_VAR_SYMBOL 3 |
335 | 345 | ||
@@ -2600,6 +2610,60 @@ static arith_t expand_and_evaluate_arith(const char *arg, int *errcode_p) | |||
2600 | } | 2610 | } |
2601 | #endif | 2611 | #endif |
2602 | 2612 | ||
2613 | #if ENABLE_HUSH_BASH_COMPAT | ||
2614 | /* ${var/[/]pattern[/repl]} helpers */ | ||
2615 | static char *strstr_pattern(char *val, const char *pattern, int *size) | ||
2616 | { | ||
2617 | while (1) { | ||
2618 | char *end = scan_and_match(val, pattern, SCAN_MOVE_FROM_RIGHT + SCAN_MATCH_LEFT_HALF); | ||
2619 | debug_printf_varexp("val:'%s' pattern:'%s' end:'%s'\n", val, pattern, end); | ||
2620 | if (end) { | ||
2621 | *size = end - val; | ||
2622 | return val; | ||
2623 | } | ||
2624 | if (*val == '\0') | ||
2625 | return NULL; | ||
2626 | /* Optimization: if "*pat" did not match the start of "string", | ||
2627 | * we know that "tring", "ring" etc will not match too: | ||
2628 | */ | ||
2629 | if (pattern[0] == '*') | ||
2630 | return NULL; | ||
2631 | val++; | ||
2632 | } | ||
2633 | } | ||
2634 | static char *replace_pattern(char *val, const char *pattern, const char *repl, char exp_op) | ||
2635 | { | ||
2636 | char *result = NULL; | ||
2637 | unsigned res_len = 0; | ||
2638 | unsigned repl_len = strlen(repl); | ||
2639 | |||
2640 | while (1) { | ||
2641 | int size; | ||
2642 | char *s = strstr_pattern(val, pattern, &size); | ||
2643 | if (!s) | ||
2644 | break; | ||
2645 | |||
2646 | result = xrealloc(result, res_len + (s - val) + repl_len + 1); | ||
2647 | memcpy(result + res_len, val, s - val); | ||
2648 | res_len += s - val; | ||
2649 | strcpy(result + res_len, repl); | ||
2650 | res_len += repl_len; | ||
2651 | debug_printf_varexp("val:'%s' s:'%s' result:'%s'\n", val, s, result); | ||
2652 | |||
2653 | val = s + size; | ||
2654 | if (exp_op == '/') | ||
2655 | break; | ||
2656 | } | ||
2657 | if (val[0] && result) { | ||
2658 | result = xrealloc(result, res_len + strlen(val) + 1); | ||
2659 | strcpy(result + res_len, val); | ||
2660 | debug_printf_varexp("val:'%s' result:'%s'\n", val, result); | ||
2661 | } | ||
2662 | debug_printf_varexp("result:'%s'\n", result); | ||
2663 | return result; | ||
2664 | } | ||
2665 | #endif | ||
2666 | |||
2603 | /* Expand all variable references in given string, adding words to list[] | 2667 | /* Expand all variable references in given string, adding words to list[] |
2604 | * at n, n+1,... positions. Return updated n (so that list[n] is next one | 2668 | * at n, n+1,... positions. Return updated n (so that list[n] is next one |
2605 | * to be filled). This routine is extremely tricky: has to deal with | 2669 | * to be filled). This routine is extremely tricky: has to deal with |
@@ -2750,7 +2814,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char | |||
2750 | 2814 | ||
2751 | var = arg; | 2815 | var = arg; |
2752 | *p = '\0'; | 2816 | *p = '\0'; |
2753 | exp_saveptr = arg[1] ? strchr("%#:-=+?", arg[1]) : NULL; | 2817 | exp_saveptr = arg[1] ? strchr(VAR_ENCODED_SUBST_OPS, arg[1]) : NULL; |
2754 | first_char = arg[0] = first_ch & 0x7f; | 2818 | first_char = arg[0] = first_ch & 0x7f; |
2755 | exp_op = 0; | 2819 | exp_op = 0; |
2756 | 2820 | ||
@@ -2767,7 +2831,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char | |||
2767 | exp_saveptr = var + 1; | 2831 | exp_saveptr = var + 1; |
2768 | } else { | 2832 | } else { |
2769 | /* ${?}, ${var}, ${var:0}, ${var[:]%0} etc */ | 2833 | /* ${?}, ${var}, ${var:0}, ${var[:]%0} etc */ |
2770 | exp_saveptr = var+1 + strcspn(var+1, "%#:-=+?"); | 2834 | exp_saveptr = var+1 + strcspn(var+1, VAR_ENCODED_SUBST_OPS); |
2771 | } | 2835 | } |
2772 | exp_op = exp_save = *exp_saveptr; | 2836 | exp_op = exp_save = *exp_saveptr; |
2773 | if (exp_op) { | 2837 | if (exp_op) { |
@@ -2775,7 +2839,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char | |||
2775 | if (exp_op == ':') { | 2839 | if (exp_op == ':') { |
2776 | exp_op = *exp_word++; | 2840 | exp_op = *exp_word++; |
2777 | if (ENABLE_HUSH_BASH_COMPAT | 2841 | if (ENABLE_HUSH_BASH_COMPAT |
2778 | && (exp_op == '\0' || !strchr("%#:-=+?"+3, exp_op)) | 2842 | && (exp_op == '\0' || !strchr(MINUS_PLUS_EQUAL_QUESTION, exp_op)) |
2779 | ) { | 2843 | ) { |
2780 | /* oops... it's ${var:N[:M]}, not ${var:?xxx} or some such */ | 2844 | /* oops... it's ${var:N[:M]}, not ${var:?xxx} or some such */ |
2781 | exp_op = ':'; | 2845 | exp_op = ':'; |
@@ -2799,7 +2863,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char | |||
2799 | val = utoa(G.root_pid); | 2863 | val = utoa(G.root_pid); |
2800 | break; | 2864 | break; |
2801 | case '!': /* bg pid */ | 2865 | case '!': /* bg pid */ |
2802 | val = G.last_bg_pid ? utoa(G.last_bg_pid) : (char*)""; | 2866 | val = G.last_bg_pid ? utoa(G.last_bg_pid) : ""; |
2803 | break; | 2867 | break; |
2804 | case '?': /* exitcode */ | 2868 | case '?': /* exitcode */ |
2805 | val = utoa(G.last_exitcode); | 2869 | val = utoa(G.last_exitcode); |
@@ -2843,13 +2907,47 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char | |||
2843 | // exp_op, to_be_freed, exp_word, loc); | 2907 | // exp_op, to_be_freed, exp_word, loc); |
2844 | free(exp_exp_word); | 2908 | free(exp_exp_word); |
2845 | if (loc) { /* match was found */ | 2909 | if (loc) { /* match was found */ |
2846 | if (scan_flags & SCAN_MATCH_LEFT_HALF) /* # or ## */ | 2910 | if (scan_flags & SCAN_MATCH_LEFT_HALF) /* #[#] */ |
2847 | val = loc; | 2911 | val = loc; |
2848 | else /* % or %% */ | 2912 | else /* %[%] */ |
2849 | *loc = '\0'; | 2913 | *loc = '\0'; |
2850 | } | 2914 | } |
2851 | } | 2915 | } |
2852 | } else if (exp_op == ':') { | 2916 | } |
2917 | #if ENABLE_HUSH_BASH_COMPAT | ||
2918 | else if (exp_op == '/' || exp_op == '\\') { | ||
2919 | /* Empty variable always gives nothing: */ | ||
2920 | // "v=''; echo ${v/*/w}" prints "" | ||
2921 | if (val && val[0]) { | ||
2922 | /* It's ${var/[/]pattern[/repl]} thing */ | ||
2923 | char *pattern, *repl, *t; | ||
2924 | pattern = expand_pseudo_dquoted(exp_word); | ||
2925 | if (!pattern) | ||
2926 | pattern = xstrdup(exp_word); | ||
2927 | debug_printf_varexp("pattern:'%s'->'%s'\n", exp_word, pattern); | ||
2928 | *p++ = SPECIAL_VAR_SYMBOL; | ||
2929 | exp_word = p; | ||
2930 | p = strchr(p, SPECIAL_VAR_SYMBOL); | ||
2931 | *p = '\0'; | ||
2932 | repl = expand_pseudo_dquoted(exp_word); | ||
2933 | debug_printf_varexp("repl:'%s'->'%s'\n", exp_word, repl); | ||
2934 | /* HACK ALERT. We depend here on the fact that | ||
2935 | * G.global_argv and results of utoa and get_local_var_value | ||
2936 | * are actually in writable memory: | ||
2937 | * replace_pattern momentarily stores NULs there. */ | ||
2938 | t = (char*)val; | ||
2939 | to_be_freed = replace_pattern(t, | ||
2940 | pattern, | ||
2941 | (repl ? repl : exp_word), | ||
2942 | exp_op); | ||
2943 | if (to_be_freed) /* at least one replace happened */ | ||
2944 | val = to_be_freed; | ||
2945 | free(pattern); | ||
2946 | free(repl); | ||
2947 | } | ||
2948 | } | ||
2949 | #endif | ||
2950 | else if (exp_op == ':') { | ||
2853 | #if ENABLE_HUSH_BASH_COMPAT && ENABLE_SH_MATH_SUPPORT | 2951 | #if ENABLE_HUSH_BASH_COMPAT && ENABLE_SH_MATH_SUPPORT |
2854 | /* It's ${var:N[:M]} bashism. | 2952 | /* It's ${var:N[:M]} bashism. |
2855 | * Note that in encoded form it has TWO parts: | 2953 | * Note that in encoded form it has TWO parts: |
@@ -3084,6 +3182,16 @@ static char *expand_string_to_string(const char *str) | |||
3084 | { | 3182 | { |
3085 | char *argv[2], **list; | 3183 | char *argv[2], **list; |
3086 | 3184 | ||
3185 | /* This is generally an optimization, but it also | ||
3186 | * handles "", which otherwise trips over !list[0] check below. | ||
3187 | * (is this ever happens that we actually get str="" here?) | ||
3188 | */ | ||
3189 | if (!strchr(str, SPECIAL_VAR_SYMBOL) && !strchr(str, '\\')) { | ||
3190 | //TODO: Can use on strings with \ too, just unbackslash() them? | ||
3191 | debug_printf_expand("string_to_string(fast)='%s'\n", str); | ||
3192 | return xstrdup(str); | ||
3193 | } | ||
3194 | |||
3087 | argv[0] = (char*)str; | 3195 | argv[0] = (char*)str; |
3088 | argv[1] = NULL; | 3196 | argv[1] = NULL; |
3089 | list = expand_variables(argv, EXPVAR_FLAG_ESCAPE_VARS | EXPVAR_FLAG_SINGLEWORD); | 3197 | list = expand_variables(argv, EXPVAR_FLAG_ESCAPE_VARS | EXPVAR_FLAG_SINGLEWORD); |
@@ -3271,7 +3379,7 @@ static void re_execute_shell(char ***to_free, const char *s, | |||
3271 | *pp++ = (char *) G.argv0_for_re_execing; | 3379 | *pp++ = (char *) G.argv0_for_re_execing; |
3272 | *pp++ = param_buf; | 3380 | *pp++ = param_buf; |
3273 | for (cur = G.top_var; cur; cur = cur->next) { | 3381 | for (cur = G.top_var; cur; cur = cur->next) { |
3274 | if (cur->varstr == hush_version_str) | 3382 | if (strcmp(cur->varstr, hush_version_str) == 0) |
3275 | continue; | 3383 | continue; |
3276 | if (cur->flg_read_only) { | 3384 | if (cur->flg_read_only) { |
3277 | *pp++ = (char *) "-R"; | 3385 | *pp++ = (char *) "-R"; |
@@ -6170,8 +6278,8 @@ static void add_till_backquote(o_string *dest, struct in_str *input) | |||
6170 | * | 6278 | * |
6171 | * Also adapted to eat ${var%...} and $((...)) constructs, since ... part | 6279 | * Also adapted to eat ${var%...} and $((...)) constructs, since ... part |
6172 | * can contain arbitrary constructs, just like $(cmd). | 6280 | * can contain arbitrary constructs, just like $(cmd). |
6173 | * In bash compat mode, it needs to also be able to stop on '}' or ':' | 6281 | * In bash compat mode, it needs to also be able to stop on ':' or '/' |
6174 | * for ${var:N[:M]} parsing. | 6282 | * for ${var:N[:M]} and ${var/P[/R]} parsing. |
6175 | */ | 6283 | */ |
6176 | #define DOUBLE_CLOSE_CHAR_FLAG 0x80 | 6284 | #define DOUBLE_CLOSE_CHAR_FLAG 0x80 |
6177 | static int add_till_closing_bracket(o_string *dest, struct in_str *input, unsigned end_ch) | 6285 | static int add_till_closing_bracket(o_string *dest, struct in_str *input, unsigned end_ch) |
@@ -6323,19 +6431,30 @@ static int parse_dollar(o_string *as_string, | |||
6323 | /* handle parameter expansions | 6431 | /* handle parameter expansions |
6324 | * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_02 | 6432 | * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_02 |
6325 | */ | 6433 | */ |
6326 | if (!strchr("%#:-=+?", ch)) /* ${var<bad_char>... */ | 6434 | if (!strchr(VAR_SUBST_OPS, ch)) /* ${var<bad_char>... */ |
6327 | goto bad_dollar_syntax; | 6435 | goto bad_dollar_syntax; |
6328 | o_addchr(dest, ch); | ||
6329 | 6436 | ||
6330 | /* Eat everything until closing '}' (or ':') */ | 6437 | /* Eat everything until closing '}' (or ':') */ |
6331 | end_ch = '}'; | 6438 | end_ch = '}'; |
6332 | if (ENABLE_HUSH_BASH_COMPAT | 6439 | if (ENABLE_HUSH_BASH_COMPAT |
6333 | && ch == ':' | 6440 | && ch == ':' |
6334 | && !strchr("%#:-=+?"+3, i_peek(input)) | 6441 | && !strchr(MINUS_PLUS_EQUAL_QUESTION, i_peek(input)) |
6335 | ) { | 6442 | ) { |
6336 | /* It's ${var:N[:M]} thing */ | 6443 | /* It's ${var:N[:M]} thing */ |
6337 | end_ch = '}' * 0x100 + ':'; | 6444 | end_ch = '}' * 0x100 + ':'; |
6338 | } | 6445 | } |
6446 | if (ENABLE_HUSH_BASH_COMPAT | ||
6447 | && ch == '/' | ||
6448 | ) { | ||
6449 | /* It's ${var/[/]pattern[/repl]} thing */ | ||
6450 | if (i_peek(input) == '/') { /* ${var//pattern[/repl]}? */ | ||
6451 | i_getch(input); | ||
6452 | nommu_addchr(as_string, '/'); | ||
6453 | ch = '\\'; | ||
6454 | } | ||
6455 | end_ch = '}' * 0x100 + '/'; | ||
6456 | } | ||
6457 | o_addchr(dest, ch); | ||
6339 | again: | 6458 | again: |
6340 | if (!BB_MMU) | 6459 | if (!BB_MMU) |
6341 | pos = dest->length; | 6460 | pos = dest->length; |
@@ -6352,14 +6471,18 @@ static int parse_dollar(o_string *as_string, | |||
6352 | if (ENABLE_HUSH_BASH_COMPAT && (end_ch & 0xff00)) { | 6471 | if (ENABLE_HUSH_BASH_COMPAT && (end_ch & 0xff00)) { |
6353 | /* close the first block: */ | 6472 | /* close the first block: */ |
6354 | o_addchr(dest, SPECIAL_VAR_SYMBOL); | 6473 | o_addchr(dest, SPECIAL_VAR_SYMBOL); |
6355 | /* while parsing N from ${var:N[:M]}... */ | 6474 | /* while parsing N from ${var:N[:M]} |
6475 | * or pattern from ${var/[/]pattern[/repl]} */ | ||
6356 | if ((end_ch & 0xff) == last_ch) { | 6476 | if ((end_ch & 0xff) == last_ch) { |
6357 | /* ...got ':' - parse the rest */ | 6477 | /* got ':' or '/'- parse the rest */ |
6358 | end_ch = '}'; | 6478 | end_ch = '}'; |
6359 | goto again; | 6479 | goto again; |
6360 | } | 6480 | } |
6361 | /* ...got '}', not ':' - it's ${var:N}! emulate :999999999 */ | 6481 | /* got '}' */ |
6362 | o_addstr(dest, "999999999"); | 6482 | if (end_ch == '}' * 0x100 + ':') { |
6483 | /* it's ${var:N} - emulate :999999999 */ | ||
6484 | o_addstr(dest, "999999999"); | ||
6485 | } /* else: it's ${var/[/]pattern} */ | ||
6363 | } | 6486 | } |
6364 | break; | 6487 | break; |
6365 | } | 6488 | } |
@@ -7186,13 +7309,6 @@ static int set_mode(const char cstate, const char mode) | |||
7186 | int hush_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | 7309 | int hush_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
7187 | int hush_main(int argc, char **argv) | 7310 | int hush_main(int argc, char **argv) |
7188 | { | 7311 | { |
7189 | static const struct variable const_shell_ver = { | ||
7190 | .next = NULL, | ||
7191 | .varstr = (char*)hush_version_str, | ||
7192 | .max_len = 1, /* 0 can provoke free(name) */ | ||
7193 | .flg_export = 1, | ||
7194 | .flg_read_only = 1, | ||
7195 | }; | ||
7196 | int opt; | 7312 | int opt; |
7197 | unsigned builtin_argc; | 7313 | unsigned builtin_argc; |
7198 | char **e; | 7314 | char **e; |
@@ -7205,10 +7321,18 @@ int hush_main(int argc, char **argv) | |||
7205 | G.argv0_for_re_execing = argv[0]; | 7321 | G.argv0_for_re_execing = argv[0]; |
7206 | #endif | 7322 | #endif |
7207 | /* Deal with HUSH_VERSION */ | 7323 | /* Deal with HUSH_VERSION */ |
7208 | G.shell_ver = const_shell_ver; /* copying struct here */ | 7324 | G.shell_ver.flg_export = 1; |
7325 | G.shell_ver.flg_read_only = 1; | ||
7326 | /* Code which handles ${var/P/R} needs writable values for all variables, | ||
7327 | * therefore we xstrdup: */ | ||
7328 | G.shell_ver.varstr = xstrdup(hush_version_str), | ||
7209 | G.top_var = &G.shell_ver; | 7329 | G.top_var = &G.shell_ver; |
7210 | debug_printf_env("unsetenv '%s'\n", "HUSH_VERSION"); | 7330 | debug_printf_env("unsetenv '%s'\n", "HUSH_VERSION"); |
7211 | unsetenv("HUSH_VERSION"); /* in case it exists in initial env */ | 7331 | unsetenv("HUSH_VERSION"); /* in case it exists in initial env */ |
7332 | /* reinstate HUSH_VERSION in environment */ | ||
7333 | debug_printf_env("putenv '%s'\n", G.shell_ver.varstr); | ||
7334 | putenv(G.shell_ver.varstr); | ||
7335 | |||
7212 | /* Initialize our shell local variables with the values | 7336 | /* Initialize our shell local variables with the values |
7213 | * currently living in the environment */ | 7337 | * currently living in the environment */ |
7214 | cur_var = G.top_var; | 7338 | cur_var = G.top_var; |
@@ -7224,9 +7348,6 @@ int hush_main(int argc, char **argv) | |||
7224 | } | 7348 | } |
7225 | e++; | 7349 | e++; |
7226 | } | 7350 | } |
7227 | /* reinstate HUSH_VERSION */ | ||
7228 | debug_printf_env("putenv '%s'\n", hush_version_str); | ||
7229 | putenv((char *)hush_version_str); | ||
7230 | 7351 | ||
7231 | /* Export PWD */ | 7352 | /* Export PWD */ |
7232 | set_pwd_var(/*exp:*/ 1); | 7353 | set_pwd_var(/*exp:*/ 1); |