# define PIPE_BUF 4096 /* amount of buffering in a pipe */
#endif
-/* Not every libc has sighandler_t. Fix it */
-typedef void (*hush_sighandler_t)(int);
-#define sighandler_t hush_sighandler_t
-
//config:config HUSH
//config: bool "hush"
//config: default y
# define ENABLE_FEATURE_EDITING 0
# undef ENABLE_FEATURE_EDITING_FANCY_PROMPT
# define ENABLE_FEATURE_EDITING_FANCY_PROMPT 0
+# undef ENABLE_FEATURE_EDITING_SAVE_ON_EXIT
+# define ENABLE_FEATURE_EDITING_SAVE_ON_EXIT 0
#endif
/* Do we support ANY keywords? */
struct command {
pid_t pid; /* 0 if exited */
int assignment_cnt; /* how many argv[i] are assignments? */
- smallint is_stopped; /* is the command currently running? */
smallint cmd_type; /* CMD_xxx */
#define CMD_NORMAL 0
#define CMD_SUBSHELL 1
#if ENABLE_HUSH_HELP
static int builtin_help(char **argv) FAST_FUNC;
#endif
+#if MAX_HISTORY && ENABLE_FEATURE_EDITING
+static int builtin_history(char **argv) FAST_FUNC;
+#endif
#if ENABLE_HUSH_LOCAL
static int builtin_local(char **argv) FAST_FUNC;
#endif
#if ENABLE_HUSH_HELP
BLTIN("help" , builtin_help , NULL),
#endif
+#if MAX_HISTORY && ENABLE_FEATURE_EDITING
+ BLTIN("history" , builtin_history , "Show command history"),
+#endif
#if ENABLE_HUSH_JOB
BLTIN("jobs" , builtin_jobs , "List jobs"),
#endif
* backgrounds (i.e. stops) or kills all members of currently running
* pipe.
*
- * Wait builtin in interruptible by signals for which user trap is set
+ * Wait builtin is interruptible by signals for which user trap is set
* or by SIGINT in interactive shell.
*
* Trap handlers will execute even within trap handlers. (right?)
* are set to '' (ignore) are NOT reset to defaults. We do the same.
*
* Problem: the above approach makes it unwieldy to catch signals while
- * we are in read builtin, of while we read commands from stdin:
+ * we are in read builtin, or while we read commands from stdin:
* masked signals are not visible!
*
* New implementation
* for them - a bit like emulating kernel pending signal mask in userspace.
* We are interested in: signals which need to have special handling
* as described above, and all signals which have traps set.
- * Signals are rocorded in pending_set.
+ * Signals are recorded in pending_set.
* After each pipe execution, we extract any pending signals
* and act on them.
*
static void hush_exit(int exitcode) NORETURN;
static void hush_exit(int exitcode)
{
+#if ENABLE_FEATURE_EDITING_SAVE_ON_EXIT
+ save_history(G.line_input_state);
+#endif
+
fflush_all();
if (G.exiting <= 0 && G.traps && G.traps[0] && G.traps[0][0]) {
char *argv[3];
* _during_ shell execution, not only if it was set when
* shell was started. Therefore, re-check LANG every time:
*/
- reinit_unicode(get_local_var_value("LANG"));
+ const char *s = get_local_var_value("LC_ALL");
+ if (!s) s = get_local_var_value("LC_CTYPE");
+ if (!s) s = get_local_var_value("LANG");
+ reinit_unicode(s);
G.flag_SIGINT = 0;
/* buglet: SIGINT will not make new prompt to appear _at once_,
ordinary_cnt = len;
o_addblock(o, str, ordinary_cnt);
if (ordinary_cnt == len)
- return;
+ return; /* NUL is already added by o_addblock */
str += ordinary_cnt;
len -= ordinary_cnt + 1; /* we are processing + 1 char below */
o_grow_by(o, sz);
o->data[o->length] = ch;
o->length++;
- o->data[o->length] = '\0';
}
+ o->data[o->length] = '\0';
}
static void o_addQblock(o_string *o, const char *str, int len)
n, string_len, string_start);
o->has_empty_slot = 0;
}
+ o->has_quoted_part = 0;
list[n] = (char*)(uintptr_t)string_len;
return n + 1;
}
) {
p += 3;
}
- if (p == word->data || p[0] != '\0') {
- /* 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);
- }
}
command->argv = add_string_to_strings(command->argv, xstrdup(word->data));
debug_print_strings("word appended to argv", command->argv);
/* (this makes bare "&" cmd a no-op.
* bash says: "syntax error near unexpected token '&'") */
if (pi->num_cmds == 0
- IF_HAS_KEYWORDS( && pi->res_word == RES_NONE)
+ IF_HAS_KEYWORDS(&& pi->res_word == RES_NONE)
) {
free_pipe_list(pi);
pi = NULL;
debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]);
/* Do we sit outside of any if's, loops or case's? */
if (!HAS_KEYWORDS
- IF_HAS_KEYWORDS(|| (ctx.ctx_res_w == RES_NONE && ctx.old_flag == 0))
+ IF_HAS_KEYWORDS(|| (ctx.ctx_res_w == RES_NONE && ctx.old_flag == 0))
) {
o_free(&dest);
#if !BB_MMU
break;
case '\'':
dest.has_quoted_part = 1;
- while (1) {
- ch = i_getch(input);
- if (ch == EOF) {
- syntax_error_unterm_ch('\'');
- goto parse_error;
+ if (next == '\'' && !ctx.pending_redirect) {
+ insert_empty_quoted_str_marker:
+ nommu_addchr(&ctx.as_string, next);
+ i_getch(input); /* eat second ' */
+ o_addchr(&dest, SPECIAL_VAR_SYMBOL);
+ o_addchr(&dest, SPECIAL_VAR_SYMBOL);
+ } else {
+ while (1) {
+ ch = i_getch(input);
+ if (ch == EOF) {
+ syntax_error_unterm_ch('\'');
+ goto parse_error;
+ }
+ nommu_addchr(&ctx.as_string, ch);
+ if (ch == '\'')
+ break;
+ o_addqchr(&dest, ch);
}
- nommu_addchr(&ctx.as_string, ch);
- if (ch == '\'')
- break;
- o_addqchr(&dest, ch);
}
break;
case '"':
dest.has_quoted_part = 1;
+ if (next == '"' && !ctx.pending_redirect)
+ goto insert_empty_quoted_str_marker;
if (dest.o_assignment == NOT_ASSIGNMENT)
dest.o_expflags |= EXP_FLAG_ESC_GLOB_CHARS;
if (!encode_string(&ctx.as_string, &dest, input, '"', /*process_bkslash:*/ 1))
/* Store given string, finalizing the word and starting new one whenever
* we encounter IFS char(s). This is used for expanding variable values.
- * End-of-string does NOT finalize word: think about 'echo -$VAR-' */
-static int expand_on_ifs(o_string *output, int n, const char *str)
+ * End-of-string does NOT finalize word: think about 'echo -$VAR-'.
+ * Return in *ended_with_ifs:
+ * 1 - ended with IFS char, else 0 (this includes case of empty str).
+ */
+static int expand_on_ifs(int *ended_with_ifs, o_string *output, int n, const char *str)
{
+ int last_is_ifs = 0;
+
while (1) {
- int word_len = strcspn(str, G.ifs);
+ int word_len;
+
+ if (!*str) /* EOL - do not finalize word */
+ break;
+ word_len = strcspn(str, G.ifs);
if (word_len) {
+ /* We have WORD_LEN leading non-IFS chars */
if (!(output->o_expflags & EXP_FLAG_GLOB)) {
o_addblock(output, str, word_len);
} else {
/*o_addblock(output, str, word_len); - WRONG: "v='\*'; echo Z$v" prints "Z*" instead of "Z\*" */
/*o_addqblock(output, str, word_len); - WRONG: "v='*'; echo Z$v" prints "Z*" instead of Z* files */
}
+ last_is_ifs = 0;
str += word_len;
+ if (!*str) /* EOL - do not finalize word */
+ break;
}
+
+ /* We know str here points to at least one IFS char */
+ last_is_ifs = 1;
+ str += strspn(str, G.ifs); /* skip IFS chars */
if (!*str) /* EOL - do not finalize word */
break;
- o_addchr(output, '\0');
- debug_print_list("expand_on_ifs", output, n);
- n = o_save_ptr(output, n);
- str += strspn(str, G.ifs); /* skip ifs chars */
+
+ /* Start new word... but not always! */
+ /* Case "v=' a'; echo ''$v": we do need to finalize empty word: */
+ if (output->has_quoted_part
+ /* Case "v=' a'; echo $v":
+ * here nothing precedes the space in $v expansion,
+ * therefore we should not finish the word
+ * (IOW: if there *is* word to finalize, only then do it):
+ */
+ || (n > 0 && output->data[output->length - 1])
+ ) {
+ o_addchr(output, '\0');
+ debug_print_list("expand_on_ifs", output, n);
+ n = o_save_ptr(output, n);
+ }
}
+
+ if (ended_with_ifs)
+ *ended_with_ifs = last_is_ifs;
debug_print_list("expand_on_ifs[1]", output, n);
return n;
}
* expansion of right-hand side of assignment == 1-element expand.
*/
char cant_be_null = 0; /* only bit 0x80 matters */
+ int ended_in_ifs = 0; /* did last unquoted expansion end with IFS chars? */
char *p;
debug_printf_expand("expand_vars_to_list: arg:'%s' singleword:%x\n", arg,
#if ENABLE_SH_MATH_SUPPORT
char arith_buf[sizeof(arith_t)*3 + 2];
#endif
+
+ if (ended_in_ifs) {
+ o_addchr(output, '\0');
+ n = o_save_ptr(output, n);
+ ended_in_ifs = 0;
+ }
+
o_addblock(output, arg, p - arg);
debug_print_list("expand_vars_to_list[1]", output, n);
arg = ++p;
cant_be_null |= first_ch; /* do it for "$@" _now_, when we know it's not empty */
if (!(first_ch & 0x80)) { /* unquoted $* or $@ */
while (G.global_argv[i]) {
- n = expand_on_ifs(output, n, G.global_argv[i]);
+ n = expand_on_ifs(NULL, output, n, G.global_argv[i]);
debug_printf_expand("expand_vars_to_list: argv %d (last %d)\n", i, G.global_argc - 1);
if (G.global_argv[i++][0] && G.global_argv[i]) {
/* this argv[] is not empty and not last:
if (G.ifs[0])
o_addchr(output, G.ifs[0]);
}
+ output->has_quoted_part = 1;
}
break;
}
case SPECIAL_VAR_SYMBOL: /* <SPECIAL_VAR_SYMBOL><SPECIAL_VAR_SYMBOL> */
/* "Empty variable", used to make "" etc to not disappear */
+ output->has_quoted_part = 1;
arg++;
cant_be_null = 0x80;
break;
debug_printf_expand("unquoted '%s', output->o_escape:%d\n", val,
!!(output->o_expflags & EXP_FLAG_ESC_GLOB_CHARS));
if (val && val[0]) {
- n = expand_on_ifs(output, n, val);
+ n = expand_on_ifs(&ended_in_ifs, output, n, val);
val = NULL;
}
} else { /* quoted $VAR, val will be appended below */
+ output->has_quoted_part = 1;
debug_printf_expand("quoted '%s', output->o_escape:%d\n", val,
!!(output->o_expflags & EXP_FLAG_ESC_GLOB_CHARS));
}
} /* end of "while (SPECIAL_VAR_SYMBOL is found) ..." */
if (arg[0]) {
+ if (ended_in_ifs) {
+ o_addchr(output, '\0');
+ n = o_save_ptr(output, n);
+ }
debug_print_list("expand_vars_to_list[a]", output, n);
/* this part is literal, and it was already pre-quoted
* if needed (much earlier), do not use o_addQstr here! */
}
-#if BB_MMU
-/* never called */
-void re_execute_shell(char ***to_free, const char *s,
- char *g_argv0, char **g_argv,
- char **builtin_argv) NORETURN;
-
static void switch_off_special_sigs(unsigned mask)
{
unsigned sig = 0;
}
}
+#if BB_MMU
+/* never called */
+void re_execute_shell(char ***to_free, const char *s,
+ char *g_argv0, char **g_argv,
+ char **builtin_argv) NORETURN;
+
static void reset_traps_to_defaults(void)
{
/* This function is always called in a child shell
}
fg_pipe->cmds[i].cmd_exitcode = ex;
} else {
- fg_pipe->cmds[i].is_stopped = 1;
fg_pipe->stopped_cmds++;
}
debug_printf_jobs("fg_pipe: alive_cmds %d stopped_cmds %d\n",
}
} else {
/* child stopped */
- pi->cmds[i].is_stopped = 1;
pi->stopped_cmds++;
}
#endif
* and we should not execute CMD */
debug_printf_exec("skipped cmd because of || or &&\n");
last_followup = pi->followup;
- continue;
+ goto dont_check_jobs_but_continue;
}
}
last_followup = pi->followup;
G.flag_break_continue = 0;
/* else: e.g. "continue 2" should *break* once, *then* continue */
} /* else: "while... do... { we are here (innermost list is not a loop!) };...done" */
- if (G.depth_break_continue != 0 || fbc == BC_BREAK)
- goto check_jobs_and_break;
+ if (G.depth_break_continue != 0 || fbc == BC_BREAK) {
+ checkjobs(NULL);
+ break;
+ }
/* "continue": simulate end of loop */
rword = RES_DONE;
continue;
#endif
#if ENABLE_HUSH_FUNCTIONS
if (G.flag_return_in_progress == 1) {
- /* same as "goto check_jobs_and_break" */
checkjobs(NULL);
break;
}
if (rword == RES_IF || rword == RES_ELIF)
cond_code = rcode;
#endif
+ check_jobs_and_continue:
+ checkjobs(NULL);
+ dont_check_jobs_but_continue: ;
#if ENABLE_HUSH_LOOPS
/* Beware of "while false; true; do ..."! */
if (pi->next
/* "while false; do...done" - exitcode 0 */
G.last_exitcode = rcode = EXIT_SUCCESS;
debug_printf_exec(": while expr is false: breaking (exitcode:EXIT_SUCCESS)\n");
- goto check_jobs_and_break;
+ break;
}
}
if (rword == RES_UNTIL) {
if (!rcode) {
debug_printf_exec(": until expr is true: breaking\n");
- check_jobs_and_break:
- checkjobs(NULL);
break;
}
}
}
#endif
-
- check_jobs_and_continue:
- checkjobs(NULL);
} /* for (pi) */
#if ENABLE_HUSH_JOB
#if ENABLE_FEATURE_EDITING
G.line_input_state = new_line_input_t(FOR_SHELL);
-# if defined MAX_HISTORY && MAX_HISTORY > 0 && ENABLE_HUSH_SAVEHISTORY
- {
- const char *hp = get_local_var_value("HISTFILE");
- if (!hp) {
- hp = get_local_var_value("HOME");
- if (hp) {
- G.line_input_state->hist_file = concat_path_file(hp, ".hush_history");
- //set_local_var(xasprintf("HISTFILE=%s", ...));
- }
- }
-# if ENABLE_FEATURE_SH_HISTFILESIZE
- hp = get_local_var_value("HISTFILESIZE");
- G.line_input_state->max_history = size_from_HISTFILESIZE(hp);
-# endif
- }
-# endif
#endif
/* Initialize some more globals to non-zero values */
/* -1 is special - makes xfuncs longjmp, not exit
* (we reset die_sleep = 0 whereever we [v]fork) */
enable_restore_tty_pgrp_on_exit(); /* sets die_sleep = -1 */
+
+# if ENABLE_HUSH_SAVEHISTORY && MAX_HISTORY > 0
+ {
+ const char *hp = get_local_var_value("HISTFILE");
+ if (!hp) {
+ hp = get_local_var_value("HOME");
+ if (hp)
+ hp = concat_path_file(hp, ".hush_history");
+ } else {
+ hp = xstrdup(hp);
+ }
+ if (hp) {
+ G.line_input_state->hist_file = hp;
+ //set_local_var(xasprintf("HISTFILE=%s", ...));
+ }
+# if ENABLE_FEATURE_SH_HISTFILESIZE
+ hp = get_local_var_value("HISTFILESIZE");
+ G.line_input_state->max_history = size_from_HISTFILESIZE(hp);
+# endif
+ }
+# endif
} else {
install_special_sighandlers();
}
* (if there are _stopped_ jobs, running ones don't count)
* # exit
* exit
- # EEE (then bash exits)
+ * EEE (then bash exits)
*
* TODO: we can use G.exiting = -1 as indicator "last cmd was exit"
*/
debug_printf_jobs("reviving %d procs, pgrp %d\n", pi->num_cmds, pi->pgrp);
for (i = 0; i < pi->num_cmds; i++) {
debug_printf_jobs("reviving pid %d\n", pi->cmds[i].pid);
- pi->cmds[i].is_stopped = 0;
}
pi->stopped_cmds = 0;
}
#endif
+#if MAX_HISTORY && ENABLE_FEATURE_EDITING
+static int FAST_FUNC builtin_history(char **argv UNUSED_PARAM)
+{
+ show_history(G.line_input_state);
+ return EXIT_SUCCESS;
+}
+#endif
+
#if ENABLE_HUSH_JOB
static int FAST_FUNC builtin_jobs(char **argv UNUSED_PARAM)
{
free(arg_path);
if (!input) {
/* bb_perror_msg("%s", *argv); - done by fopen_or_warn */
+ /* POSIX: non-interactive shell should abort here,
+ * not merely fail. So far no one complained :)
+ */
return EXIT_FAILURE;
}
close_on_exec_on(fileno(input));
/* "we are inside sourced file, ok to use return" */
G.flag_return_in_progress = -1;
#endif
- save_and_replace_G_args(&sv, argv);
+ if (argv[1])
+ save_and_replace_G_args(&sv, argv);
parse_and_run_file(input);
fclose(input);
- restore_G_args(&sv, argv);
+ if (argv[1])
+ restore_G_args(&sv, argv);
#if ENABLE_HUSH_FUNCTIONS
G.flag_return_in_progress = sv_flg;
#endif