hush: readability improvements.
 fix some more obscure bugs.
 a new redir4.tests is known to fail.

diff --git a/shell/hush.c b/shell/hush.c
index ac2410c..21590ad 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -341,7 +341,11 @@
 	REDIRECT_HEREDOC   = 4,
 	REDIRECT_IO        = 5,
 	REDIRECT_HEREDOC2  = 6, /* REDIRECT_HEREDOC after heredoc is loaded */
-	REDIRFD_CLOSE = -3,
+
+	REDIRFD_CLOSE      = -3,
+	REDIRFD_SYNTAX_ERR = -2,
+	REDIRFD_TO_FILE    = -1, /* otherwise, rd_fd if redirected to rd_dup */
+
 	HEREDOC_SKIPTABS = 1,
 	HEREDOC_QUOTED   = 2,
 } redir_type;
@@ -2427,6 +2431,7 @@
 
 	for (redir = prog->redirects; redir; redir = redir->next) {
 		if (redir->rd_type == REDIRECT_HEREDOC2) {
+			/* rd_fd<<HERE case */
 			if (squirrel && redir->rd_fd < 3) {
 				squirrel[redir->rd_fd] = dup(redir->rd_fd);
 			}
@@ -2438,15 +2443,16 @@
 			continue;
 		}
 
-		if (redir->rd_dup == -1) {
+		if (redir->rd_dup == REDIRFD_TO_FILE) {
+			/* rd_fd<*>file case (<*> is <,>,>>,<>) */
 			char *p;
 			if (redir->rd_filename == NULL) {
 				/* Something went wrong in the parse.
 				 * Pretend it didn't happen */
+				bb_error_msg("bug in redirect parse");
 				continue;
 			}
 			mode = redir_table[redir->rd_type].mode;
-//TODO: check redir for names like '\\'
 			p = expand_string_to_string(redir->rd_filename);
 			openfd = open_or_warn(p, mode);
 			free(p);
@@ -2457,6 +2463,7 @@
 				return 1;
 			}
 		} else {
+			/* rd_fd<*>rd_dup or rd_fd<*>- cases */
 			openfd = redir->rd_dup;
 		}
 
@@ -2469,7 +2476,7 @@
 				close(redir->rd_fd);
 			} else {
 				xdup2(openfd, redir->rd_fd);
-				if (redir->rd_dup == -1)
+				if (redir->rd_dup == REDIRFD_TO_FILE)
 					close(openfd);
 			}
 		}
@@ -3963,17 +3970,6 @@
 		debug_printf_parse("done_word return 0: true null, ignored\n");
 		return 0;
 	}
