hush: fix ${##}, ${#?}, ${#!} handling

function                                             old     new   delta
parse_dollar                                         786     820     +34
expand_one_var                                      1579    1592     +13
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 2/0 up/down: 47/0)               Total: 47 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
Denys Vlasenko 2017-07-26 00:07:27 +02:00
parent 1e3e2ccd5d
commit 2093ad296f
7 changed files with 106 additions and 9 deletions

View File

@ -6,7 +6,7 @@
# now some funky ones. # now some funky ones.
# ${V+word} "if V unset, then substitute nothing, else substitute word" # ${V+word} "if V unset, then substitute nothing, else substitute word"
# ${V:+word} "if V unset or '', then substitute nothing, else substitute word" # ${V:+word} "if V unset or '', then substitute nothing, else substitute word"
# bash doesn't accept ${#+}. ash prints 0 (not $#). # bash doesn't accept ${#+}. ash prints 0 (not $#): "len of $+"
echo _${#+}_ _${#:+}_ echo _${#+}_ _${#:+}_
# Forms with non-empty word work as expected in both ash and bash. # Forms with non-empty word work as expected in both ash and bash.
echo _${#+z}_ _${#:+z}_ echo _${#+z}_ _${#:+z}_

View File

@ -0,0 +1,11 @@
One:1
Two:2
Three:3
One:1
Two:2
Three:3
Ok ${#$}: 0
Ok ${#!}: 0

View File

@ -0,0 +1,31 @@
# ${#c} for any single char c means "length of $c", including all special vars
false
echo One:${#?}
(exit 10)
echo Two:${#?}
(exit 100)
echo Three:${#?}
echo
echo One:${##}
set -- 1 2 3 4 5 6 7 8 9 0
echo Two:${##}
set -- 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 \
1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 \
1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 \
1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
echo Three:${##}
echo
v=$$
test "${#v}" = "${#$}"
echo 'Ok ${#$}:' $?
echo
sleep 0 &
v=$!
test "${#v}" = "${#!}"
echo 'Ok ${#!}:' $?
# TODO: ${#-} ${#_}

View File

@ -4466,6 +4466,8 @@ static int parse_dollar(o_string *as_string,
case '@': /* args */ case '@': /* args */
goto make_one_char_var; goto make_one_char_var;
case '{': { case '{': {
char len_single_ch;
o_addchr(dest, SPECIAL_VAR_SYMBOL); o_addchr(dest, SPECIAL_VAR_SYMBOL);
ch = i_getch(input); /* eat '{' */ ch = i_getch(input); /* eat '{' */
@ -4485,6 +4487,7 @@ static int parse_dollar(o_string *as_string,
return 0; return 0;
} }
nommu_addchr(as_string, ch); nommu_addchr(as_string, ch);
len_single_ch = ch;
ch |= quote_mask; ch |= quote_mask;
/* It's possible to just call add_till_closing_bracket() at this point. /* It's possible to just call add_till_closing_bracket() at this point.
@ -4509,9 +4512,18 @@ static int parse_dollar(o_string *as_string,
/* 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 (!strchr(VAR_SUBST_OPS, ch)) /* ${var<bad_char>... */ if (!strchr(VAR_SUBST_OPS, ch)) { /* ${var<bad_char>... */
if (len_single_ch != '#'
/*|| !strchr(SPECIAL_VARS_STR, ch) - disallow errors like ${#+} ? */
|| i_peek(input) != '}'
) {
goto bad_dollar_syntax; goto bad_dollar_syntax;
}
/* else: it's "length of C" ${#C} op,
* where C is a single char
* special var name, e.g. ${#!}.
*/
}
/* Eat everything until closing '}' (or ':') */ /* Eat everything until closing '}' (or ':') */
end_ch = '}'; end_ch = '}';
if (BASH_SUBSTR if (BASH_SUBSTR
@ -4568,6 +4580,7 @@ static int parse_dollar(o_string *as_string,
} }
break; break;
} }
len_single_ch = 0; /* it can't be ${#C} op */
} }
o_addchr(dest, SPECIAL_VAR_SYMBOL); o_addchr(dest, SPECIAL_VAR_SYMBOL);
break; break;
@ -5559,10 +5572,10 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
first_char = arg[0] = arg0 & 0x7f; first_char = arg[0] = arg0 & 0x7f;
exp_op = 0; exp_op = 0;
if (first_char == '#' && arg[1] /* ${#... but not ${#} */ if (first_char == '#' && arg[1] /* ${#...} but not ${#} */
&& (!exp_saveptr /* and (not ${#<op_char>...} */ && (!exp_saveptr /* and ( not(${#<op_char>...}) */
|| (arg[1] == '?' && arg[2] == '\0') /* or ${#?} - "len of $?") */ || (arg[2] == '\0' && strchr(SPECIAL_VARS_STR, arg[1])) /* or ${#C} "len of $C" ) */
) ) /* NB: skipping ^^^specvar check mishandles ${#::2} */
) { ) {
/* It must be length operator: ${#var} */ /* It must be length operator: ${#var} */
var++; var++;

View File

@ -6,7 +6,7 @@
# now some funky ones. # now some funky ones.
# ${V+word} "if V unset, then substitute nothing, else substitute word" # ${V+word} "if V unset, then substitute nothing, else substitute word"
# ${V:+word} "if V unset or '', then substitute nothing, else substitute word" # ${V:+word} "if V unset or '', then substitute nothing, else substitute word"
# bash doesn't accept ${#+}. ash prints 0 (not $#). # bash doesn't accept ${#+}. ash prints 0 (not $#): "len of $+"
echo _${#+}_ _${#:+}_ echo _${#+}_ _${#:+}_
# Forms with non-empty word work as expected in both ash and bash. # Forms with non-empty word work as expected in both ash and bash.
echo _${#+z}_ _${#:+z}_ echo _${#+z}_ _${#:+z}_

View File

@ -0,0 +1,11 @@
One:1
Two:2
Three:3
One:1
Two:2
Three:3
Ok ${#$}: 0
Ok ${#!}: 0

View File

@ -0,0 +1,31 @@
# ${#c} for any single char c means "length of $c", including all special vars
false
echo One:${#?}
(exit 10)
echo Two:${#?}
(exit 100)
echo Three:${#?}
echo
echo One:${##}
set -- 1 2 3 4 5 6 7 8 9 0
echo Two:${##}
set -- 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 \
1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 \
1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 \
1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
echo Three:${##}
echo
v=$$
test "${#v}" = "${#$}"
echo 'Ok ${#$}:' $?
echo
sleep 0 &
v=$!
test "${#v}" = "${#!}"
echo 'Ok ${#!}:' $?
# TODO: ${#-} ${#_}