aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--shell/ash_test/ash-misc/group_in_braces.right5
-rwxr-xr-xshell/ash_test/ash-misc/group_in_braces.tests11
-rw-r--r--shell/hush.c32
-rw-r--r--shell/hush_test/hush-misc/group_in_braces.right5
-rwxr-xr-xshell/hush_test/hush-misc/group_in_braces.tests11
5 files changed, 58 insertions, 6 deletions
diff --git a/shell/ash_test/ash-misc/group_in_braces.right b/shell/ash_test/ash-misc/group_in_braces.right
new file mode 100644
index 000000000..a7064499b
--- /dev/null
+++ b/shell/ash_test/ash-misc/group_in_braces.right
@@ -0,0 +1,5 @@
+Zero:0
+Zero:0
+Zero:0
+Zero:0
+Zero:0
diff --git a/shell/ash_test/ash-misc/group_in_braces.tests b/shell/ash_test/ash-misc/group_in_braces.tests
new file mode 100755
index 000000000..f6571c35d
--- /dev/null
+++ b/shell/ash_test/ash-misc/group_in_braces.tests
@@ -0,0 +1,11 @@
+# Test cases where { cmd } does not require semicolon after "cmd"
+(exit 2); { { true; } }
+echo Zero:$?
+(exit 2); {(true)}
+echo Zero:$?
+(exit 2); { true | { true; } }
+echo Zero:$?
+(exit 2); { while false; do :; done }
+echo Zero:$?
+(exit 2); { case a in b) ;; esac }
+echo Zero:$?
diff --git a/shell/hush.c b/shell/hush.c
index 7c2f157b8..336de75ad 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -3915,12 +3915,17 @@ static int parse_group(o_string *dest, struct parse_context *ctx,
command->cmd_type = CMD_SUBSHELL;
} else {
/* bash does not allow "{echo...", requires whitespace */
- ch = i_getch(input);
- if (ch != ' ' && ch != '\t' && ch != '\n') {
+ ch = i_peek(input);
+ if (ch != ' ' && ch != '\t' && ch != '\n'
+ && ch != '(' /* but "{(..." is allowed (without whitespace) */
+ ) {
syntax_error_unexpected_ch(ch);
return 1;
}
- nommu_addchr(&ctx->as_string, ch);
+ if (ch != '(') {
+ ch = i_getch(input);
+ nommu_addchr(&ctx->as_string, ch);
+ }
}
{
@@ -4575,6 +4580,7 @@ static struct pipe *parse_stream(char **pstring,
|| dest.has_quoted_part /* ""{... - non-special */
|| (next != ';' /* }; - special */
&& next != ')' /* }) - special */
+ && next != '(' /* {( - special */
&& next != '&' /* }& and }&& ... - special */
&& next != '|' /* }|| ... - special */
&& !strchr(defifs, next) /* {word - non-special */
@@ -4657,17 +4663,31 @@ static struct pipe *parse_stream(char **pstring,
* Pathological example: { ""}; } should exec "}" cmd
*/
if (ch == '}') {
- if (!IS_NULL_CMD(ctx.command) /* cmd } */
- || dest.length != 0 /* word} */
+ if (dest.length != 0 /* word} */
|| dest.has_quoted_part /* ""} */
) {
goto ordinary_char;
}
+ if (!IS_NULL_CMD(ctx.command)) { /* cmd } */
+ /* Generally, there should be semicolon: "cmd; }"
+ * However, bash allows to omit it if "cmd" is
+ * a group. Examples:
+ * { { echo 1; } }
+ * {(echo 1)}
+ * { echo 0 >&2 | { echo 1; } }
+ * { while false; do :; done }
+ * { case a in b) ;; esac }
+ */
+ if (ctx.command->group)
+ goto term_group;
+ goto ordinary_char;
+ }
if (!IS_NULL_PIPE(ctx.pipe)) /* cmd | } */
+ /* Can't be an end of {cmd}, skip the check */
goto skip_end_trigger;
/* else: } does terminate a group */
}
-
+ term_group:
if (end_trigger && end_trigger == ch
&& (ch != ';' || heredoc_cnt == 0)
#if ENABLE_HUSH_CASE
diff --git a/shell/hush_test/hush-misc/group_in_braces.right b/shell/hush_test/hush-misc/group_in_braces.right
new file mode 100644
index 000000000..a7064499b
--- /dev/null
+++ b/shell/hush_test/hush-misc/group_in_braces.right
@@ -0,0 +1,5 @@
+Zero:0
+Zero:0
+Zero:0
+Zero:0
+Zero:0
diff --git a/shell/hush_test/hush-misc/group_in_braces.tests b/shell/hush_test/hush-misc/group_in_braces.tests
new file mode 100755
index 000000000..f6571c35d
--- /dev/null
+++ b/shell/hush_test/hush-misc/group_in_braces.tests
@@ -0,0 +1,11 @@
+# Test cases where { cmd } does not require semicolon after "cmd"
+(exit 2); { { true; } }
+echo Zero:$?
+(exit 2); {(true)}
+echo Zero:$?
+(exit 2); { true | { true; } }
+echo Zero:$?
+(exit 2); { while false; do :; done }
+echo Zero:$?
+(exit 2); { case a in b) ;; esac }
+echo Zero:$?