From 672a55e606fc50e4ffe7810bd0d9cd1cf9b980a3 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 4 Nov 2016 18:46:14 +0100 Subject: hush: allow { cmd } to not be terminated by semicolon in some cases Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-misc/group_in_braces.right | 5 ++++ shell/ash_test/ash-misc/group_in_braces.tests | 11 +++++++++ shell/hush.c | 32 ++++++++++++++++++++----- shell/hush_test/hush-misc/group_in_braces.right | 5 ++++ shell/hush_test/hush-misc/group_in_braces.tests | 11 +++++++++ 5 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 shell/ash_test/ash-misc/group_in_braces.right create mode 100755 shell/ash_test/ash-misc/group_in_braces.tests create mode 100644 shell/hush_test/hush-misc/group_in_braces.right create mode 100755 shell/hush_test/hush-misc/group_in_braces.tests 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:$? -- cgit v1.2.3