hush: cleanup pass, the biggest is - moved builtins to the end of the file,
 they really annoy in the middle of parser code. no real code changes.

diff --git a/shell/hush.c b/shell/hush.c
index 0b92c29..e64cc47 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -244,7 +244,6 @@
 	PIPE_BG  = 4,
 } pipe_style;
 
-/* might eventually control execution */
 typedef enum {
 	RES_NONE  = 0,
 #if ENABLE_HUSH_IF
@@ -265,6 +264,7 @@
 	RES_XXXX  = 12,
 	RES_SNTX  = 13
 } reserved_style;
+
 /* This holds pointers to the various results of parsing */
 struct p_context {
 	struct child_prog *child;
@@ -298,6 +298,7 @@
  * and on execution these are substituted with their values.
  * Substitution can make _several_ words out of one argv[n]!
  * Example: argv[0]=='.^C*^C.' here: echo .$*.
+ * References of the form ^C`cmd arg^C are `cmd arg` substitutions.
  */
 
 struct pipe {
@@ -341,8 +342,8 @@
 	smallint nonnull;
 	smallint has_empty_slot;
 } o_string;
+/* Used for initialization: o_string foo = NULL_O_STRING; */
 #define NULL_O_STRING { NULL }
-/* used for initialization: o_string foo = NULL_O_STRING; */
 
 /* I can almost use ordinary FILE *.  Is open_memstream() universally
  * available?  Where is it documented? */
@@ -572,7 +573,7 @@
 	return 0;
 }
 
-static char **add_strings_to_strings(int need_xstrdup, char **strings, char **add)
+static char **add_malloced_strings_to_strings(char **strings, char **add)
 {
 	int i;
 	unsigned count1;
@@ -597,19 +598,18 @@
 	v[count1 + count2] = NULL;
 	i = count2;
 	while (--i >= 0)
-		v[count1 + i] = need_xstrdup ? xstrdup(add[i]) : add[i];
+		v[count1 + i] = add[i];
 	return v;
 }
 
-/* 'add' should be a malloced pointer */
-static char **add_string_to_strings(char **strings, char *add)
+static char **add_malloced_string_to_strings(char **strings, char *add)
 {
 	char *v[2];
 
 	v[0] = add;
 	v[1] = NULL;
 
-	return add_strings_to_strings(0, strings, v);
+	return add_malloced_strings_to_strings(strings, v);
 }
 
 static void free_strings(char **strings)
@@ -713,6 +713,7 @@
 #endif
 };
 
+
 /* Signals are grouped, we handle them in batches */
 static void set_misc_sighandler(void (*handler)(int))
 {
@@ -846,333 +847,6 @@
 }
 
 
-/* built-in 'true' handler */
-static int builtin_true(char **argv ATTRIBUTE_UNUSED)
-{
-	return 0;
-}
-
-/* built-in 'test' handler */
-static int builtin_test(char **argv)
-{
-	int argc = 0;
-	while (*argv) {
-		argc++;
-		argv++;
-	}
-	return test_main(argc, argv - argc);
-}
-
-/* built-in 'test' handler */
-static int builtin_echo(char **argv)
-{
-	int argc = 0;
-	while (*argv) {
-		argc++;
-		argv++;
-	}
-	return echo_main(argc, argv - argc);
-}
-
-/* built-in 'eval' handler */
-static int builtin_eval(char **argv)
-{
-	int rcode = EXIT_SUCCESS;
-
-	if (argv[1]) {
-		char *str = expand_strvec_to_string(argv + 1);
-		parse_and_run_string(str, PARSEFLAG_EXIT_FROM_LOOP);
-		free(str);
-		rcode = last_return_code;
-	}
-	return rcode;
-}
-
-/* built-in 'cd <path>' handler */
-static int builtin_cd(char **argv)
-{
-	const char *newdir;
-	if (argv[1] == NULL) {
-		// bash does nothing (exitcode 0) if HOME is ""; if it's unset,
-		// bash says "bash: cd: HOME not set" and does nothing (exitcode 1)
-		newdir = getenv("HOME") ? : "/";
-	} else
-		newdir = argv[1];
-	if (chdir(newdir)) {
-		printf("cd: %s: %s\n", newdir, strerror(errno));
-		return EXIT_FAILURE;
-	}
-	set_cwd();
-	return EXIT_SUCCESS;
-}
-
-/* built-in 'exec' handler */
-static int builtin_exec(char **argv)
-{
-	if (argv[1] == NULL)
-		return EXIT_SUCCESS; /* bash does this */
-	{
-#if !BB_MMU
-		char **ptrs2free = alloc_ptrs(argv);
-#endif
-// FIXME: if exec fails, bash does NOT exit! We do...
-		pseudo_exec_argv(ptrs2free, argv + 1);
-		/* never returns */
-	}
-}
-
-/* built-in 'exit' handler */
-static int builtin_exit(char **argv)
-{
-// TODO: bash does it ONLY on top-level sh exit (+interacive only?)
-	//puts("exit"); /* bash does it */
-// TODO: warn if we have background jobs: "There are stopped jobs"
-// On second consecutive 'exit', exit anyway.
-	if (argv[1] == NULL)
-		hush_exit(last_return_code);
-	/* mimic bash: exit 123abc == exit 255 + error msg */
-	xfunc_error_retval = 255;
-	/* bash: exit -2 == exit 254, no error msg */
-	hush_exit(xatoi(argv[1]) & 0xff);
-}
-
-/* built-in 'export VAR=value' handler */
-static int builtin_export(char **argv)
-{
-	const char *value;
-	char *name = argv[1];
-
-	if (name == NULL) {
-		// TODO:
-		// ash emits: export VAR='VAL'
-		// bash: declare -x VAR="VAL"
-		// (both also escape as needed (quotes, $, etc))
-		char **e = environ;
-		if (e)
-			while (*e)
-				puts(*e++);
-		return EXIT_SUCCESS;
-	}
-
-	value = strchr(name, '=');
-	if (!value) {
-		/* They are exporting something without a =VALUE */
-		struct variable *var;
-
-		var = get_local_var(name);
-		if (var) {
-			var->flg_export = 1;
-			putenv(var->varstr);
-		}
-		/* bash does not return an error when trying to export
-		 * an undefined variable.  Do likewise. */
-		return EXIT_SUCCESS;
-	}
-
-	set_local_var(xstrdup(name), 1);
-	return EXIT_SUCCESS;
-}
-
-#if ENABLE_HUSH_JOB
-/* built-in 'fg' and 'bg' handler */
-static int builtin_fg_bg(char **argv)
-{
-	int i, jobnum;
-	struct pipe *pi;
-
-	if (!interactive_fd)
-		return EXIT_FAILURE;
-	/* If they gave us no args, assume they want the last backgrounded task */
-	if (!argv[1]) {
-		for (pi = job_list; pi; pi = pi->next) {
-			if (pi->jobid == last_jobid) {
-				goto found;
-			}
-		}
-		bb_error_msg("%s: no current job", argv[0]);
-		return EXIT_FAILURE;
-	}
-	if (sscanf(argv[1], "%%%d", &jobnum) != 1) {
-		bb_error_msg("%s: bad argument '%s'", argv[0], argv[1]);
-		return EXIT_FAILURE;
-	}
-	for (pi = job_list; pi; pi = pi->next) {
-		if (pi->jobid == jobnum) {
-			goto found;
-		}
-	}
-	bb_error_msg("%s: %d: no such job", argv[0], jobnum);
-	return EXIT_FAILURE;
- found:
-	// TODO: bash prints a string representation
-	// of job being foregrounded (like "sleep 1 | cat")
-	if (*argv[0] == 'f') {
-		/* Put the job into the foreground.  */
-		tcsetpgrp(interactive_fd, pi->pgrp);
-	}
-
-	/* Restart the processes in the job */
-	debug_printf_jobs("reviving %d procs, pgrp %d\n", pi->num_progs, pi->pgrp);
-	for (i = 0; i < pi->num_progs; i++) {
-		debug_printf_jobs("reviving pid %d\n", pi->progs[i].pid);
-		pi->progs[i].is_stopped = 0;
-	}
-	pi->stopped_progs = 0;
-
-	i = kill(- pi->pgrp, SIGCONT);
-	if (i < 0) {
-		if (errno == ESRCH) {
-			delete_finished_bg_job(pi);
-			return EXIT_SUCCESS;
-		} else {
-			bb_perror_msg("kill (SIGCONT)");
-		}
-	}
-
-	if (*argv[0] == 'f') {
-		remove_bg_job(pi);
-		return checkjobs_and_fg_shell(pi);
-	}
-	return EXIT_SUCCESS;
-}
-#endif
-
-/* built-in 'help' handler */
-#if ENABLE_HUSH_HELP
-static int builtin_help(char **argv ATTRIBUTE_UNUSED)
-{
-	const struct built_in_command *x;
-
-	printf("\nBuilt-in commands:\n");
-	printf("-------------------\n");
-	for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) {
-		printf("%s\t%s\n", x->cmd, x->descr);
-	}
-	printf("\n\n");
-	return EXIT_SUCCESS;
-}
-#endif
-
-#if ENABLE_HUSH_JOB
-/* built-in 'jobs' handler */
-static int builtin_jobs(char **argv ATTRIBUTE_UNUSED)
-{
-	struct pipe *job;
-	const char *status_string;
-
-	for (job = job_list; job; job = job->next) {
-		if (job->running_progs == job->stopped_progs)
-			status_string = "Stopped";
-		else
-			status_string = "Running";
-
-		printf(JOB_STATUS_FORMAT, job->jobid, status_string, job->cmdtext);
-	}
-	return EXIT_SUCCESS;
-}
-#endif
-
-/* built-in 'pwd' handler */
-static int builtin_pwd(char **argv ATTRIBUTE_UNUSED)
-{
-	puts(set_cwd());
-	return EXIT_SUCCESS;
-}
-
-/* built-in 'read VAR' handler */
-static int builtin_read(char **argv)
-{
-	char *string;
-	const char *name = argv[1] ? argv[1] : "REPLY";
-
-	string = xmalloc_reads(STDIN_FILENO, xasprintf("%s=", name), NULL);
-	return set_local_var(string, 0);
-}
-
-/* built-in 'set [VAR=value]' handler */
-static int builtin_set(char **argv)
-{
-	char *temp = argv[1];
-	struct variable *e;
-
-	if (temp == NULL)
-		for (e = top_var; e; e = e->next)
-			puts(e->varstr);
-	else
-		set_local_var(xstrdup(temp), 0);
-
-	return EXIT_SUCCESS;
-}
-
-
-/* Built-in 'shift' handler */
-static int builtin_shift(char **argv)
-{
-	int n = 1;
-	if (argv[1]) {
-		n = atoi(argv[1]);
-	}
-	if (n >= 0 && n < global_argc) {
-		global_argv[n] = global_argv[0];
-		global_argc -= n;
-		global_argv += n;
-		return EXIT_SUCCESS;
-	}
-	return EXIT_FAILURE;
-}
-
-/* Built-in '.' handler (read-in and execute commands from file) */
-static int builtin_source(char **argv)
-{
-	FILE *input;
-	int status;
-
-	if (argv[1] == NULL)
-		return EXIT_FAILURE;
-
-	/* XXX search through $PATH is missing */
-	input = fopen(argv[1], "r");
-	if (!input) {
-		bb_error_msg("cannot open '%s'", argv[1]);
-		return EXIT_FAILURE;
-	}
-	close_on_exec_on(fileno(input));
-
-	/* Now run the file */
-	/* XXX argv and argc are broken; need to save old global_argv
-	 * (pointer only is OK!) on this stack frame,
-	 * set global_argv=argv+1, recurse, and restore. */
-	status = parse_and_run_file(input);
-	fclose(input);
-	return status;
-}
-
-static int builtin_umask(char **argv)
-{
-	mode_t new_umask;
-	const char *arg = argv[1];
-	char *end;
-	if (arg) {
-		new_umask = strtoul(arg, &end, 8);
-		if (*end != '\0' || end == arg) {
-			return EXIT_FAILURE;
-		}
-	} else {
-		new_umask = umask(0);
-		printf("%.3o\n", (unsigned) new_umask);
-	}
-	umask(new_umask);
-	return EXIT_SUCCESS;
-}
-
-/* built-in 'unset VAR' handler */
-static int builtin_unset(char **argv)
-{
-	/* bash always returns true */
-	unset_local_var(argv[1]);
-	return EXIT_SUCCESS;
-}
-
 /*
  * o_string support
  */
@@ -1282,7 +956,7 @@
 
 /* A special kind of o_string for $VAR and `cmd` expansion.
  * It contains char* list[] at the beginning, which is grown in 16 element
- * increments. Actual string data starts at the next multiple of 16.
+ * increments. Actual string data starts at the next multiple of 16 * (char*).
  * list[i] contains an INDEX (int!) into this string data.
  * It means that if list[] needs to grow, data needs to be moved higher up
  * but list[i]'s need not be modified.
@@ -1355,7 +1029,8 @@
 	return ((int)list[n-1]) + string_start;
 }
 
-/* Convert every \x to x in-place, return ptr past NUL. */
+/* o_glob performs globbing on last list[], saving each result
+ * as a new list[]. unbackslash() is just a helper */
 static char *unbackslash(char *src)
 {
 	char *dst = src;
@@ -1367,7 +1042,6 @@
 	}
 	return dst;
 }
-
 static int o_glob(o_string *o, int n)
 {
 	glob_t globdata;
@@ -1416,8 +1090,8 @@
 	return n;
 }
 
-/* o_save_ptr_helper + but glob the string so far remembered
- * if o->o_glob == 1 */
+/* If o->o_glob == 1, glob the string so far remembered.
+ * Otherwise, just finish current list[] and start new */
 static int o_save_ptr(o_string *o, int n)
 {
 	if (o->o_glob)
@@ -1462,6 +1136,7 @@
 }
 
 #if ENABLE_HUSH_INTERACTIVE
+
 #if ENABLE_FEATURE_EDITING
 static void cmdedit_set_initial_prompt(void)
 {
@@ -1521,6 +1196,7 @@
 #endif
 	i->p = user_input_buf;
 }
+
 #endif  /* INTERACTIVE */
 
 /* This is the magic location that prints prompts
@@ -1604,6 +1280,7 @@
 	i->eof_flag = 0;
 }
 
+
 /* squirrel != NULL means we squirrel away copies of stdin, stdout,
  * and stderr if they are redirected. */
 static int setup_redirects(struct child_prog *prog, int squirrel[])
@@ -1659,6 +1336,7 @@
 	}
 }
 
+
 /* Called after [v]fork() in run_pipe(), or from builtin_exec().
  * Never returns.
  * XXX no exit() here.  If you don't exec, use _exit instead.
@@ -2589,6 +2267,7 @@
 	return rcode;
 }
 
+
 /* expand_strvec_to_strvec() takes a list of strings, expands
  * all variable references within and returns a pointer to
  * a list of expanded strings, possibly with larger number
@@ -2737,7 +2416,9 @@
 			} else
 				val = lookup_param(arg);
 			arg[0] = first_ch;
+#if ENABLE_HUSH_TICK
  store_val:
+#endif
 			*p = SPECIAL_VAR_SYMBOL;
 			if (!(first_ch & 0x80)) { /* unquoted $VAR */
 				if (val) {
@@ -2801,7 +2482,7 @@
 	return expand_variables(argv, 0x100);
 }
 
-/* used for expansion of right hand of assignments */
+/* Used for expansion of right hand of assignments */
 /* NB: should NOT do globbing! "export v=/bin/c*; env | grep ^v=" outputs
  * "v=/bin/c*" */
 static char *expand_string_to_string(const char *str)
@@ -2820,7 +2501,7 @@
 	return (char*)list;
 }
 
-/* used for eval */
+/* Used for "eval" builtin */
 static char* expand_strvec_to_string(char **argv)
 {
 	char **list;
@@ -2842,7 +2523,8 @@
 	return (char*)list;
 }
 
-/* This is used to get/check local shell variables */
+
+/* Used to get/check local shell variables */
 static struct variable *get_local_var(const char *name)
 {
 	struct variable *cur;
@@ -3099,12 +2781,16 @@
 			continue;
 		debug_printf("found reserved word %s, res %d\n", r->literal, r->res);
 		if (r->flag == 0) { /* '!' */
+#if ENABLE_HUSH_LOOPS
 			if (ctx->res_w == RES_IN) {
 				/* 'for a in ! a b c; ...' - ! isn't a keyword here */
 				break;
 			}
-			if (ctx->res_w == RES_FOR /* example: 'for ! a' */
-			 || ctx->ctx_inverted /* bash doesn't accept '! ! true' */
+#endif
+			if (ctx->ctx_inverted /* bash doesn't accept '! ! true' */
+#if ENABLE_HUSH_LOOPS
+			 || ctx->res_w == RES_FOR /* example: 'for ! a' */
+#endif
 			) {
 				syntax(NULL);
 				ctx->res_w = RES_SNTX;
@@ -3152,8 +2838,7 @@
 #endif
 
 /* Word is complete, look at it and update parsing context.
- * Normal return is 0.
- * Syntax or xglob errors return 1. */
+ * Normal return is 0. Syntax errors return 1. */
 static int done_word(o_string *word, struct p_context *ctx)
 {
 	struct child_prog *child = ctx->child;
@@ -3184,7 +2869,7 @@
 	}
 
 	if (word->length || word->nonnull) {
-		*glob_target = add_string_to_strings(*glob_target, xstrdup(word->data));
+		*glob_target = add_malloced_string_to_strings(*glob_target, xstrdup(word->data));
 		debug_print_strings("glob_target appended", *glob_target);
 	}
 
@@ -3282,9 +2967,9 @@
 	debug_printf_parse("done_pipe return\n");
 }
 
-/* peek ahead in the in_str to find out if we have a "&n" construct,
+/* Peek ahead in the in_str to find out if we have a "&n" construct,
  * as in "2>&1", that represents duplicating a file descriptor.
- * returns either -2 (syntax error), -1 (no &), or the number found.
+ * Return either -2 (syntax error), -1 (no &), or the number found.
  */
 static int redirect_dup_num(struct in_str *input)
 {
@@ -3395,7 +3080,7 @@
 
 	initialize_context(&inner);
 
-	/* recursion to generate command */
+	/* Recursion to generate command */
 	retcode = parse_stream(&result, &inner, input, subst_end);
 	if (retcode != 0)
 		return retcode;  /* syntax error or EOF */
@@ -3409,7 +3094,7 @@
 	close_on_exec_on(fileno(p));
 	setup_file_in_str(&pipe_str, p);
 
-	/* now send results of command back into original context */
+	/* Now send results of command back into original context */
 	eol_cnt = 0;
 	while ((ch = i_getch(&pipe_str)) != EOF) {
 		if (ch == '\n') {
@@ -3596,7 +3281,7 @@
 }
 #endif /* ENABLE_HUSH_TICK */
 
-/* return code: 0 for OK, 1 for syntax error */
+/* Return code: 0 for OK, 1 for syntax error */
 static int handle_dollar(o_string *dest, struct in_str *input)
 {
 	int ch = i_peek(input);  /* first character after the $ */
@@ -3674,7 +3359,7 @@
 	return 0;
 }
 
-/* return code is 0 for normal exit, 1 for syntax error */
+/* Return code is 0 for normal exit, 1 for syntax error */
 static int parse_stream(o_string *dest, struct p_context *ctx,
 	struct in_str *input, const char *end_trigger)
 {
@@ -3937,7 +3622,7 @@
 	set_in_charmap(ifs, CHAR_IFS);  /* are ordinary if quoted */
 }
 
-/* most recursion does not come through here, the exception is
+/* Most recursion does not come through here, the exception is
  * from builtin_source() and builtin_eval() */
 static int parse_and_run_stream(struct in_str *inp, int parse_flag)
 {
@@ -3975,7 +3660,9 @@
 			free_pipe_list(ctx.list_head, /* indent: */ 0);
 			/* Discard all unprocessed line input, force prompt on */
 			inp->p = NULL;
+#if ENABLE_HUSH_INTERACTIVE
 			inp->promptme = 1;
+#endif
 		}
 		o_free(&temp);
 		/* loop on syntax errors, return on EOF: */
@@ -4034,6 +3721,7 @@
 }
 #endif
 
+
 int hush_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 int hush_main(int argc, char **argv)
 {
@@ -4230,3 +3918,318 @@
 	return hush_main(argc, argv);
 }
 #endif
+
+
+/*
+ * Built-ins
+ */
+static int builtin_true(char **argv ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+static int builtin_test(char **argv)
+{
+	int argc = 0;
+	while (*argv) {
+		argc++;
+		argv++;
+	}
+	return test_main(argc, argv - argc);
+}
+
+static int builtin_echo(char **argv)
+{
+	int argc = 0;
+	while (*argv) {
+		argc++;
+		argv++;
+	}
+	return echo_main(argc, argv - argc);
+}
+
+static int builtin_eval(char **argv)
+{
+	int rcode = EXIT_SUCCESS;
+
+	if (argv[1]) {
+		char *str = expand_strvec_to_string(argv + 1);
+		parse_and_run_string(str, PARSEFLAG_EXIT_FROM_LOOP);
+		free(str);
+		rcode = last_return_code;
+	}
+	return rcode;
+}
+
+static int builtin_cd(char **argv)
+{
+	const char *newdir;
+	if (argv[1] == NULL) {
+		// bash does nothing (exitcode 0) if HOME is ""; if it's unset,
+		// bash says "bash: cd: HOME not set" and does nothing (exitcode 1)
+		newdir = getenv("HOME") ? : "/";
+	} else
+		newdir = argv[1];
+	if (chdir(newdir)) {
+		printf("cd: %s: %s\n", newdir, strerror(errno));
+		return EXIT_FAILURE;
+	}
+	set_cwd();
+	return EXIT_SUCCESS;
+}
+
+static int builtin_exec(char **argv)
+{
+	if (argv[1] == NULL)
+		return EXIT_SUCCESS; /* bash does this */
+	{
+#if !BB_MMU
+		char **ptrs2free = alloc_ptrs(argv);
+#endif
+// FIXME: if exec fails, bash does NOT exit! We do...
+		pseudo_exec_argv(ptrs2free, argv + 1);
+		/* never returns */
+	}
+}
+
+static int builtin_exit(char **argv)
+{
+// TODO: bash does it ONLY on top-level sh exit (+interacive only?)
+	//puts("exit"); /* bash does it */
+// TODO: warn if we have background jobs: "There are stopped jobs"
+// On second consecutive 'exit', exit anyway.
+	if (argv[1] == NULL)
+		hush_exit(last_return_code);
+	/* mimic bash: exit 123abc == exit 255 + error msg */
+	xfunc_error_retval = 255;
+	/* bash: exit -2 == exit 254, no error msg */
+	hush_exit(xatoi(argv[1]) & 0xff);
+}
+
+static int builtin_export(char **argv)
+{
+	const char *value;
+	char *name = argv[1];
+
+	if (name == NULL) {
+		// TODO:
+		// ash emits: export VAR='VAL'
+		// bash: declare -x VAR="VAL"
+		// (both also escape as needed (quotes, $, etc))
+		char **e = environ;
+		if (e)
+			while (*e)
+				puts(*e++);
+		return EXIT_SUCCESS;
+	}
+
+	value = strchr(name, '=');
+	if (!value) {
+		/* They are exporting something without a =VALUE */
+		struct variable *var;
+
+		var = get_local_var(name);
+		if (var) {
+			var->flg_export = 1;
+			putenv(var->varstr);
+		}
+		/* bash does not return an error when trying to export
+		 * an undefined variable.  Do likewise. */
+		return EXIT_SUCCESS;
+	}
+
+	set_local_var(xstrdup(name), 1);
+	return EXIT_SUCCESS;
+}
+
+#if ENABLE_HUSH_JOB
+/* built-in 'fg' and 'bg' handler */
+static int builtin_fg_bg(char **argv)
+{
+	int i, jobnum;
+	struct pipe *pi;
+
+	if (!interactive_fd)
+		return EXIT_FAILURE;
+	/* If they gave us no args, assume they want the last backgrounded task */
+	if (!argv[1]) {
+		for (pi = job_list; pi; pi = pi->next) {
+			if (pi->jobid == last_jobid) {
+				goto found;
+			}
+		}
+		bb_error_msg("%s: no current job", argv[0]);
+		return EXIT_FAILURE;
+	}
+	if (sscanf(argv[1], "%%%d", &jobnum) != 1) {
+		bb_error_msg("%s: bad argument '%s'", argv[0], argv[1]);
+		return EXIT_FAILURE;
+	}
+	for (pi = job_list; pi; pi = pi->next) {
+		if (pi->jobid == jobnum) {
+			goto found;
+		}
+	}
+	bb_error_msg("%s: %d: no such job", argv[0], jobnum);
+	return EXIT_FAILURE;
+ found:
+	// TODO: bash prints a string representation
+	// of job being foregrounded (like "sleep 1 | cat")
+	if (*argv[0] == 'f') {
+		/* Put the job into the foreground.  */
+		tcsetpgrp(interactive_fd, pi->pgrp);
+	}
+
+	/* Restart the processes in the job */
+	debug_printf_jobs("reviving %d procs, pgrp %d\n", pi->num_progs, pi->pgrp);
+	for (i = 0; i < pi->num_progs; i++) {
+		debug_printf_jobs("reviving pid %d\n", pi->progs[i].pid);
+		pi->progs[i].is_stopped = 0;
+	}
+	pi->stopped_progs = 0;
+
+	i = kill(- pi->pgrp, SIGCONT);
+	if (i < 0) {
+		if (errno == ESRCH) {
+			delete_finished_bg_job(pi);
+			return EXIT_SUCCESS;
+		} else {
+			bb_perror_msg("kill (SIGCONT)");
+		}
+	}
+
+	if (*argv[0] == 'f') {
+		remove_bg_job(pi);
+		return checkjobs_and_fg_shell(pi);
+	}
+	return EXIT_SUCCESS;
+}
+#endif
+
+#if ENABLE_HUSH_HELP
+static int builtin_help(char **argv ATTRIBUTE_UNUSED)
+{
+	const struct built_in_command *x;
+
+	printf("\nBuilt-in commands:\n");
+	printf("-------------------\n");
+	for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) {
+		printf("%s\t%s\n", x->cmd, x->descr);
+	}
+	printf("\n\n");
+	return EXIT_SUCCESS;
+}
+#endif
+
+#if ENABLE_HUSH_JOB
+static int builtin_jobs(char **argv ATTRIBUTE_UNUSED)
+{
+	struct pipe *job;
+	const char *status_string;
+
+	for (job = job_list; job; job = job->next) {
+		if (job->running_progs == job->stopped_progs)
+			status_string = "Stopped";
+		else
+			status_string = "Running";
+
+		printf(JOB_STATUS_FORMAT, job->jobid, status_string, job->cmdtext);
+	}
+	return EXIT_SUCCESS;
+}
+#endif
+
+static int builtin_pwd(char **argv ATTRIBUTE_UNUSED)
+{
+	puts(set_cwd());
+	return EXIT_SUCCESS;
+}
+
+static int builtin_read(char **argv)
+{
+	char *string;
+	const char *name = argv[1] ? argv[1] : "REPLY";
+
+	string = xmalloc_reads(STDIN_FILENO, xasprintf("%s=", name), NULL);
+	return set_local_var(string, 0);
+}
+
+/* built-in 'set [VAR=value]' handler */
+static int builtin_set(char **argv)
+{
+	char *temp = argv[1];
+	struct variable *e;
+
+	if (temp == NULL)
+		for (e = top_var; e; e = e->next)
+			puts(e->varstr);
+	else
+		set_local_var(xstrdup(temp), 0);
+
+	return EXIT_SUCCESS;
+}
+
+static int builtin_shift(char **argv)
+{
+	int n = 1;
+	if (argv[1]) {
+		n = atoi(argv[1]);
+	}
+	if (n >= 0 && n < global_argc) {
+		global_argv[n] = global_argv[0];
+		global_argc -= n;
+		global_argv += n;
+		return EXIT_SUCCESS;
+	}
+	return EXIT_FAILURE;
+}
+
+static int builtin_source(char **argv)
+{
+	FILE *input;
+	int status;
+
+	if (argv[1] == NULL)
+		return EXIT_FAILURE;
+
+	/* XXX search through $PATH is missing */
+	input = fopen(argv[1], "r");
+	if (!input) {
+		bb_error_msg("cannot open '%s'", argv[1]);
+		return EXIT_FAILURE;
+	}
+	close_on_exec_on(fileno(input));
+
+	/* Now run the file */
+	/* XXX argv and argc are broken; need to save old global_argv
+	 * (pointer only is OK!) on this stack frame,
+	 * set global_argv=argv+1, recurse, and restore. */
+	status = parse_and_run_file(input);
+	fclose(input);
+	return status;
+}
+
+static int builtin_umask(char **argv)
+{
+	mode_t new_umask;
+	const char *arg = argv[1];
+	char *end;
+	if (arg) {
+		new_umask = strtoul(arg, &end, 8);
+		if (*end != '\0' || end == arg) {
+			return EXIT_FAILURE;
+		}
+	} else {
+		new_umask = umask(0);
+		printf("%.3o\n", (unsigned) new_umask);
+	}
+	umask(new_umask);
+	return EXIT_SUCCESS;
+}
+
+static int builtin_unset(char **argv)
+{
+	/* bash always returns true */
+	unset_local_var(argv[1]);
+	return EXIT_SUCCESS;
+}
diff --git a/shell/hush_leaktool.sh b/shell/hush_leaktool.sh
index 54a19aa..54161b3 100644
--- a/shell/hush_leaktool.sh
+++ b/shell/hush_leaktool.sh
@@ -8,6 +8,6 @@
 grep -v free "$output" >temp1
 for freed in $freelist; do
     echo Dropping $freed
-    cat temp1 | grep -v $freed >temp2
+    grep -v $freed <temp1 >temp2
     mv temp2 temp1
 done