From 3aaef16569a6b9bd5ca0a2a323cc0643772f9c56 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Sat, 16 Sep 2023 17:34:39 -0700 Subject: 52154, 52155: Implement, document, and test non-forking command substitution. Comprises workers/51957, 51985, 51987, 51988, 51993, 52131, 52139, plus fixes for return values, parse errors, and trailing newlines (which were incorrectly removed) in ${ ... } --- Src/subst.c | 157 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 150 insertions(+), 7 deletions(-) (limited to 'Src/subst.c') diff --git a/Src/subst.c b/Src/subst.c index d68159227..52afd6484 100644 --- a/Src/subst.c +++ b/Src/subst.c @@ -1867,6 +1867,10 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags, * joining the array into a string (for compatibility with ksh/bash). */ int quoted_array_with_offset = 0; + /* Indicates ${|...;} */ + char *rplyvar = NULL; + /* Indicates ${ ... ;} */ + char *rplytmp = NULL; *s++ = '\0'; /* @@ -1894,8 +1898,147 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags, * flags in parentheses, but also one ksh hack. */ if (c == Inbrace) { + /* The command string to be run by ${|...;} */ + char *cmdarg = NULL; + size_t slen = 0; inbrace = 1; s++; + + /* Short-path for the nofork command substitution ${|cmd;} + * See other comments about kludges for why this is here. + * + * The command string is extracted and executed, and the + * substitution assigned. There's no (...)-flags processing, + * i.e. no ${|(U)cmd;}, because it looks quite awful and + * should not be part of command substitution in any case. + * Use ${(U)${|cmd;}} as you would for ${(U)$(cmd;)}. + */ + if (*s == '|' || *s == Bar || inblank(*s)) { + char *outbracep = s; + char sav = *s; + *s = Inbrace; + if (skipparens(Inbrace, Outbrace, &outbracep) == 0) { + slen = outbracep - s - 1; + if ((*s = sav) != Bar) { + sav = *outbracep; + *outbracep = '\0'; + tokenize(s); + *outbracep = sav; + } + } + } + if (slen > 1) { + char *outbracep = s + slen; + if (*outbracep == Outbrace) { + if ((rplyvar = itype_end(s+1, INAMESPC, 0))) { + if (*rplyvar == Inbrack && + (rplyvar = parse_subscript(++rplyvar, 1, ']'))) + ++rplyvar; + } + if (rplyvar == s+1 && *rplyvar == Bar) { + /* Is ${||...} a subtitution error or a syntax error? + zerr("bad substitution"); + return NULL; + */ + rplyvar = NULL; + } + if (rplyvar && *rplyvar == Bar) { + cmdarg = dupstrpfx(rplyvar+1, outbracep-rplyvar-1); + rplyvar = dupstrpfx(s+1,rplyvar-s-1); + } else { + cmdarg = dupstrpfx(s+1, outbracep-s-1); + rplyvar = "REPLY"; + } + if (inblank(*s)) { + /* + * Admittedly a hack. Take advantage of the enforced + * locality of REPLY and the semantics of $(level = locallevel; + /* if (rplyval) setsparam("REPLY", ztrdup(rplyval)); */ + } + + if (rplyvar && cmdarg && *cmdarg) { + int obreaks = breaks; + Eprog cmdprog; + /* Execute the shell command */ + untokenize(cmdarg); + cmdprog = parse_string(cmdarg, 0); + if (cmdprog) { + execode(cmdprog, 1, 0, "cmdsubst"); + cmdoutval = lastval; + /* "return" behaves as if in a function */ + if (retflag) { + retflag = 0; + breaks = obreaks; /* Is this ever not zero? */ + } + } else /* parse error */ + errflag |= ERRFLAG_ERROR; + if (rplytmp && !errflag) { + int onoerrs = noerrs; + noerrs = 2; + if ((cmdarg = ztuff(rplytmp))) + setsparam("REPLY", cmdarg); + noerrs = onoerrs; + } + } + + if (rplytmp) + unlink(rplytmp); + if (rplyvar) { + if (strcmp(rplyvar, "REPLY") == 0) { + if ((val = dupstring(getsparam("REPLY")))) + vunset = 0; + else { + vunset = 1; + val = dupstring(""); + } + } else { + s = dyncat(rplyvar, s); + rplyvar = NULL; + } + endparamscope(); + if (exit_pending) { + if (mypid == getpid()) { + /* + * paranoia: don't check for jobs, but there + * shouldn't be any if not interactive. + */ + stopmsg = 1; + zexit(exit_val, ZEXIT_NORMAL); + } else + _exit(exit_val); + } + } + /* * In ksh emulation a leading `!' is a special flag working * sort of like our (k). This is true only for arrays or @@ -2590,14 +2733,14 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags, * we let fetchvalue set the main string pointer s to * the end of the bit it's fetched. */ - if (!(v = fetchvalue(&vbuf, (subexp ? &ov : &s), - (wantt ? -1 : - ((unset(KSHARRAYS) || inbrace) ? 1 : -1)), - scanflags)) || - (v->pm && (v->pm->node.flags & PM_UNSET)) || - (v->flags & VALFLAG_EMPTY)) + if (!rplyvar && + (!(v = fetchvalue(&vbuf, (subexp ? &ov : &s), + (wantt ? -1 : + ((unset(KSHARRAYS) || inbrace) ? 1 : -1)), + scanflags)) || + (v->pm && (v->pm->node.flags & PM_UNSET)) || + (v->flags & VALFLAG_EMPTY))) vunset = 1; - if (wantt) { /* * Handle the (t) flag: value now becomes the type -- cgit 1.4.1