diff options
author | Rob Landley <rob@landley.net> | 2020-02-04 13:44:20 -0600 |
---|---|---|
committer | Rob Landley <rob@landley.net> | 2020-02-04 13:44:20 -0600 |
commit | 2f96b8601212ab9d28fc5a8b640ecc9ef7de251b (patch) | |
tree | 45fce0ff538728cae7a3343ddef09615f8ef1798 | |
parent | 7450b93f9784d4df625f9729a20a61a3d8923854 (diff) | |
download | toybox-2f96b8601212ab9d28fc5a8b640ecc9ef7de251b.tar.gz |
More (subshell) work (not finished), fix {block;} | pipes.
-rwxr-xr-x | tests/sh.test | 42 | ||||
-rw-r--r-- | toys/pending/sh.c | 86 |
2 files changed, 108 insertions, 20 deletions
diff --git a/tests/sh.test b/tests/sh.test index ba469e49..4895ca7b 100755 --- a/tests/sh.test +++ b/tests/sh.test @@ -4,6 +4,8 @@ [ -f testing.sh ] && . testing.sh +# TODO make fake pty wrapper for test infrastructure + #testing "name" "command" "result" "infile" "stdin" [ -z "$SH" ] && { [ -z "$TEST_HOST" ] && SH="sh" || export SH="bash" ; } @@ -55,6 +57,10 @@ testing "leading variable assignments" \ #testing "can't have space before first : but yes around arguments" \ # 'BLAH=abcdefghi; echo ${BLAH: 1 : 3 }' "bcd\n" "" "" +testing "curly brackets and pipe" \ + '{ echo one; echo two ; } | tee blah.txt; wc blah.txt' \ + "one\ntwo\n2 2 8 blah.txt\n" "" "" + # texpect "name" "command" E/O/I"string" # Prompt changes for root/normal user @@ -65,3 +71,39 @@ SH="env -i PATH=${PATH@Q} PS1='\\$ ' $SH --noediting --noprofile --norc -is" txpect "prompt and exit" "$SH" "E$P" "Iexit\n" X0 txpect "prompt and echo" "$SH" "E$P" "Iecho hello\n" "Ohello"$'\n' "E$P" X0 txpect "redirect err" "$SH" "E$P" "Iecho > /dev/full\n" "E" "E$P" X0 +txpect "wait for <(exit)" "$SH" "E$P" "Icat <(echo hello 1>&2)\n" $'Ehello\n' \ + "E$P" X0 + + + +# $@ $* $# $? $- $$ $! $0 +# always exported: PWD SHLVL _ +# ./bash -c 'echo $_' prints $BASH, but PATH search shows path? Hmmm... +# ro: UID PPID EUID $ +# IFS LINENO +# PATH HOME SHELL USER LOGNAME SHLVL HOSTNAME HOSTTYPE MACHTYPE OSTYPE OLDPWD +# PS0 PS1='$ ' PS2='> ' PS3 PS4 BASH BASH_VERSION +# ENV - if [ -n "$ENV" ]; then . "$ENV"; fi # BASH_ENV - synonym for ENV +# FUNCNEST - maximum function nesting level (abort when above) +# REPLY - set by input with no args +# OPTARG OPTIND - set by getopts builtin +# OPTERR + +# maybe not: EXECIGNORE, FIGNORE, GLOBIGNORE + +#BASHPID - synonym for $$ HERE +#BASH_SUBSHELL - SHLVL synonym +#BASH_EXECUTION_STRING - -c argument +# +#automatically set: +#OPTARG - set by getopts builtin +#OPTIND - set by getopts builtin +# +#PROMPT_COMMAND PROMPT_DIRTRIM PS0 PS1 PS2 PS3 PS4 +# +#unsettable (assignments ignored before then) +#LINENO SECONDS RANDOM +#GROUPS - id -g +#HISTCMD - history number +# +#TMOUT - used by read diff --git a/toys/pending/sh.c b/toys/pending/sh.c index 7e6f827d..5beebca2 100644 --- a/toys/pending/sh.c +++ b/toys/pending/sh.c @@ -626,7 +626,7 @@ static void subshell_callback(void) // TODO avoid prototype static int sh_run(char *new); -// Pass environment and command string to child shell +// Pass environment and command string to child shell, return PID of child static int run_subshell(char *str, int len) { pid_t pid; @@ -636,7 +636,7 @@ static int run_subshell(char *str, int len) char *s; if ((pid = fork())<0) perror_msg("fork"); - else if (pid>0) { + else if (!pid) { s = xstrndup(str, len); sh_run(s); free(s); @@ -667,6 +667,33 @@ static int run_subshell(char *str, int len) return pid; } +// turn a parsed pipeline back into a string. +static char *pl2str(struct sh_pipeline *pl) +{ + struct sh_pipeline *end = 0; + int level = 0, len = 0, i, j; + char *s, *ss, *sss; + + // 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; + + 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; + + sss = pl->arg->v[pl->arg->c]; + if (!sss) sss = ";"; + if (j) ss = stpcpy(ss, sss); + else len += strlen(sss); + +// 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 // expanded args. This can be called from command or block context. static struct sh_process *expand_redir(struct sh_arg *arg, int envlen, int *urd) @@ -1078,7 +1105,7 @@ struct sh_pipeline *block_end(struct sh_pipeline *pl) pl = pl->next; } - return 0; + return pl; } void free_function(struct sh_function *sp) @@ -1457,6 +1484,7 @@ void dump_filehandles(char *when) */ +// wait for every process in a pipeline to end static int wait_pipeline(struct sh_process *pp) { int rc = 0; @@ -1474,6 +1502,7 @@ static int wait_pipeline(struct sh_process *pp) return rc; } +// pipe data into and out of this segment, I.E. handle leading and trailing | static int pipe_segments(char *ctl, int *pipes, int **urd) { unredirect(*urd); @@ -1504,6 +1533,7 @@ static int pipe_segments(char *ctl, int *pipes, int **urd) return 0; } +// Handle && and || traversal in pipeline segments static struct sh_pipeline *skip_andor(int rc, struct sh_pipeline *pl) { char *ctl = pl->arg->v[pl->arg->c]; @@ -1521,9 +1551,9 @@ static struct sh_pipeline *skip_andor(int rc, struct sh_pipeline *pl) // run a parsed shell function. Handle flow control blocks and characters, // setup pipes and block redirection, break/continue, call builtins, // vfork/exec external commands. -static void run_function(struct sh_function *sp) +static void run_function(struct sh_pipeline *pl) { - struct sh_pipeline *pl = sp->pipeline, *end; + struct sh_pipeline *end; struct blockstack { struct blockstack *next; struct sh_pipeline *start, *end; @@ -1541,12 +1571,11 @@ static void run_function(struct sh_function *sp) // iterate through pipeline segments while (pl) { - char *s = *pl->arg->v, *ss = pl->arg->v[1]; -if (BUGBUG) dprintf(255, "type=%d %s %s\n", pl->type, pl->arg->v[0], pl->arg->v[pl->arg->c]); + struct sh_arg *arg = pl->arg; + char *s = *arg->v, *ss = arg->v[1], *ctl = arg->v[arg->c]; +if (BUGBUG) dprintf(255, "runtype=%d %s %s\n", pl->type, s, ctl); // Is this an executable segment? if (!pl->type) { - struct sh_arg *arg = pl->arg; - char *ctl = arg->v[arg->c]; // Skip disabled block if (blk && !blk->run) { @@ -1571,7 +1600,7 @@ if (BUGBUG) dprintf(255, "type=%d %s %s\n", pl->type, pl->arg->v[0], pl->arg->v[ // 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 || arg->c>2 || ss[strspn(ss, "0123456789")]) { syntax_err("bad %s", s); break; } @@ -1605,6 +1634,7 @@ if (BUGBUG) dprintf(255, "type=%d %s %s\n", pl->type, pl->arg->v[0], pl->arg->v[ // TODO: "echo | read i" is backgroundable with ctrl-Z despite read = builtin. // probably have to inline run_command here to do that? Implicit () // also "X=42 | true; echo $X" doesn't get X. +// I.E. run_subshell() here sometimes? (But when?) dlist_add_nomalloc((void *)&pplist, (void *)run_command(arg)); } @@ -1632,7 +1662,7 @@ if (BUGBUG) dprintf(255, "type=%d %s %s\n", pl->type, pl->arg->v[0], pl->arg->v[ } // If previous piped into this block, save context until block end - if (pipe_segments(0, pipes, &urd)) break; + if (pipe_segments(end->arg->v[end->arg->c], pipes, &urd)) break; // It's a new block we're running, save context and add it to the stack. new = xzalloc(sizeof(*blk)); @@ -1649,7 +1679,7 @@ if (BUGBUG) dprintf(255, "type=%d %s %s\n", pl->type, pl->arg->v[0], pl->arg->v[ *pipes = -1; // Perform redirects listed at end of block - pp = expand_redir(blk->end->arg, 0, blk->urd); + pp = expand_redir(end->arg, 1, blk->urd); blk->urd = pp->urd; if (pp->arg.c) perror_msg("unexpected %s", *pp->arg.v); llist_traverse(pp->delete, free); @@ -1659,9 +1689,7 @@ if (BUGBUG) dprintf(255, "type=%d %s %s\n", pl->type, pl->arg->v[0], pl->arg->v[ // What flow control statement is this? -// TODO ( subshell - - // if/then/elif/else/fi, while until/do/done - no special handling needed + // {/} if/then/elif/else/fi, while until/do/done - no special handling // for select/do/done if (!strcmp(s, "for") || !strcmp(s, "select")) { @@ -1677,10 +1705,25 @@ dprintf(2, "TODO skipped init for((;;)), need math parser\n"); expand_arg(&blk->farg, pl->next->arg->v[i], 0, &blk->fdelete); } else expand_arg(&blk->farg, "\"$@\"", 0, &blk->fdelete); } - pl = pl->next; - } -// TODO case/esac {/} [[/]] (/) ((/)) function/} +// TODO case/esac [[/]] (/) ((/)) function/} + +/* +TODO: a | b | c needs subshell for builtins? + - anything that can produce output + - echo declare dirs + (a; b; c) like { } but subshell + when to auto-exec? ps vs sh -c 'ps' vs sh -c '(ps)' +*/ + + // subshell + } else if (!strcmp(s, "(")) { + if (!CFG_TOYBOX_FORK) { + ss = pl2str(pl->next); + run_subshell(ss, strlen(ss)); + } + pl = blk->end; + } // gearshift from block start to block body (end of flow control test) } else if (pl->type == 2) { @@ -1705,6 +1748,9 @@ dprintf(2, "TODO skipped running for((;;)), need math parser\n"); // end of block, may have trailing redirections and/or pipe } else if (pl->type == 3) { + // if we end a block we're not in, we started in a block. + if (!blk) break; + // repeating block? if (blk->run && !strcmp(s, "done")) { pl = blk->start; @@ -1750,7 +1796,7 @@ static int sh_run(char *new) // TODO switch the fmemopen for -c to use this? Error checking? $(blah) memset(&scratch, 0, sizeof(struct sh_function)); - if (!parse_line(new, &scratch)) run_function(&scratch); + if (!parse_line(new, &scratch)) run_function(scratch.pipeline); free_function(&scratch); rc = toys.exitval; toys.exitval = 0; @@ -1972,7 +2018,7 @@ if (BUGBUG) { int fd = open("/dev/tty", O_RDWR); dup2(fd, 255); close(fd); } if (BUGBUG) dump_state(&scratch); if (prompt != 1) { // TODO: ./blah.sh one two three: put one two three in scratch.arg - if (!prompt) run_function(&scratch); + if (!prompt) run_function(scratch.pipeline); free_function(&scratch); prompt = 0; } |