-	/* If this word wasn't an assignment, next ones definitely
-	 * can't be assignments. Even if they look like ones. */
-	if (word->o_assignment != DEFINITELY_ASSIGNMENT
-	 && word->o_assignment != WORD_IS_KEYWORD
-	) {
-		word->o_assignment = NOT_ASSIGNMENT;
-	} else {
-		if (word->o_assignment == DEFINITELY_ASSIGNMENT)
-			command->assignment_cnt++;
-		word->o_assignment = MAYBE_ASSIGNMENT;
-	}
 
 	if (ctx->pending_redirect) {
 		/* We do not glob in e.g. >*.tmp case. bash seems to glob here
@@ -3989,29 +3985,47 @@
 		 * the expansion would result in one word."
 		 */
 		ctx->pending_redirect->rd_filename = xstrdup(word->data);
+		/* Cater for >\file case:
+		 * >\a creates file a; >\\a, >"\a", >"\\a" create file \a
+		 * Same with heredocs:
+		 * for <<\H delim is H; <<\\H, <<"\H", <<"\\H" - \H
+		 */
+		unbackslash(ctx->pending_redirect->rd_filename);
+		/* Is it <<"HEREDOC"? */
 		if (ctx->pending_redirect->rd_type == REDIRECT_HEREDOC
 		 && word->o_quoted
 		) {
 			ctx->pending_redirect->rd_dup |= HEREDOC_QUOTED;
 		}
-		word->o_assignment = NOT_ASSIGNMENT;
 		debug_printf_parse("word stored in rd_filename: '%s'\n", word->data);
 	} else {
-		/* "{ echo foo; } echo bar" - bad */
-		/* NB: bash allows e.g.:
-		 * if true; then { echo foo; } fi
-		 * while if false; then false; fi do break; done
-		 * and disallows:
-		 * while if false; then false; fi; do; break; done
-		 * TODO? */
+		/* If this word wasn't an assignment, next ones definitely
+		 * can't be assignments. Even if they look like ones. */
+		if (word->o_assignment != DEFINITELY_ASSIGNMENT
+		 && word->o_assignment != WORD_IS_KEYWORD
+		) {
+			word->o_assignment = NOT_ASSIGNMENT;
+		} else {
+			if (word->o_assignment == DEFINITELY_ASSIGNMENT)
+				command->assignment_cnt++;
+			word->o_assignment = MAYBE_ASSIGNMENT;
+		}
+
 		if (command->group) {
+			/* "{ echo foo; } echo bar" - bad */
+			/* NB: bash allows e.g.:
+			 * if true; then { echo foo; } fi
+			 * while if false; then false; fi do break; done
+			 * and disallows:
+			 * while if false; then false; fi; do; break; done
+			 * TODO? */
 			syntax_error_at(word->data);
 			debug_printf_parse("done_word return 1: syntax error, "
 					"groups and arglists don't mix\n");
 			return 1;
 		}
 #if HAS_KEYWORDS
-#if ENABLE_HUSH_CASE
+# if ENABLE_HUSH_CASE
 		if (ctx->ctx_dsemicolon
 		 && strcmp(word->data, "esac") != 0 /* not "... pattern) cmd;; esac" */
 		) {
@@ -4019,12 +4033,12 @@
 			/* ctx->ctx_res_w = RES_MATCH; */
 			ctx->ctx_dsemicolon = 0;
 		} else
-#endif
+# endif
 		if (!command->argv /* if it's the first word... */
-#if ENABLE_HUSH_LOOPS
+# if ENABLE_HUSH_LOOPS
 		 && ctx->ctx_res_w != RES_FOR /* ...not after FOR or IN */
 		 && ctx->ctx_res_w != RES_IN
-#endif
+# endif
 		) {
 			debug_printf_parse(": checking '%s' for reserved-ness\n", word->data);
 			if (reserved_word(word, ctx)) {
@@ -4090,20 +4104,23 @@
 
 /* Peek ahead in the input to find out if we have a "&n" construct,
  * as in "2>&1", that represents duplicating a file descriptor.
- * Return: REDIRFD_CLOSE (-3) if >&- "close fd" construct is seen,
- * -2 (syntax error), -1 if no & was seen, or the number found.
+ * Return:
+ * REDIRFD_CLOSE if >&- "close fd" construct is seen,
+ * REDIRFD_SYNTAX_ERR if syntax error,
+ * REDIRFD_TO_FILE if no & was seen,
+ * or the number found.
  */
 #if BB_MMU
-#define redirect_dup_num(as_string, input) \
-	redirect_dup_num(input)
+#define parse_redir_right_fd(as_string, input) \
+	parse_redir_right_fd(input)
 #endif
-static int redirect_dup_num(o_string *as_string, struct in_str *input)
+static int parse_redir_right_fd(o_string *as_string, struct in_str *input)
 {
 	int ch, d, ok;
 
 	ch = i_peek(input);
 	if (ch != '&')
-		return -1;
+		return REDIRFD_TO_FILE;
 
 	ch = i_getch(input);  /* get the & */
 	nommu_addchr(as_string, ch);
@@ -4127,10 +4144,10 @@
 //TODO: this is the place to catch ">&file" bashism (redirect both fd 1 and 2)
 
 	bb_error_msg("ambiguous redirect");
-	return -2;
+	return REDIRFD_SYNTAX_ERR;
 }
 
-/* Return code is 0 normally, 1 if a syntax error is detected
+/* Return code is 0 normal, 1 if a syntax error is detected
  */
 static int parse_redirect(struct parse_context *ctx,
 		int fd,
@@ -4142,12 +4159,12 @@
 	struct redir_struct **redirp;
 	int dup_num;
 
-	dup_num = -1;
+	dup_num = REDIRFD_TO_FILE;
 	if (style != REDIRECT_HEREDOC) {
-		/* Check for a '2>&1' type redirect */
-		dup_num = redirect_dup_num(&ctx->as_string, input);
-		if (dup_num == -2)
-			return 1;  /* syntax error */
+		/* Check for a '>&1' type redirect */
+		dup_num = parse_redir_right_fd(&ctx->as_string, input);
+		if (dup_num == REDIRFD_SYNTAX_ERR)
+			return 1;
 	} else {
 		int ch = i_peek(input);
 		dup_num = (ch == '-'); /* HEREDOC_SKIPTABS bit is 1 */
@@ -4158,7 +4175,7 @@
 		}
 	}
 
-	if (style == REDIRECT_OVERWRITE && dup_num == -1) {
+	if (style == REDIRECT_OVERWRITE && dup_num == REDIRFD_TO_FILE) {
 		int ch = i_peek(input);
 		if (ch == '|') {
 			/* >|FILE redirect ("clobbering" >).
@@ -4185,7 +4202,7 @@
 				redir_table[style].descrip);
 
 	redir->rd_dup = dup_num;
-	if (style != REDIRECT_HEREDOC && dup_num != -1) {
+	if (style != REDIRECT_HEREDOC && dup_num != REDIRFD_TO_FILE) {
 		/* Erik had a check here that the file descriptor in question
 		 * is legit; I postpone that to "run time"
 		 * A "-" representation of "close me" shows up as a -3 here */
@@ -4862,9 +4879,6 @@
 		 * only when followed by one of the following characters:
 		 * $, `, ", \, or <newline>.  A double quote may be quoted
 		 * within double quotes by preceding it with a backslash.
-		 * If enabled, history expansion will be performed unless
-		 * an ! appearing in double quotes is escaped using
-		 * a backslash. The backslash preceding the ! is not removed."
 		 */
 		if (strchr("$`\"\\", next) != NULL) {
 			o_addqchr(dest, i_getch(input));
@@ -5081,17 +5095,71 @@
 		if (is_ifs)
 			continue;
 
-		if (dest.o_assignment == MAYBE_ASSIGNMENT) {
-			/* ch is a special char and thus this word
-			 * cannot be an assignment */
-			dest.o_assignment = NOT_ASSIGNMENT;
-		}
-
 		next = '\0';
 		if (ch != '\n') {
 			next = i_peek(input);
 		}
 
+		/* Catch <, > before deciding whether this word is
+		 * an assignment. a=1 2>z b=2: b=2 is still assignment */
+		switch (ch) {
+		case '>':
+			redir_fd = redirect_opt_num(&dest);
+			if (done_word(&dest, &ctx)) {
+				goto parse_error;
+			}
+			redir_style = REDIRECT_OVERWRITE;
+			if (next == '>') {
+				redir_style = REDIRECT_APPEND;
+				ch = i_getch(input);
+				nommu_addchr(&ctx.as_string, ch);
+			}
+#if 0
+			else if (next == '(') {
+				syntax_error(">(process) not supported");
+				goto parse_error;
+			}
+#endif
+			if (parse_redirect(&ctx, redir_fd, redir_style, input))
+				goto parse_error;
+			continue; /* back to top of while (1) */
+		case '<':
+			redir_fd = redirect_opt_num(&dest);
+			if (done_word(&dest, &ctx)) {
+				goto parse_error;
+			}
+			redir_style = REDIRECT_INPUT;
+			if (next == '<') {
+				redir_style = REDIRECT_HEREDOC;
+				heredoc_cnt++;
+				debug_printf_parse("++heredoc_cnt=%d\n", heredoc_cnt);
+				ch = i_getch(input);
+				nommu_addchr(&ctx.as_string, ch);
+			} else if (next == '>') {
+				redir_style = REDIRECT_IO;
+				ch = i_getch(input);
+				nommu_addchr(&ctx.as_string, ch);
+			}
+#if 0
+			else if (next == '(') {
+				syntax_error("<(process) not supported");
+				goto parse_error;
+			}
+#endif
+			if (parse_redirect(&ctx, redir_fd, redir_style, input))
+				goto parse_error;
+			continue; /* back to top of while (1) */
+		}
+
+		if (dest.o_assignment == MAYBE_ASSIGNMENT
+		 /* check that we are not in word in "a=1 2>word b=1": */
+		 && !ctx.pending_redirect
+		) {
+			/* ch is a special char and thus this word
+			 * cannot be an assignment */
+			dest.o_assignment = NOT_ASSIGNMENT;
+		}
+
 		switch (ch) {
 		case '#':
 			if (dest.length == 0) {
@@ -5171,52 +5239,6 @@
 			break;
 		}
 #endif
-		case '>':
-			redir_fd = redirect_opt_num(&dest);
-			if (done_word(&dest, &ctx)) {
-				goto parse_error;
-			}
-			redir_style = REDIRECT_OVERWRITE;
-			if (next == '>') {
-				redir_style = REDIRECT_APPEND;
-				ch = i_getch(input);
-				nommu_addchr(&ctx.as_string, ch);
-			}
-#if 0
-			else if (next == '(') {
-				syntax_error(">(process) not supported");
-				goto parse_error;
-			}
-#endif
-			if (parse_redirect(&ctx, redir_fd, redir_style, input))
-				goto parse_error;
-			break;
-		case '<':
-			redir_fd = redirect_opt_num(&dest);
-			if (done_word(&dest, &ctx)) {
-				goto parse_error;
-			}
-			redir_style = REDIRECT_INPUT;
-			if (next == '<') {
-				redir_style = REDIRECT_HEREDOC;
-				heredoc_cnt++;
-				debug_printf_parse("++heredoc_cnt=%d\n", heredoc_cnt);
-				ch = i_getch(input);
-				nommu_addchr(&ctx.as_string, ch);
-			} else if (next == '>') {
-				redir_style = REDIRECT_IO;
-				ch = i_getch(input);
-				nommu_addchr(&ctx.as_string, ch);
-			}
-#if 0
-			else if (next == '(') {
-				syntax_error("<(process) not supported");
-				goto parse_error;
-			}
-#endif
-			if (parse_redirect(&ctx, redir_fd, redir_style, input))
-				goto parse_error;
-			break;
 		case ';':
 #if ENABLE_HUSH_CASE
  case_semi:
diff --git a/shell/hush_test/hush-misc/redir1.right b/shell/hush_test/hush-misc/redir1.right
index ac90b4a..15515d1 100644
--- a/shell/hush_test/hush-misc/redir1.right
+++ b/shell/hush_test/hush-misc/redir1.right
@@ -1,3 +1,5 @@
+Test 0:  var:ok
+File created:ok
 Test 1:  var:ok
 File created:ok
 Test 2:  var:ok
diff --git a/shell/hush_test/hush-misc/redir1.tests b/shell/hush_test/hush-misc/redir1.tests
index 7e20451..70e9e17 100755
--- a/shell/hush_test/hush-misc/redir1.tests
+++ b/shell/hush_test/hush-misc/redir1.tests
@@ -1,5 +1,11 @@
 rm shell_test_$$ 2>/dev/null
 var=bad
+>shell_test_$$ var=ok
+echo "Test 0:  var:$var"
+test -f shell_test_$$ && echo "File created:ok"
+
+rm shell_test_$$ 2>/dev/null
+var=bad
 var=ok >shell_test_$$
 echo "Test 1:  var:$var"
 test -f shell_test_$$ && echo "File created:ok"
diff --git a/shell/hush_test/hush-misc/redir4.right b/shell/hush_test/hush-misc/redir4.right
new file mode 100644
index 0000000..ada6c2d
--- /dev/null
+++ b/shell/hush_test/hush-misc/redir4.right
@@ -0,0 +1,25 @@
+shell_test
+\shell_test
+\shell_test
+\shell_test
+Here1
+Ok1
+Here2
+Ok2
+Here3
+Ok3
+Here4
+Ok4
+How with variable refs
+shell_test_1
+\shell_test_1
+\shell_test_1
+\shell_test_1
+Here1
+Ok1
+Here2
+Ok2
+Here3
+Ok3
+Here4
+Ok4
diff --git a/shell/hush_test/hush-misc/redir4.tests b/shell/hush_test/hush-misc/redir4.tests
new file mode 100755
index 0000000..ac2a441
--- /dev/null
+++ b/shell/hush_test/hush-misc/redir4.tests
@@ -0,0 +1,80 @@
+rm *shell_test* 2>/dev/null
+
+>\shell_test
+echo *shell_test*
+rm *shell_test*
+
+>\\shell_test
+echo *shell_test*
+rm *shell_test*
+
+>"\shell_test"
+echo *shell_test*
+rm *shell_test*
+
+>"\\shell_test"
+echo *shell_test*
+rm *shell_test*
+
+
+cat <<\shell_test
+Here1
+shell_test
+echo Ok1
+
+cat <<\\shell_test
+Here2
+\shell_test
+echo Ok2
+
+cat <<"\shell_test"
+Here3
+\shell_test
+echo Ok3
+
+cat <<"\\shell_test"
+Here4
+\shell_test
+echo Ok4
+
+
+echo How with variable refs
+i=1
+
+
+>\shell_test_$i
+echo *shell_test*
+rm *shell_test*
+
+>\\shell_test_$i
+echo *shell_test*
+rm *shell_test*
+
+>"\shell_test_$i"
+echo *shell_test*
+rm *shell_test*
+
+>"\\shell_test_$i"
+echo *shell_test*
+rm *shell_test*
+
+
+cat <<\shell_test_$i
+Here1
+shell_test_$i
+echo Ok1
+
+cat <<\\shell_test_$i
+Here2
+\shell_test_$i
+echo Ok2
+
+cat <<"\shell_test_$i"
+Here3
+\shell_test_$i
+echo Ok3
+
+cat <<"\\shell_test_$i"
+Here4
+\shell_test_$i
+echo Ok4