* in word = GLOB, quoting should be significant on char-by-char basis: a*cd"*"
*/
//config:config HUSH
-//config: bool "hush (68 kb)"
+//config: bool "hush (70 kb)"
//config: default y
//config: select SHELL_HUSH
//config: help
* therefore we don't show them either.
*/
//usage:#define hush_trivial_usage
-//usage: "[-enxl] [-c 'SCRIPT' [ARG0 ARGS] | FILE [ARGS] | -s [ARGS]]"
+//usage: "[-enxl] [-c 'SCRIPT' [ARG0 ARGS] | FILE ARGS | -s ARGS]"
//usage:#define hush_full_usage "\n\n"
//usage: "Unix shell interpreter"
# define F_DUPFD_CLOEXEC F_DUPFD
#endif
-#if ENABLE_FEATURE_SH_EMBEDDED_SCRIPTS && !(ENABLE_ASH || ENABLE_SH_IS_ASH || ENABLE_BASH_IS_ASH)
+#if ENABLE_FEATURE_SH_EMBEDDED_SCRIPTS && !ENABLE_SHELL_ASH
# include "embedded_scripts.h"
#else
# define NUM_SCRIPTS 0
#define NULL_O_STRING { NULL }
#ifndef debug_printf_parse
-static const char *const assignment_flag[] = {
+static const char *const assignment_flag[] ALIGN_PTR = {
"MAYBE_ASSIGNMENT",
"DEFINITELY_ASSIGNMENT",
"NOT_ASSIGNMENT",
#if ENABLE_HUSH_INTERACTIVE
smallint promptmode; /* 0: PS1, 1: PS2 */
#endif
+ /* set by signal handler if SIGINT is received _and_ its trap is not set */
smallint flag_SIGINT;
#ifndef __U_BOOT__
#if ENABLE_HUSH_LOOPS
#if ENABLE_HUSH_READONLY
static int builtin_readonly(char **argv) FAST_FUNC;
#endif
+static int builtin_false(char **argv) FAST_FUNC;
#if ENABLE_HUSH_JOB
static int builtin_fg_bg(char **argv) FAST_FUNC;
static int builtin_jobs(char **argv) FAST_FUNC;
#if ENABLE_HUSH_EXPORT
BLTIN("export" , builtin_export , "Set environment variables"),
#endif
+ BLTIN("false" , builtin_false , NULL),
#if ENABLE_HUSH_JOB
BLTIN("fg" , builtin_fg_bg , "Bring job to foreground"),
#endif
{
bb_error_msg("syntax error: unterminated %s", s);
//? source4.tests fails: in bash, echo ${^} in script does not terminate the script
+// (but bash --posix, or if bash is run as "sh", does terminate in script, so maybe uncomment this?)
// die_if_script();
}
* SIGQUIT: ignore
* SIGTERM (interactive): ignore
* SIGHUP (interactive):
- * send SIGCONT to stopped jobs, send SIGHUP to all jobs and exit
+ * Send SIGCONT to stopped jobs, send SIGHUP to all jobs and exit.
+ * Kernel would do this for us ("orphaned process group" handling
+ * according to POSIX) if we are a session leader and thus our death
+ * frees the controlling tty, but to be bash-compatible, we also do it
+ * for every interactive shell's death by SIGHUP.
+ * (Also, we need to restore tty pgrp, otherwise e.g. Midnight Commander
+ * backgrounds when hush started from it gets killed by SIGHUP).
* SIGTTIN, SIGTTOU, SIGTSTP (if job control is on): ignore
* Note that ^Z is handled not by trapping SIGTSTP, but by seeing
* that all pipe members are stopped. Try this in bash:
static void record_pending_signo(int sig)
{
sigaddset(&G.pending_set, sig);
+#if ENABLE_FEATURE_EDITING
+ if (sig != SIGCHLD
+ || (G_traps && G_traps[SIGCHLD] && G_traps[SIGCHLD][0])
+ /* ^^^ if SIGCHLD, interrupt line reading only if it has a trap */
+ ) {
+ bb_got_signal = sig; /* for read_line_input: "we got a signal" */
+ }
+#endif
#if ENABLE_HUSH_FAST
if (sig == SIGCHLD) {
G.count_SIGCHLD++;
break;
#if ENABLE_HUSH_JOB
case SIGHUP: {
-//TODO: why are we doing this? ash and dash don't do this,
-//they have no handler for SIGHUP at all,
-//they rely on kernel to send SIGHUP+SIGCONT to orphaned process groups
- struct pipe *job;
- debug_printf_exec("%s: sig:%d default SIGHUP handler\n", __func__, sig);
- /* bash is observed to signal whole process groups,
- * not individual processes */
- for (job = G.job_list; job; job = job->next) {
- if (job->pgrp <= 0)
- continue;
- debug_printf_exec("HUPing pgrp %d\n", job->pgrp);
- if (kill(- job->pgrp, SIGHUP) == 0)
- kill(- job->pgrp, SIGCONT);
+ /* if (G_interactive_fd) - no need to check, the handler
+ * is only installed if we *are* interactive */
+ {
+ /* bash compat: "Before exiting, an interactive
+ * shell resends the SIGHUP to all jobs, running
+ * or stopped. Stopped jobs are sent SIGCONT
+ * to ensure that they receive the SIGHUP."
+ */
+ struct pipe *job;
+ debug_printf_exec("%s: sig:%d default SIGHUP handler\n", __func__, sig);
+ /* bash is observed to signal whole process groups,
+ * not individual processes */
+ for (job = G.job_list; job; job = job->next) {
+ if (job->pgrp <= 0)
+ continue;
+ debug_printf_exec("HUPing pgrp %d\n", job->pgrp);
+ if (kill(- job->pgrp, SIGHUP) == 0)
+ kill(- job->pgrp, SIGCONT);
+ }
}
+ /* this restores tty pgrp, then kills us with SIGHUP */
sigexit(SIGHUP);
}
#endif
/*
* Shell and environment variable support
*/
-static struct variable **get_ptr_to_local_var(const char *name, unsigned len)
+static struct variable **get_ptr_to_local_var(const char *name)
{
struct variable **pp;
struct variable *cur;
pp = &G.top_var;
while ((cur = *pp) != NULL) {
- if (strncmp(cur->varstr, name, len) == 0 && cur->varstr[len] == '=')
+ if (varcmp(cur->varstr, name) == 0)
return pp;
pp = &cur->next;
}
static const char* FAST_FUNC get_local_var_value(const char *name)
{
struct variable **vpp;
- unsigned len = strlen(name);
if (G.expanded_assignments) {
char **cpp = G.expanded_assignments;
while (*cpp) {
char *cp = *cpp;
- if (strncmp(cp, name, len) == 0 && cp[len] == '=')
- return cp + len + 1;
+ if (varcmp(cp, name) == 0)
+ return strchr(cp, '=') + 1;
cpp++;
}
}
- vpp = get_ptr_to_local_var(name, len);
+ vpp = get_ptr_to_local_var(name);
if (vpp)
- return (*vpp)->varstr + len + 1;
+ return strchr((*vpp)->varstr, '=') + 1;
#ifndef __U_BOOT__
if (strcmp(name, "PPID") == 0)
#ifndef __U_BOOT__
#if ENABLE_HUSH_GETOPTS
-static void handle_changed_special_names(const char *name, unsigned name_len)
+static void handle_changed_special_names(const char *name)
{
- if (name_len == 6) {
- if (strncmp(name, "OPTIND", 6) == 0) {
- G.getopt_count = 0;
- return;
- }
+ if (varcmp(name, "OPTIND") == 0) {
+ G.getopt_count = 0;
+ return;
}
}
#else
#endif /* !__U_BOOT__ */
free(free_me);
- handle_changed_special_names(cur->varstr, name_len - 1);
+ handle_changed_special_names(cur->varstr);
return retval;
}
#ifndef __U_BOOT__
+static int set_local_var0(char *str)
+{
+ return set_local_var(str, 0);
+}
+
static void FAST_FUNC set_local_var_from_halves(const char *name, const char *val)
{
char *var = xasprintf("%s=%s", name, val);
- set_local_var(var, /*flag:*/ 0);
+ set_local_var0(var);
}
/* Used at startup and after each cd */
#endif /* !__U_BOOT__ */
#if ENABLE_HUSH_UNSET || ENABLE_HUSH_GETOPTS
-static int unset_local_var_len(const char *name, int name_len)
+static int unset_local_var(const char *name)
{
struct variable *cur;
struct variable **cur_pp;
cur_pp = &G.top_var;
while ((cur = *cur_pp) != NULL) {
- if (strncmp(cur->varstr, name, name_len) == 0
- && cur->varstr[name_len] == '='
- ) {
+ if (varcmp(cur->varstr, name) == 0) {
if (cur->flg_read_only) {
bb_error_msg("%s: readonly variable", name);
return EXIT_FAILURE;
}
/* Handle "unset LINENO" et al even if did not find the variable to unset */
- handle_changed_special_names(name, name_len);
+ handle_changed_special_names(name);
return EXIT_SUCCESS;
}
-
-static int unset_local_var(const char *name)
-{
- return unset_local_var_len(name, strlen(name));
-}
#endif
eq = strchr(*s, '=');
if (HUSH_DEBUG && !eq)
bb_simple_error_msg_and_die("BUG in varexp4");
- var_pp = get_ptr_to_local_var(*s, eq - *s);
+ var_pp = get_ptr_to_local_var(*s);
if (var_pp) {
var_p = *var_pp;
if (var_p->flg_read_only) {
for (;;) {
reinit_unicode_for_hush();
G.flag_SIGINT = 0;
- /* buglet: SIGINT will not make new prompt to appear _at once_,
- * only after <Enter>. (^C works immediately) */
- r = read_line_input(G.line_input_state, prompt_str,
+
+ bb_got_signal = 0;
+ if (!sigisemptyset(&G.pending_set)) {
+ /* Whoops, already got a signal, do not call read_line_input */
+ bb_got_signal = r = -1;
+ } else {
+ /* For shell, LI_INTERRUPTIBLE is set:
+ * read_line_input will abort on either
+ * getting EINTR in poll() and bb_got_signal became != 0,
+ * or if it sees bb_got_signal != 0
+ * (IOW: if signal arrives before poll() is reached).
+ * Interactive testcases:
+ * (while kill -INT $$; do sleep 1; done) &
+ * #^^^ prints ^C, prints prompt, repeats
+ * trap 'echo I' int; (while kill -INT $$; do sleep 1; done) &
+ * #^^^ prints ^C, prints "I", prints prompt, repeats
+ * trap 'echo T' term; (while kill $$; do sleep 1; done) &
+ * #^^^ prints "T", prints prompt, repeats
+ * #(bash 5.0.17 exits after first "T", looks like a bug)
+ */
+ r = read_line_input(G.line_input_state, prompt_str,
G.user_input_buf, CONFIG_FEATURE_EDITING_MAX_LEN-1
- );
- /* read_line_input intercepts ^C, "convert" it to SIGINT */
- if (r == 0) {
- raise(SIGINT);
+ );
+ /* read_line_input intercepts ^C, "convert" it to SIGINT */
+ if (r == 0)
+ raise(SIGINT);
+ }
+ /* bash prints ^C (before running a trap, if any)
+ * both on keyboard ^C and on real SIGINT (non-kbd generated).
+ */
+ if (sigismember(&G.pending_set, SIGINT)) {
+ write(STDOUT_FILENO, "^C\n", 3);
+ G.last_exitcode = 128 | SIGINT;
}
check_and_run_traps();
- if (r != 0 && !G.flag_SIGINT)
+ if (r == 0) /* keyboard ^C? */
+ continue; /* go back, read another input line */
+ if (r > 0) /* normal input? (no ^C, no ^D, no signals) */
break;
- /* ^C or SIGINT: repeat */
- /* bash prints ^C even on real SIGINT (non-kbd generated) */
- write(STDOUT_FILENO, "^C\n", 3);
- G.last_exitcode = 128 | SIGINT;
- }
- if (r < 0) {
- /* EOF/error detected */
- /* ^D on interactive input goes to next line before exiting: */
- write(STDOUT_FILENO, "\n", 1);
- i->p = NULL;
- i->peek_buf[0] = r = EOF;
- return r;
+ if (!bb_got_signal) {
+ /* r < 0: ^D/EOF/error detected (but not signal) */
+ /* ^D on interactive input goes to next line before exiting: */
+ write(STDOUT_FILENO, "\n", 1);
+ i->p = NULL;
+ i->peek_buf[0] = r = EOF;
+ return r;
+ }
+ /* it was a signal: go back, read another input line */
}
i->p = G.user_input_buf;
return (unsigned char)*i->p++;
if (ch != '\0') {
i->p++;
i->last_char = ch;
+#if ENABLE_HUSH_LINENO_VAR
+ if (ch == '\n') {
+ G.parse_lineno++;
+ debug_printf_parse("G.parse_lineno++ = %u\n", G.parse_lineno);
+ }
+#endif
return ch;
}
return EOF;
* NEXT points past the terminator of the first element, and REST
* points past the final }. We will accumulate result names from
* recursive runs for each brace alternative in the buffer using
- * GLOB_APPEND. */
+ * GLOB_APPEND. */
p = begin + 1;
while (1) {
#ifndef debug_print_tree
static void debug_print_tree(struct pipe *pi, int lvl)
{
- static const char *const PIPE[] = {
+ static const char *const PIPE[] ALIGN_PTR = {
[PIPE_SEQ] = "SEQ",
[PIPE_AND] = "AND",
[PIPE_OR ] = "OR" ,
[RES_XXXX ] = "XXXX" ,
[RES_SNTX ] = "SNTX" ,
};
- static const char *const CMDTYPE[] = {
+ static const char *const CMDTYPE[] ALIGN_PTR = {
"{}",
"()",
"[noglob]",
|| endofname(command->argv[0])[0] != '\0'
) {
/* bash says just "not a valid identifier" */
- syntax_error("bad variable name in for");
+ syntax_error("bad for loop variable");
return 1;
}
/* Force FOR to have just one word (variable name) */
syntax_error_unexpected_ch(ch);
return -1;
}
+//bash allows functions named "123", "..", "return"!
+// if (endofname(command->argv[0])[0] != '\0') {
+// syntax_error("bad function name");
+// return -1;
+// }
nommu_addchr(&ctx->as_string, ch);
command->cmd_type = CMD_FUNCDEF;
goto skip;
# undef as_string
}
#else
-# #define parse_dollar_squote(as_string, dest, input) 0
+# define parse_dollar_squote(as_string, dest, input) 0
#endif /* BASH_DOLLAR_SQUOTE */
#endif /* !__U_BOOT__ */
/* Expanding ARG in ${var+ARG}, ${var-ARG}
*/
-static int encode_then_append_var_plusminus(o_string *output, int n,
+static NOINLINE int encode_then_append_var_plusminus(o_string *output, int n,
char *str, int dquoted)
{
struct in_str input;
if (!dest.o_expflags) {
if (ch == EOF)
break;
- if (!dquoted && strchr(G.ifs, ch)) {
+ if (!dquoted && !(output->o_expflags & EXP_FLAG_SINGLEWORD) && strchr(G.ifs, ch)) {
/* PREFIX${x:d${e}f ...} and we met space: expand "d${e}f" and start new word.
* do not assume we are at the start of the word (PREFIX above).
*/
/* ${var/[/]pattern[/repl]} helpers */
static char *strstr_pattern(char *val, const char *pattern, int *size)
{
- int sz = strcspn(pattern, "*?[\\");
- if (pattern[sz] == '\0') {
+ int first_escaped = (pattern[0] == '\\' && pattern[1]);
+ /* "first_escaped" trick allows to treat e.g. "\*no_glob_chars"
+ * as literal too (as it is semi-common, and easy to accomodate
+ * by just using str + 1).
+ */
+ int sz = strcspn(pattern + first_escaped * 2, "*?[\\");
+ if ((pattern + first_escaped * 2)[sz] == '\0') {
/* Optimization for trivial patterns.
* Testcase for very slow replace (performs about 22k replaces):
* x=::::::::::::::::::::::
* x=$x$x;x=$x$x;x=$x$x;x=$x$x;x=$x$x;x=$x$x;x=$x$x;x=$x$x;x=$x$x;x=$x$x;echo ${#x}
* echo "${x//:/|}"
*/
- *size = sz;
- return strstr(val, pattern);
+ *size = sz + first_escaped;
+ return strstr(val, pattern + first_escaped);
}
while (1) {
val = NULL;
} else {
char *new_var = xasprintf("%s=%s", var, val);
- set_local_var(new_var, /*flag:*/ 0);
+ set_local_var0(new_var);
}
}
}
if (!cur->flg_export || cur->flg_read_only)
cnt += 2;
}
+# if ENABLE_HUSH_LINENO_VAR
+ cnt += 2;
+# endif
# if ENABLE_HUSH_FUNCTIONS
for (funcp = G.top_func; funcp; funcp = funcp->next)
cnt += 3;
*pp++ = cur->varstr;
}
}
+# if ENABLE_HUSH_LINENO_VAR
+ *pp++ = (char *) "-L";
+ *pp++ = utoa(G.execute_lineno);
+# endif
# if ENABLE_HUSH_FUNCTIONS
for (funcp = G.top_func; funcp; funcp = funcp->next) {
*pp++ = (char *) "-F";
#endif /* __U_BOOT__ */
{
struct in_str input;
+#ifndef __U_BOOT__
+ IF_HUSH_LINENO_VAR(unsigned sv = G.parse_lineno;)
+#else /* __U_BOOT__ */
//IF_HUSH_LINENO_VAR(unsigned sv = G.parse_lineno;)
+#endif /* __U_BOOT__ */
setup_string_in_str(&input, s);
#ifndef __U_BOOT__
#else /* __U_BOOT__ */
return parse_and_run_stream(&input, '\0');
#endif /* __U_BOOT__ */
- //IF_HUSH_LINENO_VAR(G.parse_lineno = sv;)
+#ifndef __U_BOOT__
+ IF_HUSH_LINENO_VAR(unsigned sv = G.parse_lineno;)
+#else /* __U_BOOT__ */
+ //IF_HUSH_LINENO_VAR(unsigned sv = G.parse_lineno;)
+#endif /* __U_BOOT__ */
}
#ifdef __U_BOOT__
if (is_prefixed_with(s, "trap")
&& skip_whitespace(s + 4)[0] == '\0'
) {
- static const char *const argv[] = { NULL, NULL };
+ static const char *const argv[] ALIGN_PTR = { NULL, NULL };
builtin_trap((char**)argv);
fflush_all(); /* important */
_exit(0);
return find_builtin_helper(name, bltins2, &bltins2[ARRAY_SIZE(bltins2)]);
}
-#if ENABLE_HUSH_JOB && EDITING_HAS_get_exe_name
-static const char * FAST_FUNC get_builtin_name(int i)
+#if ENABLE_HUSH_JOB && ENABLE_FEATURE_TAB_COMPLETION
+static const char * FAST_FUNC hush_command_name(int i)
{
if (/*i >= 0 && */ i < ARRAY_SIZE(bltins1)) {
return bltins1[i].b_cmd;
if (i < ARRAY_SIZE(bltins2)) {
return bltins2[i].b_cmd;
}
+# if ENABLE_HUSH_FUNCTIONS
+ {
+ struct function *funcp;
+ i -= ARRAY_SIZE(bltins2);
+ for (funcp = G.top_func; funcp; funcp = funcp->next) {
+ if (--i < 0)
+ return funcp->name;
+ }
+ }
+# endif
return NULL;
}
#endif
* expand_assignments(): think about ... | var=`sleep 1` | ...
*/
free_strings(new_env);
- _exit(EXIT_SUCCESS);
+ _exit_SUCCESS();
}
sv_shadowed = G.shadowed_vars_pp;
/* Case when we are here: ... | >file */
debug_printf_exec("pseudo_exec'ed null command\n");
- _exit(EXIT_SUCCESS);
+ _exit_SUCCESS();
}
#if ENABLE_HUSH_JOB
#endif
debug_printf_env("set shell var:'%s'->'%s'\n", *argv, p);
#ifndef __U_BOOT__
- if (set_local_var(p, /*flag:*/ 0)) {
+ if (set_local_var0(p)) {
#else /* __U_BOOT__ */
if (set_local_var_modern(p, /*flag:*/ 0)) {
#endif
#endif
#ifndef __U_BOOT__
- debug_printf_exec("run_list start lvl %d\n", G.run_list_level);
+ debug_printf_exec("run_list lvl %d start\n", G.run_list_level);
debug_enter();
#endif /* !__U_BOOT__ */
#endif /* !__U_BOOT__ */
IF_HAS_KEYWORDS(rword = pi->res_word;)
- debug_printf_exec(": rword=%d cond_code=%d last_rword=%d\n",
+ debug_printf_exec(": rword:%d cond_code:%d last_rword:%d\n",
rword, cond_code, last_rword);
sv_errexit_depth = G.errexit_depth;
}
}
last_followup = pi->followup;
- IF_HAS_KEYWORDS(last_rword = rword;)
#if ENABLE_HUSH_IF
- if (cond_code) {
+ if (cond_code != 0) {
if (rword == RES_THEN) {
/* if false; then ... fi has exitcode 0! */
G.last_exitcode = rcode = EXIT_SUCCESS;
/* "if <false> THEN cmd": skip cmd */
+ debug_printf_exec("skipped THEN cmd because IF condition was false\n");
+ last_rword = rword;
continue;
}
} else {
- if (rword == RES_ELSE || rword == RES_ELIF) {
+ if (rword == RES_ELSE
+ || (rword == RES_ELIF && last_rword != RES_ELIF)
+ ) {
/* "if <true> then ... ELSE/ELIF cmd":
* skip cmd and all following ones */
+ debug_printf_exec("skipped ELSE/ELIF branch because IF condition was true\n");
break;
}
+ //if (rword == RES_THEN): "if <true> THEN cmd", run cmd (fall through)
}
#endif
+ IF_HAS_KEYWORDS(last_rword = rword;)
#if ENABLE_HUSH_LOOPS
if (rword == RES_FOR) { /* && pi->num_cmds - always == 1 */
if (!for_lcur) {
static const char encoded_dollar_at[] ALIGN1 = {
SPECIAL_VAR_SYMBOL, '@' | 0x80, SPECIAL_VAR_SYMBOL, '\0'
}; /* encoded representation of "$@" */
- static const char *const encoded_dollar_at_argv[] = {
+ static const char *const encoded_dollar_at_argv[] ALIGN_PTR = {
encoded_dollar_at, NULL
}; /* argv list with one element: "$@" */
char **vals;
/* Insert next value from for_lcur */
/* note: *for_lcur already has quotes removed, $var expanded, etc */
#ifndef __U_BOOT__
- set_local_var(xasprintf("%s=%s", pi->cmds[0].argv[0], *for_lcur++), /*flag:*/ 0);
+ set_local_var_from_halves(pi->cmds[0].argv[0], *for_lcur++);
#else /* __U_BOOT__ */
/* We cannot use xasprintf, so we emulate it. */
char *full_var;
);
/* TODO: which FNM_xxx flags to use? */
cond_code = (fnmatch(pattern, case_word, /*flags:*/ 0) != 0);
- debug_printf_exec("fnmatch(pattern:'%s',str:'%s'):%d\n",
+ debug_printf_exec("cond_code=fnmatch(pattern:'%s',str:'%s'):%d\n",
pattern, case_word, cond_code);
free(pattern);
if (cond_code == 0) {
G.flag_break_continue = 0;
#endif
#endif /* !__U_BOOT__ */
+#ifndef __U_BOOT__
+ rcode = r = G.o_opt[OPT_O_NOEXEC] ? 0 : run_pipe(pi);
+ /* NB: rcode is a smalluint, r is int */
+#else /* __U_BOOT__ */
rcode = r = run_pipe(pi); /* NB: rcode is a smalluint, r is int */
-#ifdef __U_BOOT__
if (r <= EXIT_RET_CODE) {
int previous_rcode = G.last_exitcode;
/*
/* Analyze how result affects subsequent commands */
#if ENABLE_HUSH_IF
- if (rword == RES_IF || rword == RES_ELIF)
+ if (rword == RES_IF || rword == RES_ELIF) {
+ debug_printf_exec("cond_code=rcode:%d\n", rcode);
cond_code = rcode;
+ }
#endif
#ifndef __U_BOOT__
check_jobs_and_continue:
#endif
#ifndef __U_BOOT__
debug_leave();
- debug_printf_exec("run_list lvl %d return %d\n", G.run_list_level + 1, rcode);
+ debug_printf_exec("run_list lvl %d return %d\n", G.run_list_level, rcode);
#endif /* !__U_BOOT__ */
return rcode;
}
int idx;
switch (mode) {
case 'n':
- G.o_opt[OPT_O_NOEXEC] = state;
+ /* set -n has no effect in interactive shell */
+ /* Try: while set -n; do echo $-; done */
+ if (!G_interactive_fd)
+ G.o_opt[OPT_O_NOEXEC] = state;
break;
case 'x':
IF_HUSH_MODE_X(G_x_mode = state;)
INIT_G();
if (EXIT_SUCCESS != 0) /* if EXIT_SUCCESS == 0, it is already done */
G.last_exitcode = EXIT_SUCCESS;
+#if !BB_MMU
+ /* "Big heredoc" support via "sh -< STRING" invocation.
+ * Check it first (do not bother to run the usual init code,
+ * it is not needed for this case).
+ */
+ if (argv[1]
+ && argv[1][0] == '-' && argv[1][1] == '<' /*&& !argv[1][2]*/
+ /*&& argv[2] && !argv[3] - we don't check some conditions */
+ ) {
+ full_write1_str(argv[2]);
+ _exit(0);
+ }
+ G.argv0_for_re_execing = argv[0];
+#endif
#if ENABLE_HUSH_TRAP
# if ENABLE_HUSH_FUNCTIONS
G.return_exitcode = -1;
#if ENABLE_HUSH_FAST
G.count_SIGCHLD++; /* ensure it is != G.handled_SIGCHLD */
#endif
-#if !BB_MMU
- G.argv0_for_re_execing = argv[0];
-#endif
cached_getpid = getpid(); /* for tcsetpgrp() during init */
G.root_pid = cached_getpid; /* for $PID (NOMMU can override via -$HEXPID:HEXPPID:...) */
- G.root_ppid = getppid(); /* for $PPID (NOMMU can override) */
+ G.root_ppid = getppid(); /* for $PPID (NOMMU can override) */
/* Deal with HUSH_VERSION */
debug_printf_env("unsetenv '%s'\n", "HUSH_VERSION");
int opt = getopt(argc, argv, "+" /* stop at 1st non-option */
"cexinsl"
#if !BB_MMU
- "<:$:R:V:"
+ "$:R:V:"
+# if ENABLE_HUSH_LINENO_VAR
+ "L:"
+# endif
# if ENABLE_HUSH_FUNCTIONS
"F:"
# endif
/* Well, we cannot just declare interactiveness,
* we have to have some stuff (ctty, etc) */
/* G_interactive_fd++; */
+//There are a few cases where bash -i -c 'SCRIPT'
+//has visible effect (differs from bash -c 'SCRIPT'):
+//it ignores TERM:
+// bash -i -c 'kill $$; echo ALIVE'
+// ALIVE
+//it resets SIG_IGNed HUP to SIG_DFL:
+// trap '' hup; bash -i -c 'kill -hup $$; echo ALIVE'
+// Hangup [the message is not printed by bash, it's the shell which started it]
+//is talkative about jobs and exiting:
+// bash -i -c 'sleep 1 & exit'
+// [1] 16170
+// exit
+//includes $ENV file (only if run as "sh"):
+// echo last >/tmp/ENV; ENV=/tmp/ENV sh -i -c 'echo HERE'
+// last: cannot open /var/log/wtmp: No such file or directory
+// HERE
+//(under "bash", it's the opposite: it runs $BASH_ENV file only *without* -i).
+//
+//ash -i -c 'sleep 3; sleep 3', on ^C, drops into a prompt instead of exiting
+//(this may be a bug, bash does not do this).
+//(ash -i -c 'sleep 3' won't show this, the last command gets auto-"exec"ed)
+//
+//None of the above feel like useful features people would rely on.
break;
case 's':
G.opt_s = 1;
flags |= OPT_login;
break;
#if !BB_MMU
- case '<': /* "big heredoc" support */
- full_write1_str(optarg);
- _exit(0);
case '$': {
unsigned long long empty_trap_mask;
case 'V':
set_local_var(xstrdup(optarg), opt == 'R' ? SETFLAG_MAKE_RO : 0);
break;
+# if ENABLE_HUSH_LINENO_VAR
+ case 'L':
+ G.parse_lineno = xatou(optarg);
+ break;
+# endif
# if ENABLE_HUSH_FUNCTIONS
case 'F': {
struct function *funcp = new_function(optarg);
# if ENABLE_FEATURE_EDITING
G.line_input_state = new_line_input_t(FOR_SHELL);
-# if EDITING_HAS_get_exe_name
- G.line_input_state->get_exe_name = get_builtin_name;
+# if ENABLE_FEATURE_TAB_COMPLETION
+ G.line_input_state->get_exe_name = hush_command_name;
+# endif
+# if EDITING_HAS_sh_get_var
+ G.line_input_state->sh_get_var = get_local_var_value;
# endif
# endif
# if ENABLE_HUSH_SAVEHISTORY && MAX_HISTORY > 0
return 0;
}
+static int FAST_FUNC builtin_false(char **argv UNUSED_PARAM)
+{
+ return 1;
+}
+
#if ENABLE_HUSH_TEST || ENABLE_HUSH_ECHO || ENABLE_HUSH_PRINTF || ENABLE_HUSH_KILL
static NOINLINE int run_applet_main(char **argv, int (*applet_main_func)(int argc, char **argv))
{
}
#endif
-static char **skip_dash_dash(char **argv)
-{
- argv++;
- if (argv[0] && argv[0][0] == '-' && argv[0][1] == '-' && argv[0][2] == '\0')
- argv++;
- return argv;
-}
-
static int FAST_FUNC builtin_cd(char **argv)
{
const char *newdir;
}
#endif
-#if ENABLE_HUSH_EXPORT || ENABLE_HUSH_TRAP
+#if ENABLE_HUSH_EXPORT || ENABLE_HUSH_READONLY || ENABLE_HUSH_SET || ENABLE_HUSH_TRAP
static void print_escaped(const char *s)
{
+//TODO? bash "set" does not quote variables which contain only alnums and "%+,-./:=@_~",
+// (but "export" quotes all variables, even with only these chars).
+// I think quoting strings with %+,=~ looks better
+// (example: "set" printing var== instead of var='=' looks strange)
+// IOW: do not quote "-./:@_": / is used in pathnames, : in PATH, -._ often in file names, @ in emails
+
if (*s == '\'')
goto squote;
do {
if (*name_end == '\0') {
struct variable *var, **vpp;
- vpp = get_ptr_to_local_var(name, name_end - name);
+ vpp = get_ptr_to_local_var(name);
var = vpp ? *vpp : NULL;
if (flags & SETFLAG_UNEXPORT) {
if (!p) /* wtf? take next variable */
continue;
- /* export var= */
- printf("export %.*s", (int)(p - s) + 1, s);
+ /* "export VAR=" */
+ printf("%s %.*s", "export", (int)(p - s) + 1, s);
print_escaped(p + 1);
putchar('\n');
# endif
bb_error_msg("%s: not in a function", argv[0]);
return EXIT_FAILURE; /* bash compat */
}
+//TODO? ash and bash support "local -" special form,
+//which saves/restores $- around function call (including async returns, such as ^C)
+//(IOW: it makes "set +/-..." effects local)
argv++;
/* Since all builtins run in a nested variable level,
* need to use level - 1 here. Or else the variable will be removed at once
struct variable *e;
for (e = G.top_var; e; e = e->next) {
if (e->flg_read_only) {
-//TODO: quote value: readonly VAR='VAL'
- printf("readonly %s\n", e->varstr);
+ const char *s = e->varstr;
+ const char *p = strchr(s, '=');
+
+ if (!p) /* wtf? take next variable */
+ continue;
+ /* "readonly VAR=" */
+ printf("%s %.*s", "readonly", (int)(p - s) + 1, s);
+ print_escaped(p + 1);
+ putchar('\n');
}
}
return EXIT_SUCCESS;
if (arg == NULL) {
struct variable *e;
- for (e = G.top_var; e; e = e->next)
- puts(e->varstr);
+ for (e = G.top_var; e; e = e->next) {
+ const char *s = e->varstr;
+ const char *p = strchr(s, '=');
+
+ if (!p) /* wtf? take next variable */
+ continue;
+ /* var= */
+ printf("%.*s", (int)(p - s) + 1, s);
+ print_escaped(p + 1);
+ putchar('\n');
+ }
return EXIT_SUCCESS;
}
/* TODO: bash prints a string representation
* of job being foregrounded (like "sleep 1 | cat") */
if (argv[0][0] == 'f' && G_saved_tty_pgrp) {
- /* Put the job into the foreground. */
+ /* Put the job into the foreground. */
tcsetpgrp(G_interactive_fd, pi->pgrp);
}