hush: allow { cmd } to not be terminated by semicolon in some cases
authorDenys Vlasenko <vda.linux@googlemail.com>
Fri, 4 Nov 2016 17:46:14 +0000 (18:46 +0100)
committerDenys Vlasenko <vda.linux@googlemail.com>
Fri, 4 Nov 2016 17:46:14 +0000 (18:46 +0100)
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
shell/ash_test/ash-misc/group_in_braces.right [new file with mode: 0644]
shell/ash_test/ash-misc/group_in_braces.tests [new file with mode: 0755]
shell/hush.c
shell/hush_test/hush-misc/group_in_braces.right [new file with mode: 0644]
shell/hush_test/hush-misc/group_in_braces.tests [new file with mode: 0755]

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 (file)
index 0000000..a706449
--- /dev/null
@@ -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 (executable)
index 0000000..f6571c3
--- /dev/null
@@ -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:$?
index 7c2f157b8c5c8a9eefdd7b8f170a987b63a71873..336de75adc371ff1009649b1f797207d29e1967a 100644 (file)
@@ -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 */
                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;
                }
                        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 */
                 || dest.has_quoted_part     /* ""{... - non-special */
                 || (next != ';'             /* }; - special */
                    && next != ')'           /* }) - special */
+                   && next != '('           /* {( - special */
                    && next != '&'           /* }& and }&& ... - special */
                    && next != '|'           /* }|| ... - special */
                    && !strchr(defifs, next) /* {word - non-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 == '}') {
                 * 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;
                        }
                         || 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 | } */
                        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 */
                }
                                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
                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 (file)
index 0000000..a706449
--- /dev/null
@@ -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 (executable)
index 0000000..f6571c3
--- /dev/null
@@ -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:$?