diff options
-rw-r--r-- | tests/sh.test | 5 | ||||
-rw-r--r-- | toys/pending/sh.c | 117 |
2 files changed, 81 insertions, 41 deletions
diff --git a/tests/sh.test b/tests/sh.test index 67921c1a..1d021acd 100644 --- a/tests/sh.test +++ b/tests/sh.test @@ -107,6 +107,9 @@ testing 'default exports' \ testing "leading assignment fail" \ "{ \$SH -c 'X=\${a?blah} > walroid';ls walroid;} 2>/dev/null" '' '' '' +testing "lineno" "$SH input" "5 one\n6 one\n5 two\n6 two\n" \ + '#!/bin/bash\n\nfor i in one two\ndo\n echo $LINENO $i\n echo $LINENO $i\ndone\n' "" + ######################################################################### # Change EVAL to call sh -c for us, using "bash" explicitly for the host. export EVAL="$SH -c" @@ -191,6 +194,8 @@ testing 'wildcards' 'echo w[v-x]w w[x-v]w abc/*/ghi' \ testing '$(( ) )' 'echo ab$((echo hello) | tr e x)cd' "abhxllocd\n" "" "" testing '$((x=y)) lifetime' 'a=boing; echo $a $a$((a=4))$a $a' 'boing boing44 4\n' '' '' +testing 'quote' "echo \"'\"" "'\n" "" "" + # Loops and flow control testing "case" 'for i in A C J B; do case "$i" in A) echo got A ;; B) echo and B ;; C) echo then C ;; *) echo default ;; esac; done' \ "got A\nthen C\ndefault\nand B\n" "" "" diff --git a/toys/pending/sh.c b/toys/pending/sh.c index 218b8236..5bbe939d 100644 --- a/toys/pending/sh.c +++ b/toys/pending/sh.c @@ -207,7 +207,7 @@ GLOBALS( // keep ifs here: used to work around compiler limitation in run_command() char *ifs, *isexec, *wcpat; - unsigned options, jobcnt; + unsigned options, jobcnt, LINENO; int hfd, pid, bangpid, varslen, cdcount; long long SECONDS; @@ -222,6 +222,7 @@ GLOBALS( char *name; struct sh_pipeline { // pipeline segments: linked list of arg w/metadata struct sh_pipeline *next, *prev, *end; + unsigned lineno; int count, here, type; // TODO abuse type to replace count during parsing struct sh_arg { char **v; @@ -246,7 +247,8 @@ GLOBALS( struct sh_function scratch; struct sh_arg arg; struct arg_list *delete; - long lineno, shift; + unsigned lineno; + long shift; } *cc; // job list, command line for $*, scratch space for do_wildcard_files() @@ -437,7 +439,7 @@ static char *getvar(char *s) if (c == 'S') sprintf(toybuf, "%lld", (millitime()-TT.SECONDS)/1000); else if (c == 'R') sprintf(toybuf, "%ld", random()&((1<<16)-1)); - else if (c == 'L') sprintf(toybuf, "%ld", TT.cc->lineno); + else if (c == 'L') sprintf(toybuf, "%u", TT.LINENO); else if (c == 'G') sprintf(toybuf, "TODO: GROUPS"); return toybuf; @@ -515,7 +517,12 @@ static char *parse_word(char *start, int early, int quote) // Redirections. 123<<file- parses as 2 args: "123<<" "file-". s = end + redir_prefix(end); - if ((i = anystart(s, (void *)redirectors))) return s+i; + + if (strstart(&s, "<(") || strstart(&s, ">(")) { + toybuf[quote++]=')'; + end = s; + } else if ((i = anystart(s, (void *)redirectors))) return s+i; + if (strstart(&s, "<(") || strstart(&s, ">(")) { toybuf[quote++]=')'; end = s; @@ -572,7 +579,7 @@ static char *parse_word(char *start, int early, int quote) // Things the same unquoted or in most non-single-quote contexts // start new quote context? - if (strchr("\"'`", *end)) toybuf[quote++] = *end; + if (strchr("'\"`"+(q == '"'), *end)) toybuf[quote++] = *end; // backslash escapes else if (*end == '\\') { @@ -1806,30 +1813,32 @@ static char *expand_one_arg(char *new, unsigned flags, struct arg_list **del) // TODO |& // turn a parsed pipeline back into a string. -static char *pl2str(struct sh_pipeline *pl) +static char *pl2str(struct sh_pipeline *pl, int one) { - struct sh_pipeline *end = 0; - int level = 0, len = 0, i, j; - char *s, *ss, *sss; + struct sh_pipeline *end = 0, *pp; + int len, i; + char *s, *ss; - // measure, then allocate - for (j = 0; ; j++) for (end = pl; end; end = end->next) { - if (end->type == 1) level++; - else if (end->type == 3 && --level<0) break; + // Find end of block (or one argument) + if (one) end = pl->next; + else for (end = pl, len = 0; end; end = end->next) + if (end->type == 1) len++; + else if (end->type == 3 && --len<0) break; - for (i = 0; i<pl->arg->c; i++) - if (j) ss += sprintf(ss, "%s ", pl->arg->v[i]); - else len += strlen(pl->arg->v[i])+1; + // measure, then allocate + for (ss = 0;; ss = xmalloc(len+1)) { + for (pp = pl; pp != end; pp = pp->next) { + for (i = len = 0; i<pp->arg->c; i++) + len += snprintf(ss+len, ss ? INT_MAX : 0, "%s ", pp->arg->v[i]); + if (!(s = pp->arg->v[pp->arg->c])) s = ";"+(pp->next==end); + len += snprintf(ss+len, ss ? INT_MAX : 0, s); + } - if (!(sss = pl->arg->v[pl->arg->c])) sss = ";"+!end->next; - if (j) ss = stpcpy(ss, sss); - else len += strlen(sss); + if (ss) return ss; + } // TODO test output with case and function // TODO add HERE documents back in - if (j) return s; - s = ss = xmalloc(len+1); - } } // Expand arguments and perform redirections. Return new process object with @@ -2220,6 +2229,7 @@ static struct sh_pipeline *add_pl(struct sh_function *sp, struct sh_arg **arg) struct sh_pipeline *pl = xzalloc(sizeof(struct sh_pipeline)); *arg = pl->arg; + if (TT.cc) pl->lineno = TT.cc->lineno; dlist_add_nomalloc((void *)&sp->pipeline, (void *)pl); return pl->end = pl; @@ -2311,8 +2321,6 @@ static int parse_line(char *line, struct sh_function *sp) // Parse next word and detect overflow (too many nested quotes). if ((end = parse_word(start, 0, 0)) == (void *)1) goto flush; -// dprintf(2, "word[%ld]=%.*s (%s)\n", end ? end-start : 0, (int)(end ? end-start : 0), start, ex); - // Is this a new pipeline segment? if (!pl) pl = add_pl(sp, &arg); @@ -2329,8 +2337,11 @@ static int parse_line(char *line, struct sh_function *sp) // Ok, we have a word. What does it _mean_? // case/esac parsing is weird (unbalanced parentheses!), handle first - i = ex && !strcmp(ex, "esac") && (pl->type || (*start==';' && end-start>1)); + + i = ex && !strcmp(ex, "esac") && + ((pl->type && pl->type != 3) || (*start==';' && end-start>1)); if (i) { + // Premature EOL in type 1 (case x\nin) or 2 (at start or after ;;) is ok if (end == start) { if (pl->type==128 && arg->c==2) break; // case x\nin @@ -2712,7 +2723,7 @@ static void do_prompt(char *prompt) if (c=='!') { if (*prompt=='!') prompt++; else { - pp += snprintf(pp, len, "%ld", TT.cc->lineno); + pp += snprintf(pp, len, "%u", TT.cc->lineno); continue; } } else if (c=='\\') { @@ -2766,7 +2777,9 @@ static void run_function(struct sh_pipeline *pl) struct blockstack *blk = 0, *new; struct sh_process *pplist = 0; // processes piping into current level int *urd = 0, pipes[2] = {-1, -1}; - long i; + long i, j, k; + + if (!pl) return; // TODO: "echo | read i" is backgroundable with ctrl-Z despite read = builtin. // probably have to inline run_command here to do that? Implicit () @@ -2785,25 +2798,39 @@ TODO: a | b | c needs subshell for builtins? TT.hfd = 10; - if (TT.options&OPT_x) { - char *s = pl2str(pl); - - do_prompt(getvar("PS4")); - dprintf(2, "%s\n", s); - free(s); - } - // iterate through pipeline segments while (pl) { char *ctl = pl->end->arg->v[pl->end->arg->c], **vv, *s = *pl->arg->v, *ss = pl->arg->v[1]; // Skip disabled blocks, handle pipes + TT.LINENO = pl->lineno; if (pl->type<2) { if (blk && !blk->run) { pl = pl->end->next; continue; } + + if (TT.options&OPT_x) { + struct sh_callstack *sc; + char *ss, *ps4 = getvar("PS4"); + + // duplicate first char of ps4 call depth times + if (ps4 && *ps4) { + for (i = 0, sc = TT.cc; sc; sc = sc->next) i++; + j = getutf8(ps4, k = strlen(ps4), 0); + ss = xmalloc(i*j+k); + for (k = 0; k<i; k++) memcpy(ss+k*j, ps4, j); + strcpy(ss+k*j, ps4+j); + do_prompt(ss); + free(ss); + + ss = pl2str(pl, 1); + dprintf(2, "%s\n", ss); + free(ss); + } + } + if (pipe_segments(ctl, pipes, &urd)) break; } @@ -2817,7 +2844,7 @@ TODO: a | b | c needs subshell for builtins? // How many layers to peel off? i = ss ? atol(ss) : 0; if (i<1) i = 1; - if (!blk || pl->arg->c>2 || ss[strspn(ss, "0123456789")]) { + if (!blk || pl->arg->c>2 || (ss && ss[strspn(ss, "0123456789")])) { syntax_err(s); break; } @@ -2875,7 +2902,7 @@ TODO: a | b | c needs subshell for builtins? } else { // Create new process if (!CFG_TOYBOX_FORK) { - ss = pl2str(pl->next); + ss = pl2str(pl->next, 0); pp->pid = run_subshell(ss, strlen(ss)); free(ss); } else if (!(pp->pid = fork())) { @@ -2941,7 +2968,7 @@ dprintf(2, "TODO skipped init for((;;)), need math parser\n"); break; } else vv += **vv == '('; } - arg.c = 0; + arg.c = arg2.c = 0; if ((err = expand_arg_nobrace(&arg, *vv++, NO_SPLIT, &blk->fdelete, &arg2))) break; s = arg.c ? *arg.v : ""; @@ -3038,6 +3065,7 @@ static int sh_run(char *new) struct sh_function scratch; // TODO switch the fmemopen for -c to use this? Error checking? $(blah) +// TODO Merge this with do_source() memset(&scratch, 0, sizeof(struct sh_function)); if (!parse_line(new, &scratch)) run_function(scratch.pipeline); @@ -3141,7 +3169,7 @@ int do_source(char *name, FILE *ff) do { new = prompt_getline(ff, more+1); - if (!TT.cc->lineno++ && new && !memcmp(new, "\177ELF", 4)) { + if (!(TT.LINENO = TT.cc->lineno++) && new && !memcmp(new, "\177ELF", 4)) { error_msg("'%s' is ELF", name); free(new); @@ -3337,7 +3365,7 @@ void sh_main(void) // Read and execute lines from file if (do_source(cc ? : *toys.optargs, ff)) - error_exit("%ld:unfinished line"+4*!TT.cc->lineno, TT.cc->lineno); + error_exit("%u:unfinished line"+3*!TT.cc->lineno, TT.cc->lineno); } // TODO: ./blah.sh one two three: put one two three in scratch.arg @@ -3424,6 +3452,13 @@ void set_main(void) char *cc, *ostr[] = {"braceexpand", "noclobber", "xtrace"}; int ii, jj, kk, oo = 0, dd = 0; + if (!*toys.optargs) { +// TODO escape properly + for (ii = 0; ii<TT.varslen; ii++) printf("%s\n", TT.vars[ii].str); + + return; + } + // Handle options for (ii = 0;; ii++) { if ((cc = toys.optargs[ii]) && !(dd = stridx("-+", *cc)+1) && oo--) { @@ -3660,7 +3695,7 @@ void shift_main(void) void source_main(void) { - char *name = *toys.optargs; + char *name = toys.optargs[1]; FILE *ff = fpathopen(name); if (!ff) return perror_msg_raw(name); |