aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Landley <rob@landley.net>2020-02-04 13:44:20 -0600
committerRob Landley <rob@landley.net>2020-02-04 13:44:20 -0600
commit2f96b8601212ab9d28fc5a8b640ecc9ef7de251b (patch)
tree45fce0ff538728cae7a3343ddef09615f8ef1798
parent7450b93f9784d4df625f9729a20a61a3d8923854 (diff)
downloadtoybox-2f96b8601212ab9d28fc5a8b640ecc9ef7de251b.tar.gz
More (subshell) work (not finished), fix {block;} | pipes.
-rwxr-xr-xtests/sh.test42
-rw-r--r--toys/pending/sh.c86
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;
}