aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDenys Vlasenko <vda.linux@googlemail.com>2020-10-31 03:34:07 +0100
committerDenys Vlasenko <vda.linux@googlemail.com>2020-10-31 03:34:07 +0100
commitd2241f59022c38d4b171e56eea42e216ecccfdd9 (patch)
treeedb75c2530f493c9e3f193f346d8125fe79c107f
parent112453acf24520b4655f9f36da41d8ac591b1a60 (diff)
downloadbusybox-d2241f59022c38d4b171e56eea42e216ecccfdd9.tar.gz
shell: better support of [[ ]] bashism
Still rather rudimentary for ash function old new delta binop 433 589 +156 check_operator 65 101 +36 done_word 736 769 +33 test_main 405 418 +13 parse_stream 2227 2238 +11 ops_texts 124 133 +9 ops_table 80 86 +6 run_pipe 1557 1562 +5 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 8/0 up/down: 269/0) Total: 269 bytes Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
-rw-r--r--coreutils/test.c82
-rw-r--r--shell/ash.c15
-rw-r--r--shell/hush.c57
-rw-r--r--shell/hush_test/hush-test2/andor1.right6
-rwxr-xr-xshell/hush_test/hush-test2/andor1.tests7
-rw-r--r--shell/hush_test/hush-test2/noglob1.right2
-rwxr-xr-xshell/hush_test/hush-test2/noglob1.tests3
-rw-r--r--shell/hush_test/hush-test2/strops1.right8
-rwxr-xr-xshell/hush_test/hush-test2/strops1.tests15
-rw-r--r--shell/hush_test/hush-test2/strops2.right6
-rwxr-xr-xshell/hush_test/hush-test2/strops2.tests12
-rw-r--r--shell/hush_test/hush-test2/strops3.right7
-rwxr-xr-xshell/hush_test/hush-test2/strops3.tests13
13 files changed, 209 insertions, 24 deletions
diff --git a/coreutils/test.c b/coreutils/test.c
index a08986130..ac7b546a3 100644
--- a/coreutils/test.c
+++ b/coreutils/test.c
@@ -76,6 +76,8 @@
//usage: "1\n"
#include "libbb.h"
+#include <regex.h>
+#include <fnmatch.h>
/* This is a NOFORK applet. Be very careful! */
@@ -146,6 +148,14 @@
#define TEST_DEBUG 0
+#if ENABLE_TEST2 \
+ || (ENABLE_ASH_BASH_COMPAT && ENABLE_ASH_TEST) \
+ || (ENABLE_HUSH_BASH_COMPAT && ENABLE_HUSH_TEST)
+# define BASH_TEST2 1
+#else
+# define BASH_TEST2 0
+#endif
+
enum token {
EOI,
@@ -184,6 +194,10 @@ enum token {
STRLT,
STRGT,
+#if BASH_TEST2
+ REGEX,
+#endif
+
INTEQ, /* int ops */
INTNE,
INTGE,
@@ -257,6 +271,9 @@ static const char *const TOKSTR[] = {
"STRNE",
"STRLT",
"STRGT",
+#if BASH_TEST2
+ "REGEX",
+#endif
"INTEQ",
"INTNE",
"INTGE",
@@ -320,6 +337,9 @@ static const struct operator_t ops_table[] = {
{ /* "!=" */ STRNE , BINOP },
{ /* "<" */ STRLT , BINOP },
{ /* ">" */ STRGT , BINOP },
+#if BASH_TEST2
+ { /* "=~" */ REGEX , BINOP },
+#endif
{ /* "-eq"*/ INTEQ , BINOP },
{ /* "-ne"*/ INTNE , BINOP },
{ /* "-ge"*/ INTGE , BINOP },
@@ -332,6 +352,10 @@ static const struct operator_t ops_table[] = {
{ /* "!" */ UNOT , BUNOP },
{ /* "-a" */ BAND , BBINOP },
{ /* "-o" */ BOR , BBINOP },
+#if BASH_TEST2
+ { /* "&&" */ BAND , BBINOP },
+ { /* "||" */ BOR , BBINOP },
+#endif
{ /* "(" */ LPAREN , PAREN },
{ /* ")" */ RPAREN , PAREN },
};
@@ -365,6 +389,9 @@ static const char ops_texts[] ALIGN1 =
"!=" "\0"
"<" "\0"
">" "\0"
+#if BASH_TEST2
+ "=~" "\0"
+#endif
"-eq" "\0"
"-ne" "\0"
"-ge" "\0"
@@ -377,6 +404,10 @@ static const char ops_texts[] ALIGN1 =
"!" "\0"
"-a" "\0"
"-o" "\0"
+#if BASH_TEST2
+ "&&" "\0"
+ "||" "\0"
+#endif
"(" "\0"
")" "\0"
;
@@ -397,6 +428,9 @@ struct test_statics {
const struct operator_t *last_operator;
gid_t *group_array;
int ngroups;
+#if BASH_TEST2
+ bool bash_test2;
+#endif
jmp_buf leaving;
};
@@ -408,6 +442,7 @@ extern struct test_statics *const test_ptr_to_statics;
#define last_operator (S.last_operator)
#define group_array (S.group_array )
#define ngroups (S.ngroups )
+#define bash_test2 (S.bash_test2 )
#define leaving (S.leaving )
#define INIT_S() do { \
@@ -501,6 +536,20 @@ static enum token check_operator(const char *s)
n = index_in_strings(ops_texts, s);
if (n < 0)
return OPERAND;
+
+#if BASH_TEST2
+ if (ops_table[n].op_num == REGEX && !bash_test2) {
+ /* =~ is only for [[ ]] */
+ return OPERAND;
+ }
+ if (ops_table[n].op_num == BAND || ops_table[n].op_num == BOR) {
+ /* [ ] accepts -a and -o but not && and || */
+ /* [[ ]] accepts && and || but not -a and -o */
+ if (bash_test2 == (s[0] == '-'))
+ return OPERAND;
+ }
+#endif
+
last_operator = &ops_table[n];
return ops_table[n].op_num;
}
@@ -536,6 +585,29 @@ static int binop(void)
/*if (op->op_num == INTLT)*/
return val1 < val2;
}
+#if BASH_TEST2
+ if (bash_test2) {
+ if (op->op_num == STREQ) {
+ val1 = fnmatch(opnd2, opnd1, 0);
+ return val1 == 0;
+ }
+ if (op->op_num == STRNE) {
+ val1 = fnmatch(opnd2, opnd1, 0);
+ return val1 != 0;
+ }
+ if (op->op_num == REGEX) {
+ regex_t re_buffer;
+ memset(&re_buffer, 0, sizeof(re_buffer));
+ if (regcomp(&re_buffer, opnd2, REG_EXTENDED)) { // REG_NEWLINE?
+ /* Bad regex */
+ longjmp(leaving, 2); /* [[ a =~ * ]]; echo $? - prints 2 (silently, no error msg) */
+ }
+ val1 = regexec(&re_buffer, opnd1, 0, NULL, 0);
+ regfree(&re_buffer);
+ return val1 == 0;
+ }
+ }
+#endif
if (is_str_op(op->op_num)) {
val1 = strcmp(opnd1, opnd2);
if (op->op_num == STREQ)
@@ -824,6 +896,9 @@ int test_main(int argc, char **argv)
{
int res;
const char *arg0;
+#if BASH_TEST2
+ bool bt2 = 0;
+#endif
arg0 = bb_basename(argv[0]);
if ((ENABLE_TEST1 || ENABLE_TEST2 || ENABLE_ASH_TEST || ENABLE_HUSH_TEST)
@@ -840,6 +915,9 @@ int test_main(int argc, char **argv)
bb_simple_error_msg("missing ]]");
return 2;
}
+#if BASH_TEST2
+ bt2 = 1;
+#endif
}
argv[argc] = NULL;
}
@@ -848,6 +926,10 @@ int test_main(int argc, char **argv)
/* We must do DEINIT_S() prior to returning */
INIT_S();
+#if BASH_TEST2
+ bash_test2 = bt2;
+#endif
+
res = setjmp(leaving);
if (res)
goto ret;
diff --git a/shell/ash.c b/shell/ash.c
index 58da0a2a0..cfcc0b818 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -207,17 +207,17 @@
#define IF_BASH_SUBSTR IF_ASH_BASH_COMPAT
/* BASH_TEST2: [[ EXPR ]]
* Status of [[ support:
- * We replace && and || with -a and -o
+ * && and || work as they should
+ * = is glob match operator, not equality operator: STR = GLOB
+ * (in GLOB, quoting is significant on char-by-char basis: a*cd"*")
+ * == same as =
+ * add =~ regex match operator: STR =~ REGEX
* TODO:
* singleword+noglob expansion:
* v='a b'; [[ $v = 'a b' ]]; echo 0:$?
* [[ /bin/n* ]]; echo 0:$?
- * -a/-o are not AND/OR ops! (they are just strings)
* quoting needs to be considered (-f is an operator, "-f" and ""-f are not; etc)
- * = is glob match operator, not equality operator: STR = GLOB
- * (in GLOB, quoting is significant on char-by-char basis: a*cd"*")
- * == same as =
- * add =~ regex match operator: STR =~ REGEX
+ * ( ) < > should not have special meaning
*/
#define BASH_TEST2 (ENABLE_ASH_BASH_COMPAT * ENABLE_ASH_TEST)
#define BASH_SOURCE ENABLE_ASH_BASH_COMPAT
@@ -11823,7 +11823,8 @@ simplecmd(void)
tokpushback = 1;
goto out;
}
- wordtext = (char *) (t == TAND ? "-a" : "-o");
+ /* pass "&&" or "||" to [[ ]] as literal args */
+ wordtext = (char *) (t == TAND ? "&&" : "||");
#endif
case TWORD:
n = stzalloc(sizeof(struct narg));
diff --git a/shell/hush.c b/shell/hush.c
index bc6e6014f..7c1e1d748 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -84,13 +84,12 @@
* [[ args ]] are CMD_SINGLEWORD_NOGLOB:
* v='a b'; [[ $v = 'a b' ]]; echo 0:$?
* [[ /bin/n* ]]; echo 0:$?
+ * = is glob match operator, not equality operator: STR = GLOB
+ * (in GLOB, quoting is significant on char-by-char basis: a*cd"*")
+ * == same as =
+ * =~ is regex match operator: STR =~ REGEX
* TODO:
- * &&/|| are AND/OR ops, -a/-o are not
* quoting needs to be considered (-f is an operator, "-f" and ""-f are not; etc)
- * = is glob match operator, not equality operator: STR = GLOB
- * (in GLOB, quoting is significant on char-by-char basis: a*cd"*")
- * == same as =
- * add =~ regex match operator: STR =~ REGEX
*/
//config:config HUSH
//config: bool "hush (68 kb)"
@@ -651,14 +650,16 @@ struct command {
smallint cmd_type; /* CMD_xxx */
#define CMD_NORMAL 0
#define CMD_SUBSHELL 1
-#if BASH_TEST2 || ENABLE_HUSH_LOCAL || ENABLE_HUSH_EXPORT || ENABLE_HUSH_READONLY
-/* used for "[[ EXPR ]]", and to prevent word splitting and globbing in
- * "export v=t*"
- */
-# define CMD_SINGLEWORD_NOGLOB 2
+#if BASH_TEST2
+/* used for "[[ EXPR ]]" */
+# define CMD_TEST2_SINGLEWORD_NOGLOB 2
+#endif
+#if ENABLE_HUSH_LOCAL || ENABLE_HUSH_EXPORT || ENABLE_HUSH_READONLY
+/* used to prevent word splitting and globbing in "export v=t*" */
+# define CMD_SINGLEWORD_NOGLOB 3
#endif
#if ENABLE_HUSH_FUNCTIONS
-# define CMD_FUNCDEF 3
+# define CMD_FUNCDEF 4
#endif
smalluint cmd_exitcode;
@@ -4112,6 +4113,14 @@ static int done_word(struct parse_context *ctx)
ctx->ctx_dsemicolon = 0;
} else
# endif
+# if defined(CMD_TEST2_SINGLEWORD_NOGLOB)
+ if (command->cmd_type == CMD_TEST2_SINGLEWORD_NOGLOB
+ && strcmp(ctx->word.data, "]]") == 0
+ ) {
+ /* allow "[[ ]] >file" etc */
+ command->cmd_type = CMD_SINGLEWORD_NOGLOB;
+ } else
+# endif
if (!command->argv /* if it's the first word... */
# if ENABLE_HUSH_LOOPS
&& ctx->ctx_res_w != RES_FOR /* ...not after FOR or IN */
@@ -4146,11 +4155,13 @@ static int done_word(struct parse_context *ctx)
(ctx->ctx_res_w == RES_SNTX));
return (ctx->ctx_res_w == RES_SNTX);
}
+# if defined(CMD_TEST2_SINGLEWORD_NOGLOB)
+ if (strcmp(ctx->word.data, "[[") == 0) {
+ command->cmd_type = CMD_TEST2_SINGLEWORD_NOGLOB;
+ } else
+# endif
# if defined(CMD_SINGLEWORD_NOGLOB)
if (0
-# if BASH_TEST2
- || strcmp(ctx->word.data, "[[") == 0
-# endif
/* In bash, local/export/readonly are special, args
* are assignments and therefore expansion of them
* should be "one-word" expansion:
@@ -4172,7 +4183,8 @@ static int done_word(struct parse_context *ctx)
) {
command->cmd_type = CMD_SINGLEWORD_NOGLOB;
}
- /* fall through */
+# else
+ { /* empty block to pair "if ... else" */ }
# endif
}
#endif /* HAS_KEYWORDS */
@@ -5354,9 +5366,15 @@ static struct pipe *parse_stream(char **pstring,
if (ch != '\n')
next = i_peek_and_eat_bkslash_nl(input);
- is_special = "{}<>;&|()#" /* special outside of "str" */
+ is_special = "{}<>&|();#" /* special outside of "str" */
"$\"" IF_HUSH_TICK("`") /* always special */
SPECIAL_VAR_SYMBOL_STR;
+#if defined(CMD_TEST2_SINGLEWORD_NOGLOB)
+ if (ctx.command->cmd_type == CMD_TEST2_SINGLEWORD_NOGLOB) {
+ /* In [[ ]], {}<>&|() are not special */
+ is_special += 8;
+ } else
+#endif
/* Are { and } special here? */
if (ctx.command->argv /* word [word]{... - non-special */
|| ctx.word.length /* word{... - non-special */
@@ -6953,7 +6971,7 @@ static char **expand_strvec_to_strvec(char **argv)
return expand_variables(argv, EXP_FLAG_GLOB | EXP_FLAG_ESC_GLOB_CHARS);
}
-#if defined(CMD_SINGLEWORD_NOGLOB)
+#if defined(CMD_SINGLEWORD_NOGLOB) || defined(CMD_TEST2_SINGLEWORD_NOGLOB)
static char **expand_strvec_to_strvec_singleword_noglob(char **argv)
{
return expand_variables(argv, EXP_FLAG_SINGLEWORD);
@@ -9133,6 +9151,11 @@ static NOINLINE int run_pipe(struct pipe *pi)
}
/* Expand the rest into (possibly) many strings each */
+#if defined(CMD_TEST2_SINGLEWORD_NOGLOB)
+ if (command->cmd_type == CMD_TEST2_SINGLEWORD_NOGLOB)
+ argv_expanded = expand_strvec_to_strvec_singleword_noglob(argv + command->assignment_cnt);
+ else
+#endif
#if defined(CMD_SINGLEWORD_NOGLOB)
if (command->cmd_type == CMD_SINGLEWORD_NOGLOB)
argv_expanded = expand_strvec_to_strvec_singleword_noglob(argv + command->assignment_cnt);
diff --git a/shell/hush_test/hush-test2/andor1.right b/shell/hush_test/hush-test2/andor1.right
new file mode 100644
index 000000000..038c7a681
--- /dev/null
+++ b/shell/hush_test/hush-test2/andor1.right
@@ -0,0 +1,6 @@
+1:YES
+2:no
+3:YES
+4:YES
+5:no
+6:no
diff --git a/shell/hush_test/hush-test2/andor1.tests b/shell/hush_test/hush-test2/andor1.tests
new file mode 100755
index 000000000..c449de7e6
--- /dev/null
+++ b/shell/hush_test/hush-test2/andor1.tests
@@ -0,0 +1,7 @@
+e=''
+[[ a && b ]] && echo 1:YES
+[[ a && '' ]] || echo 2:no
+[[ a || b ]] && echo 3:YES
+[[ '' || b ]] && echo 4:YES
+[[ "" || "$e" ]] || echo 5:no
+[[ "" || $e ]] || echo 6:no
diff --git a/shell/hush_test/hush-test2/noglob1.right b/shell/hush_test/hush-test2/noglob1.right
new file mode 100644
index 000000000..d0c3f1d8e
--- /dev/null
+++ b/shell/hush_test/hush-test2/noglob1.right
@@ -0,0 +1,2 @@
+1:YES:0
+2:YES:0
diff --git a/shell/hush_test/hush-test2/noglob1.tests b/shell/hush_test/hush-test2/noglob1.tests
new file mode 100755
index 000000000..963bacbd3
--- /dev/null
+++ b/shell/hush_test/hush-test2/noglob1.tests
@@ -0,0 +1,3 @@
+v='*.tests'
+[[ *.tests ]]; echo 1:YES:$?
+[[ $v ]]; echo 2:YES:$?
diff --git a/shell/hush_test/hush-test2/strops1.right b/shell/hush_test/hush-test2/strops1.right
new file mode 100644
index 000000000..590496301
--- /dev/null
+++ b/shell/hush_test/hush-test2/strops1.right
@@ -0,0 +1,8 @@
+1:YES:0
+2:YES:0
+3:YES:0
+4:YES:0
+5:YES:0
+6:YES:0
+7:YES:0
+8:no:1
diff --git a/shell/hush_test/hush-test2/strops1.tests b/shell/hush_test/hush-test2/strops1.tests
new file mode 100755
index 000000000..bb24e2a2f
--- /dev/null
+++ b/shell/hush_test/hush-test2/strops1.tests
@@ -0,0 +1,15 @@
+v='*.z'
+[[ a.z = *.z ]]; echo 1:YES:$?
+[[ a.z == $v ]]; echo 2:YES:$?
+
+# wildcards can match a slash
+[[ a/b = a*b ]]; echo 3:YES:$?
+[[ a/b == a?b ]]; echo 4:YES:$?
+
+# wildcards can match a leading dot
+[[ a/.b = a/*b ]]; echo 5:YES:$?
+[[ a/.b == a/?b ]]; echo 6:YES:$?
+
+# wildcards can be escaped
+[[ abc = a*c ]]; echo 7:YES:$?
+[[ abc == a\*c ]]; echo 8:no:$?
diff --git a/shell/hush_test/hush-test2/strops2.right b/shell/hush_test/hush-test2/strops2.right
new file mode 100644
index 000000000..8ddb4b0f0
--- /dev/null
+++ b/shell/hush_test/hush-test2/strops2.right
@@ -0,0 +1,6 @@
+1:ERR2:2
+2:YES:0
+3:YES:0
+4:YES:0
+5:no:1
+6:YES:0
diff --git a/shell/hush_test/hush-test2/strops2.tests b/shell/hush_test/hush-test2/strops2.tests
new file mode 100755
index 000000000..ab325bc9f
--- /dev/null
+++ b/shell/hush_test/hush-test2/strops2.tests
@@ -0,0 +1,12 @@
+# malformed regex
+[[ a =~ * ]]; echo 1:ERR2:$?
+
+[[ a/b =~ a.b ]]; echo 2:YES:$?
+[[ a/b =~ /*b ]]; echo 3:YES:$?
+
+v='[]b.-]'
+[[ a/.b] =~ $v ]]; echo 4:YES:$?
+
+v=']b.-'
+[[ a/.b] =~ $v ]]; echo 5:no:$?
+[[ a/.b] =~ [$v] ]]; echo 6:YES:$?
diff --git a/shell/hush_test/hush-test2/strops3.right b/shell/hush_test/hush-test2/strops3.right
new file mode 100644
index 000000000..14cc04fdc
--- /dev/null
+++ b/shell/hush_test/hush-test2/strops3.right
@@ -0,0 +1,7 @@
+1:YES:0
+2:YES:0
+3:no:1
+4:YES:0
+2u:YES:0
+3u:YES:0
+4u:YES:0
diff --git a/shell/hush_test/hush-test2/strops3.tests b/shell/hush_test/hush-test2/strops3.tests
new file mode 100755
index 000000000..927476671
--- /dev/null
+++ b/shell/hush_test/hush-test2/strops3.tests
@@ -0,0 +1,13 @@
+# regex should accept '+' operator
+[[ abcdef =~ a[b-z]+ ]]; echo 1:YES:$?
+
+# newline matches by "match any" patterns
+v='
+'
+[[ "$v" =~ . ]]; echo 2:YES:$?
+[[ "$v" =~ "[$v]" ]]; echo 3:no:$? # hmm bash does return 1... why?
+[[ "$v" =~ [^a] ]]; echo 4:YES:$?
+# should work even without quotes:
+[[ $v =~ . ]]; echo 2u:YES:$?
+[[ $v =~ [$v] ]]; echo 3u:YES:$?
+[[ $v =~ [^a] ]]; echo 4u:YES:$?