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 <vda.linux@googlemail.com>
This commit is contained in:
parent
53b513331a
commit
4d8e5fdc1d
84
shell/hush.c
84
shell/hush.c
@ -46,7 +46,6 @@
|
|||||||
* brace expansion: one/{two,three,four}
|
* brace expansion: one/{two,three,four}
|
||||||
* reserved words: function select
|
* reserved words: function select
|
||||||
* advanced test: [[ ]]
|
* advanced test: [[ ]]
|
||||||
* substrings: ${var:1:5}
|
|
||||||
* process substitution: <(list) and >(list)
|
* process substitution: <(list) and >(list)
|
||||||
* =~: regex operator
|
* =~: regex operator
|
||||||
* let EXPR [EXPR...]
|
* let EXPR [EXPR...]
|
||||||
@ -2586,7 +2585,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char
|
|||||||
var++;
|
var++;
|
||||||
} else {
|
} else {
|
||||||
/* maybe handle parameter expansion */
|
/* maybe handle parameter expansion */
|
||||||
exp_saveptr = var + strcspn(var, ":-=+?%#");
|
exp_saveptr = var + strcspn(var, "%#:-=+?");
|
||||||
exp_save = *exp_saveptr;
|
exp_save = *exp_saveptr;
|
||||||
if (exp_save) {
|
if (exp_save) {
|
||||||
exp_word = exp_saveptr;
|
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 */
|
else if (loc) /* % or %% and match was found */
|
||||||
*loc = '\0';
|
*loc = '\0';
|
||||||
}
|
}
|
||||||
} else { /* one of :-=+? */
|
} else if (!strchr("%#:-=+?"+3, exp_op)) {
|
||||||
//TODO: check validity of exp_op. Currently it can be anything.
|
#if ENABLE_HUSH_BASH_COMPAT
|
||||||
//TODO: handle ${VAR:N[:M]} here. N, M can be expressions similar to $((EXPR)): 2+2, 2+var etc
|
/* 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:
|
/* Standard-mandated substitution ops:
|
||||||
* ${var?word} - indicate error if unset
|
* ${var?word} - indicate error if unset
|
||||||
* If var is unset, word (or a message indicating it is 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;
|
*exp_saveptr = exp_save;
|
||||||
}
|
} /* if (exp_op) */
|
||||||
|
|
||||||
arg[0] = first_ch;
|
arg[0] = first_ch;
|
||||||
#if ENABLE_HUSH_TICK
|
#if ENABLE_HUSH_TICK
|
||||||
@ -5926,14 +5954,15 @@ static int handle_dollar(o_string *as_string,
|
|||||||
goto make_one_char_var;
|
goto make_one_char_var;
|
||||||
case '{': {
|
case '{': {
|
||||||
bool first_char, all_digits;
|
bool first_char, all_digits;
|
||||||
int expansion;
|
bool in_expansion_param;
|
||||||
|
|
||||||
ch = i_getch(input);
|
ch = i_getch(input);
|
||||||
nommu_addchr(as_string, ch);
|
nommu_addchr(as_string, ch);
|
||||||
o_addchr(dest, SPECIAL_VAR_SYMBOL);
|
o_addchr(dest, SPECIAL_VAR_SYMBOL);
|
||||||
|
|
||||||
/* TODO: maybe someone will try to escape the '}' */
|
// TODO: need to handle "a=ab}; echo ${a%\}}"
|
||||||
expansion = 0;
|
// and "a=abc; c=c; echo ${a%${c}}"
|
||||||
|
in_expansion_param = false;
|
||||||
first_char = true;
|
first_char = true;
|
||||||
all_digits = false;
|
all_digits = false;
|
||||||
while (1) {
|
while (1) {
|
||||||
@ -5957,45 +5986,22 @@ static int handle_dollar(o_string *as_string,
|
|||||||
goto char_ok;
|
goto char_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (expansion < 2
|
if (!in_expansion_param
|
||||||
&& ( (all_digits && !isdigit(ch))
|
&& ( (all_digits && !isdigit(ch)) /* met non-digit: 123w */
|
||||||
|| (!all_digits && !isalnum(ch) && ch != '_')
|
|| (!all_digits && !isalnum(ch) && ch != '_') /* met non-name char: abc% */
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
/* handle parameter expansions
|
/* handle parameter expansions
|
||||||
* http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_02
|
* http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_02
|
||||||
*/
|
*/
|
||||||
if (first_char)
|
if (first_char /* bad (empty var name): "${%..." */
|
||||||
goto case_default;
|
|| !strchr("%#:-=+?", ch) /* bad: "${var<bad_char>..." */
|
||||||
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:
|
|
||||||
syntax_error_unterm_str("${name}");
|
syntax_error_unterm_str("${name}");
|
||||||
debug_printf_parse("handle_dollar return 1: unterminated ${name}\n");
|
debug_printf_parse("handle_dollar return 1: unterminated ${name}\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
in_expansion_param = true;
|
||||||
}
|
}
|
||||||
char_ok:
|
char_ok:
|
||||||
debug_printf_parse(": '%c'\n", ch);
|
debug_printf_parse(": '%c'\n", ch);
|
||||||
|
29
shell/hush_test/hush-vars/param_expand_bash_substring.right
Normal file
29
shell/hush_test/hush-vars/param_expand_bash_substring.right
Normal file
@ -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|
|
46
shell/hush_test/hush-vars/param_expand_bash_substring.tests
Executable file
46
shell/hush_test/hush-vars/param_expand_bash_substring.tests
Executable file
@ -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}|"'
|
Loading…
x
Reference in New Issue
Block a user