summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDenys Vlasenko <dvlasenk@redhat.com>2010-09-05 14:45:38 +0200
committerDenys Vlasenko <dvlasenk@redhat.com>2010-09-05 14:47:58 +0200
commit36f774a0cd2bf8dd72b192aab93831c5ac0c58f0 (patch)
treeafa0b3d247e4dd163b89b7b61f4e3e0f3143b102
parent701e127f7d892909a58c6f3333e23588ccef9e22 (diff)
downloadbusybox-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>
-rwxr-xr-xshell/ash_test/ash-vars/var_bash3.tests7
-rw-r--r--shell/hush.c179
-rw-r--r--shell/hush_test/hush-vars/var_bash1.right14
-rwxr-xr-xshell/hush_test/hush-vars/var_bash1.tests18
-rw-r--r--shell/hush_test/hush-vars/var_bash2.right10
-rwxr-xr-xshell/hush_test/hush-vars/var_bash2.tests24
-rw-r--r--shell/hush_test/hush-vars/var_bash3.right20
-rwxr-xr-xshell/hush_test/hush-vars/var_bash3.tests41
-rw-r--r--shell/hush_test/hush-vars/var_bash4.right23
-rwxr-xr-xshell/hush_test/hush-vars/var_bash4.tests47
-rw-r--r--shell/hush_test/hush-vars/var_bash5.right4
-rwxr-xr-xshell/hush_test/hush-vars/var_bash5.tests11
12 files changed, 362 insertions, 36 deletions
diff --git a/shell/ash_test/ash-vars/var_bash3.tests b/shell/ash_test/ash-vars/var_bash3.tests
index eca3318e2..146dbb6a5 100755
--- a/shell/ash_test/ash-vars/var_bash3.tests
+++ b/shell/ash_test/ash-vars/var_bash3.tests
@@ -3,13 +3,6 @@ r=${a//b/\041#}
3echo 1 $r 3echo 1 $r
4echo 2 ${a//b/\041#} 4echo 2 ${a//b/\041#}
5echo 3 "${a//b/\041#}" 5echo 3 "${a//b/\041#}"
6# --- var_bash3.xx
7# +++ var_bash3.right
8# -1 a\041#c
9# +1 a041#c
10# 2 a041#c
11# -3 a041#c
12# +3 a\041#c
13 6
14a='abc' 7a='abc'
15r=${a//b/\\041#} 8r=${a//b/\\041#}
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 */
2615static 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}
2634static 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
6177static int add_till_closing_bracket(o_string *dest, struct in_str *input, unsigned end_ch) 6285static 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)
7186int hush_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 7309int hush_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
7187int hush_main(int argc, char **argv) 7310int 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);
diff --git a/shell/hush_test/hush-vars/var_bash1.right b/shell/hush_test/hush-vars/var_bash1.right
new file mode 100644
index 000000000..c0a07699b
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_bash1.right
@@ -0,0 +1,14 @@
1
2
3f
4bcdef
5abcdef
6abcdef
7bcde
8abcd
9abcd
10abcdef
11bcdef
12abcdef
13abcdef
14abcdef
diff --git a/shell/hush_test/hush-vars/var_bash1.tests b/shell/hush_test/hush-vars/var_bash1.tests
new file mode 100755
index 000000000..24d3c9a00
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_bash1.tests
@@ -0,0 +1,18 @@
1var=abcdef
2
3echo ${var:7}
4echo ${var:6}
5echo ${var:5}
6echo ${var:1}
7echo ${var:0}
8echo ${var:-1}
9
10echo ${var:1:4}
11echo ${var:0:4}
12echo ${var::4}
13echo ${var:-1:4}
14
15echo ${var:1:7}
16echo ${var:0:7}
17echo ${var::7}
18echo ${var:-1:7}
diff --git a/shell/hush_test/hush-vars/var_bash2.right b/shell/hush_test/hush-vars/var_bash2.right
new file mode 100644
index 000000000..acba5c6fb
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_bash2.right
@@ -0,0 +1,10 @@
1abc123xcba123
2abx123dcba123
3abx123dxba123
4abcx23dcba123
5abcxxxdcbaxxx
6abx
7xba123
8abx23
9abc23dcba123
10abcdcba
diff --git a/shell/hush_test/hush-vars/var_bash2.tests b/shell/hush_test/hush-vars/var_bash2.tests
new file mode 100755
index 000000000..29c526cef
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_bash2.tests
@@ -0,0 +1,24 @@
1var=abc123dcba123
2
3echo ${var/d/x}
4echo ${var/c/x}
5echo ${var//c/x}
6echo ${var/[123]/x}
7echo ${var//[123]/x}
8echo ${var/c*/x}
9echo ${var/*c/x}
10
11# must match longest match: result is "abx23"
12echo ${var/c*1/x}
13
14# empty replacement - 2nd slash can be omitted
15echo ${var/[123]}
16echo ${var//[123]}
17
18### ash doesn't support
19### # match only at the beginning:
20### echo ${var/#a/x}
21### echo ${var/#b/x} # should not match
22### echo ${var//#b/x} # should not match
23### # match only at the end:
24### echo ${var/%3/x}
diff --git a/shell/hush_test/hush-vars/var_bash3.right b/shell/hush_test/hush-vars/var_bash3.right
new file mode 100644
index 000000000..a97c850ea
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_bash3.right
@@ -0,0 +1,20 @@
11 a041#c
22 a041#c
33 a\041#c
44 a\041#c
55 a\041#c
66 a\041#c
77 a\041#c
88 a\041#c
99 a\041#c
1010 a\c
1111 a\c
1212 a\c
1313 a\\c
1414 a\\c
1515 a\\c
1616 a\tc
1717 a\tc
1818 a\tc
1919 atc
2020 a\tc
diff --git a/shell/hush_test/hush-vars/var_bash3.tests b/shell/hush_test/hush-vars/var_bash3.tests
new file mode 100755
index 000000000..146dbb6a5
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_bash3.tests
@@ -0,0 +1,41 @@
1a='abc'
2r=${a//b/\041#}
3echo 1 $r
4echo 2 ${a//b/\041#}
5echo 3 "${a//b/\041#}"
6
7a='abc'
8r=${a//b/\\041#}
9echo 4 $r
10echo 5 ${a//b/\\041#}
11echo 6 "${a//b/\\041#}"
12
13a='abc'
14b='\041#'
15r=${a//b/$b}
16echo 7 $r
17echo 8 ${a//b/$b}
18echo 9 "${a//b/$b}"
19
20a='abc'
21b='\'
22r="${a//b/$b}"
23echo 10 $r
24echo 11 ${a//b/$b}
25echo 12 "${a//b/$b}"
26
27a='abc'
28b='\\'
29r="${a//b/$b}"
30echo 13 $r
31echo 14 ${a//b/$b}
32echo 15 "${a//b/$b}"
33
34a='abc'
35b='\t'
36r="${a//b/$b}"
37echo 16 $r
38echo 17 ${a//b/$b}
39echo 18 "${a//b/$b}"
40echo 19 ${a//b/\t}
41echo 20 "${a//b/\t}"
diff --git a/shell/hush_test/hush-vars/var_bash4.right b/shell/hush_test/hush-vars/var_bash4.right
new file mode 100644
index 000000000..600e8532f
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_bash4.right
@@ -0,0 +1,23 @@
1Source: a*b\*c
2Replace str: _\\_\z_
3Pattern: single backslash and star: "replace literal star"
4In assignment: a_\_z_b\*c
5Unquoted: a_\_z_b\*c
6Quoted: a_\_\z_b\*c
7Pattern: double backslash and star: "replace backslash and everything after it"
8In assignment: a*b_\_z_
9Unquoted: a*b_\_z_
10Quoted: a*b_\_\z_
11
12Source: a\bc
13Replace str: _\\_\z_
14Pattern: single backslash and b: "replace b"
15In assignment: a\_\_z_c
16Unquoted: a\_\_z_c
17Quoted: a\_\_\z_c
18Pattern: double backslash and b: "replace backslash and b"
19In assignment: a_\_z_c
20Unquoted: a_\_z_c
21Quoted: a_\_\z_c
22
23Done: 0
diff --git a/shell/hush_test/hush-vars/var_bash4.tests b/shell/hush_test/hush-vars/var_bash4.tests
new file mode 100755
index 000000000..d5470614b
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_bash4.tests
@@ -0,0 +1,47 @@
1# This testcase demonstrates that backslashes are treated differently
2# in 1st and 2nd parts of ${var/search/repl}:
3# if quoted ("${var/search/repl}"), and repl contains \a (a non-special char),
4# the backslash in repl stays; if unquoted, backslash is removed.
5# But search part does not act like that: \a is always converted to just a,
6# even in quotes.
7#
8# bash4 (and probably bash3 too): "Quoted:" results are different from
9# unquoted and assignment expansions - they have a backslash before z.
10
11v='a*b\*c'
12echo 'Source: ' "$v"
13echo 'Replace str: ' '_\\_\z_'
14
15echo 'Pattern: ' 'single backslash and star: "replace literal star"'
16r=${v/\*/_\\_\z_}
17echo 'In assignment:' "$r"
18echo 'Unquoted: ' ${v/\*/_\\_\z_}
19echo 'Quoted: ' "${v/\*/_\\_\z_}"
20
21echo 'Pattern: ' 'double backslash and star: "replace backslash and everything after it"'
22r=${v/\\*/_\\_\z_}
23echo 'In assignment:' "$r"
24echo 'Unquoted: ' ${v/\\*/_\\_\z_}
25echo 'Quoted: ' "${v/\\*/_\\_\z_}"
26
27echo
28
29v='a\bc'
30echo 'Source: ' "$v"
31echo 'Replace str: ' '_\\_\z_'
32
33echo 'Pattern: ' 'single backslash and b: "replace b"'
34r=${v/\b/_\\_\z_}
35echo 'In assignment:' "$r"
36echo 'Unquoted: ' ${v/\b/_\\_\z_}
37echo 'Quoted: ' "${v/\b/_\\_\z_}"
38
39echo 'Pattern: ' 'double backslash and b: "replace backslash and b"'
40r=${v/\\b/_\\_\z_}
41echo 'In assignment:' "$r"
42echo 'Unquoted: ' ${v/\\b/_\\_\z_}
43echo 'Quoted: ' "${v/\\b/_\\_\z_}"
44
45echo
46
47echo Done: $?
diff --git a/shell/hush_test/hush-vars/var_bash5.right b/shell/hush_test/hush-vars/var_bash5.right
new file mode 100644
index 000000000..278ed3228
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_bash5.right
@@ -0,0 +1,4 @@
1a/
2a/d
3a/e/f
4Done: 0
diff --git a/shell/hush_test/hush-vars/var_bash5.tests b/shell/hush_test/hush-vars/var_bash5.tests
new file mode 100755
index 000000000..7f482a554
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_bash5.tests
@@ -0,0 +1,11 @@
1# This testcase checks whether slashes in ${v/a/b} are parsed before
2# or after expansions
3
4v='a/b/c'
5s='b/c'
6r='e/f'
7echo "${v/$s}"
8echo "${v/$s/d}"
9echo "${v/$s/$r}"
10
11echo Done: $?