From 40477e2fdb3d32f4d368ee4f7c72ded4a2398082 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 20 May 2010 02:09:45 +0200 Subject: shell: make it possible to alias one of shells to "bash" function old new delta packed_usage 27047 27054 +7 applet_names 2227 2232 +5 applet_main 1304 1308 +4 applet_nameofs 652 654 +2 applet_install_loc 163 164 +1 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 5/0 up/down: 19/0) Total: 19 bytes Signed-off-by: Denys Vlasenko --- shell/Config.in | 43 +++++++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 10 deletions(-) (limited to 'shell') diff --git a/shell/Config.in b/shell/Config.in index cf599dff4..286a3415e 100644 --- a/shell/Config.in +++ b/shell/Config.in @@ -6,11 +6,11 @@ menu "Shells" choice - prompt "Choose your default shell" + prompt "Choose which shell is aliased to 'sh' name" default FEATURE_SH_IS_NONE help - Choose a shell. The ash shell is the most bash compatible - and full featured one. + Choose which shell you want to be executed by 'sh' alias. + The ash shell is the most bash compatible and full featured one. config FEATURE_SH_IS_ASH select ASH @@ -21,15 +21,38 @@ config FEATURE_SH_IS_HUSH select HUSH bool "hush" -####config FEATURE_SH_IS_LASH -#### select LASH -#### bool "lash" +config FEATURE_SH_IS_NONE + bool "none" -####config FEATURE_SH_IS_MSH -#### select MSH -#### bool "msh" +endchoice -config FEATURE_SH_IS_NONE +choice + prompt "Choose which shell is aliased to 'bash' name" + default FEATURE_BASH_IS_NONE + help + Choose which shell you want to be executed by 'bash' alias. + The ash shell is the most bash compatible and full featured one. + + Note that selecting this option does not switch on any bash + compatibility code. It merely makes it possible to install + /bin/bash (sym)link and run scripts which start with + #!/bin/bash line. + + Many systems use it in scripts which use bash-specific features, + even simple ones like $RANDOM. Without this option, busybox + can't be used for running them because it won't recongnize + "bash" as a supported applet name. + +config FEATURE_BASH_IS_ASH + select ASH + bool "ash" + depends on !NOMMU + +config FEATURE_BASH_IS_HUSH + select HUSH + bool "hush" + +config FEATURE_BASH_IS_NONE bool "none" endchoice -- cgit v1.2.3-55-g6feb From b131ccec9c917efd735a353cb0f2cb14862192f1 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 20 May 2010 03:39:43 +0200 Subject: hush: support "cd -- DIR" and such function old new delta skip_dash_dash - 33 +33 builtin_exit 43 48 +5 builtin_umask 121 125 +4 builtin_shift 115 119 +4 builtin_cd 71 75 +4 builtin_wait 271 274 +3 builtin_source 171 174 +3 builtin_exec 57 60 +3 builtin_eval 46 45 -1 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 7/1 up/down: 59/-1) Total: 58 bytes Signed-off-by: Denys Vlasenko --- shell/hush.c | 46 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 14 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 8baccf246..fcfbd06f3 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -7258,11 +7258,20 @@ static int FAST_FUNC builtin_printf(char **argv) } #endif +static char **skip_dash_dash(char **argv) +{ + argv++; + if (argv[0] && argv[0][0] == '-' && argv[0][1] == '-' && argv[0][2] == '\0') + argv++; + return argv; +} + static int FAST_FUNC builtin_eval(char **argv) { int rcode = EXIT_SUCCESS; - if (*++argv) { + argv = skip_dash_dash(argv); + if (*argv) { char *str = expand_strvec_to_string(argv); /* bash: * eval "echo Hi; done" ("done" is syntax error): @@ -7277,7 +7286,10 @@ static int FAST_FUNC builtin_eval(char **argv) static int FAST_FUNC builtin_cd(char **argv) { - const char *newdir = argv[1]; + const char *newdir; + + argv = skip_dash_dash(argv); + newdir = argv[0]; if (newdir == NULL) { /* bash does nothing (exitcode 0) if HOME is ""; if it's unset, * bash says "bash: cd: HOME not set" and does nothing @@ -7301,7 +7313,8 @@ static int FAST_FUNC builtin_cd(char **argv) static int FAST_FUNC builtin_exec(char **argv) { - if (*++argv == NULL) + argv = skip_dash_dash(argv); + if (argv[0] == NULL) return EXIT_SUCCESS; /* bash does this */ /* Careful: we can end up here after [v]fork. Do not restore @@ -7334,12 +7347,13 @@ static int FAST_FUNC builtin_exit(char **argv) */ /* note: EXIT trap is run by hush_exit */ - if (*++argv == NULL) + argv = skip_dash_dash(argv); + if (argv[0] == NULL) hush_exit(G.last_exitcode); /* mimic bash: exit 123abc == exit 255 + error msg */ xfunc_error_retval = 255; /* bash: exit -2 == exit 254, no error msg */ - hush_exit(xatoi(*argv) & 0xff); + hush_exit(xatoi(argv[0]) & 0xff); } static void print_escaped(const char *s) @@ -7668,7 +7682,7 @@ static int FAST_FUNC builtin_help(char **argv UNUSED_PARAM) "------------------\n"); for (x = bltins1; x != &bltins1[ARRAY_SIZE(bltins1)]; x++) { if (x->b_descr) - printf("%s\t%s\n", x->b_cmd, x->b_descr); + printf("%-10s%s\n", x->b_cmd, x->b_descr); } bb_putchar('\n'); return EXIT_SUCCESS; @@ -7851,8 +7865,9 @@ static int FAST_FUNC builtin_set(char **argv) static int FAST_FUNC builtin_shift(char **argv) { int n = 1; - if (argv[1]) { - n = atoi(argv[1]); + argv = skip_dash_dash(argv); + if (argv[0]) { + n = atoi(argv[0]); } if (n >= 0 && n < G.global_argc) { if (G.global_args_malloced) { @@ -7877,12 +7892,13 @@ static int FAST_FUNC builtin_source(char **argv) smallint sv_flg; #endif - arg_path = NULL; - filename = *++argv; + argv = skip_dash_dash(argv); + filename = argv[0]; if (!filename) { /* bash says: "bash: .: filename argument required" */ return 2; /* bash compat */ } + arg_path = NULL; if (!strchr(filename, '/')) { arg_path = find_in_path(filename); if (arg_path) @@ -7920,11 +7936,12 @@ static int FAST_FUNC builtin_umask(char **argv) mode_t mask; mask = umask(0); - if (argv[1]) { + argv = skip_dash_dash(argv); + if (argv[0]) { mode_t old_mask = mask; mask ^= 0777; - rc = bb_parse_mode(argv[1], &mask); + rc = bb_parse_mode(argv[0], &mask); mask ^= 0777; if (rc == 0) { mask = old_mask; @@ -7932,7 +7949,7 @@ static int FAST_FUNC builtin_umask(char **argv) * bash: umask: 'q': invalid symbolic mode operator * bash: umask: 999: octal number out of range */ - bb_error_msg("%s: '%s' invalid mode", argv[0], argv[1]); + bb_error_msg("umask: '%s' invalid mode", argv[0]); } } else { rc = 1; @@ -7988,7 +8005,8 @@ static int FAST_FUNC builtin_wait(char **argv) int ret = EXIT_SUCCESS; int status, sig; - if (*++argv == NULL) { + argv = skip_dash_dash(argv); + if (argv[0] == NULL) { /* Don't care about wait results */ /* Note 1: must wait until there are no more children */ /* Note 2: must be interruptible */ -- cgit v1.2.3-55-g6feb From 44c86ce5d7ee7c641a2c8c392f059e19050abd11 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 20 May 2010 04:22:55 +0200 Subject: hush: code shrink text data bss dec hex filename 843121 453 6828 850402 cf9e2 busybox_old 843108 453 6828 850389 cf9d5 busybox_unstripped Signed-off-by: Denys Vlasenko --- shell/hush.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index fcfbd06f3..df81eaae7 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -7949,7 +7949,7 @@ static int FAST_FUNC builtin_umask(char **argv) * bash: umask: 'q': invalid symbolic mode operator * bash: umask: 999: octal number out of range */ - bb_error_msg("umask: '%s' invalid mode", argv[0]); + bb_error_msg("%s: invalid mode '%s'", "umask", argv[0]); } } else { rc = 1; -- cgit v1.2.3-55-g6feb From a0ec4f500c7b8b2ac1c7e34c9a2ee7504c7f8914 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 20 May 2010 12:50:42 +0200 Subject: ash: eliminate 16 bytes in bss text data bss dec hexfilename 841423 441 7572 849436 cf61cbusybox_old 841430 441 7556 849427 cf613busybox_unstripped Signed-off-by: Denys Vlasenko --- shell/ash.c | 48 ++++++++++++++++++++++-------------------------- 1 file changed, 22 insertions(+), 26 deletions(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 83886c610..7efd477d1 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -7547,8 +7547,10 @@ changepath(const char *new) if (*old != *new) { firstchange = idx; if ((*old == '\0' && *new == ':') - || (*old == ':' && *new == '\0')) + || (*old == ':' && *new == '\0') + ) { firstchange++; + } old = new; /* ignore subsequent differences */ } if (*new == '\0') @@ -7557,7 +7559,8 @@ changepath(const char *new) idx_bltin = idx; if (*new == ':') idx++; - new++, old++; + new++; + old++; } if (builtinloc < 0 && idx_bltin >= 0) builtinloc = idx_bltin; /* zap builtins */ @@ -7633,23 +7636,6 @@ static const char *const tokname_array[] = { "\1}", }; -static const char * -tokname(int tok) -{ - static char buf[16]; - -//try this: -//if (tok < TSEMI) return tokname_array[tok] + 1; -//sprintf(buf, "\"%s\"", tokname_array[tok] + 1); -//return buf; - - if (tok >= TSEMI) - buf[0] = '"'; - sprintf(buf + (tok >= TSEMI), "%s%c", - tokname_array[tok] + 1, (tok >= TSEMI ? '"' : 0)); - return buf; -} - /* Wrapper around strcmp for qsort/bsearch/... */ static int pstrcmp(const void *a, const void *b) @@ -10280,7 +10266,16 @@ static struct nodelist *backquotelist; static union node *redirnode; static struct heredoc *heredoc; -/* +static const char * +tokname(char *buf, int tok) +{ + if (tok < TSEMI) + return tokname_array[tok] + 1; + sprintf(buf, "\"%s\"", tokname_array[tok] + 1); + return buf; +} + +/* raise_error_unexpected_syntax: * Called when an unexpected token is read during the parse. The argument * is the token that is expected, or -1 if more than one type of token can * occur at this point. @@ -10290,11 +10285,12 @@ static void raise_error_unexpected_syntax(int token) { char msg[64]; + char buf[16]; int l; - l = sprintf(msg, "unexpected %s", tokname(lasttoken)); + l = sprintf(msg, "unexpected %s", tokname(buf, lasttoken)); if (token >= 0) - sprintf(msg + l, " (expecting %s)", tokname(token)); + sprintf(msg + l, " (expecting %s)", tokname(buf, token)); raise_error_syntax(msg); /* NOTREACHED */ } @@ -10682,7 +10678,7 @@ parse_command(void) n1->nbinary.ch1 = list(0); got = readtoken(); if (got != TDO) { - TRACE(("expecting DO got %s %s\n", tokname(got), + TRACE(("expecting DO got '%s' %s\n", tokname_array[got] + 1, got == TWORD ? wordtext : "")); raise_error_unexpected_syntax(TDO); } @@ -11766,7 +11762,7 @@ readtoken(void) pp = findkwd(wordtext); if (pp) { lasttoken = t = pp - tokname_array; - TRACE(("keyword %s recognized\n", tokname(t))); + TRACE(("keyword '%s' recognized\n", tokname_array[t] + 1)); goto out; } } @@ -11787,9 +11783,9 @@ readtoken(void) checkkwd = 0; #if DEBUG if (!alreadyseen) - TRACE(("token %s %s\n", tokname(t), t == TWORD ? wordtext : "")); + TRACE(("token '%s' %s\n", tokname_array[t] + 1, t == TWORD ? wordtext : "")); else - TRACE(("reread token %s %s\n", tokname(t), t == TWORD ? wordtext : "")); + TRACE(("reread token '%s' %s\n", tokname_array[t] + 1, t == TWORD ? wordtext : "")); #endif return t; } -- cgit v1.2.3-55-g6feb From 131ed3bcc9c9eabcb4bd6a063c24c6f9922f1491 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 20 May 2010 12:56:14 +0200 Subject: update shell/README Signed-off-by: Denys Vlasenko --- shell/README | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'shell') diff --git a/shell/README b/shell/README index 550c712d3..6a9f5b6ae 100644 --- a/shell/README +++ b/shell/README @@ -34,7 +34,7 @@ Shell Command Language It says that shell must implement special built-ins. Special built-ins differ from regular ones by the fact that variable assignments -done on special builtin is *PRESERVED*. That is, +done on special builtin are *PRESERVED*. That is, VAR=VAL special_builtin; echo $VAR @@ -43,7 +43,7 @@ should print VAL. (Another distinction is that an error in special built-in should abort the shell, but this is not such a critical difference, and moreover, at least bash's "set" does not follow this rule, -which is even codified in autoconf now...). +which is even codified in autoconf configure logic now...) List of special builtins: @@ -73,7 +73,7 @@ unset [-fv] name... In practice, no one uses this obscure feature - none of these builtins gives any special reasons to play such dirty tricks. -However. This section says that *function invocation* should act +However. This section also says that *function invocation* should act similar to special built-in. That is, variable assignments done on function invocation should be preserved after function invocation. -- cgit v1.2.3-55-g6feb From cddbb610cb0ea8d74668653aeaded710d2d13768 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 20 May 2010 14:27:09 +0200 Subject: hush: fix var=`exit 2` not setting $? to 2 Signed-off-by: Denys Vlasenko --- shell/hush.c | 15 ++++++++++----- shell/hush_test/hush-psubst/falsetick.right | 27 +++++++++++++++++++++++++++ shell/hush_test/hush-psubst/falsetick.tests | 22 ++++++++++++++++++++++ 3 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 shell/hush_test/hush-psubst/falsetick.right create mode 100755 shell/hush_test/hush-psubst/falsetick.tests (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index df81eaae7..8da9439c1 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -2523,7 +2523,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char * and $IFS-splitted */ debug_printf_subst("SUBST '%s' first_ch %x\n", arg, first_ch); G.last_exitcode = process_command_subs(&subst_result, arg); - debug_printf_subst("SUBST RES '%s'\n", subst_result.data); + debug_printf_subst("SUBST RES:%d '%s'\n", G.last_exitcode, subst_result.data); val = subst_result.data; goto store_val; #endif @@ -4117,9 +4117,9 @@ static NOINLINE int run_pipe(struct pipe *pi) if (argv[command->assignment_cnt] == NULL) { /* Assignments, but no command */ - /* Ensure redirects take effect. Try "a=t >file" */ + /* Ensure redirects take effect (that is, create files). + * Try "a=t >file": */ rcode = setup_redirects(command, squirrel); -//FIXME: "false; q=`false`; echo $?" should print 1 restore_redirects(squirrel); /* Set shell variables */ while (*argv) { @@ -4129,6 +4129,11 @@ static NOINLINE int run_pipe(struct pipe *pi) set_local_var(p, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0); argv++; } + /* Redirect error sets $? to 1. Othervise, + * if evaluating assignment value set $?, retain it. + * Try "false; q=`exit 2`; echo $?" - should print 2: */ + if (rcode == 0) + rcode = G.last_exitcode; /* Do we need to flag set_local_var() errors? * "assignment to readonly var" and "putenv error" */ @@ -4186,7 +4191,7 @@ static NOINLINE int run_pipe(struct pipe *pi) old_vars = set_vars_and_save_old(new_env); if (!funcp) { debug_printf_exec(": builtin '%s' '%s'...\n", - x->cmd, argv_expanded[1]); + x->b_cmd, argv_expanded[1]); rcode = x->b_function(argv_expanded) & 0xff; fflush_all(); } @@ -6814,7 +6819,7 @@ int hush_main(int argc, char **argv) struct variable *cur_var; INIT_G(); - if (EXIT_SUCCESS) /* if EXIT_SUCCESS == 0, is already done */ + if (EXIT_SUCCESS) /* if EXIT_SUCCESS == 0, it is already done */ G.last_exitcode = EXIT_SUCCESS; #if !BB_MMU G.argv0_for_re_execing = argv[0]; diff --git a/shell/hush_test/hush-psubst/falsetick.right b/shell/hush_test/hush-psubst/falsetick.right new file mode 100644 index 000000000..0b98fb778 --- /dev/null +++ b/shell/hush_test/hush-psubst/falsetick.right @@ -0,0 +1,27 @@ +0 +0 +0 +0 +2 +2 +2 +2 +hush: can't open '/does/not/exist': No such file or directory +1 +hush: can't open '/does/not/exist': No such file or directory +1 +hush: can't open '/does/not/exist': No such file or directory +1 +hush: can't open '/does/not/exist': No such file or directory +1 +hush: can't open '/does/not/exist': No such file or directory +1 +hush: can't open '/does/not/exist': No such file or directory +1 +hush: can't open '/does/not/exist': No such file or directory +1 +hush: can't open '/does/not/exist': No such file or directory +1 +hush: can't open '/does/not/exist': No such file or directory +1 +Done: a=b diff --git a/shell/hush_test/hush-psubst/falsetick.tests b/shell/hush_test/hush-psubst/falsetick.tests new file mode 100755 index 000000000..44d2eae8b --- /dev/null +++ b/shell/hush_test/hush-psubst/falsetick.tests @@ -0,0 +1,22 @@ +# Exitcode 0 (`` has no exitcode, but assignment has): +true; a=``; echo $? +false; a=``; echo $? +true; a=$(); echo $? +false; a=$(); echo $? +# Exitcode 2 (`cmd` expansion sets exitcode after assignment set it to 0): +true; a=`exit 2`; echo $? +false; a=`exit 2`; echo $? +true; a=$(exit 2); echo $? +false; a=$(exit 2); echo $? +# Exitcode 1 (redirect sets exitcode to 1 on error after them): +true; a=`` >/does/not/exist; echo $? +false; a=`` >/does/not/exist; echo $? +true; a=$() >/does/not/exist; echo $? +false; a=$() >/does/not/exist; echo $? +true; a=`exit 2` >/does/not/exist; echo $? +false; a=`exit 2` >/does/not/exist; echo $? +true; a=$(exit 2) >/does/not/exist; echo $? +false; a=$(exit 2) >/does/not/exist; echo $? +# ...and assignment still happens despite redirect error: +true; a=$(echo b) >/does/not/exist; echo $? +echo "Done: a=$a" -- cgit v1.2.3-55-g6feb From e3be7842be3ccac389efd2ac51b18773c58852c5 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 20 May 2010 16:27:42 +0200 Subject: hush: shrink variable expansion code function old new delta expand_vars_to_list 2164 2012 -152 Signed-off-by: Denys Vlasenko --- shell/hush.c | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 8da9439c1..824a5b52e 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -2568,35 +2568,33 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char #endif default: /* varname */ case_default: { - bool exp_len = false; - bool exp_null = false; char *var = arg; + bool exp_len; + char exp_op; char exp_save = exp_save; /* for compiler */ - char exp_op = exp_op; /* for compiler */ + char *exp_saveptr = exp_saveptr; /* points to expansion operator */ char *exp_word = exp_word; /* for compiler */ - size_t exp_off = 0; *p = '\0'; arg[0] = first_ch & 0x7f; /* prepare for expansions */ + exp_len = false; + exp_op = 0; if (var[0] == '#') { /* handle length expansion ${#var} */ exp_len = true; ++var; } else { /* maybe handle parameter expansion */ - exp_off = strcspn(var, ":-=+?%#"); - if (!var[exp_off]) - exp_off = 0; - if (exp_off) { - exp_save = var[exp_off]; - exp_null = exp_save == ':'; - exp_word = var + exp_off; - if (exp_null) - ++exp_word; + 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++; - var[exp_off] = '\0'; + *exp_saveptr = '\0'; } } @@ -2615,7 +2613,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char debug_printf_expand("expand: length of '%s' = ", val); val = utoa(val ? strlen(val) : 0); debug_printf_expand("%s\n", val); - } else if (exp_off) { + } else if (exp_op) { if (exp_op == '%' || exp_op == '#') { if (val) { /* we need to do a pattern match */ @@ -2623,7 +2621,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char char *loc; scan_t scan = pick_scan(exp_op, *exp_word, &match_at_left); if (exp_op == *exp_word) /* ## or %% */ - ++exp_word; + exp_word++; val = dyn_val = xstrdup(val); loc = scan(dyn_val, exp_word, match_at_left); if (match_at_left) /* # or ## */ @@ -2631,13 +2629,14 @@ 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 { + } else { /* one of :-=+? */ +//TODO: handle ${VAR:N[:M]} here. N, M can be expressions similar to $((EXPR)): 2+2, 2+var etc /* we need to do an expansion */ - int exp_test = (!val || (exp_null && !val[0])); + int exp_test = (!val || ((exp_save == ':') && !val[0])); if (exp_op == '+') exp_test = !exp_test; debug_printf_expand("expand: op:%c (null:%s) test:%i\n", exp_op, - exp_null ? "true" : "false", exp_test); + (exp_save == ':') ? "true" : "false", exp_test); if (exp_test) { if (exp_op == '?') { //TODO: how interactive bash aborts expansion mid-command? @@ -2666,7 +2665,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char } } - var[exp_off] = exp_save; + *exp_saveptr = exp_save; } arg[0] = first_ch; -- cgit v1.2.3-55-g6feb From ee0775dd13a84316f6fd49c930237e6dec241dba Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 20 May 2010 16:37:53 +0200 Subject: hush: small code shrink function old new delta expand_vars_to_list 2012 1999 -13 Signed-off-by: Denys Vlasenko --- shell/hush.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 824a5b52e..d5cea07a1 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -2569,7 +2569,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char default: /* varname */ case_default: { char *var = arg; - bool exp_len; + char exp_len; /* '#' if it's ${#var} */ char exp_op; char exp_save = exp_save; /* for compiler */ char *exp_saveptr = exp_saveptr; /* points to expansion operator */ @@ -2579,12 +2579,11 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char arg[0] = first_ch & 0x7f; /* prepare for expansions */ - exp_len = false; exp_op = 0; - if (var[0] == '#') { + exp_len = var[0]; + if (exp_len == '#') { /* handle length expansion ${#var} */ - exp_len = true; - ++var; + var++; } else { /* maybe handle parameter expansion */ exp_saveptr = var + strcspn(var, ":-=+?%#"); @@ -2609,8 +2608,8 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char val = get_local_var_value(var); /* handle any expansions */ - if (exp_len) { - debug_printf_expand("expand: length of '%s' = ", val); + if (exp_len == '#') { + debug_printf_expand("expand: length(%s)=", val); val = utoa(val ? strlen(val) : 0); debug_printf_expand("%s\n", val); } else if (exp_op) { -- cgit v1.2.3-55-g6feb From 53b513331add89e83c1327579f3da91ebbe97570 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 20 May 2010 21:46:45 +0200 Subject: hush: explain various parameter expansion ops in comments Signed-off-by: Denys Vlasenko --- shell/hush.c | 44 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 8 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index d5cea07a1..06e9af21a 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -2614,8 +2614,16 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char debug_printf_expand("%s\n", val); } else if (exp_op) { if (exp_op == '%' || exp_op == '#') { + /* Standard-mandated substring removal ops: + * ${parameter%word} - remove smallest suffix pattern + * ${parameter%%word} - remove largest suffix pattern + * ${parameter#word} - remove smallest prefix pattern + * ${parameter##word} - remove largest prefix pattern + * + * Word is expanded to produce a glob pattern. + * Then var's value is matched to it and matching part removed. + */ if (val) { - /* we need to do a pattern match */ bool match_at_left; char *loc; scan_t scan = pick_scan(exp_op, *exp_word, &match_at_left); @@ -2629,18 +2637,38 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char *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 - /* we need to do an expansion */ - int exp_test = (!val || ((exp_save == ':') && !val[0])); + /* Standard-mandated substitution ops: + * ${var?word} - indicate error if unset + * If var is unset, word (or a message indicating it is unset + * if word is null) is written to standard error + * and the shell exits with a non-zero exit status. + * Otherwise, the value of var is substituted. + * ${var-word} - use default value + * If var is unset, word is substituted. + * ${var=word} - assign and use default value + * If var is unset, word is assigned to var. + * In all cases, final value of var is substituted. + * ${var+word} - use alternative value + * If var is unset, null is substituted. + * Otherwise, word is substituted. + * + * Word is subjected to tilde expansion, parameter expansion, + * command substitution, and arithmetic expansion. + * If word is not needed, it is not expanded. + * + * Colon forms (${var:-word}, ${var:=word} etc) do the same, + * but also treat null var as if it is unset. + */ + int use_word = (!val || ((exp_save == ':') && !val[0])); if (exp_op == '+') - exp_test = !exp_test; + use_word = !use_word; debug_printf_expand("expand: op:%c (null:%s) test:%i\n", exp_op, - (exp_save == ':') ? "true" : "false", exp_test); - if (exp_test) { + (exp_save == ':') ? "true" : "false", use_word); + if (use_word) { if (exp_op == '?') { //TODO: how interactive bash aborts expansion mid-command? - /* ${var?[error_msg_if_unset]} */ - /* ${var:?[error_msg_if_unset_or_null]} */ /* mimic bash message */ die_if_script("%s: %s", var, -- cgit v1.2.3-55-g6feb From 4d8e5fdc1d4bb14ebfecdf6ce19e15024103659f Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 21 May 2010 01:15:42 +0200 Subject: hush: optional support for ${var:N:M} bashism function old new delta expand_vars_to_list 1999 2183 +184 handle_dollar 682 623 -59 Signed-off-by: Denys Vlasenko --- shell/hush.c | 84 ++++++++++++---------- .../hush-vars/param_expand_bash_substring.right | 29 ++++++++ .../hush-vars/param_expand_bash_substring.tests | 46 ++++++++++++ 3 files changed, 120 insertions(+), 39 deletions(-) create mode 100644 shell/hush_test/hush-vars/param_expand_bash_substring.right create mode 100755 shell/hush_test/hush-vars/param_expand_bash_substring.tests (limited to 'shell') 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..." */ + ) { 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}|"' -- cgit v1.2.3-55-g6feb From 4f2c59b267a4840986c95e22bd302b4216bfb8cf Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 21 May 2010 01:25:16 +0200 Subject: hush: remove extra comparison from prev commit Signed-off-by: Denys Vlasenko --- shell/hush.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 0333395f8..945077d87 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -2647,14 +2647,12 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char 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); + //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:}" */ - ) { + if (*end == '\0') { //bb_error_msg("from val:'%s'", val); if (!val || beg >= strlen(val)) val = ""; -- cgit v1.2.3-55-g6feb From 73e013fca7afd2edc9ba8530df77c8210a14700b Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 21 May 2010 15:24:12 +0200 Subject: hush: handle ${var:NUM:} too Signed-off-by: Denys Vlasenko --- shell/hush.c | 10 ++++++++-- shell/hush_test/hush-vars/param_expand_bash_substring.right | 12 ++++++++++++ shell/hush_test/hush-vars/param_expand_bash_substring.tests | 12 ++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 945077d87..6cf8899b0 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -2649,12 +2649,17 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char 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); + 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); - if (!val || beg >= strlen(val)) + if (len == 0 || !val || beg >= strlen(val)) val = ""; else val = dyn_val = xstrndup(val + beg, len); @@ -2663,6 +2668,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char #endif { die_if_script("malformed ${%s...}", var); + val = ""; } } else { /* one of "-=+?" */ /* Standard-mandated substitution ops: 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 9cd465938..6e3eb3ba6 100644 --- a/shell/hush_test/hush-vars/param_expand_bash_substring.right +++ b/shell/hush_test/hush-vars/param_expand_bash_substring.right @@ -7,23 +7,35 @@ hush: syntax error: unterminated ${name} 1:1 =|| 1:1:2=|| 1::2 =|| +1:1: =|| +1:: =|| 1 =|0123| 1:1 =|123| 1:1:2=|12| 1::2 =|01| +1:1: =|| +1:: =|| f =|| f:1 =|| f:1:2=|| f::2 =|| +f:1: =|| +f:: =|| f =|| f:1 =|| f:1:2=|| f::2 =|| +f:1: =|| +f:: =|| f =|a| f:1 =|| f:1:2=|| f::2 =|a| +f:1: =|| +f:: =|| f =|0123456789| f:1 =|123456789| f:1:2=|12| f::2 =|01| +f:1: =|| +f:: =|| 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 6a1765559..eedd435ed 100755 --- a/shell/hush_test/hush-vars/param_expand_bash_substring.tests +++ b/shell/hush_test/hush-vars/param_expand_bash_substring.tests @@ -19,28 +19,40 @@ export var=0123456789 "$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::}|"' "$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::}|"' "$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::}|"' "$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::}|"' "$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::}|"' "$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::}|"' -- cgit v1.2.3-55-g6feb From 214b8ca3dcfd28d4aa2bb58cd89ac941ab23d6d0 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 21 May 2010 15:35:44 +0200 Subject: hush: add a list of unsupported builtins Signed-off-by: Denys Vlasenko --- shell/hush.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 6cf8899b0..c713ce808 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -61,6 +61,8 @@ * grep for "TODO" and fix (some of them are easy) * special variables (done: PWD, PPID, RANDOM) * follow IFS rules more precisely, including update semantics + * builtins mandated by standards we don't support: + * [un]alias, command, fc, getopts, newgrp, readonly, times * export builtin should be special, its arguments are assignments * and therefore expansion of them should be "one-word" expansion: * $ export i=`echo 'a b'` # export has one arg: "i=a b" -- cgit v1.2.3-55-g6feb From 349ef96bb5eae3c487884dd0e88c84a6ba0a1efa Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 21 May 2010 15:46:24 +0200 Subject: hush: reorganized TODO comment at top Signed-off-by: Denys Vlasenko --- shell/hush.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index c713ce808..1937d24e4 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -37,9 +37,14 @@ * handle the recursion implicit in the various substitutions, especially * across continuation lines. * - * POSIX syntax not implemented: + * TODOs: + * grep for "TODO" and fix (some of them are easy) + * special variables (done: PWD, PPID, RANDOM) + * tilde expansion * aliases - * Tilde Expansion + * follow IFS rules more precisely, including update semantics + * builtins mandated by standards we don't support: + * [un]alias, command, fc, getopts, newgrp, readonly, times * * Bash compat TODO: * redirection of stdout+stderr: &> and >& @@ -49,20 +54,13 @@ * process substitution: <(list) and >(list) * =~: regex operator * let EXPR [EXPR...] - * Each EXPR is an arithmetic expression (ARITHMETIC EVALUATION) - * If the last arg evaluates to 0, let returns 1; 0 otherwise. - * NB: let `echo 'a=a + 1'` - error (IOW: multi-word expansion is used) + * Each EXPR is an arithmetic expression (ARITHMETIC EVALUATION) + * If the last arg evaluates to 0, let returns 1; 0 otherwise. + * NB: let `echo 'a=a + 1'` - error (IOW: multi-word expansion is used) * ((EXPR)) - * The EXPR is evaluated according to ARITHMETIC EVALUATION. - * This is exactly equivalent to let "EXPR". + * The EXPR is evaluated according to ARITHMETIC EVALUATION. + * This is exactly equivalent to let "EXPR". * $[EXPR]: synonym for $((EXPR)) - * - * TODOs: - * grep for "TODO" and fix (some of them are easy) - * special variables (done: PWD, PPID, RANDOM) - * follow IFS rules more precisely, including update semantics - * builtins mandated by standards we don't support: - * [un]alias, command, fc, getopts, newgrp, readonly, times * export builtin should be special, its arguments are assignments * and therefore expansion of them should be "one-word" expansion: * $ export i=`echo 'a b'` # export has one arg: "i=a b" -- cgit v1.2.3-55-g6feb From 3f78cec34745069cf0a92a16dfccff66d98ef5ba Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 21 May 2010 17:54:46 +0200 Subject: hush: handle expansions in ${var?expanded_word} constructs function old new delta expand_vars_to_list 2209 2229 +20 Signed-off-by: Denys Vlasenko --- shell/hush.c | 42 ++++++++++++++-------- .../hush-vars/param_expand_indicate_error.right | 15 ++++++++ .../hush-vars/param_expand_indicate_error.tests | 20 +++++++++++ 3 files changed, 62 insertions(+), 15 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 1937d24e4..6d91a534a 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -2403,6 +2403,23 @@ static char *expand_pseudo_dquoted(const char *str) return exp_str; } +#if ENABLE_SH_MATH_SUPPORT +static arith_t expand_and_evaluate_arith(const char *arg, int *errcode_p) +{ + arith_eval_hooks_t hooks; + arith_t res; + char *exp_str; + + hooks.lookupvar = get_local_var_value; + hooks.setvar = set_local_var_from_halves; + hooks.endofname = endofname; + exp_str = expand_pseudo_dquoted(arg); + res = arith(exp_str ? exp_str : arg, errcode_p, &hooks); + free(exp_str); + return res; +} +#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 @@ -2427,7 +2444,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char while ((p = strchr(arg, SPECIAL_VAR_SYMBOL)) != NULL) { char first_ch; int i; - char *dyn_val = NULL; + char *to_be_freed = NULL; const char *val = NULL; #if ENABLE_HUSH_TICK o_string subst_result = NULL_O_STRING; @@ -2528,21 +2545,13 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char #endif #if ENABLE_SH_MATH_SUPPORT case '+': { /* +cmd */ - arith_eval_hooks_t hooks; arith_t res; int errcode; - char *exp_str; arg++; /* skip '+' */ *p = '\0'; /* replace trailing */ debug_printf_subst("ARITH '%s' first_ch %x\n", arg, first_ch); - - exp_str = expand_pseudo_dquoted(arg); - hooks.lookupvar = get_local_var_value; - hooks.setvar = set_local_var_from_halves; - hooks.endofname = endofname; - res = arith(exp_str ? exp_str : arg, &errcode, &hooks); - free(exp_str); + res = expand_and_evaluate_arith(arg, &errcode); if (errcode < 0) { const char *msg = "error in arithmetic"; @@ -2628,8 +2637,8 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char scan_t scan = pick_scan(exp_op, *exp_word, &match_at_left); if (exp_op == *exp_word) /* ## or %% */ exp_word++; - val = dyn_val = xstrdup(val); - loc = scan(dyn_val, exp_word, match_at_left); + val = to_be_freed = xstrdup(val); + loc = scan(to_be_freed, exp_word, match_at_left); if (match_at_left) /* # or ## */ val = loc; else if (loc) /* % or %% and match was found */ @@ -2662,7 +2671,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char if (len == 0 || !val || beg >= strlen(val)) val = ""; else - val = dyn_val = xstrndup(val + beg, len); + val = to_be_freed = xstrndup(val + beg, len); //bb_error_msg("val:'%s'", val); } else #endif @@ -2699,13 +2708,16 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char debug_printf_expand("expand: op:%c (null:%s) test:%i\n", exp_op, (exp_save == ':') ? "true" : "false", use_word); if (use_word) { + to_be_freed = expand_pseudo_dquoted(exp_word); + if (to_be_freed) + exp_word = to_be_freed; if (exp_op == '?') { -//TODO: how interactive bash aborts expansion mid-command? /* mimic bash message */ die_if_script("%s: %s", var, exp_word[0] ? exp_word : "parameter null or not set" ); +//TODO: how interactive bash aborts expansion mid-command? } else { val = exp_word; } @@ -2750,7 +2762,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char if (val) { o_addQstr(output, val, strlen(val)); } - free(dyn_val); + free(to_be_freed); /* Do the check to avoid writing to a const string */ if (*p != SPECIAL_VAR_SYMBOL) *p = SPECIAL_VAR_SYMBOL; diff --git a/shell/hush_test/hush-vars/param_expand_indicate_error.right b/shell/hush_test/hush-vars/param_expand_indicate_error.right index 590bb2001..06fcc5104 100644 --- a/shell/hush_test/hush-vars/param_expand_indicate_error.right +++ b/shell/hush_test/hush-vars/param_expand_indicate_error.right @@ -1,26 +1,41 @@ hush: syntax error: unterminated ${name} 0 0 +==== _ hush: 1: parameter null or not set hush: 1: parameter null or not set hush: 1: message1 hush: 1: message1 +hush: 1: unset! +hush: 1: null or unset! +==== _aaaa _aaaa _aaaa _aaaa _aaaa +_aaaa +_aaaa +==== _ hush: f: parameter null or not set hush: f: parameter null or not set hush: f: message3 hush: f: message3 +hush: f: unset! +hush: f: null or unset! +==== _ _ hush: f: parameter null or not set _ hush: f: message4 +_ +hush: f: null or unset! +==== +_fff +_fff _fff _fff _fff diff --git a/shell/hush_test/hush-vars/param_expand_indicate_error.tests b/shell/hush_test/hush-vars/param_expand_indicate_error.tests index bccba3e1b..be14b1e37 100755 --- a/shell/hush_test/hush-vars/param_expand_indicate_error.tests +++ b/shell/hush_test/hush-vars/param_expand_indicate_error.tests @@ -5,36 +5,56 @@ "$THIS_SH" -c 'echo ${:?}' # then some funky ones +# note: bash prints 1 - treats it as "length of $#"? We print 0 "$THIS_SH" -c 'echo ${#?}' +# bash prints 0 "$THIS_SH" -c 'echo ${#:?}' # now some valid ones +export msg_unset="unset!" +export msg_null_or_unset="null or unset!" + +echo ==== "$THIS_SH" -c 'set --; echo _$1' "$THIS_SH" -c 'set --; echo _${1?}' "$THIS_SH" -c 'set --; echo _${1:?}' "$THIS_SH" -c 'set --; echo _${1?message1}' "$THIS_SH" -c 'set --; echo _${1:?message1}' +"$THIS_SH" -c 'set --; echo _${1?$msg_unset}' +"$THIS_SH" -c 'set --; echo _${1:?$msg_null_or_unset}' +echo ==== "$THIS_SH" -c 'set -- aaaa; echo _$1' "$THIS_SH" -c 'set -- aaaa; echo _${1?}' "$THIS_SH" -c 'set -- aaaa; echo _${1:?}' "$THIS_SH" -c 'set -- aaaa; echo _${1?word}' "$THIS_SH" -c 'set -- aaaa; echo _${1:?word}' +"$THIS_SH" -c 'set -- aaaa; echo _${1?$msg_unset}' +"$THIS_SH" -c 'set -- aaaa; echo _${1:?$msg_null_or_unset}' +echo ==== "$THIS_SH" -c 'unset f; echo _$f' "$THIS_SH" -c 'unset f; echo _${f?}' "$THIS_SH" -c 'unset f; echo _${f:?}' "$THIS_SH" -c 'unset f; echo _${f?message3}' "$THIS_SH" -c 'unset f; echo _${f:?message3}' +"$THIS_SH" -c 'unset f; echo _${f?$msg_unset}' +"$THIS_SH" -c 'unset f; echo _${f:?$msg_null_or_unset}' +echo ==== "$THIS_SH" -c 'f=; echo _$f' "$THIS_SH" -c 'f=; echo _${f?}' "$THIS_SH" -c 'f=; echo _${f:?}' "$THIS_SH" -c 'f=; echo _${f?word}' "$THIS_SH" -c 'f=; echo _${f:?message4}' +"$THIS_SH" -c 'f=; echo _${f?$msg_unset}' +"$THIS_SH" -c 'f=; echo _${f:?$msg_null_or_unset}' +echo ==== "$THIS_SH" -c 'f=fff; echo _$f' "$THIS_SH" -c 'f=fff; echo _${f?}' "$THIS_SH" -c 'f=fff; echo _${f:?}' "$THIS_SH" -c 'f=fff; echo _${f?word}' "$THIS_SH" -c 'f=fff; echo _${f:?word}' +"$THIS_SH" -c 'f=fff; echo _${f?$msg_unset}' +"$THIS_SH" -c 'f=fff; echo _${f:?$msg_null_or_unset}' -- cgit v1.2.3-55-g6feb From 7436950a7516d1f4498285ccc81bf6d926f3af5e Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 21 May 2010 19:52:01 +0200 Subject: hush: fix a=abc; c=c; echo ${a%${c}} function old new delta expand_vars_to_list 2229 2302 +73 add_till_closing_paren 286 313 +27 handle_dollar 623 574 -49 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 2/1 up/down: 100/-49) Total: 51 bytes Signed-off-by: Denys Vlasenko --- shell/hush.c | 118 +++++++++++++++-------------- shell/hush_test/hush-vars/var3.right | 2 +- shell/hush_test/hush-vars/var_posix1.right | 6 +- shell/hush_test/hush-vars/var_posix1.tests | 9 ++- 4 files changed, 75 insertions(+), 60 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 6d91a534a..a3df5edcd 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -2638,11 +2638,21 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char if (exp_op == *exp_word) /* ## or %% */ exp_word++; val = to_be_freed = xstrdup(val); - loc = scan(to_be_freed, exp_word, match_at_left); - if (match_at_left) /* # or ## */ - val = loc; - else if (loc) /* % or %% and match was found */ - *loc = '\0'; + { + char *exp_exp_word = expand_pseudo_dquoted(exp_word); + if (exp_exp_word) + exp_word = exp_exp_word; + loc = scan(to_be_freed, exp_word, match_at_left); + //bb_error_msg("op:%c str:'%s' pat:'%s' res:'%s'", + // exp_op, to_be_freed, exp_word, loc); + free(exp_exp_word); + } + if (loc) { /* match was found */ + if (match_at_left) /* # or ## */ + val = loc; + else /* % or %% */ + *loc = '\0'; + } } } else if (!strchr("%#:-=+?"+3, exp_op)) { #if ENABLE_HUSH_BASH_COMPAT @@ -5876,20 +5886,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 * echo $(echo \(\(TEST\) BEST) ((TEST) BEST + * + * BUG: enter: echo $(( `printf '(\x28 1'` + `echo 2))` )) + * on the command line, press Enter. You get > prompt which is impossible + * to exit with ^C. */ -static void add_till_closing_paren(o_string *dest, struct in_str *input, bool dbl) +#define DOUBLE_CLOSE_CHAR_FLAG 0x80 +static void add_till_closing_paren(o_string *dest, struct in_str *input, char end_ch) { int count = 0; + char dbl = end_ch & DOUBLE_CLOSE_CHAR_FLAG; + end_ch &= (DOUBLE_CLOSE_CHAR_FLAG-1); while (1) { int ch = i_getch(input); if (ch == EOF) { syntax_error_unterm_ch(')'); /*xfunc_die(); - redundant */ } - if (ch == '(') + if (ch == '(' || ch == '{') count++; - if (ch == ')') { - if (--count < 0) { + if (ch == ')' || ch == '}') { + count--; + if (count < 0 && ch == end_ch) { if (!dbl) break; if (i_peek(input) == ')') { @@ -5969,62 +5987,52 @@ static int handle_dollar(o_string *as_string, case '@': /* args */ goto make_one_char_var; case '{': { - bool first_char, all_digits; - bool in_expansion_param; + o_addchr(dest, SPECIAL_VAR_SYMBOL); - ch = i_getch(input); + ch = i_getch(input); /* eat '{' */ nommu_addchr(as_string, ch); - o_addchr(dest, SPECIAL_VAR_SYMBOL); -// 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; + ch = i_getch(input); /* first char after '{' */ + nommu_addchr(as_string, ch); + /* It should be ${?}, or ${#var}, + * or even ${?+subst} - operator acting on a special variable, + * or the beginning of variable name. + */ + if (!strchr("$!?#*@_", ch) && !isalnum(ch)) { /* not one of those */ + bad_dollar_syntax: + syntax_error_unterm_str("${name}"); + debug_printf_parse("handle_dollar return 1: unterminated ${name}\n"); + return 1; + } + ch |= quote_mask; + + /* It's possible to just call add_till_closing_paren() at this point. + * However, this regresses some of our testsuite cases + * which check invalid constructs like ${%}. + * Oh well... let's check that the var name part is fine... */ + while (1) { + o_addchr(dest, ch); + debug_printf_parse(": '%c'\n", ch); + ch = i_getch(input); nommu_addchr(as_string, ch); - if (ch == '}') { + if (ch == '}') break; - } - if (first_char) { - if (ch == '#') { - /* ${#var}: length of var contents */ - goto char_ok; - } - if (isdigit(ch)) { - all_digits = true; - goto char_ok; - } - /* They're being verbose and doing ${?} */ - if (i_peek(input) == '}' && strchr("$!?#*@_", ch)) - goto char_ok; - } - - if (!in_expansion_param - && ( (all_digits && !isdigit(ch)) /* met non-digit: 123w */ - || (!all_digits && !isalnum(ch) && ch != '_') /* met non-name char: abc% */ - ) - ) { + if (!isalnum(ch) && ch != '_') { /* handle parameter expansions * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_02 */ - if (first_char /* bad (empty var name): "${%..." */ - || !strchr("%#:-=+?", ch) /* bad: "${var..." */ - ) { - syntax_error_unterm_str("${name}"); - debug_printf_parse("handle_dollar return 1: unterminated ${name}\n"); - return 1; - } - in_expansion_param = true; + if (!strchr("%#:-=+?", ch)) /* ${var... */ + goto bad_dollar_syntax; + /* Eat everything until closing '}' */ + o_addchr(dest, ch); +//TODO: add nommu_addchr hack here + add_till_closing_paren(dest, input, '}'); + break; } - char_ok: - debug_printf_parse(": '%c'\n", ch); - o_addchr(dest, ch | quote_mask); - quote_mask = 0; - first_char = false; - } /* while (1) */ + } o_addchr(dest, SPECIAL_VAR_SYMBOL); break; } @@ -6044,7 +6052,7 @@ static int handle_dollar(o_string *as_string, # if !BB_MMU pos = dest->length; # endif - add_till_closing_paren(dest, input, true); + add_till_closing_paren(dest, input, ')' | DOUBLE_CLOSE_CHAR_FLAG); # if !BB_MMU if (as_string) { o_addstr(as_string, dest->data + pos); @@ -6062,7 +6070,7 @@ static int handle_dollar(o_string *as_string, # if !BB_MMU pos = dest->length; # endif - add_till_closing_paren(dest, input, false); + add_till_closing_paren(dest, input, ')'); # if !BB_MMU if (as_string) { o_addstr(as_string, dest->data + pos); diff --git a/shell/hush_test/hush-vars/var3.right b/shell/hush_test/hush-vars/var3.right index 5e28d2fab..40e67fdf5 100644 --- a/shell/hush_test/hush-vars/var3.right +++ b/shell/hush_test/hush-vars/var3.right @@ -1,2 +1,2 @@ -hush: syntax error: unterminated ${name} +hush: invalid number '1q' hush: syntax error: unterminated ${name} diff --git a/shell/hush_test/hush-vars/var_posix1.right b/shell/hush_test/hush-vars/var_posix1.right index e6cba2758..813437e2f 100644 --- a/shell/hush_test/hush-vars/var_posix1.right +++ b/shell/hush_test/hush-vars/var_posix1.right @@ -23,6 +23,7 @@ babcdcd ababcdcd Empty: ababcdcd}_tail +ababcdcd_tail ababcd ababcd ababcd @@ -32,5 +33,8 @@ ababcdc ababcdcd Empty: ababcdcd}_tail +ababcdcd_tail ababcdcd -end +ab +ab +End diff --git a/shell/hush_test/hush-vars/var_posix1.tests b/shell/hush_test/hush-vars/var_posix1.tests index c1f64094d..e48fd98c7 100755 --- a/shell/hush_test/hush-vars/var_posix1.tests +++ b/shell/hush_test/hush-vars/var_posix1.tests @@ -31,7 +31,7 @@ echo ${var##?} echo ${var#*} echo Empty:${var##*} echo ${var#}}_tail -# UNFIXED BUG: echo ${var#\}}_tail +echo ${var#\}}_tail echo ${var%cd} echo ${var%%cd} @@ -42,7 +42,10 @@ echo ${var%%?} echo ${var%*} echo Empty:${var%%*} echo ${var#}}_tail -# UNFIXED BUG: echo ${var#\}}_tail +echo ${var#\}}_tail echo ${var%\\*} -echo end +a=ab}; echo ${a%\}}; +a=abc; c=c; echo ${a%${c}} + +echo End -- cgit v1.2.3-55-g6feb From a6ad397ea92cd9c53973243728d3e52640fe63ec Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sat, 22 May 2010 00:26:06 +0200 Subject: hush: fix more obscure ${var%...} cases function old new delta add_till_closing_paren 313 359 +46 builtin_exit 48 47 -1 Signed-off-by: Denys Vlasenko --- shell/hush.c | 39 ++++++++++++++++++------------ shell/hush_test/hush-arith/arith.right | 9 +++++++ shell/hush_test/hush-arith/arith.tests | 19 +++++++++------ shell/hush_test/hush-vars/var_posix1.right | 1 + shell/hush_test/hush-vars/var_posix1.tests | 1 + 5 files changed, 45 insertions(+), 24 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index a3df5edcd..32b90876f 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -45,6 +45,8 @@ * follow IFS rules more precisely, including update semantics * builtins mandated by standards we don't support: * [un]alias, command, fc, getopts, newgrp, readonly, times + * make complex ${var%...} constructs support optional + * make here documents optional * * Bash compat TODO: * redirection of stdout+stderr: &> and >& @@ -5887,36 +5889,36 @@ static void add_till_backquote(o_string *dest, struct in_str *input) * echo $(echo 'TEST)' BEST) TEST) BEST * echo $(echo \(\(TEST\) BEST) ((TEST) BEST * - * BUG: enter: echo $(( `printf '(\x28 1'` + `echo 2))` )) - * on the command line, press Enter. You get > prompt which is impossible - * to exit with ^C. + * Also adapted to eat ${var%...} constructs, since ... part + * can contain arbitrary constructs, just like $(cmd). */ #define DOUBLE_CLOSE_CHAR_FLAG 0x80 static void add_till_closing_paren(o_string *dest, struct in_str *input, char end_ch) { - int count = 0; char dbl = end_ch & DOUBLE_CLOSE_CHAR_FLAG; end_ch &= (DOUBLE_CLOSE_CHAR_FLAG-1); while (1) { int ch = i_getch(input); if (ch == EOF) { - syntax_error_unterm_ch(')'); + syntax_error_unterm_ch(end_ch); /*xfunc_die(); - redundant */ } - if (ch == '(' || ch == '{') - count++; - if (ch == ')' || ch == '}') { - count--; - if (count < 0 && ch == end_ch) { - if (!dbl) - break; - if (i_peek(input) == ')') { - i_getch(input); - break; - } + if (ch == end_ch) { + if (!dbl) + break; + /* we look for closing )) of $((EXPR)) */ + if (i_peek(input) == end_ch) { + i_getch(input); /* eat second ')' */ + break; } } o_addchr(dest, ch); + if (ch == '(' || ch == '{') { + ch = (ch == '(' ? ')' : '}'); + add_till_closing_paren(dest, input, ch); + o_addchr(dest, ch); + continue; + } if (ch == '\'') { add_till_single_quote(dest, input); o_addchr(dest, ch); @@ -5927,6 +5929,11 @@ static void add_till_closing_paren(o_string *dest, struct in_str *input, char en o_addchr(dest, ch); continue; } + if (ch == '`') { + add_till_backquote(dest, input); + o_addchr(dest, ch); + continue; + } if (ch == '\\') { /* \x. Copy verbatim. Important for \(, \) */ ch = i_getch(input); diff --git a/shell/hush_test/hush-arith/arith.right b/shell/hush_test/hush-arith/arith.right index 83155fb03..718c26ad0 100644 --- a/shell/hush_test/hush-arith/arith.right +++ b/shell/hush_test/hush-arith/arith.right @@ -43,21 +43,30 @@ Format: 'expected actual' 4 4 29 29 5 5 +unary plus, minus -4 -4 4 4 +conditional expressions 1 1 32 32 32 32 1 1 1 1 32 32 +check that parentheses in `cmd` are interpreted correctly +3 3 +check that the unevaluated part of the ternary operator does not do evaluation or assignment 20 20 30 30 20 20 30 30 +check precedence of assignment vs. conditional operator hush: error in arithmetic +check precedence of assignment vs. conditional operator +associativity of assignment-operator operator 6 6 6,5,3 6,5,3 +octal, hex 263 263 255 255 40 40 diff --git a/shell/hush_test/hush-arith/arith.tests b/shell/hush_test/hush-arith/arith.tests index 57e66e888..bc6b341d1 100755 --- a/shell/hush_test/hush-arith/arith.tests +++ b/shell/hush_test/hush-arith/arith.tests @@ -75,11 +75,11 @@ echo 4 $(( iv &= 4 )) echo 29 $(( iv += (jv + 9))) echo 5 $(( (iv + 4) % 7 )) -# unary plus, minus +echo unary plus, minus echo -4 $(( +4 - 8 )) echo 4 $(( -4 + 8 )) -# conditional expressions +echo conditional expressions echo 1 $(( 4<5 ? 1 : 32)) echo 32 $(( 4>5 ? 1 : 32)) echo 32 $(( 4>(2+3) ? 1 : 32)) @@ -87,8 +87,11 @@ echo 1 $(( 4<(2+3) ? 1 : 32)) echo 1 $(( (2+2)<(2+3) ? 1 : 32)) echo 32 $(( (2+2)>(2+3) ? 1 : 32)) -# check that the unevaluated part of the ternary operator does not do -# evaluation or assignment +echo 'check that parentheses in `cmd` are interpreted correctly' +# \x28 is '(' +echo 3 $(( ( `printf '(\x28 1'` + `echo 2\)\)` ) )) + +echo check that the unevaluated part of the ternary operator does not do evaluation or assignment x=i+=2 y=j+=2 #ash# declare -i i=1 j=1 @@ -109,20 +112,20 @@ echo 20 $((1 ? 20 : (x+=2))) echo 30 $((0 ? (y+=2) : 30)) #ash# echo $i,$y # ash mishandles this -# check precedence of assignment vs. conditional operator +echo check precedence of assignment vs. conditional operator # should be an error #ash# declare -i x=2 x=2 #ashnote# bash reports error but continues, ash aborts - using subshell to 'emulate' bash: ( y=$((1 ? 20 : x+=2)) ) -# check precedence of assignment vs. conditional operator +echo check precedence of assignment vs. conditional operator #ash# declare -i x=2 x=2 # ash says "line NNN: syntax error: 0 ? x+=2 : 20" #ash# echo 20 $((0 ? x+=2 : 20)) -# associativity of assignment-operator operator +echo associativity of assignment-operator operator #ash# declare -i i=1 j=2 k=3 i=1 j=2 @@ -130,7 +133,7 @@ k=3 echo 6 $((i += j += k)) echo 6,5,3 $i,$j,$k -# octal, hex +echo octal, hex echo 263 $(( 0x100 | 007 )) echo 255 $(( 0xff )) #ash# echo 255 $(( 16#ff )) diff --git a/shell/hush_test/hush-vars/var_posix1.right b/shell/hush_test/hush-vars/var_posix1.right index 813437e2f..7ff618ad0 100644 --- a/shell/hush_test/hush-vars/var_posix1.right +++ b/shell/hush_test/hush-vars/var_posix1.right @@ -37,4 +37,5 @@ ababcdcd_tail ababcdcd ab ab +ab End diff --git a/shell/hush_test/hush-vars/var_posix1.tests b/shell/hush_test/hush-vars/var_posix1.tests index e48fd98c7..82abe8198 100755 --- a/shell/hush_test/hush-vars/var_posix1.tests +++ b/shell/hush_test/hush-vars/var_posix1.tests @@ -47,5 +47,6 @@ echo ${var%\\*} a=ab}; echo ${a%\}}; a=abc; c=c; echo ${a%${c}} +a=ab{{c; echo ${a%`echo {{c`} echo End -- cgit v1.2.3-55-g6feb From ddc62f64baad8abdeb587b13afe8d47fb347981c Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sat, 22 May 2010 00:53:32 +0200 Subject: hush: fix a nommu todo in recent commits Signed-off-by: Denys Vlasenko --- shell/hush.c | 62 ++++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 25 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 32b90876f..0f0151a21 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -5767,7 +5767,9 @@ static int parse_group(o_string *dest, struct parse_context *ctx, } { -#if !BB_MMU +#if BB_MMU +# define as_string NULL +#else char *as_string = NULL; #endif pipe_list = parse_stream(&as_string, input, endch); @@ -5778,9 +5780,8 @@ static int parse_group(o_string *dest, struct parse_context *ctx, /* empty ()/{} or parse error? */ if (!pipe_list || pipe_list == ERR_PTR) { /* parse_stream already emitted error msg */ -#if !BB_MMU - free(as_string); -#endif + if (!BB_MMU) + free(as_string); debug_printf_parse("parse_group return 1: " "parse_stream returned %p\n", pipe_list); return 1; @@ -5792,6 +5793,7 @@ static int parse_group(o_string *dest, struct parse_context *ctx, debug_printf_parse("end of group, remembering as:'%s'\n", command->group_as_string); #endif +#undef as_string } debug_printf_parse("parse_group return 0\n"); return 0; @@ -5893,7 +5895,7 @@ static void add_till_backquote(o_string *dest, struct in_str *input) * can contain arbitrary constructs, just like $(cmd). */ #define DOUBLE_CLOSE_CHAR_FLAG 0x80 -static void add_till_closing_paren(o_string *dest, struct in_str *input, char end_ch) +static void add_till_closing_bracket(o_string *dest, struct in_str *input, char end_ch) { char dbl = end_ch & DOUBLE_CLOSE_CHAR_FLAG; end_ch &= (DOUBLE_CLOSE_CHAR_FLAG-1); @@ -5915,7 +5917,7 @@ static void add_till_closing_paren(o_string *dest, struct in_str *input, char en o_addchr(dest, ch); if (ch == '(' || ch == '{') { ch = (ch == '(' ? ')' : '}'); - add_till_closing_paren(dest, input, ch); + add_till_closing_bracket(dest, input, ch); o_addchr(dest, ch); continue; } @@ -5952,6 +5954,7 @@ static void add_till_closing_paren(o_string *dest, struct in_str *input, char en #if BB_MMU #define handle_dollar(as_string, dest, input) \ handle_dollar(dest, input) +#define as_string NULL #endif static int handle_dollar(o_string *as_string, o_string *dest, @@ -6013,12 +6016,14 @@ static int handle_dollar(o_string *as_string, } ch |= quote_mask; - /* It's possible to just call add_till_closing_paren() at this point. + /* It's possible to just call add_till_closing_bracket() at this point. * However, this regresses some of our testsuite cases * which check invalid constructs like ${%}. * Oh well... let's check that the var name part is fine... */ while (1) { + unsigned pos; + o_addchr(dest, ch); debug_printf_parse(": '%c'\n", ch); @@ -6035,8 +6040,15 @@ static int handle_dollar(o_string *as_string, goto bad_dollar_syntax; /* Eat everything until closing '}' */ o_addchr(dest, ch); -//TODO: add nommu_addchr hack here - add_till_closing_paren(dest, input, '}'); + if (!BB_MMU) + pos = dest->length; + add_till_closing_bracket(dest, input, '}'); +#if !BB_MMU + if (as_string) { + o_addstr(as_string, dest->data + pos); + o_addchr(as_string, '}'); + } +#endif break; } } @@ -6045,9 +6057,8 @@ static int handle_dollar(o_string *as_string, } #if ENABLE_SH_MATH_SUPPORT || ENABLE_HUSH_TICK case '(': { -# if !BB_MMU - int pos; -# endif + unsigned pos; + ch = i_getch(input); nommu_addchr(as_string, ch); # if ENABLE_SH_MATH_SUPPORT @@ -6056,17 +6067,16 @@ static int handle_dollar(o_string *as_string, nommu_addchr(as_string, ch); o_addchr(dest, SPECIAL_VAR_SYMBOL); o_addchr(dest, /*quote_mask |*/ '+'); -# if !BB_MMU - pos = dest->length; -# endif - add_till_closing_paren(dest, input, ')' | DOUBLE_CLOSE_CHAR_FLAG); -# if !BB_MMU + if (!BB_MMU) + pos = dest->length; + add_till_closing_bracket(dest, input, ')' | DOUBLE_CLOSE_CHAR_FLAG); +#if !BB_MMU if (as_string) { o_addstr(as_string, dest->data + pos); o_addchr(as_string, ')'); o_addchr(as_string, ')'); } -# endif +#endif o_addchr(dest, SPECIAL_VAR_SYMBOL); break; } @@ -6074,16 +6084,15 @@ static int handle_dollar(o_string *as_string, # if ENABLE_HUSH_TICK o_addchr(dest, SPECIAL_VAR_SYMBOL); o_addchr(dest, quote_mask | '`'); -# if !BB_MMU - pos = dest->length; -# endif - add_till_closing_paren(dest, input, ')'); -# if !BB_MMU + if (!BB_MMU) + pos = dest->length; + add_till_closing_bracket(dest, input, ')'); +#if !BB_MMU if (as_string) { o_addstr(as_string, dest->data + pos); o_addchr(as_string, ')'); } -# endif +#endif o_addchr(dest, SPECIAL_VAR_SYMBOL); # endif break; @@ -6108,11 +6117,13 @@ static int handle_dollar(o_string *as_string, } debug_printf_parse("handle_dollar return 0\n"); return 0; +#undef as_string } #if BB_MMU #define parse_stream_dquoted(as_string, dest, input, dquote_end) \ parse_stream_dquoted(dest, input, dquote_end) +#define as_string NULL #endif static int parse_stream_dquoted(o_string *as_string, o_string *dest, @@ -6176,7 +6187,7 @@ static int parse_stream_dquoted(o_string *as_string, } #if ENABLE_HUSH_TICK if (ch == '`') { - //int pos = dest->length; + //unsigned pos = dest->length; o_addchr(dest, SPECIAL_VAR_SYMBOL); o_addchr(dest, 0x80 | '`'); add_till_backquote(dest, input); @@ -6194,6 +6205,7 @@ static int parse_stream_dquoted(o_string *as_string, dest->o_assignment = DEFINITELY_ASSIGNMENT; } goto again; +#undef as_string } /* -- cgit v1.2.3-55-g6feb From 1e811b12317d0eab4e78d848caa640cca497a0a7 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sat, 22 May 2010 03:12:29 +0200 Subject: 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 ++++++++++++++------- .../hush-vars/param_expand_bash_substring.right | 10 ++ .../hush-vars/param_expand_bash_substring.tests | 98 +++++++++------- 3 files changed, 152 insertions(+), 86 deletions(-) (limited to 'shell') 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 @@ +# first try some invalid patterns # 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}' @@ -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::}|"' - -"$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::}|"' - -"$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::}|"' - -"$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::}|"' - -"$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::}|"' - -"$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::}|"' +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::}|" + +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::}|" + +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::}|" + +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::}|" + +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::}|" + +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 -- cgit v1.2.3-55-g6feb From 8a33679694b0fdf459d69868f85c081cab5687cb Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sat, 22 May 2010 06:05:02 +0200 Subject: hush: fix "hush -c 'echo $#'" showing -1 Signed-off-by: Denys Vlasenko --- shell/hush.c | 1 - shell/hush_test/hush-vars/param_expand_len.right | 5 +++++ shell/hush_test/hush-vars/param_expand_len.tests | 7 ++++++- 3 files changed, 11 insertions(+), 2 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 41d5fcab2..7645a34a4 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -7080,7 +7080,6 @@ int hush_main(int argc, char **argv) /* -c 'script' (no params): prevent empty $0 */ G.global_argv--; /* points to argv[i] of 'script' */ G.global_argv[0] = argv[0]; - G.global_argc--; } /* else -c 'script' ARG0 [ARG1...]: $0 is ARG0 */ init_sigmasks(); parse_and_run_string(optarg); diff --git a/shell/hush_test/hush-vars/param_expand_len.right b/shell/hush_test/hush-vars/param_expand_len.right index 2d633a148..96e8cb59b 100644 --- a/shell/hush_test/hush-vars/param_expand_len.right +++ b/shell/hush_test/hush-vars/param_expand_len.right @@ -1,4 +1,9 @@ +0 +0 +1 +Make sure len parsing doesnt break arg count 0 0 4 4 +Testing len op 4 3 2 1 0 0 0 3 0 diff --git a/shell/hush_test/hush-vars/param_expand_len.tests b/shell/hush_test/hush-vars/param_expand_len.tests index 90f47d2fb..fe20a45e9 100755 --- a/shell/hush_test/hush-vars/param_expand_len.tests +++ b/shell/hush_test/hush-vars/param_expand_len.tests @@ -1,9 +1,14 @@ -# make sure len parsing doesnt break arg count +"$THIS_SH" -c 'echo $#' +"$THIS_SH" -c 'echo $#' arg0 +"$THIS_SH" -c 'echo $#' arg0 arg1 + +echo Make sure len parsing doesnt break arg count set -- echo $# ${#} set -- aaaa bbb cc d echo $# ${#} +echo Testing len op echo ${#1} ${#2} ${#3} ${#4} ${#5} ${#6} unset e -- cgit v1.2.3-55-g6feb From e85248afa23434b78e48fe09b57eea5f6657410d Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sat, 22 May 2010 06:20:26 +0200 Subject: hush: fix segfault in ${?:N:M} function old new delta expand_vars_to_list 2374 2409 +35 builtin_umask 132 133 +1 builtin_exit 47 48 +1 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 3/0 up/down: 37/0) Total: 37 bytes Signed-off-by: Denys Vlasenko --- shell/hush.c | 81 +++++++++++++--------- shell/hush_test/hush-vars/param_expand_alt.right | 2 +- shell/hush_test/hush-vars/param_expand_alt.tests | 4 +- .../hush-vars/param_expand_bash_substring.right | 13 ++++ .../hush-vars/param_expand_bash_substring.tests | 15 ++++ 5 files changed, 78 insertions(+), 37 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 7645a34a4..08e63785d 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -179,9 +179,13 @@ #define ERR_PTR ((void*)(long)1) -#define JOB_STATUS_FORMAT "[%d] %-22s %.40s\n" +#define JOB_STATUS_FORMAT "[%d] %-22s %.40s\n" -#define SPECIAL_VAR_SYMBOL 3 +#define _SPECIAL_VARS_STR "_*@$!?#" +#define SPECIAL_VARS_STR ("_*@$!?#" + 1) +#define NUMERIC_SPECVARS_STR ("_*@$!?#" + 3) + +#define SPECIAL_VAR_SYMBOL 3 struct variable; @@ -2472,21 +2476,6 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char switch (first_ch & 0x7f) { /* Highest bit in first_ch indicates that var is double-quoted */ - case '$': /* pid */ - val = utoa(G.root_pid); - break; - case '!': /* bg pid */ - val = G.last_bg_pid ? utoa(G.last_bg_pid) : (char*)""; - break; - case '?': /* exitcode */ - val = utoa(G.last_exitcode); - break; - case '#': /* argc */ - if (arg[1] != SPECIAL_VAR_SYMBOL) - /* actually, it's a ${#var} */ - goto case_default; - val = utoa(G.global_argc ? G.global_argc-1 : 0); - break; case '*': case '@': i = 1; @@ -2581,27 +2570,35 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char break; } #endif - default: /* varname */ - case_default: { - char *var = arg; - char exp_len; /* '#' if it's ${#var} */ + default: { /* varname */ + char *var; + char first_char; char exp_op; char exp_save = exp_save; /* for compiler */ - char *exp_saveptr = exp_saveptr; /* points to expansion operator */ + char *exp_saveptr; /* points to expansion operator */ char *exp_word = exp_word; /* for compiler */ + var = arg; *p = '\0'; - arg[0] = first_ch & 0x7f; - - /* prepare for expansions */ + exp_saveptr = arg[1] ? strchr("%#:-=+?", arg[1]) : NULL; + first_char = arg[0] = first_ch & 0x7f; exp_op = 0; - exp_len = var[0]; - if (exp_len == '#') { + + if (first_char == '#' && arg[1] && !exp_saveptr) { /* handle length expansion ${#var} */ var++; + exp_op = 'L'; } else { /* maybe handle parameter expansion */ - exp_saveptr = var + strcspn(var, "%#:-=+?"); + if (exp_saveptr /* if 2nd char is one of expansion operators */ + && strchr(NUMERIC_SPECVARS_STR, first_char) /* 1st char is special variable */ + ) { + /* ${?:0}, ${#[:]%0} etc */ + exp_saveptr = var + 1; + } else { + /* ${?}, ${var}, ${var:0}, ${var[:]%0} etc */ + exp_saveptr = var+1 + strcspn(var+1, "%#:-=+?"); + } exp_op = exp_save = *exp_saveptr; if (exp_op) { exp_word = exp_saveptr + 1; @@ -2616,7 +2613,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char } } *exp_saveptr = '\0'; - } + } /* else: it's not an expansion op, but bare ${var} */ } /* lookup the variable in question */ @@ -2626,11 +2623,27 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char if (i < G.global_argc) val = G.global_argv[i]; /* else val remains NULL: $N with too big N */ - } else - val = get_local_var_value(var); + } else { + switch (var[0]) { + case '$': /* pid */ + val = utoa(G.root_pid); + break; + case '!': /* bg pid */ + val = G.last_bg_pid ? utoa(G.last_bg_pid) : (char*)""; + break; + case '?': /* exitcode */ + val = utoa(G.last_exitcode); + break; + case '#': /* argc */ + val = utoa(G.global_argc ? G.global_argc-1 : 0); + break; + default: + val = get_local_var_value(var); + } + } /* handle any expansions */ - if (exp_len == '#') { + if (exp_op == 'L') { debug_printf_expand("expand: length(%s)=", val); val = utoa(val ? strlen(val) : 0); debug_printf_expand("%s\n", val); @@ -2761,7 +2774,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char } } } - } + } /* one of "-=+?" */ *exp_saveptr = exp_save; } /* if (exp_op) */ @@ -6031,7 +6044,7 @@ static int handle_dollar(o_string *as_string, * or even ${?+subst} - operator acting on a special variable, * or the beginning of variable name. */ - if (!strchr("$!?#*@_", ch) && !isalnum(ch)) { /* not one of those */ + if (!strchr(_SPECIAL_VARS_STR, ch) && !isalnum(ch)) { /* not one of those */ bad_dollar_syntax: syntax_error_unterm_str("${name}"); debug_printf_parse("handle_dollar return 1: unterminated ${name}\n"); diff --git a/shell/hush_test/hush-vars/param_expand_alt.right b/shell/hush_test/hush-vars/param_expand_alt.right index 4d2197a5e..67f18d69c 100644 --- a/shell/hush_test/hush-vars/param_expand_alt.right +++ b/shell/hush_test/hush-vars/param_expand_alt.right @@ -1,6 +1,6 @@ hush: syntax error: unterminated ${name} hush: syntax error: unterminated ${name} -_0 _0 +__ __ _ _ _ _ _ _aaaa _ _ _word _word _ _ _ _ _ diff --git a/shell/hush_test/hush-vars/param_expand_alt.tests b/shell/hush_test/hush-vars/param_expand_alt.tests index dcdca86d4..3b646b142 100755 --- a/shell/hush_test/hush-vars/param_expand_alt.tests +++ b/shell/hush_test/hush-vars/param_expand_alt.tests @@ -2,8 +2,8 @@ "$THIS_SH" -c 'echo ${+} ; echo moo' "$THIS_SH" -c 'echo ${:+} ; echo moo' -# now some funky ones -echo _${#+} _${#:+} +# now some funky ones. (bash doesn't accept ${#+}) +echo _${#+}_ _${#:+}_ # now some valid ones set -- 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 53b8836ff..2f4c51d06 100644 --- a/shell/hush_test/hush-vars/param_expand_bash_substring.right +++ b/shell/hush_test/hush-vars/param_expand_bash_substring.right @@ -39,6 +39,19 @@ f:1:2=|12| f::2 =|01| f:1: =|| f:: =|| +Substrings from special vars +? =|0| +?:1 =|| +?:1:2=|| +?::2 =|0| +?:1: =|| +?:: =|| +# =|11| +#:1 =|1| +#:1:2=|1| +#::2 =|11| +#:1: =|| +#:: =|| Substrings with expressions f =|01234567| f:1+1:2+2 =|2345| 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 a80523add..5c9552dba 100755 --- a/shell/hush_test/hush-vars/param_expand_bash_substring.tests +++ b/shell/hush_test/hush-vars/param_expand_bash_substring.tests @@ -55,6 +55,21 @@ f=0123456789; echo "f::2 =|${f::2}|" f=0123456789; echo "f:1: =|${f:1:}|" f=0123456789; echo "f:: =|${f::}|" +echo "Substrings from special vars" +echo '? '"=|$?|" +echo '?:1 '"=|${?:1}|" +echo '?:1:2'"=|${?:1:2}|" +echo '?::2 '"=|${?::2}|" +echo '?:1: '"=|${?:1:}|" +echo '?:: '"=|${?::}|" +set -- 1 2 3 4 5 6 7 8 9 10 11 +echo '# '"=|$#|" +echo '#:1 '"=|${#:1}|" +echo '#:1:2'"=|${#:1:2}|" +echo '#::2 '"=|${#::2}|" +echo '#:1: '"=|${#:1:}|" +echo '#:: '"=|${#::}|" + echo "Substrings with expressions" f=01234567; echo 'f '"=|$f|" f=01234567; echo 'f:1+1:2+2 '"=|${f:1+1:2+2}|" -- cgit v1.2.3-55-g6feb From 5ae8f1cdbecd3bdf502992c21a77aae65299c410 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sat, 22 May 2010 06:32:11 +0200 Subject: hush: fix hush -c 'echo $0' - was showing empty string Signed-off-by: Denys Vlasenko --- shell/hush.c | 1 + 1 file changed, 1 insertion(+) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 08e63785d..8125a63f1 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -7093,6 +7093,7 @@ int hush_main(int argc, char **argv) /* -c 'script' (no params): prevent empty $0 */ G.global_argv--; /* points to argv[i] of 'script' */ G.global_argv[0] = argv[0]; + G.global_argc++; } /* else -c 'script' ARG0 [ARG1...]: $0 is ARG0 */ init_sigmasks(); parse_and_run_string(optarg); -- cgit v1.2.3-55-g6feb From 2e48d536cee3c2fb261780eb2a01972bd936a56c Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sat, 22 May 2010 17:30:39 +0200 Subject: hush: reduce #ifdef forest a bit, rename handle_dollar -> parse_dollar Signed-off-by: Denys Vlasenko --- shell/hush.c | 45 ++++++++++++++++++++------------------------- 1 file changed, 20 insertions(+), 25 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 8125a63f1..e9d31b436 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -50,6 +50,7 @@ * * Bash compat TODO: * redirection of stdout+stderr: &> and >& + * subst operator: ${var/[/]expr/expr} * brace expansion: one/{two,three,four} * reserved words: function select * advanced test: [[ ]] @@ -1830,6 +1831,7 @@ 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) { @@ -2618,7 +2620,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char /* lookup the variable in question */ if (isdigit(var[0])) { - /* handle_dollar() should have vetted var for us */ + /* parse_dollar() should have vetted var for us */ i = xatoi_u(var); if (i < G.global_argc) val = G.global_argv[i]; @@ -4545,11 +4547,11 @@ static void debug_print_tree(struct pipe *pi, int lvl) fprintf(stderr, " group %s: (argv=%p)%s%s\n", CMDTYPE[command->cmd_type], argv -#if !BB_MMU +# if !BB_MMU , " group_as_string:", command->group_as_string -#else +# else , "", "" -#endif +# endif ); debug_print_tree(command->group, lvl+1); prn++; @@ -5988,18 +5990,18 @@ static int add_till_closing_bracket(o_string *dest, struct in_str *input, unsign /* Return code: 0 for OK, 1 for syntax error */ #if BB_MMU -#define handle_dollar(as_string, dest, input) \ - handle_dollar(dest, input) +#define parse_dollar(as_string, dest, input) \ + parse_dollar(dest, input) #define as_string NULL #endif -static int handle_dollar(o_string *as_string, +static int parse_dollar(o_string *as_string, o_string *dest, struct in_str *input) { int ch = i_peek(input); /* first character after the $ */ unsigned char quote_mask = dest->o_escape ? 0x80 : 0; - debug_printf_parse("handle_dollar entered: ch='%c'\n", ch); + debug_printf_parse("parse_dollar entered: ch='%c'\n", ch); if (isalpha(ch)) { ch = i_getch(input); nommu_addchr(as_string, ch); @@ -6047,7 +6049,7 @@ static int handle_dollar(o_string *as_string, if (!strchr(_SPECIAL_VARS_STR, ch) && !isalnum(ch)) { /* not one of those */ bad_dollar_syntax: syntax_error_unterm_str("${name}"); - debug_printf_parse("handle_dollar return 1: unterminated ${name}\n"); + debug_printf_parse("parse_dollar return 1: unterminated ${name}\n"); return 1; } ch |= quote_mask; @@ -6129,13 +6131,11 @@ static int handle_dollar(o_string *as_string, if (!BB_MMU) pos = dest->length; add_till_closing_bracket(dest, input, ')' | DOUBLE_CLOSE_CHAR_FLAG); -#if !BB_MMU if (as_string) { o_addstr(as_string, dest->data + pos); o_addchr(as_string, ')'); o_addchr(as_string, ')'); } -#endif o_addchr(dest, SPECIAL_VAR_SYMBOL); break; } @@ -6146,12 +6146,10 @@ static int handle_dollar(o_string *as_string, if (!BB_MMU) pos = dest->length; add_till_closing_bracket(dest, input, ')'); -#if !BB_MMU if (as_string) { o_addstr(as_string, dest->data + pos); o_addchr(as_string, ')'); } -#endif o_addchr(dest, SPECIAL_VAR_SYMBOL); # endif break; @@ -6174,7 +6172,7 @@ static int handle_dollar(o_string *as_string, default: o_addQchr(dest, '$'); } - debug_printf_parse("handle_dollar return 0\n"); + debug_printf_parse("parse_dollar return 0\n"); return 0; #undef as_string } @@ -6237,9 +6235,9 @@ static int parse_stream_dquoted(o_string *as_string, goto again; } if (ch == '$') { - if (handle_dollar(as_string, dest, input) != 0) { + if (parse_dollar(as_string, dest, input) != 0) { debug_printf_parse("parse_stream_dquoted return 1: " - "handle_dollar returned non-0\n"); + "parse_dollar returned non-0\n"); return 1; } goto again; @@ -6602,9 +6600,9 @@ static struct pipe *parse_stream(char **pstring, #endif break; case '$': - if (handle_dollar(&ctx.as_string, &dest, input) != 0) { + if (parse_dollar(&ctx.as_string, &dest, input) != 0) { debug_printf_parse("parse_stream parse error: " - "handle_dollar returned non-0\n"); + "parse_dollar returned non-0\n"); goto parse_error; } break; @@ -6630,19 +6628,16 @@ static struct pipe *parse_stream(char **pstring, break; #if ENABLE_HUSH_TICK case '`': { -#if !BB_MMU - int pos; -#endif + unsigned pos; + o_addchr(&dest, SPECIAL_VAR_SYMBOL); o_addchr(&dest, '`'); -#if !BB_MMU pos = dest.length; -#endif add_till_backquote(&dest, input); -#if !BB_MMU +# if !BB_MMU o_addstr(&ctx.as_string, dest.data + pos); o_addchr(&ctx.as_string, '`'); -#endif +# endif o_addchr(&dest, SPECIAL_VAR_SYMBOL); //debug_printf_subst("SUBST RES3 '%s'\n", dest.data + pos); break; -- cgit v1.2.3-55-g6feb From 8391c4800cda0eda6e64c464cc0ea85d1785a768 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sat, 22 May 2010 17:50:43 +0200 Subject: hush: trivial code shrink function old new delta static_get 26 22 -4 Signed-off-by: Denys Vlasenko --- shell/hush.c | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index e9d31b436..72cfb232a 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -814,10 +814,10 @@ static void xxfree(void *ptr) fdprintf(2, "free %p\n", ptr); free(ptr); } -#define xmalloc(s) xxmalloc(__LINE__, s) -#define xrealloc(p, s) xxrealloc(__LINE__, p, s) -#define xstrdup(s) xxstrdup(__LINE__, s) -#define free(p) xxfree(p) +# define xmalloc(s) xxmalloc(__LINE__, s) +# define xrealloc(p, s) xxrealloc(__LINE__, p, s) +# define xstrdup(s) xxstrdup(__LINE__, s) +# define free(p) xxfree(p) #endif @@ -1161,9 +1161,9 @@ static void SIGCHLD_handler(int sig UNUSED_PARAM) #if ENABLE_HUSH_JOB /* After [v]fork, in child: do not restore tty pgrp on xfunc death */ -#define disable_restore_tty_pgrp_on_exit() (die_sleep = 0) +# define disable_restore_tty_pgrp_on_exit() (die_sleep = 0) /* After [v]fork, in parent: restore tty pgrp on xfunc death */ -#define enable_restore_tty_pgrp_on_exit() (die_sleep = -1) +# define enable_restore_tty_pgrp_on_exit() (die_sleep = -1) /* Restores tty foreground process group, and exits. * May be called as signal handler for fatal signal @@ -1189,8 +1189,8 @@ static void sigexit(int sig) } #else -#define disable_restore_tty_pgrp_on_exit() ((void)0) -#define enable_restore_tty_pgrp_on_exit() ((void)0) +# define disable_restore_tty_pgrp_on_exit() ((void)0) +# define enable_restore_tty_pgrp_on_exit() ((void)0) #endif @@ -1522,8 +1522,8 @@ static void unset_vars(char **strings) } #if ENABLE_SH_MATH_SUPPORT -#define is_name(c) ((c) == '_' || isalpha((unsigned char)(c))) -#define is_in_name(c) ((c) == '_' || isalnum((unsigned char)(c))) +# define is_name(c) ((c) == '_' || isalpha((unsigned char)(c))) +# define is_in_name(c) ((c) == '_' || isalnum((unsigned char)(c))) static char* FAST_FUNC endofname(const char *name) { char *p; @@ -1607,10 +1607,11 @@ static struct variable *set_vars_and_save_old(char **strings) */ static int FAST_FUNC static_get(struct in_str *i) { - int ch = *i->p++; - if (ch != '\0') + int ch = *i->p; + if (ch != '\0') { + i->p++; return ch; - i->p--; + } return EOF; } @@ -1662,7 +1663,7 @@ static void get_user_input(struct in_str *i) const char *prompt_str; prompt_str = setup_prompt_string(i->promptmode); -#if ENABLE_FEATURE_EDITING +# if ENABLE_FEATURE_EDITING /* Enable command line editing only while a command line * is actually being read */ do { @@ -1678,7 +1679,7 @@ static void get_user_input(struct in_str *i) G.user_input_buf[0] = EOF; /* yes, it will be truncated, it's ok */ G.user_input_buf[1] = '\0'; } -#else +# else do { G.flag_SIGINT = 0; fputs(prompt_str, stdout); @@ -1688,7 +1689,7 @@ static void get_user_input(struct in_str *i) //do we need check_and_run_traps(0)? (maybe only if stdin) } while (G.flag_SIGINT); i->eof_flag = (r == EOF); -#endif +# endif i->p = G.user_input_buf; } @@ -2221,7 +2222,7 @@ static int o_glob(o_string *o, int n) return n; } -#else +#else /* !HUSH_BRACE_EXP */ /* Helper */ static int glob_needed(const char *s) @@ -2298,7 +2299,7 @@ static int o_glob(o_string *o, int n) return n; } -#endif +#endif /* !HUSH_BRACE_EXP */ /* If o->o_glob == 1, glob the string so far remembered. * Otherwise, just finish current list[] and start new */ @@ -3033,7 +3034,7 @@ static void re_execute_shell(char ***to_free, const char *s, char *g_argv0, char **g_argv, char **builtin_argv) { -#define NOMMU_HACK_FMT ("-$%x:%x:%x:%x:%x:%llx" IF_HUSH_LOOPS(":%x")) +# define NOMMU_HACK_FMT ("-$%x:%x:%x:%x:%x:%llx" IF_HUSH_LOOPS(":%x")) /* delims + 2 * (number of bytes in printed hex numbers) */ char param_buf[sizeof(NOMMU_HACK_FMT) + 2 * (sizeof(int)*6 + sizeof(long long)*1)]; char *heredoc_argv[4]; @@ -3078,7 +3079,7 @@ static void re_execute_shell(char ***to_free, const char *s, , empty_trap_mask IF_HUSH_LOOPS(, G.depth_of_loop) ); -#undef NOMMU_HACK_FMT +# undef NOMMU_HACK_FMT /* 1:hush 2:-$::: * 3:-c 4: 5: 6:NULL */ -- cgit v1.2.3-55-g6feb