diff --git a/Build.sh b/Build.sh index 7b4c405..585ca2a 100644 --- a/Build.sh +++ b/Build.sh @@ -1,5 +1,5 @@ #!/bin/sh -srcversion='$MirOS: src/bin/mksh/Build.sh,v 1.588 2012/10/22 16:53:20 tg Exp $' +srcversion='$MirOS: src/bin/mksh/Build.sh,v 1.589 2012/10/22 20:19:08 tg Exp $' #- # Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, # 2011, 2012 @@ -1370,6 +1370,9 @@ ac_ifcpp 'ifdef MKSH_CONSERVATIVE_FDS' isset_MKSH_CONSERVATIVE_FDS '' \ #ac_ifcpp 'ifdef MKSH_DISABLE_DEPRECATED' isset_MKSH_DISABLE_DEPRECATED '' \ # "if deprecated features are to be omitted" && \ # check_categories="$check_categories nodeprecated" +ac_ifcpp 'ifdef MKSH_DISABLE_EXPERIMENTAL' isset_MKSH_DISABLE_EXPERIMENTAL '' \ + "if experimental features are to be omitted" && \ + check_categories="$check_categories noexperimental" # # Environment: headers @@ -1492,7 +1495,7 @@ else #define EXTERN #define MKSH_INCLUDES_ONLY #include "sh.h" - __RCSID("$MirOS: src/bin/mksh/Build.sh,v 1.588 2012/10/22 16:53:20 tg Exp $"); + __RCSID("$MirOS: src/bin/mksh/Build.sh,v 1.589 2012/10/22 20:19:08 tg Exp $"); int main(void) { printf("Hello, World!\n"); return (0); } EOF case $cm in diff --git a/check.t b/check.t index b4951e2..ccb38ca 100644 --- a/check.t +++ b/check.t @@ -1,4 +1,4 @@ -# $MirOS: src/bin/mksh/check.t,v 1.561 2012/10/21 21:55:01 tg Exp $ +# $MirOS: src/bin/mksh/check.t,v 1.562 2012/10/22 20:19:10 tg Exp $ # $OpenBSD: bksl-nl.t,v 1.2 2001/01/28 23:04:56 niklas Exp $ # $OpenBSD: history.t,v 1.5 2001/01/28 23:04:56 niklas Exp $ # $OpenBSD: read.t,v 1.3 2003/03/10 03:48:16 david Exp $ @@ -29,7 +29,7 @@ # http://www.freebsd.org/cgi/cvsweb.cgi/src/tools/regression/bin/test/regress.sh?rev=HEAD expected-stdout: - @(#)MIRBSD KSH R40 2012/10/21 + @(#)MIRBSD KSH R40 2012/10/22 description: Check version of shell. stdin: @@ -38,7 +38,7 @@ name: KSH_VERSION category: shell:legacy-no --- expected-stdout: - @(#)LEGACY KSH R40 2012/10/21 + @(#)LEGACY KSH R40 2012/10/22 description: Check version of legacy shell. stdin: @@ -9553,6 +9553,27 @@ expected-stdout: x=$(( echo $(true >&3 ) $((1+ 2)) ) | tr u x ) } --- +name: funsub-1 +description: + Check that non-subenvironment command substitution works +category: !noexperimental +stdin: + set -e + foo=bar + echo "ob $foo ." + echo "${ + echo "ib $foo :" + foo=baz + echo "ia $foo :" + false + }" . + echo "oa $foo ." +expected-stdout: + ob bar . + ib bar : + ia baz : . + oa baz . +--- name: test-stnze-1 description: Check that the short form [ $x ] works diff --git a/eval.c b/eval.c index 119d2bd..f0a5c00 100644 --- a/eval.c +++ b/eval.c @@ -23,7 +23,7 @@ #include "sh.h" -__RCSID("$MirOS: src/bin/mksh/eval.c,v 1.128 2012/08/24 21:15:42 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/eval.c,v 1.129 2012/10/22 20:19:12 tg Exp $"); /* * string expansion @@ -58,7 +58,7 @@ typedef struct Expand { #define IFS_NWS 2 /* have seen IFS non-white-space */ static int varsub(Expand *, const char *, const char *, int *, int *); -static int comsub(Expand *, const char *); +static int comsub(Expand *, const char *, int); static char *trimsub(char *, char *, int); static void glob(char *, XPtrV *, bool); static void globit(XString *, char **, char *, XPtrV *, int); @@ -277,18 +277,33 @@ expand(const char *cp, /* input word */ quote = st->quotew; continue; case COMSUB: +#ifndef MKSH_DISABLE_EXPERIMENTAL + case FUNSUB: +#endif tilde_ok = 0; if (f & DONTRUNCOMMAND) { word = IFS_WORD; *dp++ = '$'; - *dp++ = '('; +#ifndef MKSH_DISABLE_EXPERIMENTAL + if (c == FUNSUB) { + *dp++ = '{'; + *dp++ = ' '; + } else +#endif + *dp++ = '('; while (*sp != '\0') { Xcheck(ds, dp); *dp++ = *sp++; } - *dp++ = ')'; +#ifndef MKSH_DISABLE_EXPERIMENTAL + if (c == FUNSUB) { + *dp++ = ';'; + *dp++ = '}'; + } else +#endif + *dp++ = ')'; } else { - type = comsub(&x, sp); + type = comsub(&x, sp, c); if (type == XCOM && (f&DOBLANK)) doblank++; sp = strnul(sp) + 1; @@ -1272,7 +1287,7 @@ varsub(Expand *xp, const char *sp, const char *word, * Run the command in $(...) and read its output. */ static int -comsub(Expand *xp, const char *cp) +comsub(Expand *xp, const char *cp, int fn MKSH_A_UNUSED) { Source *s, *sold; struct op *t; @@ -1307,6 +1322,30 @@ comsub(Expand *xp, const char *cp) SHF_MAPHI|SHF_CLEXEC); if (shf == NULL) errorf("%s: %s %s", name, "can't open", "$() input"); +#ifndef MKSH_DISABLE_EXPERIMENTAL + } else if (fn == FUNSUB) { + int ofd1; + struct temp *tf = NULL; + + /* create a temporary file, open for writing */ + maketemp(ATEMP, TT_FUNSUB, &tf); + if (!tf->shf) { + errorf("can't %s temporary file %s: %s", + "create", tf->tffn, strerror(errno)); + } + /* save stdout and make the temporary file it */ + ofd1 = savefd(1); + ksh_dup2(shf_fileno(tf->shf), 1, false); + /* run tree, with output thrown into the tempfile */ + execute(t, XXCOM | XERROK, NULL); + /* close the tempfile and restore regular stdout */ + shf_close(tf->shf); + restfd(1, ofd1); + /* now open, unlink and free the tempfile for reading */ + shf = shf_open(tf->tffn, O_RDONLY, 0, SHF_MAPHI | SHF_CLEXEC); + unlink(tf->tffn); + afree(tf, ATEMP); +#endif } else { int ofd1, pv[2]; diff --git a/lex.c b/lex.c index 22e956e..2a81495 100644 --- a/lex.c +++ b/lex.c @@ -23,7 +23,7 @@ #include "sh.h" -__RCSID("$MirOS: src/bin/mksh/lex.c,v 1.168 2012/10/03 17:24:20 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/lex.c,v 1.169 2012/10/22 20:19:13 tg Exp $"); /* * states while lexing word @@ -109,7 +109,7 @@ void yyskiputf8bom(void); static int backslash_skip; static int ignore_backslash_newline; static struct sretrace_info *retrace_info; -uint8_t subshell_nesting_level = 0; +int subshell_nesting_type = 0; /* optimised getsc_bn() */ #define o_getsc() (*source->str != '\0' && *source->str != '\\' && \ @@ -262,6 +262,13 @@ yylex(int cf) while (!((c = getsc()) == 0 || ((state == SBASE || state == SHEREDELIM || state == SHERESTRING) && ctype(c, C_LEX1)))) { +#ifndef MKSH_DISABLE_EXPERIMENTAL + if (state == SBASE && + subshell_nesting_type == /*{*/ '}' && + c == /*{*/ '}') + /* possibly end ${ :;} */ + break; +#endif accept_nonword: Xcheck(ws, wp); switch (state) { @@ -395,14 +402,30 @@ yylex(int cf) } else { ungetsc(c); subst_command: - sp = yyrecursive(); + c = COMSUB; +#ifndef MKSH_DISABLE_EXPERIMENTAL + subst_command2: +#endif + sp = yyrecursive(c); cz = strlen(sp) + 1; XcheckN(ws, wp, cz); - *wp++ = COMSUB; + *wp++ = c; memcpy(wp, sp, cz); wp += cz; } } else if (c == '{') /*}*/ { +#ifndef MKSH_DISABLE_EXPERIMENTAL + c = getsc(); + if (ctype(c, C_IFSWS)) { + /* + * non-subenvironment + * "command" substitution + */ + c = FUNSUB; + goto subst_command2; + } + ungetsc(c); +#endif *wp++ = OSUBST; *wp++ = '{'; /*}*/ wp = get_brace_var(&ws, wp); @@ -1171,7 +1194,7 @@ readhere(struct ioword *iop) /* end of here document marker, what to do? */ switch (c) { case /*(*/ ')': - if (!subshell_nesting_level) + if (!subshell_nesting_type) /*- * not allowed outside $(...) or (...) * => mismatch diff --git a/main.c b/main.c index 7e448aa..15a132a 100644 --- a/main.c +++ b/main.c @@ -34,7 +34,7 @@ #include #endif -__RCSID("$MirOS: src/bin/mksh/main.c,v 1.232 2012/10/22 16:53:22 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/main.c,v 1.233 2012/10/22 20:19:14 tg Exp $"); extern char **environ; @@ -1377,7 +1377,7 @@ void restfd(int fd, int ofd) { if (fd == 2) - shf_flush(&shf_iob[fd]); + shf_flush(&shf_iob[/* fd */ 2]); if (ofd < 0) /* original fd closed */ close(fd); @@ -1591,6 +1591,18 @@ maketemp(Area *ap, Temp_type type, struct temp **tlist) /* do another cycle */ } +#ifndef MKSH_DISABLE_EXPERIMENTAL + if (type == TT_FUNSUB) { + int nfd; + + /* map us high and mark as close-on-exec */ + if ((nfd = savefd(i)) != i) { + close(i); + i = nfd; + } + } +#endif + /* shf_fdopen cannot fail, so no fd leak */ tp->shf = shf_fdopen(i, SHF_WR, NULL); diff --git a/mksh.1 b/mksh.1 index 5f1b2a6..2537e52 100644 --- a/mksh.1 +++ b/mksh.1 @@ -1,4 +1,4 @@ -.\" $MirOS: src/bin/mksh/mksh.1,v 1.295 2012/10/21 17:42:51 tg Exp $ +.\" $MirOS: src/bin/mksh/mksh.1,v 1.296 2012/10/22 20:19:14 tg Exp $ .\" $OpenBSD: ksh.1,v 1.144 2012/07/08 08:13:20 guenther Exp $ .\"- .\" Copyright © 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, @@ -74,7 +74,7 @@ .\" with -mandoc, it might implement .Mx itself, but we want to .\" use our own definition. And .Dd must come *first*, always. .\" -.Dd $Mdocdate: October 21 2012 $ +.Dd $Mdocdate: October 22 2012 $ .\" .\" Check which macro package we use, and do other -mdoc setup. .\" @@ -1146,9 +1146,14 @@ command substitutions take the form .Pf $( Ns Ar command Ns \&) or (deprecated) .Pf \` Ns Ar command Ns \` +or (executed in the current environment) +.Pf ${\ \& Ar command Ns \&;} and strip trailing newlines; and arithmetic substitutions take the form .Pf $(( Ns Ar expression Ns )) . +Parsing the current-environment command substitution requires a space, +tab or newline after the opening brace and that the closing brace be +recognised as a keyword (i.e. is preceded by a newline or semicolon). .Pp If a substitution appears outside of double quotes, the results of the substitution are generally subject to word or field splitting according to @@ -1226,6 +1231,8 @@ A command substitution is replaced by the output generated by the specified command which is run in a subshell. For .Pf $( Ns Ar command Ns \&) +and +.Pf ${\ \& Ar command Ns \&;} substitutions, normal quoting rules are used when .Ar command is parsed; however, for the deprecated diff --git a/sh.h b/sh.h index 344db1a..bcd9831 100644 --- a/sh.h +++ b/sh.h @@ -157,9 +157,9 @@ #endif #ifdef EXTERN -__RCSID("$MirOS: src/bin/mksh/sh.h,v 1.596 2012/10/22 16:53:22 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/sh.h,v 1.597 2012/10/22 20:19:16 tg Exp $"); #endif -#define MKSH_VERSION "R40 2012/10/21" +#define MKSH_VERSION "R40 2012/10/22" /* arithmetic types: C implementation */ #if !HAVE_CAN_INTTYPES @@ -773,8 +773,12 @@ EXTERN const char TC_LEX1[] E_INIT("|&;<>() \t\n"); typedef uint8_t Temp_type; /* expanded heredoc */ #define TT_HEREDOC_EXP 0 -/* temp file used for history editing (fc -e) */ +/* temporary file used for history editing (fc -e) */ #define TT_HIST_EDIT 1 +#ifndef MKSH_DISABLE_EXPERIMENTAL +/* temporary file used during in-situ command substitution */ +#define TT_FUNSUB 2 +#endif /* temp/heredoc files. The file is removed when the struct is freed. */ struct temp { @@ -1286,6 +1290,9 @@ struct op { #define SPAT 10 /* separate pattern: | */ #define CPAT 11 /* close pattern: ) */ #define ADELIM 12 /* arbitrary delimiter: ${foo:2:3} ${foo/bar/baz} */ +#ifndef MKSH_DISABLE_EXPERIMENTAL +#define FUNSUB 14 /* ${ foo;} substitution (NUL terminated) */ +#endif /* * IO redirection @@ -1321,7 +1328,6 @@ struct ioword { #define XBGND BIT(2) /* command & */ #define XPIPEI BIT(3) /* input is pipe */ #define XPIPEO BIT(4) /* output is pipe */ -#define XPIPE (XPIPEI|XPIPEO) /* member of pipe */ #define XXCOM BIT(5) /* `...` command */ #define XPCLOSE BIT(6) /* exchild: close close_fd in parent */ #define XCCLOSE BIT(7) /* exchild: close close_fd in child */ @@ -1922,7 +1928,7 @@ ssize_t shf_vfprintf(struct shf *, const char *, va_list) void initkeywords(void); struct op *compile(Source *, bool); bool parse_usec(const char *, struct timeval *); -char *yyrecursive(void); +char *yyrecursive(int); /* tree.c */ void fptreef(struct shf *, int, const char *, ...); char *snptreef(char *, ssize_t, const char *, ...); diff --git a/syn.c b/syn.c index 44a4337..43d17c5 100644 --- a/syn.c +++ b/syn.c @@ -23,12 +23,11 @@ #include "sh.h" -__RCSID("$MirOS: src/bin/mksh/syn.c,v 1.81 2012/10/03 15:50:32 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/syn.c,v 1.82 2012/10/22 20:19:18 tg Exp $"); +extern int subshell_nesting_type; extern void yyskiputf8bom(void); -extern uint8_t subshell_nesting_level; - struct nesting_state { int start_token; /* token than began nesting (eg, FOR) */ int start_line; /* line nesting began on */ @@ -364,12 +363,15 @@ get_command(int cf) Leave: break; - case '(': /*)*/ + case '(': /*)*/ { + int subshell_nesting_type_saved; Subshell: - ++subshell_nesting_level; + subshell_nesting_type_saved = subshell_nesting_type; + subshell_nesting_type = ')'; t = nested(TPAREN, '(', ')'); - --subshell_nesting_level; + subshell_nesting_type = subshell_nesting_type_saved; break; + } case '{': /*}*/ t = nested(TBRACE, '{', '}'); @@ -1116,16 +1118,29 @@ parse_usec(const char *s, struct timeval *tv) * a COMSUB recursively using the main shell parser and lexer */ char * -yyrecursive(void) +yyrecursive(int subtype MKSH_A_UNUSED) { struct op *t; char *cp; bool old_reject; - int old_symbol, old_salias; + int old_symbol, old_salias, old_nesting_type; struct ioword **old_herep; + int stok, etok; + +#ifndef MKSH_DISABLE_EXPERIMENTAL + if (subtype == FUNSUB) { + stok = '{'; + etok = '}'; + } else +#endif + { + stok = '('; + etok = ')'; + } /* tell the lexer to accept a closing parenthesis as EOD */ - ++subshell_nesting_level; + old_nesting_type = subshell_nesting_type; + subshell_nesting_type = etok; /* push reject state, parse recursively, pop reject state */ old_reject = reject; @@ -1135,7 +1150,7 @@ yyrecursive(void) old_salias = sALIAS; sALIAS = 0; /* we use TPAREN as a helper container here */ - t = nested(TPAREN, '(', ')'); + t = nested(TPAREN, stok, etok); sALIAS = old_salias; herep = old_herep; reject = old_reject; @@ -1145,6 +1160,6 @@ yyrecursive(void) cp = snptreef(NULL, 0, "%T", t->left); tfree(t, ATEMP); - --subshell_nesting_level; + subshell_nesting_type = old_nesting_type; return (cp); } diff --git a/tree.c b/tree.c index 678f530..1669473 100644 --- a/tree.c +++ b/tree.c @@ -23,7 +23,7 @@ #include "sh.h" -__RCSID("$MirOS: src/bin/mksh/tree.c,v 1.64 2012/08/17 18:34:25 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/tree.c,v 1.65 2012/10/22 20:19:18 tg Exp $"); #define INDENT 8 @@ -342,6 +342,12 @@ wdvarput(struct shf *shf, const char *wp, int quotelevel, int opmode) shf_putc(c, shf); shf_puts(cs, shf); break; +#ifndef MKSH_DISABLE_EXPERIMENTAL + case FUNSUB: + shf_puts("${ ", shf); + cs = ";}"; + goto pSUB; +#endif case EXPRSUB: shf_puts("$((", shf); cs = "))"; @@ -581,6 +587,9 @@ wdscan(const char *wp, int c) wp++; break; case COMSUB: +#ifndef MKSH_DISABLE_EXPERIMENTAL + case FUNSUB: +#endif case EXPRSUB: while (*wp++ != 0) ; @@ -820,6 +829,11 @@ dumpwdvar_i(struct shf *shf, const char *wp, int quotelevel) closeandout: shf_putc('>', shf); break; +#ifndef MKSH_DISABLE_EXPERIMENTAL + case FUNSUB: + shf_puts("FUNSUB<", shf); + goto dumpsub; +#endif case EXPRSUB: shf_puts("EXPRSUB<", shf); goto dumpsub;