diff --git a/shell/hush.c b/shell/hush.c index f38f3752b..c71ddb14f 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -1344,7 +1344,7 @@ static int setup_redirects(struct child_prog *prog, int squirrel[]) if (redir->dup == -1) { char *p; mode = redir_table[redir->rd_type].mode; -//TODO: check redir to names like '\\' +//TODO: check redir for names like '\\' p = expand_string_to_string(redir->rd_filename); openfd = open_or_warn(p, mode); free(p); @@ -2854,17 +2854,7 @@ static int reserved_word(const o_string *word, struct p_context *ctx) continue; debug_printf("found reserved word %s, res %d\n", r->literal, r->res); if (r->flag == 0) { /* '!' */ -#if ENABLE_HUSH_LOOPS - if (ctx->ctx_res_w == RES_IN) { - /* 'for a in ! a b c; ...' - ! isn't a keyword here */ - break; - } -#endif - if (ctx->ctx_inverted /* bash doesn't accept '! ! true' */ -#if ENABLE_HUSH_LOOPS - || ctx->ctx_res_w == RES_FOR /* example: 'for ! a' */ -#endif - ) { + if (ctx->ctx_inverted) { /* bash doesn't accept '! ! true' */ syntax(NULL); IF_HAS_KEYWORDS(ctx->ctx_res_w = RES_SNTX;) } @@ -2885,7 +2875,7 @@ static int reserved_word(const o_string *word, struct p_context *ctx) *new = *ctx; /* physical copy */ initialize_context(ctx); ctx->stack = new; - } else if (ctx->ctx_res_w == RES_NONE || !(ctx->old_flag & (1 << r->res))) { + } else if (/*ctx->ctx_res_w == RES_NONE ||*/ !(ctx->old_flag & (1 << r->res))) { syntax(NULL); ctx->ctx_res_w = RES_SNTX; return 1; @@ -2940,7 +2930,10 @@ static int done_word(o_string *word, struct p_context *ctx) return 1; } #if HAS_KEYWORDS - if (!child->argv) { /* if it's the first word... */ + if (!child->argv /* if it's the first word... */ + && ctx->ctx_res_w != RES_FOR /* ...not after FOR or IN */ + && ctx->ctx_res_w != RES_IN + ) { debug_printf_parse(": checking '%s' for reserved-ness\n", word->data); if (reserved_word(word, ctx)) { o_reset(word); @@ -2953,9 +2946,9 @@ static int done_word(o_string *word, struct p_context *ctx) if (word->nonnull /* word had "xx" or 'xx' at least as part of it. */ /* optimization: and if it's ("" or '') or ($v... or `cmd`...): */ && (word->data[0] == '\0' || word->data[0] == SPECIAL_VAR_SYMBOL) - /* (otherwise it's "abc".... and is already safe) */ + /* (otherwise it's known to be not empty and is already safe) */ ) { - /* but exclude "$@" - it expands to no word despite "" */ + /* exclude "$@" - it can expand to no word despite "" */ char *p = word->data; while (p[0] == SPECIAL_VAR_SYMBOL && (p[1] & 0x7f) == '@' @@ -2964,7 +2957,9 @@ static int done_word(o_string *word, struct p_context *ctx) p += 3; } if (p == word->data || p[0] != '\0') { - /* Insert "empty variable" reference, this makes + /* saw no "$@", or not only "$@" but some + * real text is there too */ + /* insert "empty variable" reference, this makes * e.g. "", $empty"" etc to not disappear */ o_addchr(word, SPECIAL_VAR_SYMBOL); o_addchr(word, SPECIAL_VAR_SYMBOL); @@ -2979,8 +2974,13 @@ static int done_word(o_string *word, struct p_context *ctx) #if ENABLE_HUSH_LOOPS /* Force FOR to have just one word (variable name) */ - if (ctx->ctx_res_w == RES_FOR) + /* NB: basically, this makes hush see "for v in ..." syntax as if + * as it is "for v; in ...". FOR and IN become two pipe structs + * in parse tree. */ + if (ctx->ctx_res_w == RES_FOR) { +//TODO: check that child->argv[0] is a valid variable name! done_pipe(ctx, PIPE_SEQ); + } #endif debug_printf_parse("done_word return 0\n"); return 0; @@ -3030,19 +3030,28 @@ static void done_pipe(struct p_context *ctx, pipe_style type) debug_printf_parse("done_pipe entered, followup %d\n", type); not_null = done_command(ctx); /* implicit closure of previous command */ ctx->pipe->followup = type; - IF_HAS_KEYWORDS(ctx->pipe->res_word = ctx->ctx_res_w;) IF_HAS_KEYWORDS(ctx->pipe->pi_inverted = ctx->ctx_inverted;) IF_HAS_KEYWORDS(ctx->ctx_inverted = 0;) + IF_HAS_KEYWORDS(ctx->pipe->res_word = ctx->ctx_res_w;) /* Without this check, even just on command line generates * tree of three NOPs (!). Which is harmless but annoying. * IOW: it is safe to do it unconditionally. - * RES_IN case is for "for a in; do ..." (empty IN set) - * to work. */ - if (not_null USE_HUSH_LOOPS(|| ctx->pipe->res_word == RES_IN)) { + * RES_NONE case is for "for a in; do ..." (empty IN set) + * to work, possibly other cases too. */ + if (not_null IF_HAS_KEYWORDS(|| ctx->ctx_res_w != RES_NONE)) { struct pipe *new_p = new_pipe(); ctx->pipe->next = new_p; ctx->pipe = new_p; ctx->child = NULL; /* needed! */ + /* RES_IF, RES_WHILE etc are "sticky" - + * they remain set for commands inside if/while. + * This is used to control execution. + * RES_FOR and RES_IN are NOT sticky (needed to support + * cases where variable or value happens to match a keyword): + */ + if (ctx->ctx_res_w == RES_FOR + || ctx->ctx_res_w == RES_IN) + ctx->ctx_res_w = RES_NONE; /* Create the memory for child, roughly: * ctx->pipe->progs = new struct child_prog; * ctx->pipe->progs[0].family = ctx->pipe; @@ -3444,7 +3453,7 @@ static int handle_dollar(o_string *dest, struct in_str *input) return 0; } -/* Scan input, call done_word() whenever full IFS delemited word was seen. +/* Scan input, call done_word() whenever full IFS delimited word was seen. * call done_pipe if '\n' was seen (and end_trigger != NULL) * Return if (non-quoted) char in end_trigger was seen; or on parse error. */ /* Return code is 0 if end_trigger char is met, @@ -3517,7 +3526,9 @@ static int parse_stream(o_string *dest, struct p_context *ctx, //err chk? done_pipe(ctx, PIPE_SEQ); } - if (!HAS_KEYWORDS IF_HAS_KEYWORDS(|| ctx->ctx_res_w == RES_NONE)) { + if (!HAS_KEYWORDS + IF_HAS_KEYWORDS(|| (ctx->ctx_res_w == RES_NONE && ctx->old_flag == 0)) + ) { debug_printf_parse("parse_stream return 0: end_trigger char found\n"); return 0; } diff --git a/shell/hush_test/hush-misc/for_with_keywords.right b/shell/hush_test/hush-misc/for_with_keywords.right new file mode 100644 index 000000000..eb04e9af9 --- /dev/null +++ b/shell/hush_test/hush-misc/for_with_keywords.right @@ -0,0 +1,4 @@ +do +done +then +OK: 0 diff --git a/shell/hush_test/hush-misc/for_with_keywords.tests b/shell/hush_test/hush-misc/for_with_keywords.tests new file mode 100755 index 000000000..a8b8e4264 --- /dev/null +++ b/shell/hush_test/hush-misc/for_with_keywords.tests @@ -0,0 +1,2 @@ +for if in do done then; do echo $if; done +echo OK: $?