diff --git a/CWRU/CWRU.chlog b/CWRU/CWRU.chlog index dbcaedd9..571df988 100644 --- a/CWRU/CWRU.chlog +++ b/CWRU/CWRU.chlog @@ -10110,3 +10110,56 @@ parse.y token about to be returned; don't worry about trying to set it after the fact Report from Collin Funk + + 8/28 + ---- +parse.y + - CHECK_FOR_RESERVED_WORD: change word_top to keep track of the line + number where a compound command begins; call set_word_top on the + current token about to be returned + - read_token_word: call set_word_top if special_case_tokens returns + something; currently used for `{' + - set_word_top: add `{' to the list of compound commands we track + - grammar: every time we parse a group command or equivalent for the + `for' and `select' compound commands, decrement word_top after we + see the `}' + - parse_dparen: set arith_lineno from word_lineno[word_top] since we + now set it for FOR when we read it + + 8/29 + ---- +parse.y + - read_token: don't turn on PST_SUBSHELL after reading a left paren + if the last token was a WORD, since that's a function definition, + not a subshell + - read_token,parse_dparen: don't turn on PST_SUBSHELL if we run into + a left paren while parsing a conditional command (PST_CONDCMD is + set in parser_state) + - set_word_top: add `(' to the tokens saved in word_lineno + - grammar: decrement word_top in the subshell_command production + - read_token,parse_dparen: call set_word_top where we add PST_SUBSHELL + to parser_state + + 8/30 + ---- +parse.y + - set_word_top: add COND_START to the list of tokens that set + word_lineno[word_top] + - grammar: decrement word_top after COND_END + - rename: word_lineno -> compoundcmd_lineno, word_top -> compoundcmd_top, + set_word_top -> set_compoundcmd_top + +lib/readline/signals.c + - _rl_release_sigint: don't check for signals if we're already + handling a received signal + Report from Tycho Kirchner and + Eduardo A. Bustamante López + + 8/31 + ---- +builtins/printf.def + - printf_builtin: when parsing a time specification format, don't + increment the format pointer to check for 'T' unless it points + to a closing right paren, rejecting the format if it's not a `)' + when the loop breaks + Report from Andrey Kovalev diff --git a/builtins/printf.def b/builtins/printf.def index 18f3f659..d7fc8f87 100644 --- a/builtins/printf.def +++ b/builtins/printf.def @@ -597,8 +597,8 @@ printf_builtin (WORD_LIST *list) break; *t++ = *fmt++; } - *t = '\0'; - if (*++fmt != 'T') + *t = '\0'; /*(*/ + if (*fmt != ')' || *++fmt != 'T') { builtin_warning (_("`%c': invalid time format specification"), *fmt); fmt = start; diff --git a/lib/readline/doc/rltech.texi b/lib/readline/doc/rltech.texi index 0aa73ef6..2218916b 100644 --- a/lib/readline/doc/rltech.texi +++ b/lib/readline/doc/rltech.texi @@ -643,7 +643,6 @@ Set to a value denoting Readline's current editing mode. A value of means that vi mode is active. @end deftypevar - @node Readline Convenience Functions @section Readline Convenience Functions diff --git a/lib/readline/doc/rluser.texi b/lib/readline/doc/rluser.texi index 5bcbaa29..0449452e 100644 --- a/lib/readline/doc/rluser.texi +++ b/lib/readline/doc/rluser.texi @@ -643,7 +643,8 @@ attempts word completion. The default is @samp{off}. @vindex force-meta-prefix If set to @samp{on}, Readline modifies its behavior when binding key sequences containing @kbd{\M-} or @code{Meta-} -(@pxref{Key Bindings}) by converting a key sequence of the form +(see @code{Key Bindings} in @ref{Readline Init File Syntax}) +by converting a key sequence of the form @kbd{\M-}@var{C} or @code{Meta-}@var{C} to the two-character sequence @kbd{ESC}@var{C} (adding the meta prefix). If @code{force-meta-prefix} is set to @samp{off} (the default), @@ -964,7 +965,7 @@ control prefix @item @kbd{\M-} adding the meta prefix or converting the following character to a meta character, as described above under @code{force-meta-prefix} -(@pxref{Variable Settings}). +(see @code{Variable Settings} in @ref{Readline Init File Syntax}). @item @kbd{\e} an escape character @item @kbd{\\} diff --git a/lib/readline/signals.c b/lib/readline/signals.c index a67f6214..1cdb7629 100644 --- a/lib/readline/signals.c +++ b/lib/readline/signals.c @@ -671,7 +671,8 @@ _rl_release_sigint (void) return; sigint_blocked = 0; - RL_CHECK_SIGNALS (); + if (RL_ISSTATE (RL_STATE_SIGHANDLER) == 0) + RL_CHECK_SIGNALS (); } /* Cause SIGWINCH to not be delivered until the corresponding call to diff --git a/parse.y b/parse.y index af7e3ab6..ccca5819 100644 --- a/parse.y +++ b/parse.y @@ -185,7 +185,7 @@ static void free_string_list (void); static char *read_a_line (int); -static int set_word_top (int); +static int set_compoundcmd_top (int); static int reserved_word_acceptable (int); static int yylex (void); @@ -341,12 +341,20 @@ static int token_before_that; /* The token read prior to token_before_that. */ static int two_tokens_ago; -/* The line number in a script where the word in a `case WORD', `select WORD' - or `for WORD' begins. This is a nested command maximum, since the array - index is decremented after a case, select, or for command is parsed. */ +/* Someday compoundcmd_lineno will be an array of these structs. */ +struct tokeninfo { + int tok; + int lineno; +}; + +/* The line number in a script where a compound command begins. The + compound commands for which this is tracked are the ones listed in + set_compoundcmd_top(). + This is a nested command maximum, since the array index is decremented + after a compound command is parsed. */ #define MAX_COMPOUND_NEST 256 -static int word_lineno[MAX_COMPOUND_NEST+1]; -static int word_top = -1; +static int compoundcmd_lineno[MAX_COMPOUND_NEST+1]; +static int compoundcmd_top = -1; /* If non-zero, it is the token that we want read_token to return regardless of what text is (or isn't) present to be read. This @@ -857,12 +865,12 @@ shell_command: for_command | WHILE compound_list DO compound_list DONE { $$ = make_while_command ($2, $4); - if (word_top >= 0) word_top--; + if (compoundcmd_top >= 0) compoundcmd_top--; } | UNTIL compound_list DO compound_list DONE { $$ = make_until_command ($2, $4); - if (word_top >= 0) word_top--; + if (compoundcmd_top >= 0) compoundcmd_top--; } | select_command { $$ = $1; } @@ -882,43 +890,47 @@ shell_command: for_command for_command: FOR WORD newline_list DO compound_list DONE { - $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5, word_lineno[word_top]); - if (word_top >= 0) word_top--; + $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5, compoundcmd_lineno[compoundcmd_top]); + if (compoundcmd_top >= 0) compoundcmd_top--; } | FOR WORD newline_list '{' compound_list '}' { - $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5, word_lineno[word_top]); - if (word_top >= 0) word_top--; + $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5, compoundcmd_lineno[compoundcmd_top]); + if (compoundcmd_top >= 0) compoundcmd_top--; /* RBRACE */ + if (compoundcmd_top >= 0) compoundcmd_top--; /* FOR */ } | FOR WORD ';' newline_list DO compound_list DONE { - $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6, word_lineno[word_top]); - if (word_top >= 0) word_top--; + $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6, compoundcmd_lineno[compoundcmd_top]); + if (compoundcmd_top >= 0) compoundcmd_top--; } | FOR WORD ';' newline_list '{' compound_list '}' { - $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6, word_lineno[word_top]); - if (word_top >= 0) word_top--; + $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6, compoundcmd_lineno[compoundcmd_top]); + if (compoundcmd_top >= 0) compoundcmd_top--; /* RBRACE */ + if (compoundcmd_top >= 0) compoundcmd_top--; /* FOR */ } | FOR WORD newline_list IN word_list list_terminator newline_list DO compound_list DONE { - $$ = make_for_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9, word_lineno[word_top]); - if (word_top >= 0) word_top--; + $$ = make_for_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9, compoundcmd_lineno[compoundcmd_top]); + if (compoundcmd_top >= 0) compoundcmd_top--; } | FOR WORD newline_list IN word_list list_terminator newline_list '{' compound_list '}' { - $$ = make_for_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9, word_lineno[word_top]); - if (word_top >= 0) word_top--; + $$ = make_for_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9, compoundcmd_lineno[compoundcmd_top]); + if (compoundcmd_top >= 0) compoundcmd_top--; /* RBRACE */ + if (compoundcmd_top >= 0) compoundcmd_top--; /* FOR */ } | FOR WORD newline_list IN list_terminator newline_list DO compound_list DONE { - $$ = make_for_command ($2, (WORD_LIST *)NULL, $8, word_lineno[word_top]); - if (word_top >= 0) word_top--; + $$ = make_for_command ($2, (WORD_LIST *)NULL, $8, compoundcmd_lineno[compoundcmd_top]); + if (compoundcmd_top >= 0) compoundcmd_top--; } | FOR WORD newline_list IN list_terminator newline_list '{' compound_list '}' { - $$ = make_for_command ($2, (WORD_LIST *)NULL, $8, word_lineno[word_top]); - if (word_top >= 0) word_top--; + $$ = make_for_command ($2, (WORD_LIST *)NULL, $8, compoundcmd_lineno[compoundcmd_top]); + if (compoundcmd_top >= 0) compoundcmd_top--; /* RBRACE */ + if (compoundcmd_top >= 0) compoundcmd_top--; /* FOR */ } ; @@ -926,84 +938,90 @@ arith_for_command: FOR ARITH_FOR_EXPRS list_terminator newline_list DO compound_ { $$ = make_arith_for_command ($2, $6, arith_for_lineno); if ($$ == 0) YYERROR; - if (word_top >= 0) word_top--; + if (compoundcmd_top >= 0) compoundcmd_top--; } | FOR ARITH_FOR_EXPRS list_terminator newline_list '{' compound_list '}' { $$ = make_arith_for_command ($2, $6, arith_for_lineno); if ($$ == 0) YYERROR; - if (word_top >= 0) word_top--; + if (compoundcmd_top >= 0) compoundcmd_top--; /* RBRACE */ + if (compoundcmd_top >= 0) compoundcmd_top--; /* FOR */ } | FOR ARITH_FOR_EXPRS DO compound_list DONE { $$ = make_arith_for_command ($2, $4, arith_for_lineno); if ($$ == 0) YYERROR; - if (word_top >= 0) word_top--; + if (compoundcmd_top >= 0) compoundcmd_top--; } | FOR ARITH_FOR_EXPRS '{' compound_list '}' { $$ = make_arith_for_command ($2, $4, arith_for_lineno); if ($$ == 0) YYERROR; - if (word_top >= 0) word_top--; + if (compoundcmd_top >= 0) compoundcmd_top--; /* RBRACE */ + if (compoundcmd_top >= 0) compoundcmd_top--; /* FOR */ } ; select_command: SELECT WORD newline_list DO compound_list DONE { - $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5, word_lineno[word_top]); - if (word_top >= 0) word_top--; + $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5, compoundcmd_lineno[compoundcmd_top]); + if (compoundcmd_top >= 0) compoundcmd_top--; } | SELECT WORD newline_list '{' compound_list '}' { - $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5, word_lineno[word_top]); - if (word_top >= 0) word_top--; + $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5, compoundcmd_lineno[compoundcmd_top]); + if (compoundcmd_top >= 0) compoundcmd_top--; /* RBRACE */ + if (compoundcmd_top >= 0) compoundcmd_top--; /* SELECT */ } | SELECT WORD ';' newline_list DO compound_list DONE { - $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6, word_lineno[word_top]); - if (word_top >= 0) word_top--; + $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6, compoundcmd_lineno[compoundcmd_top]); + if (compoundcmd_top >= 0) compoundcmd_top--; } | SELECT WORD ';' newline_list '{' compound_list '}' { - $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6, word_lineno[word_top]); - if (word_top >= 0) word_top--; + $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6, compoundcmd_lineno[compoundcmd_top]); + if (compoundcmd_top >= 0) compoundcmd_top--; /* RBRACE */ + if (compoundcmd_top >= 0) compoundcmd_top--; /* SELECT */ } | SELECT WORD newline_list IN word_list list_terminator newline_list DO compound_list DONE { - $$ = make_select_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9, word_lineno[word_top]); - if (word_top >= 0) word_top--; + $$ = make_select_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9, compoundcmd_lineno[compoundcmd_top]); + if (compoundcmd_top >= 0) compoundcmd_top--; } | SELECT WORD newline_list IN word_list list_terminator newline_list '{' compound_list '}' { - $$ = make_select_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9, word_lineno[word_top]); - if (word_top >= 0) word_top--; + $$ = make_select_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9, compoundcmd_lineno[compoundcmd_top]); + if (compoundcmd_top >= 0) compoundcmd_top--; /* RBRACE */ + if (compoundcmd_top >= 0) compoundcmd_top--; /* SELECT */ } | SELECT WORD newline_list IN list_terminator newline_list DO compound_list DONE { - $$ = make_select_command ($2, (WORD_LIST *)NULL, $8, word_lineno[word_top]); - if (word_top >= 0) word_top--; + $$ = make_select_command ($2, (WORD_LIST *)NULL, $8, compoundcmd_lineno[compoundcmd_top]); + if (compoundcmd_top >= 0) compoundcmd_top--; } | SELECT WORD newline_list IN list_terminator newline_list '{' compound_list '}' { - $$ = make_select_command ($2, (WORD_LIST *)NULL, $8, word_lineno[word_top]); - if (word_top >= 0) word_top--; + $$ = make_select_command ($2, (WORD_LIST *)NULL, $8, compoundcmd_lineno[compoundcmd_top]); + if (compoundcmd_top >= 0) compoundcmd_top--; /* RBRACE */ + if (compoundcmd_top >= 0) compoundcmd_top--; /* SELECT */ } ; case_command: CASE WORD newline_list IN newline_list ESAC { - $$ = make_case_command ($2, (PATTERN_LIST *)NULL, word_lineno[word_top]); - if (word_top >= 0) word_top--; + $$ = make_case_command ($2, (PATTERN_LIST *)NULL, compoundcmd_lineno[compoundcmd_top]); + if (compoundcmd_top >= 0) compoundcmd_top--; } | CASE WORD newline_list IN case_clause_sequence newline_list ESAC { - $$ = make_case_command ($2, $5, word_lineno[word_top]); - if (word_top >= 0) word_top--; + $$ = make_case_command ($2, $5, compoundcmd_lineno[compoundcmd_top]); + if (compoundcmd_top >= 0) compoundcmd_top--; } | CASE WORD newline_list IN case_clause ESAC { - $$ = make_case_command ($2, $5, word_lineno[word_top]); - if (word_top >= 0) word_top--; + $$ = make_case_command ($2, $5, compoundcmd_lineno[compoundcmd_top]); + if (compoundcmd_top >= 0) compoundcmd_top--; } ; @@ -1054,6 +1072,7 @@ subshell: '(' compound_list ')' { $$ = make_subshell_command ($2); $$->flags |= CMD_WANT_SUBSHELL; + if (compoundcmd_top >= 0) compoundcmd_top--; /* RPAREN */ } ; @@ -1133,23 +1152,26 @@ coproc: COPROC shell_command if_command: IF compound_list THEN compound_list FI { $$ = make_if_command ($2, $4, (COMMAND *)NULL); - if (word_top >= 0) word_top--; + if (compoundcmd_top >= 0) compoundcmd_top--; } | IF compound_list THEN compound_list ELSE compound_list FI { $$ = make_if_command ($2, $4, $6); - if (word_top >= 0) word_top--; + if (compoundcmd_top >= 0) compoundcmd_top--; } | IF compound_list THEN compound_list elif_clause FI { $$ = make_if_command ($2, $4, $5); - if (word_top >= 0) word_top--; + if (compoundcmd_top >= 0) compoundcmd_top--; } ; group_command: '{' compound_list '}' - { $$ = make_group_command ($2); } + { + $$ = make_group_command ($2); + if (compoundcmd_top >= 0) compoundcmd_top--; /* RBRACE */ + } ; arith_command: ARITH_CMD @@ -1157,7 +1179,10 @@ arith_command: ARITH_CMD ; cond_command: COND_START COND_CMD COND_END - { $$ = $2; } + { + $$ = $2; + if (compoundcmd_top >= 0) compoundcmd_top--; /* COND_END */ + } ; elif_clause: ELIF compound_list THEN compound_list @@ -3137,7 +3162,7 @@ static int open_brace_count; else if (word_token_alist[i].token == '}' && open_brace_count) \ open_brace_count--; \ \ - set_word_top (word_token_alist[i].token); \ + set_compoundcmd_top (word_token_alist[i].token); \ \ if (posixly_correct) \ parser_state &= ~PST_ALEXPNEXT; \ @@ -3711,11 +3736,16 @@ read_token (int command) } /* case pattern lists may be preceded by an optional left paren. If - we're not trying to parse a case pattern list, the left paren - indicates a subshell. */ - /* XXX - check for last_read_token != WORD before setting PST_SUBSHELL? */ - if MBTEST(character == '(' && (parser_state & PST_CASEPAT) == 0) /* ) */ - parser_state |= PST_SUBSHELL; + we're not trying to parse a case pattern list or a conditional + command (which may use parens for grouping), and the last token + wasn't a WORD (indicating a function definition), the left paren + introduces a subshell. */ + if MBTEST(character == '(' && (parser_state & (PST_CASEPAT|PST_CONDCMD)) == 0 && /* ) */ + last_read_token != WORD) + { + set_compoundcmd_top (character); + parser_state |= PST_SUBSHELL; + } /*(*/ else if MBTEST((parser_state & PST_CASEPAT) && character == ')') parser_state &= ~PST_CASEPAT; @@ -4840,7 +4870,7 @@ parse_dparen (int c) #if defined (ARITH_FOR_COMMAND) if (last_read_token == FOR) { - arith_for_lineno = line_number; + arith_for_lineno = compoundcmd_top[compoundcmd_lineno]; cmdtyp = parse_arith_cmd (&wval, 0); if (cmdtyp == 1) { @@ -4872,8 +4902,11 @@ parse_dparen (int c) { push_string (wval, 0, (alias_t *)NULL); pushed_string_list->flags = PSH_DPAREN; - if ((parser_state & PST_CASEPAT) == 0) - parser_state |= PST_SUBSHELL; + if ((parser_state & (PST_CASEPAT|PST_CONDCMD)) == 0) + { + set_compoundcmd_top (c); + parser_state |= PST_SUBSHELL; + } return (c); } else /* ERROR */ @@ -5667,7 +5700,10 @@ got_token: /* Check for special case tokens. */ result = (last_shell_getc_is_singlebyte) ? special_case_tokens (token) : -1; if (result >= 0) - return result; /* don't need to set word_top in any of these cases */ + { + set_compoundcmd_top (result); /* need to set compoundcmd_top if it returns `{' */ /*}*/ + return result; + } #if defined (ALIAS) /* Posix.2 does not allow reserved words to be aliased, so check for all @@ -5791,7 +5827,7 @@ got_token: } static inline int -set_word_top (int t) +set_compoundcmd_top (int t) { switch (t) { @@ -5801,14 +5837,17 @@ set_word_top (int t) case IF: case WHILE: case UNTIL: - if (word_top < MAX_COMPOUND_NEST) - word_top++; - word_lineno[word_top] = line_number; + case '{': /*}*/ + case '(': /*)*/ + case COND_START: + if (compoundcmd_top < MAX_COMPOUND_NEST) + compoundcmd_top++; + compoundcmd_lineno[compoundcmd_top] = line_number; break; default: break; } - return word_top; + return compoundcmd_top; } /* Return 1 if TOKSYM is a token that after being read would allow @@ -6782,8 +6821,8 @@ report_syntax_error (const char *message) { if (EOF_Reached && shell_eof_token && current_token != shell_eof_token) parser_error (line_number, _("unexpected EOF while looking for matching `%c'"), shell_eof_token); - else if (EOF_Reached && word_top >= 0) - parser_error (line_number, _("syntax error: unexpected end of file from command on line %d"), word_lineno[word_top]); + else if (EOF_Reached && compoundcmd_top >= 0) + parser_error (line_number, _("syntax error: unexpected end of file from command on line %d"), compoundcmd_lineno[compoundcmd_top]); else { msg = EOF_Reached ? _("syntax error: unexpected end of file") : _("syntax error"); diff --git a/tests/cond.right b/tests/cond.right index 01859347..215a5514 100644 --- a/tests/cond.right +++ b/tests/cond.right @@ -154,9 +154,9 @@ ok 6 ok 7 ok 8 bash: -c: line 1: unexpected token `EOF', expected `)' -bash: -c: line 2: syntax error: unexpected end of file +bash: -c: line 2: syntax error: unexpected end of file from command on line 1 bash: -c: line 1: unexpected EOF while looking for `]]' -bash: -c: line 2: syntax error: unexpected end of file +bash: -c: line 2: syntax error: unexpected end of file from command on line 1 bash: -c: line 1: syntax error in conditional expression: unexpected token `]' bash: -c: line 1: syntax error near `]' bash: -c: line 1: `[[ ( -t X ) ]' diff --git a/tests/errors.right b/tests/errors.right index f2d12935..d9ddde73 100644 --- a/tests/errors.right +++ b/tests/errors.right @@ -240,7 +240,7 @@ after non-special builtin: 1 ./errors7.sub: line 27: x: readonly variable ./errors7.sub: line 29: x: readonly variable ./errors7.sub: line 32: v: readonly variable -./errors8.sub: eval: line 7: syntax error: unexpected end of file +./errors8.sub: eval: line 7: syntax error: unexpected end of file from command on line 6 ok 1 ./errors8.sub: line 8: v: readonly variable ok 2 diff --git a/tests/exportfunc.right b/tests/exportfunc.right index ad6b58c6..fa3decd3 100644 --- a/tests/exportfunc.right +++ b/tests/exportfunc.right @@ -1,7 +1,7 @@ exportfunc ok 1 exportfunc ok 2 ./exportfunc.tests: line 37: cve7169-bad: No such file or directory -./exportfunc.tests: eval: line 44: syntax error: unexpected end of file +./exportfunc.tests: eval: line 44: syntax error: unexpected end of file from command on line 42 ./exportfunc.tests: line 43: cve7169-bad2: No such file or directory ./exportfunc1.sub: line 14: maximum here-document count exceeded ./exportfunc.tests: line 72: HELLO_WORLD: No such file or directory diff --git a/tests/heredoc.right b/tests/heredoc.right index a3ef0336..29e4ed07 100644 --- a/tests/heredoc.right +++ b/tests/heredoc.right @@ -95,7 +95,7 @@ hello \END end hello\END ./heredoc3.sub: line 98: warning: here-document at line 96 delimited by end-of-file (wanted `EOF') -./heredoc3.sub: line 99: syntax error: unexpected end of file +./heredoc3.sub: line 99: syntax error: unexpected end of file from command on line 96 heredoc1 EOF Ok:0