* Copyright (c) 1997-2005 Herbert Xu <herbert@gondor.apana.org.au>
* was re-ported from NetBSD and debianized.
*
- * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
*/
/*
#include <sys/times.h>
#include "shell_common.h"
-#include "math.h"
+#if ENABLE_SH_MATH_SUPPORT
+# include "math.h"
+#endif
#if ENABLE_ASH_RANDOM_SUPPORT
# include "random.h"
#else
# define CLEAR_RANDOM_T(rnd) ((void)0)
#endif
-#define SKIP_definitions 1
-#include "applet_tables.h"
-#undef SKIP_definitions
+#include "NUM_APPLETS.h"
#if NUM_APPLETS == 1
/* STANDALONE does not make sense, and won't compile */
# undef CONFIG_FEATURE_SH_STANDALONE
# error "Do not even bother, ash will not run on NOMMU machine"
#endif
+//applet:IF_ASH(APPLET(ash, _BB_DIR_BIN, _BB_SUID_DROP))
+//applet:IF_FEATURE_SH_IS_ASH(APPLET_ODDNAME(sh, ash, _BB_DIR_BIN, _BB_SUID_DROP, sh))
+//applet:IF_FEATURE_BASH_IS_ASH(APPLET_ODDNAME(bash, ash, _BB_DIR_BIN, _BB_SUID_DROP, bash))
+
+//kbuild:lib-$(CONFIG_ASH) += ash.o ash_ptr_hack.o shell_common.o
+//kbuild:lib-$(CONFIG_ASH_RANDOM_SUPPORT) += random.o
+
+//config:config ASH
+//config: bool "ash"
+//config: default y
+//config: depends on !NOMMU
+//config: help
+//config: Tha 'ash' shell adds about 60k in the default configuration and is
+//config: the most complete and most pedantically correct shell included with
+//config: busybox. This shell is actually a derivative of the Debian 'dash'
+//config: shell (by Herbert Xu), which was created by porting the 'ash' shell
+//config: (written by Kenneth Almquist) from NetBSD.
+//config:
+//config:config ASH_BASH_COMPAT
+//config: bool "bash-compatible extensions"
+//config: default y
+//config: depends on ASH
+//config: help
+//config: Enable bash-compatible extensions.
+//config:
+//config:config ASH_JOB_CONTROL
+//config: bool "Job control"
+//config: default y
+//config: depends on ASH
+//config: help
+//config: Enable job control in the ash shell.
+//config:
+//config:config ASH_ALIAS
+//config: bool "alias support"
+//config: default y
+//config: depends on ASH
+//config: help
+//config: Enable alias support in the ash shell.
+//config:
+//config:config ASH_GETOPTS
+//config: bool "Builtin getopt to parse positional parameters"
+//config: default y
+//config: depends on ASH
+//config: help
+//config: Enable getopts builtin in the ash shell.
+//config:
+//config:config ASH_BUILTIN_ECHO
+//config: bool "Builtin version of 'echo'"
+//config: default y
+//config: depends on ASH
+//config: help
+//config: Enable support for echo, builtin to ash.
+//config:
+//config:config ASH_BUILTIN_PRINTF
+//config: bool "Builtin version of 'printf'"
+//config: default y
+//config: depends on ASH
+//config: help
+//config: Enable support for printf, builtin to ash.
+//config:
+//config:config ASH_BUILTIN_TEST
+//config: bool "Builtin version of 'test'"
+//config: default y
+//config: depends on ASH
+//config: help
+//config: Enable support for test, builtin to ash.
+//config:
+//config:config ASH_CMDCMD
+//config: bool "'command' command to override shell builtins"
+//config: default y
+//config: depends on ASH
+//config: help
+//config: Enable support for the ash 'command' builtin, which allows
+//config: you to run the specified command with the specified arguments,
+//config: even when there is an ash builtin command with the same name.
+//config:
+//config:config ASH_MAIL
+//config: bool "Check for new mail on interactive shells"
+//config: default n
+//config: depends on ASH
+//config: help
+//config: Enable "check for new mail" in the ash shell.
+//config:
+//config:config ASH_OPTIMIZE_FOR_SIZE
+//config: bool "Optimize for size instead of speed"
+//config: default y
+//config: depends on ASH
+//config: help
+//config: Compile ash for reduced size at the price of speed.
+//config:
+//config:config ASH_RANDOM_SUPPORT
+//config: bool "Pseudorandom generator and $RANDOM variable"
+//config: default y
+//config: depends on ASH
+//config: help
+//config: Enable pseudorandom generator and dynamic variable "$RANDOM".
+//config: Each read of "$RANDOM" will generate a new pseudorandom value.
+//config: You can reset the generator by using a specified start value.
+//config: After "unset RANDOM" the generator will switch off and this
+//config: variable will no longer have special treatment.
+//config:
+//config:config ASH_EXPAND_PRMT
+//config: bool "Expand prompt string"
+//config: default y
+//config: depends on ASH
+//config: help
+//config: "PS#" may contain volatile content, such as backquote commands.
+//config: This option recreates the prompt string from the environment
+//config: variable each time it is displayed.
+//config:
+
+//usage:#define ash_trivial_usage NOUSAGE_STR
+//usage:#define ash_full_usage ""
+//usage:#define sh_trivial_usage NOUSAGE_STR
+//usage:#define sh_full_usage ""
+//usage:#define bash_trivial_usage NOUSAGE_STR
+//usage:#define bash_full_usage ""
+
/* ============ Hash table sizes. Configurable. */
for (p = arg->narg.text; *p; p++) {
switch ((unsigned char)*p) {
case CTLESC:
- putc(*++p, fp);
+ p++;
+ putc(*p, fp);
break;
case CTLVAR:
putc('$', fp);
if (subtype == VSLENGTH)
putc('#', fp);
- while (*p != '=')
- putc(*p++, fp);
+ while (*p != '=') {
+ putc(*p, fp);
+ p++;
+ }
if (subtype & VSNUL)
putc(':', fp);
# define optindval() (voptind.var_text + 7)
#endif
-
-#define is_name(c) ((c) == '_' || isalpha((unsigned char)(c)))
-#define is_in_name(c) ((c) == '_' || isalnum((unsigned char)(c)))
-
#if ENABLE_ASH_GETOPTS
static void FAST_FUNC
getoptsreset(const char *value)
}
#endif
+/* math.h has these, otherwise define our private copies */
+#if !ENABLE_SH_MATH_SUPPORT
+#define is_name(c) ((c) == '_' || isalpha((unsigned char)(c)))
+#define is_in_name(c) ((c) == '_' || isalnum((unsigned char)(c)))
/*
- * Return of a legal variable name (a letter or underscore followed by zero or
- * more letters, underscores, and digits).
+ * Return the pointer to the first char which is not part of a legal variable name
+ * (a letter or underscore followed by letters, underscores, and digits).
*/
-static char* FAST_FUNC
+static const char*
endofname(const char *name)
{
- char *p;
-
- p = (char *) name;
- if (!is_name(*p))
- return p;
- while (*++p) {
- if (!is_in_name(*p))
+ if (!is_name(*name))
+ return name;
+ while (*++name) {
+ if (!is_in_name(*name))
break;
}
- return p;
+ return name;
}
+#endif
/*
* Compares two strings up to the first = or '\0'. The first
static void
setvar(const char *name, const char *val, int flags)
{
- char *p, *q;
- size_t namelen;
+ const char *q;
+ char *p;
char *nameeq;
+ size_t namelen;
size_t vallen;
q = endofname(name);
} else {
vallen = strlen(val);
}
+
INT_OFF;
nameeq = ckmalloc(namelen + vallen + 2);
- p = (char *)memcpy(nameeq, name, namelen) + namelen;
+ p = memcpy(nameeq, name, namelen) + namelen;
if (val) {
*p++ = '=';
- p = (char *)memcpy(p, val, vallen) + vallen;
+ p = memcpy(p, val, vallen) + vallen;
}
*p = '\0';
setvareq(nameeq, flags | VNOSAVE);
#endif
static void
-setprompt(int whichprompt)
+setprompt_if(smallint do_set, int whichprompt)
{
const char *prompt;
-#if ENABLE_ASH_EXPAND_PRMT
- struct stackmark smark;
-#endif
+ IF_ASH_EXPAND_PRMT(struct stackmark smark;)
+
+ if (!do_set)
+ return;
needprompt = 0;
INT_ON;
}
}
+ may_have_traps = 0;
}
/* Lives far away from here, needed for forkchild */
* revealed that it was a regular file, and the file has not been
* replaced, return the file descriptor.
*/
- if (fstat(fd, &finfo2) == 0 && !S_ISREG(finfo2.st_mode)
- && finfo.st_dev == finfo2.st_dev && finfo.st_ino == finfo2.st_ino)
+ if (fstat(fd, &finfo2) == 0
+ && !S_ISREG(finfo2.st_mode)
+ && finfo.st_dev == finfo2.st_dev
+ && finfo.st_ino == finfo2.st_ino
+ ) {
return fd;
+ }
/* The file has been replaced. badness. */
close(fd);
break;
case NFROMTO:
fname = redir->nfile.expfname;
- f = open(fname, O_RDWR|O_CREAT|O_TRUNC, 0666);
+ f = open(fname, O_RDWR|O_CREAT, 0666);
if (f < 0)
goto ecreate;
break;
static arith_t
ash_arith(const char *s)
{
- arith_eval_hooks_t math_hooks;
+ arith_state_t math_state;
arith_t result;
- int errcode = 0;
- math_hooks.lookupvar = lookupvar;
- math_hooks.setvar = setvar2;
- math_hooks.endofname = endofname;
+ math_state.lookupvar = lookupvar;
+ math_state.setvar = setvar2;
+ //math_state.endofname = endofname;
INT_OFF;
- result = arith(s, &errcode, &math_hooks);
- if (errcode < 0) {
- if (errcode == -3)
- ash_msg_and_raise_error("exponent less than 0");
- if (errcode == -2)
- ash_msg_and_raise_error("divide by zero");
- if (errcode == -5)
- ash_msg_and_raise_error("expression recursion loop detected");
- raise_error_syntax(s);
- }
+ result = arith(&math_state, s);
+ if (math_state.errmsg)
+ ash_msg_and_raise_error(math_state.errmsg);
INT_ON;
return result;
/*
* Our own itoa().
*/
+#if !ENABLE_SH_MATH_SUPPORT
+/* cvtnum() is used even if math support is off (to prepare $? values and such) */
+typedef long arith_t;
+# define ARITH_FMT "%ld"
+#endif
static int
cvtnum(arith_t num)
{
int len;
expdest = makestrspace(32, expdest);
- len = fmtstr(expdest, 32, arith_t_fmt, num);
+ len = fmtstr(expdest, 32, ARITH_FMT, num);
STADJUST(len, expdest);
return len;
}
return;
if (ifsfirst.endoff > endoff) {
- while (ifsfirst.next != NULL) {
+ while (ifsfirst.next) {
struct ifsregion *ifsp;
INT_OFF;
ifsp = ifsfirst.next->next;
ifsfirst.next = ifsp;
INT_ON;
}
- if (ifsfirst.begoff > endoff)
+ if (ifsfirst.begoff > endoff) {
ifslastp = NULL;
- else {
+ } else {
ifslastp = &ifsfirst;
ifsfirst.endoff = endoff;
}
ifslastp = &ifsfirst;
while (ifslastp->next && ifslastp->next->begoff < endoff)
- ifslastp=ifslastp->next;
- while (ifslastp->next != NULL) {
+ ifslastp = ifslastp->next;
+ while (ifslastp->next) {
struct ifsregion *ifsp;
INT_OFF;
ifsp = ifslastp->next->next;
if (quoted == 0)
recordregion(startloc, dest - (char *)stackblock(), 0);
- TRACE(("evalbackq: size=%d: \"%.*s\"\n",
- (dest - (char *)stackblock()) - startloc,
- (dest - (char *)stackblock()) - startloc,
+ TRACE(("evalbackq: size:%d:'%.*s'\n",
+ (int)((dest - (char *)stackblock()) - startloc),
+ (int)((dest - (char *)stackblock()) - startloc),
stackblock() + startloc));
}
flags &= ~EXP_TILDE;
tilde:
q = p;
- if (*q == CTLESC && (flags & EXP_QWORD))
+ if ((unsigned char)*q == CTLESC && (flags & EXP_QWORD))
q++;
if (*q == '~')
p = exptilde(p, q, flags);
c = p[length];
if (c) {
if (!(c & 0x80)
-#if ENABLE_SH_MATH_SUPPORT
- || c == CTLENDARI
-#endif
+ IF_SH_MATH_SUPPORT(|| c == CTLENDARI)
) {
/* c == '=' || c == ':' || c == CTLENDARI */
length++;
/* "$@" syntax adherence hack */
if (!inquotes
&& memcmp(p, dolatstr, 4) == 0
- && ( p[4] == CTLQUOTEMARK
- || (p[4] == CTLENDVAR && p[5] == CTLQUOTEMARK)
+ && ( p[4] == (char)CTLQUOTEMARK
+ || (p[4] == (char)CTLENDVAR && p[5] == (char)CTLQUOTEMARK)
)
) {
p = evalvar(p + 1, flags, /* var_str_list: */ NULL) + 1;
#endif
}
}
- breakloop:
- ;
+ breakloop: ;
}
static char *
-scanleft(char *startp, char *rmesc, char *rmescend UNUSED_PARAM, char *str, int quotes,
- int zero)
-{
-// This commented out code was added by James Simmons <jsimmons@infradead.org>
-// as part of a larger change when he added support for ${var/a/b}.
-// However, it broke # and % operators:
-//
-//var=ababcdcd
-// ok bad
-//echo ${var#ab} abcdcd abcdcd
-//echo ${var##ab} abcdcd abcdcd
-//echo ${var#a*b} abcdcd ababcdcd (!)
-//echo ${var##a*b} cdcd cdcd
-//echo ${var#?} babcdcd ababcdcd (!)
-//echo ${var##?} babcdcd babcdcd
-//echo ${var#*} ababcdcd babcdcd (!)
-//echo ${var##*}
-//echo ${var%cd} ababcd ababcd
-//echo ${var%%cd} ababcd abab (!)
-//echo ${var%c*d} ababcd ababcd
-//echo ${var%%c*d} abab ababcdcd (!)
-//echo ${var%?} ababcdc ababcdc
-//echo ${var%%?} ababcdc ababcdcd (!)
-//echo ${var%*} ababcdcd ababcdcd
-//echo ${var%%*}
-//
-// Commenting it back out helped. Remove it completely if it really
-// is not needed.
-
- char *loc, *loc2; //, *full;
+scanleft(char *startp, char *rmesc, char *rmescend UNUSED_PARAM,
+ char *pattern, int quotes, int zero)
+{
+ char *loc, *loc2;
char c;
loc = startp;
loc2 = rmesc;
do {
- int match; // = strlen(str);
+ int match;
const char *s = loc2;
c = *loc2;
*loc2 = '\0';
s = rmesc;
}
- match = pmatch(str, s); // this line was deleted
-
-// // chop off end if its '*'
-// full = strrchr(str, '*');
-// if (full && full != str)
-// match--;
-//
-// // If str starts with '*' replace with s.
-// if ((*str == '*') && strlen(s) >= match) {
-// full = xstrdup(s);
-// strncpy(full+strlen(s)-match+1, str+1, match-1);
-// } else
-// full = xstrndup(str, match);
-// match = strncmp(s, full, strlen(full));
-// free(full);
-//
+ match = pmatch(pattern, s);
+
*loc2 = c;
- if (match) // if (!match)
+ if (match)
return loc;
if (quotes && (unsigned char)*loc == CTLESC)
loc++;
loc++;
loc2++;
} while (c);
- return 0;
+ return NULL;
}
static char *
-scanright(char *startp, char *rmesc, char *rmescend, char *pattern, int quotes, int match_at_start)
+scanright(char *startp, char *rmesc, char *rmescend,
+ char *pattern, int quotes, int match_at_start)
{
#if !ENABLE_ASH_OPTIMIZE_FOR_SIZE
int try2optimize = match_at_start;
}
}
}
- return 0;
+ return NULL;
}
static void varunset(const char *, const char *, const char *, int) NORETURN;
msg = umsg;
}
}
- ash_msg_and_raise_error("%.*s: %s%s", end - var - 1, var, msg, tail);
+ ash_msg_and_raise_error("%.*s: %s%s", (int)(end - var - 1), var, msg, tail);
}
#if ENABLE_ASH_BASH_COMPAT
static char *
-parse_sub_pattern(char *arg, int inquotes)
+parse_sub_pattern(char *arg, int varflags)
{
char *idx, *repl = NULL;
unsigned char c;
+ //char *org_arg = arg;
+ //bb_error_msg("arg:'%s' varflags:%x", arg, varflags);
idx = arg;
while (1) {
c = *arg;
}
}
*idx++ = c;
- if (!inquotes && c == '\\' && arg[1] == '\\')
- arg++; /* skip both \\, not just first one */
arg++;
+ /*
+ * Example: v='ab\c'; echo ${v/\\b/_\\_\z_}
+ * The result is a_\_z_c (not a\_\_z_c)!
+ *
+ * Enable debug prints in this function and you'll see:
+ * ash: arg:'\\b/_\\_z_' varflags:d
+ * ash: pattern:'\\b' repl:'_\_z_'
+ * That is, \\b is interpreted as \\b, but \\_ as \_!
+ * IOW: search pattern and replace string treat backslashes
+ * differently! That is the reason why we check repl below:
+ */
+ if (c == '\\' && *arg == '\\' && repl && !(varflags & VSQUOTE))
+ arg++; /* skip both '\', not just first one */
}
*idx = c; /* NUL */
+ //bb_error_msg("pattern:'%s' repl:'%s'", org_arg, repl);
return repl;
}
#endif /* ENABLE_ASH_BASH_COMPAT */
static const char *
-subevalvar(char *p, char *str, int strloc, int subtype,
+subevalvar(char *p, char *varname, int strloc, int subtype,
int startloc, int varflags, int quotes, struct strlist *var_str_list)
{
struct nodelist *saveargbackq = argbackq;
char *startp;
char *loc;
char *rmesc, *rmescend;
- IF_ASH_BASH_COMPAT(char *repl = NULL;)
- IF_ASH_BASH_COMPAT(char null = '\0';)
+ char *str;
+ IF_ASH_BASH_COMPAT(const char *repl = NULL;)
IF_ASH_BASH_COMPAT(int pos, len, orig_len;)
int saveherefd = herefd;
int amount, workloc, resetloc;
int zero;
char *(*scan)(char*, char*, char*, char*, int, int);
+ //bb_error_msg("subevalvar(p:'%s',varname:'%s',strloc:%d,subtype:%d,startloc:%d,varflags:%x,quotes:%d)",
+ // p, varname, strloc, subtype, startloc, varflags, quotes);
+
herefd = -1;
argstr(p, (subtype != VSASSIGN && subtype != VSQUESTION) ? EXP_CASE : 0,
var_str_list);
switch (subtype) {
case VSASSIGN:
- setvar(str, startp, 0);
+ setvar(varname, startp, 0);
amount = startp - expdest;
STADJUST(amount, expdest);
return startp;
+ case VSQUESTION:
+ varunset(p, varname, startp, varflags);
+ /* NOTREACHED */
+
#if ENABLE_ASH_BASH_COMPAT
case VSSUBSTR:
loc = str = stackblock() + strloc;
STADJUST(amount, expdest);
return loc;
#endif
-
- case VSQUESTION:
- varunset(p, str, startp, varflags);
- /* NOTREACHED */
}
+
resetloc = expdest - (char *)stackblock();
/* We'll comeback here if we grow the stack while handling
char *idx, *end;
if (!repl) {
- repl = parse_sub_pattern(str, varflags & VSQUOTE);
+ repl = parse_sub_pattern(str, varflags);
+ //bb_error_msg("repl:'%s'", repl);
if (!repl)
- repl = &null;
+ repl = nullstr;
}
/* If there's no pattern to match, return the expansion unmolested */
if (str[0] == '\0')
- return 0;
+ return NULL;
len = 0;
idx = startp;
while (idx < end) {
try_to_match:
loc = scanright(idx, rmesc, rmescend, str, quotes, 1);
+ //bb_error_msg("scanright('%s'):'%s'", str, loc);
if (!loc) {
/* No match, advance */
char *restart_detect = stackblock();
idx = loc;
}
- for (loc = repl; *loc; loc++) {
+ //bb_error_msg("repl:'%s'", repl);
+ for (loc = (char*)repl; *loc; loc++) {
char *restart_detect = stackblock();
+ if (quotes && *loc == '\\') {
+ STPUTC(CTLESC, expdest);
+ len++;
+ }
STPUTC(*loc, expdest);
if (stackblock() != restart_detect)
goto restart;
}
if (subtype == VSREPLACE) {
+ //bb_error_msg("tail:'%s', quotes:%x", idx, quotes);
while (*idx) {
char *restart_detect = stackblock();
STPUTC(*idx, expdest);
/* We've put the replaced text into a buffer at workloc, now
* move it to the right place and adjust the stack.
*/
- startp = stackblock() + startloc;
STPUTC('\0', expdest);
- memmove(startp, stackblock() + workloc, len);
- startp[len++] = '\0';
- amount = expdest - ((char *)stackblock() + startloc + len - 1);
+ startp = (char *)stackblock() + startloc;
+ memmove(startp, (char *)stackblock() + workloc, len + 1);
+ //bb_error_msg("startp:'%s'", startp);
+ amount = expdest - (startp + len);
STADJUST(-amount, expdest);
return startp;
}
vsplus:
if (varlen < 0) {
argstr(
- p, flags | EXP_TILDE |
- (quoted ? EXP_QWORD : EXP_WORD),
+ p,
+ flags | (quoted ? EXP_TILDE|EXP_QWORD : EXP_TILDE|EXP_WORD),
var_str_list
);
goto end;
*/
STPUTC('\0', expdest);
patloc = expdest - (char *)stackblock();
- if (0 == subevalvar(p, /* str: */ NULL, patloc, subtype,
+ if (NULL == subevalvar(p, /* varname: */ NULL, patloc, subtype,
startloc, varflags,
//TODO: | EXP_REDIR too? All other such places do it too
/* quotes: */ flags & (EXP_FULL | EXP_CASE),
exparg.lastp = &sp->next;
}
-static char *expdir;
-
/*
* Do metacharacter (i.e. *, ?, [...]) expansion.
*/
static void
-expmeta(char *enddir, char *name)
+expmeta(char *expdir, char *enddir, char *name)
{
char *p;
const char *cp;
for (p = enddir, cp = dp->d_name; (*p++ = *cp++) != '\0';)
continue;
p[-1] = '/';
- expmeta(p, endname);
+ expmeta(expdir, p, endname);
}
}
}
/* TODO - EXP_REDIR */
while (str) {
+ char *expdir;
struct strlist **savelastp;
struct strlist *sp;
char *p;
int i = strlen(str->text);
expdir = ckmalloc(i < 2048 ? 2048 : i); /* XXX */
}
-
- expmeta(expdir, p);
+ expmeta(expdir, expdir, p);
free(expdir);
if (p != str->text)
free(p);
for (cmdp = *pp; cmdp; cmdp = cmdp->next) {
if (cmdp->cmdtype == CMDNORMAL
|| (cmdp->cmdtype == CMDBUILTIN
- && !IS_BUILTIN_REGULAR(cmdp->param.cmd)
+ && !IS_BUILTIN_REGULAR(cmdp->param.cmd)
&& builtinloc > 0)
) {
cmdp->rehash = 1;
/* Called to execute a trap.
* Single callsite - at the end of evaltree().
- * If we return non-zero, exaltree raises EXEXIT exception.
+ * If we return non-zero, evaltree raises EXEXIT exception.
*
* Perhaps we should avoid entering new trap handlers
* while we are executing a trap handler. [is it a TODO?]
out:
exception_handler = savehandler;
+
out1:
+ /* Order of checks below is important:
+ * signal handlers trigger before exit caused by "set -e".
+ */
+ if (pending_sig && dotrap())
+ goto exexit;
if (checkexit & exitstatus)
evalskip |= SKIPEVAL;
- else if (pending_sig && dotrap())
- goto exexit;
if (flags & EV_EXIT) {
exexit:
while ((lvp = localvars) != NULL) {
localvars = lvp->next;
vp = lvp->vp;
- TRACE(("poplocalvar %s\n", vp ? vp->text : "-"));
+ TRACE(("poplocalvar %s\n", vp ? vp->var_text : "-"));
if (vp == NULL) { /* $- saved */
memcpy(optlist, lvp->text, sizeof(optlist));
free((char*)lvp->text);
/* Print the command if xflag is set. */
if (xflag) {
int n;
- const char *p = " %s";
+ const char *p = " %s" + 1;
- p++;
fdprintf(preverrout_fd, p, expandstr(ps4val()));
-
sp = varlist.list;
for (n = 0; n < 2; n++) {
while (sp) {
fdprintf(preverrout_fd, p, sp->text);
sp = sp->next;
- if (*p == '%') {
- p--;
- }
+ p = " %s";
}
sp = arglist.list;
}
static int
goodname(const char *p)
{
- return !*endofname(p);
+ return endofname(p)[0] == '\0';
}
startlinno = g_parsefile->linno;
bqlist = NULL;
quotef = 0;
- oldstyle = 0;
prevsyntax = 0;
#if ENABLE_ASH_EXPAND_PRMT
pssyntax = (syntax == PSSYNTAX);
STARTSTACKSTR(out);
loop:
/* For each line, until end of word */
- {
- CHECKEND(); /* set c to PEOF if at end of here document */
- for (;;) { /* until end of line or end of word */
- CHECKSTRSPACE(4, out); /* permit 4 calls to USTPUTC */
- switch (SIT(c, syntax)) {
- case CNL: /* '\n' */
- if (syntax == BASESYNTAX)
- goto endword; /* exit outer loop */
- USTPUTC(c, out);
- g_parsefile->linno++;
- if (doprompt)
- setprompt(2);
- c = pgetc();
- goto loop; /* continue outer loop */
- case CWORD:
- USTPUTC(c, out);
- break;
- case CCTL:
- if (eofmark == NULL || dblquote)
- USTPUTC(CTLESC, out);
+ CHECKEND(); /* set c to PEOF if at end of here document */
+ for (;;) { /* until end of line or end of word */
+ CHECKSTRSPACE(4, out); /* permit 4 calls to USTPUTC */
+ switch (SIT(c, syntax)) {
+ case CNL: /* '\n' */
+ if (syntax == BASESYNTAX)
+ goto endword; /* exit outer loop */
+ USTPUTC(c, out);
+ g_parsefile->linno++;
+ setprompt_if(doprompt, 2);
+ c = pgetc();
+ goto loop; /* continue outer loop */
+ case CWORD:
+ USTPUTC(c, out);
+ break;
+ case CCTL:
+ if (eofmark == NULL || dblquote)
+ USTPUTC(CTLESC, out);
#if ENABLE_ASH_BASH_COMPAT
- if (c == '\\' && bash_dollar_squote) {
- c = decode_dollar_squote();
- if (c & 0x100) {
- USTPUTC('\\', out);
- c = (unsigned char)c;
- }
+ if (c == '\\' && bash_dollar_squote) {
+ c = decode_dollar_squote();
+ if (c & 0x100) {
+ USTPUTC('\\', out);
+ c = (unsigned char)c;
}
+ }
#endif
- USTPUTC(c, out);
- break;
- case CBACK: /* backslash */
- c = pgetc_without_PEOA();
- if (c == PEOF) {
+ USTPUTC(c, out);
+ break;
+ case CBACK: /* backslash */
+ c = pgetc_without_PEOA();
+ if (c == PEOF) {
+ USTPUTC(CTLESC, out);
+ USTPUTC('\\', out);
+ pungetc();
+ } else if (c == '\n') {
+ setprompt_if(doprompt, 2);
+ } else {
+#if ENABLE_ASH_EXPAND_PRMT
+ if (c == '$' && pssyntax) {
USTPUTC(CTLESC, out);
USTPUTC('\\', out);
- pungetc();
- } else if (c == '\n') {
- if (doprompt)
- setprompt(2);
- } else {
-#if ENABLE_ASH_EXPAND_PRMT
- if (c == '$' && pssyntax) {
- USTPUTC(CTLESC, out);
- USTPUTC('\\', out);
- }
+ }
#endif
- if (dblquote && c != '\\'
- && c != '`' && c != '$'
- && (c != '"' || eofmark != NULL)
- ) {
- USTPUTC(CTLESC, out);
- USTPUTC('\\', out);
- }
- if (SIT(c, SQSYNTAX) == CCTL)
- USTPUTC(CTLESC, out);
- USTPUTC(c, out);
- quotef = 1;
+ /* Backslash is retained if we are in "str" and next char isn't special */
+ if (dblquote
+ && c != '\\'
+ && c != '`'
+ && c != '$'
+ && (c != '"' || eofmark != NULL)
+ ) {
+ USTPUTC(CTLESC, out);
+ USTPUTC('\\', out);
}
- break;
- case CSQUOTE:
- syntax = SQSYNTAX;
+ if (SIT(c, SQSYNTAX) == CCTL)
+ USTPUTC(CTLESC, out);
+ USTPUTC(c, out);
+ quotef = 1;
+ }
+ break;
+ case CSQUOTE:
+ syntax = SQSYNTAX;
quotemark:
- if (eofmark == NULL) {
- USTPUTC(CTLQUOTEMARK, out);
+ if (eofmark == NULL) {
+ USTPUTC(CTLQUOTEMARK, out);
+ }
+ break;
+ case CDQUOTE:
+ syntax = DQSYNTAX;
+ dblquote = 1;
+ goto quotemark;
+ case CENDQUOTE:
+ IF_ASH_BASH_COMPAT(bash_dollar_squote = 0;)
+ if (eofmark != NULL && arinest == 0
+ && varnest == 0
+ ) {
+ USTPUTC(c, out);
+ } else {
+ if (dqvarnest == 0) {
+ syntax = BASESYNTAX;
+ dblquote = 0;
}
- break;
- case CDQUOTE:
- syntax = DQSYNTAX;
- dblquote = 1;
+ quotef = 1;
goto quotemark;
- case CENDQUOTE:
- IF_ASH_BASH_COMPAT(bash_dollar_squote = 0;)
- if (eofmark != NULL && arinest == 0
- && varnest == 0
- ) {
- USTPUTC(c, out);
- } else {
- if (dqvarnest == 0) {
- syntax = BASESYNTAX;
- dblquote = 0;
- }
- quotef = 1;
- goto quotemark;
- }
- break;
- case CVAR: /* '$' */
- PARSESUB(); /* parse substitution */
- break;
- case CENDVAR: /* '}' */
- if (varnest > 0) {
- varnest--;
- if (dqvarnest > 0) {
- dqvarnest--;
- }
- USTPUTC(CTLENDVAR, out);
- } else {
- USTPUTC(c, out);
+ }
+ break;
+ case CVAR: /* '$' */
+ PARSESUB(); /* parse substitution */
+ break;
+ case CENDVAR: /* '}' */
+ if (varnest > 0) {
+ varnest--;
+ if (dqvarnest > 0) {
+ dqvarnest--;
}
- break;
+ c = CTLENDVAR;
+ }
+ USTPUTC(c, out);
+ break;
#if ENABLE_SH_MATH_SUPPORT
- case CLP: /* '(' in arithmetic */
- parenlevel++;
- USTPUTC(c, out);
- break;
- case CRP: /* ')' in arithmetic */
- if (parenlevel > 0) {
- USTPUTC(c, out);
- --parenlevel;
- } else {
- if (pgetc() == ')') {
- if (--arinest == 0) {
- USTPUTC(CTLENDARI, out);
- syntax = prevsyntax;
- dblquote = (syntax == DQSYNTAX);
- } else
- USTPUTC(')', out);
- } else {
- /*
- * unbalanced parens
- * (don't 2nd guess - no error)
- */
- pungetc();
- USTPUTC(')', out);
+ case CLP: /* '(' in arithmetic */
+ parenlevel++;
+ USTPUTC(c, out);
+ break;
+ case CRP: /* ')' in arithmetic */
+ if (parenlevel > 0) {
+ parenlevel--;
+ } else {
+ if (pgetc() == ')') {
+ if (--arinest == 0) {
+ syntax = prevsyntax;
+ dblquote = (syntax == DQSYNTAX);
+ c = CTLENDARI;
}
+ } else {
+ /*
+ * unbalanced parens
+ * (don't 2nd guess - no error)
+ */
+ pungetc();
}
- break;
+ }
+ USTPUTC(c, out);
+ break;
#endif
- case CBQUOTE: /* '`' */
- PARSEBACKQOLD();
- break;
- case CENDFILE:
- goto endword; /* exit outer loop */
- case CIGN:
- break;
- default:
- if (varnest == 0) {
+ case CBQUOTE: /* '`' */
+ PARSEBACKQOLD();
+ break;
+ case CENDFILE:
+ goto endword; /* exit outer loop */
+ case CIGN:
+ break;
+ default:
+ if (varnest == 0) {
#if ENABLE_ASH_BASH_COMPAT
- if (c == '&') {
- if (pgetc() == '>')
- c = 0x100 + '>'; /* flag &> */
- pungetc();
- }
-#endif
- goto endword; /* exit outer loop */
+ if (c == '&') {
+ if (pgetc() == '>')
+ c = 0x100 + '>'; /* flag &> */
+ pungetc();
}
- IF_ASH_ALIAS(if (c != PEOA))
- USTPUTC(c, out);
-
+#endif
+ goto endword; /* exit outer loop */
}
- c = pgetc_fast();
- } /* for (;;) */
- }
+ IF_ASH_ALIAS(if (c != PEOA))
+ USTPUTC(c, out);
+ }
+ c = pgetc_fast();
+ } /* for (;;) */
endword:
+
#if ENABLE_SH_MATH_SUPPORT
if (syntax == ARISYNTAX)
raise_error_syntax("missing '))'");
unsigned char subtype;
int typeloc;
int flags;
- char *p;
- static const char types[] ALIGN1 = "}-+?=";
c = pgetc();
if (c > 255 /* PEOA or PEOF */
#endif
USTPUTC('$', out);
pungetc();
- } else if (c == '(') { /* $(command) or $((arith)) */
+ } else if (c == '(') {
+ /* $(command) or $((arith)) */
if (pgetc() == '(') {
#if ENABLE_SH_MATH_SUPPORT
PARSEARITH();
PARSEBACKQNEW();
}
} else {
+ /* $VAR, $<specialchar>, ${...}, or PEOA/PEOF */
USTPUTC(CTLVAR, out);
typeloc = out - (char *)stackblock();
USTPUTC(VSNORMAL, out);
if (c == '#') {
c = pgetc();
if (c == '}')
- c = '#';
+ c = '#'; /* ${#} - same as $# */
else
- subtype = VSLENGTH;
- } else
+ subtype = VSLENGTH; /* ${#VAR} */
+ } else {
subtype = 0;
+ }
}
if (c <= 255 /* not PEOA or PEOF */ && is_name(c)) {
+ /* $[{[#]]NAME[}] */
do {
STPUTC(c, out);
c = pgetc();
} while (c <= 255 /* not PEOA or PEOF */ && is_in_name(c));
} else if (isdigit(c)) {
+ /* $[{[#]]NUM[}] */
do {
STPUTC(c, out);
c = pgetc();
} while (isdigit(c));
} else if (is_special(c)) {
+ /* $[{[#]]<specialchar>[}] */
USTPUTC(c, out);
c = pgetc();
} else {
badsub:
raise_error_syntax("bad substitution");
}
- if (c != '}' && subtype == VSLENGTH)
+ if (c != '}' && subtype == VSLENGTH) {
+ /* ${#VAR didn't end with } */
goto badsub;
+ }
STPUTC('=', out);
flags = 0;
if (subtype == 0) {
+ /* ${VAR...} but not $VAR or ${#VAR} */
+ /* c == first char after VAR */
switch (c) {
case ':':
c = pgetc();
#if ENABLE_ASH_BASH_COMPAT
if (c == ':' || c == '$' || isdigit(c)) {
- pungetc();
+//TODO: support more general format ${v:EXPR:EXPR},
+// where EXPR follows $(()) rules
subtype = VSSUBSTR;
- break;
+ pungetc();
+ break; /* "goto do_pungetc" is bigger (!) */
}
#endif
flags = VSNUL;
/*FALLTHROUGH*/
- default:
- p = strchr(types, c);
+ default: {
+ static const char types[] ALIGN1 = "}-+?=";
+ const char *p = strchr(types, c);
if (p == NULL)
goto badsub;
subtype = p - types + VSNORMAL;
break;
+ }
case '%':
case '#': {
int cc = c;
- subtype = c == '#' ? VSTRIMLEFT : VSTRIMRIGHT;
+ subtype = (c == '#' ? VSTRIMLEFT : VSTRIMRIGHT);
c = pgetc();
- if (c == cc)
- subtype++;
- else
- pungetc();
+ if (c != cc)
+ goto do_pungetc;
+ subtype++;
break;
}
#if ENABLE_ASH_BASH_COMPAT
case '/':
+ /* ${v/[/]pattern/repl} */
+//TODO: encode pattern and repl separately.
+// Currently ${v/$var_with_slash/repl} is horribly broken
subtype = VSREPLACE;
c = pgetc();
- if (c == '/')
- subtype++; /* VSREPLACEALL */
- else
- pungetc();
+ if (c != '/')
+ goto do_pungetc;
+ subtype++; /* VSREPLACEALL */
break;
#endif
}
} else {
+ do_pungetc:
pungetc();
}
if (dblquote || arinest)
treatment to some slashes, and then push the string and
reread it as input, interpreting it normally. */
char *pout;
- int pc;
size_t psavelen;
char *pstr;
-
STARTSTACKSTR(pout);
for (;;) {
- if (needprompt) {
- setprompt(2);
- }
+ int pc;
+
+ setprompt_if(needprompt, 2);
pc = pgetc();
switch (pc) {
case '`':
pc = pgetc();
if (pc == '\n') {
g_parsefile->linno++;
- if (doprompt)
- setprompt(2);
+ setprompt_if(doprompt, 2);
/*
* If eating a newline, avoid putting
* the newline into the new character
tokpushback = 0;
return lasttoken;
}
- if (needprompt) {
- setprompt(2);
- }
+ setprompt_if(needprompt, 2);
startlinno = g_parsefile->linno;
for (;;) { /* until token or start of word found */
c = pgetc_fast();
break; /* return readtoken1(...) */
}
startlinno = ++g_parsefile->linno;
- if (doprompt)
- setprompt(2);
+ setprompt_if(doprompt, 2);
} else {
const char *p;
tokpushback = 0;
return lasttoken;
}
- if (needprompt) {
- setprompt(2);
- }
+ setprompt_if(needprompt, 2);
startlinno = g_parsefile->linno;
for (;;) { /* until token or start of word found */
c = pgetc_fast();
case '\\':
if (pgetc() == '\n') {
startlinno = ++g_parsefile->linno;
- if (doprompt)
- setprompt(2);
+ setprompt_if(doprompt, 2);
continue;
}
pungetc();
tokpushback = 0;
doprompt = interact;
- if (doprompt)
- setprompt(doprompt);
+ setprompt_if(doprompt, doprompt);
needprompt = 0;
t = readtoken();
if (t == TEOF)
heredoclist = NULL;
while (here) {
- if (needprompt) {
- setprompt(2);
- }
- readtoken1(pgetc(), here->here->type == NHERE? SQSYNTAX : DQSYNTAX,
+ setprompt_if(needprompt, 2);
+ readtoken1(pgetc(), here->here->type == NHERE ? SQSYNTAX : DQSYNTAX,
here->eofmark, here->striptabs);
n = stzalloc(sizeof(struct narg));
n->narg.type = NARG;
p = grabstackstr(concat);
}
evalstring(p, ~SKIPEVAL);
-
}
return exitstatus;
}
}
if ((act & DO_NOFUNC)
|| !prefix(pathopt, "func")
- ) { /* ignore unimplemented options */
+ ) { /* ignore unimplemented options */
continue;
}
}
/* bash re-enables SIGHUP which is SIG_IGNed on entry.
* Try: "trap '' HUP; bash; echo RET" and type "kill -HUP $$"
*/
- signal(SIGHUP, SIG_DFL);
+ signal(SIGHUP, SIG_DFL);
/* from var.c: */
{
if (e == EXERROR)
exitstatus = 2;
s = state;
- if (e == EXEXIT || s == 0 || iflag == 0 || shlvl)
+ if (e == EXEXIT || s == 0 || iflag == 0 || shlvl) {
exitshell();
- if (e == EXINT)
+ }
+ if (e == EXINT) {
outcslow('\n', stderr);
+ }
popstackmark(&smark);
FORCE_INT_ON; /* enable interrupts */
}
}
#endif
- if (/* argv[0] && */ argv[0][0] == '-')
+ if (argv[0] && argv[0][0] == '-')
isloginsh = 1;
if (isloginsh) {
state = 1;
_mcleanup();
}
#endif
+ TRACE(("End of main reached\n"));
exitshell();
/* NOTREACHED */
}