[git commit] hush: fix ${##}, ${#?}, ${#!} handling

Denys Vlasenko vda.linux at googlemail.com
Tue Jul 25 22:07:27 UTC 2017


commit: https://git.busybox.net/busybox/commit/?id=2093ad296f8a4528ad0e106b52074871a2bf070e
branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master

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 at googlemail.com>
---
 shell/ash_test/ash-vars/param_expand_alt.tests    |  2 +-
 shell/ash_test/ash-vars/param_expand_len1.right   | 11 ++++++++
 shell/ash_test/ash-vars/param_expand_len1.tests   | 31 +++++++++++++++++++++++
 shell/hush.c                                      | 27 +++++++++++++++-----
 shell/hush_test/hush-vars/param_expand_alt.tests  |  2 +-
 shell/hush_test/hush-vars/param_expand_len1.right | 11 ++++++++
 shell/hush_test/hush-vars/param_expand_len1.tests | 31 +++++++++++++++++++++++
 7 files changed, 106 insertions(+), 9 deletions(-)

diff --git a/shell/ash_test/ash-vars/param_expand_alt.tests b/shell/ash_test/ash-vars/param_expand_alt.tests
index c9c4249..d804524 100755
--- a/shell/ash_test/ash-vars/param_expand_alt.tests
+++ b/shell/ash_test/ash-vars/param_expand_alt.tests
@@ -6,7 +6,7 @@
 # now some funky ones.
 # ${V+word} "if V unset, 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 _${#+}_ _${#:+}_
 # Forms with non-empty word work as expected in both ash and bash.
 echo _${#+z}_ _${#:+z}_
diff --git a/shell/ash_test/ash-vars/param_expand_len1.right b/shell/ash_test/ash-vars/param_expand_len1.right
new file mode 100644
index 0000000..dff3c7b
--- /dev/null
+++ b/shell/ash_test/ash-vars/param_expand_len1.right
@@ -0,0 +1,11 @@
+One:1
+Two:2
+Three:3
+
+One:1
+Two:2
+Three:3
+
+Ok ${#$}: 0
+
+Ok ${#!}: 0
diff --git a/shell/ash_test/ash-vars/param_expand_len1.tests b/shell/ash_test/ash-vars/param_expand_len1.tests
new file mode 100755
index 0000000..e1beab3
--- /dev/null
+++ b/shell/ash_test/ash-vars/param_expand_len1.tests
@@ -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: ${#-} ${#_}
diff --git a/shell/hush.c b/shell/hush.c
index 11b33f4..d0225ed 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -4466,6 +4466,8 @@ static int parse_dollar(o_string *as_string,
 	case '@': /* args */
 		goto make_one_char_var;
 	case '{': {
+		char len_single_ch;
+
 		o_addchr(dest, SPECIAL_VAR_SYMBOL);
 
 		ch = i_getch(input); /* eat '{' */
@@ -4485,6 +4487,7 @@ static int parse_dollar(o_string *as_string,
 			return 0;
 		}
 		nommu_addchr(as_string, ch);
+		len_single_ch = ch;
 		ch |= quote_mask;
 
 		/* 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
 				 * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_02
 				 */
-				if (!strchr(VAR_SUBST_OPS, ch)) /* ${var<bad_char>... */
-					goto bad_dollar_syntax;
-
+				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;
+					}
+					/* else: it's "length of C" ${#C} op,
+					 * where C is a single char
+					 * special var name, e.g. ${#!}.
+					 */
+				}
 				/* Eat everything until closing '}' (or ':') */
 				end_ch = '}';
 				if (BASH_SUBSTR
@@ -4568,6 +4580,7 @@ static int parse_dollar(o_string *as_string,
 				}
 				break;
 			}
+			len_single_ch = 0; /* it can't be ${#C} op */
 		}
 		o_addchr(dest, SPECIAL_VAR_SYMBOL);
 		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;
 	exp_op = 0;
 
-	if (first_char == '#' && arg[1] /* ${#... but not ${#} */
-	 && (!exp_saveptr               /* and (not ${#<op_char>...} */
-	    || (arg[1] == '?' && arg[2] == '\0') /* or ${#?} - "len of $?") */
-	    )
+	if (first_char == '#' && arg[1] /* ${#...} but not ${#} */
+	 && (!exp_saveptr               /* and ( not(${#<op_char>...}) */
+	    || (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} */
 		var++;
diff --git a/shell/hush_test/hush-vars/param_expand_alt.tests b/shell/hush_test/hush-vars/param_expand_alt.tests
index c9c4249..d804524 100755
--- a/shell/hush_test/hush-vars/param_expand_alt.tests
+++ b/shell/hush_test/hush-vars/param_expand_alt.tests
@@ -6,7 +6,7 @@
 # now some funky ones.
 # ${V+word} "if V unset, 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 _${#+}_ _${#:+}_
 # Forms with non-empty word work as expected in both ash and bash.
 echo _${#+z}_ _${#:+z}_
diff --git a/shell/hush_test/hush-vars/param_expand_len1.right b/shell/hush_test/hush-vars/param_expand_len1.right
new file mode 100644
index 0000000..dff3c7b
--- /dev/null
+++ b/shell/hush_test/hush-vars/param_expand_len1.right
@@ -0,0 +1,11 @@
+One:1
+Two:2
+Three:3
+
+One:1
+Two:2
+Three:3
+
+Ok ${#$}: 0
+
+Ok ${#!}: 0
diff --git a/shell/hush_test/hush-vars/param_expand_len1.tests b/shell/hush_test/hush-vars/param_expand_len1.tests
new file mode 100755
index 0000000..e1beab3
--- /dev/null
+++ b/shell/hush_test/hush-vars/param_expand_len1.tests
@@ -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: ${#-} ${#_}


More information about the busybox-cvs mailing list