setting it to 0 for the expansion so it doesn't affect things like
command substitution.
From a report by Grisha Levit <grishalevit@gmail.com>
+
+parse.y
+ - parse_comsub: handle ${Ccommand; }, where C == space, tab, newline,
+ or '|'
+ - read_token_word: understand ${Ccommand; } and call parse_comsub to
+ parse the contents
+ - parse_matched_pair: understand ${Ccommand; } inside another paired
+ construct
+ - new `funsub' production, like comsub but for ${Ccommand;} nofork
+ command substitution
+
+execute_cmd.c
+ - execute_simple_command: if we fork because of pipes in or out, clear
+ the fifo list, since subshell aren't supposed to inherit fifos
+
+ 5/8
+ ---
+parser.h
+ - PST_FUNSUBST: new parser state, parsing the command in ${Ccommand;}.
+ Primarily used to have read_token_word return `}' as a separate
+ token even if it's not delimited like a reserved word
+
+subst.h
+ - SX_FUNSUB: new flag, tell xparse_dolparen we are parsing the command
+ in a ${Ccommand;} nofork command substitution
+
+parse.y
+ - xparse_dolparen: handle parsing ${Ccommand;} nofork command
+ substitution like parse_comsub
+ - read_token: if we're parsing a foreground command substitution,
+ treat a word that begins with an unquoted `}' as a `}' token to
+ terminate the ${Ccommand;} construct without requiring that the
+ close brace be delimited like a reserved word
+ - no_semi_successors: add DOLBRACE
+ - reserved_word_acceptable: add DOLBRACE
+
+subst.c
+ - extract_function_subst: like extract_command_subst, but for nofork
+ command substitutions; calls xparse_dolparen with the SX_FUNSUB
+ flag to differentiate
+ - string_extract_double_quoted,extract_delimited_string,
+ extract_heredoc_dolbrace_string,extract_dollar_brace_string,
+ param_expand: call extract_function_subst as appropriate
+ - function_substitute: like command_substitute, but for nofork command
+ substitutions. Just a stub for now
reconfig: force
sh $(srcdir)/configure -C
-loadables:
+# force loadables to wait until the shell is built
+loadables: .made
cd $(LOADABLES_DIR) && $(MAKE) $(MFLAGS) all
#newversion: mkversion
.B %q
causes \fBprintf\fP to output the corresponding
\fIargument\fP in a format that can be reused as shell input.
-\fB%q\fP and \fB%Q\fP use the \fB$''\fP quoting style if any characters
+\fB%q\fP and \fB%Q\fP use the \fB$\(aq\(aq\fP quoting style if any characters
in the argument string require it, and backslash quoting otherwise.
If the format string uses the \fIprintf\fP alternate form, these two
formats quote the argument string using single quotes.
@subsubsection ANSI-C Quoting
@cindex quoting, ANSI
-Character sequences of the form $'@var{string}' are treated as a special
-kind of single quotes.
+Character sequences of the form @code{$'@var{string}'} are treated as
+a special kind of single quotes.
The sequence expands to @var{string}, with backslash-escaped characters
in @var{string} replaced as specified by the ANSI C standard.
Backslash escape sequences, if present, are decoded as follows:
coproc_closeall ();
#endif
+#if defined (PROCESS_SUBSTITUTION)
+ clear_fifo_list (); /* subshells don't inherit fifos */
+#endif
+
last_asynchronous_pid = old_last_async_pid;
if (async)
/* The old code did not test already_forked and only did this if
subshell_environment&SUBSHELL_COMSUB != 0 (comsubs and procsubs). Other
uses of the no-fork optimization left FIFOs in $TMPDIR */
- if (already_forked == 0 && (cmdflags & CMD_NO_FORK) && fifos_pending() > 0)
+ if (already_forked == 0 && (cmdflags & CMD_NO_FORK) && fifos_pending () > 0)
+ cmdflags &= ~CMD_NO_FORK;
+
+ if (dofork && already_forked && (subshell_environment & SUBSHELL_PIPE) &&
+ (cmdflags & CMD_NO_FORK) && fifos_pending () > 0)
+#if 0
cmdflags &= ~CMD_NO_FORK;
+#else
+ ; /* can't turn off nofork here, too many processes have the FIFOs open */
+#endif
#endif
result = execute_disk_command (words, simple_command->redirects, command_line,
pipe_in, pipe_out, async, fds_to_close,
cmdflags);
+#if 0
+/* If we forked but still have to fork again to run the disk command, we
+ did so because we created FIFOs. We can't just execve the command in case
+ it dies of a signal without a chance to clean up the FIFOs, so we fork
+ again, then make sure we wait for the child from execute_disk_command(),
+ unlink the FIFOs we created, and exit ourselves. */
+if (dofork && already_forked && (cmdflags & CMD_NO_FORK) == 0)
+ {
+ result = wait_for (last_made_pid, 0);
+#if defined (PROCESS_SUBSTITUTION)
+ if (fifos_pending ())
+ unlink_fifo_list ();
+#endif
+ sh_exit (result & 0xFF);
+ }
+#endif
+
return_result:
bind_lastarg (lastarg);
FREE (command_line);
%type <command> cond_command
%type <command> arith_for_command
%type <command> coproc
-%type <command> comsub
+%type <command> comsub funsub
%type <command> function_def function_body if_command elif_clause subshell
%type <redirect> redirection redirection_list
%type <element> simple_command_element
eof_encountered = 0;
YYACCEPT;
}
+ | funsub
+ {
+ /* This is special; look at the production and how
+ parse_comsub/parse_valsub sets token_to_read */
+ global_command = $1;
+ eof_encountered = 0;
+ YYACCEPT;
+ }
| '\n'
{
/* Case of regular command, but not a very
{
$$ = (COMMAND *)NULL;
}
- | DOLBRACE compound_list '}'
+ ;
+
+funsub: DOLBRACE compound_list '}'
{
$$ = $2;
}
if (ch == '(') /* ) */
nestret = parse_comsub (0, '(', ')', &nestlen, (rflags|P_COMMAND) & ~P_DQUOTE);
else if (ch == '{') /* } */
- nestret = parse_matched_pair (0, '{', '}', &nestlen, P_FIRSTCLOSE|P_DOLBRACE|rflags);
+ {
+ int npeek;
+
+ npeek = shell_getc (1);
+ shell_ungetc (npeek);
+ if (FUNSUB_CHAR (npeek))
+ nestret = parse_comsub (0, '{', '}', &nestlen, rflags|P_COMMAND);
+ else
+ nestret = parse_matched_pair (0, '{', '}', &nestlen, P_FIRSTCLOSE|P_DOLBRACE|rflags);
+ }
else if (ch == '[') /* ] */
nestret = parse_matched_pair (0, '[', ']', &nestlen, rflags|P_ARITH);
f &= ~PST_CMDSUBST;
fprintf (stderr, "PST_CMDSUBST%s", f ? "|" : "");
}
+ if (f & PST_FUNSUBST)
+ {
+ f &= ~PST_FUNSUBST;
+ fprintf (stderr, "PST_FUNSUBST%s", f ? "|" : "");
+ }
if (f & PST_CASESTMT)
{
f &= ~PST_CASESTMT;
parse_comsub (int qc, int open, int close, size_t *lenp, int flags)
{
int peekc, r;
- int start_lineno, local_extglob, was_extpat;
+ int start_lineno, dolbrace_spec, local_extglob, was_extpat;
char *ret, *tcmd;
size_t retlen;
sh_parser_state_t ps;
{
peekc = shell_getc (1);
shell_ungetc (peekc);
- if (peekc == '(') /*)*/
+ if (peekc == '(') /* ) */
return (parse_matched_pair (qc, open, close, lenp, P_ARITH));
}
+ else if (open == '{') /* } */
+ {
+ peekc = shell_getc (1);
+ if (FUNSUB_CHAR (peekc))
+ dolbrace_spec = peekc;
+ else
+ {
+ shell_ungetc (peekc);
+ return parse_matched_pair (qc, open, close, lenp, flags);
+ }
+ }
/*itrace("parse_comsub: qc = `%c' open = %c close = %c", qc, open, close);*/
parser_state &= ~(PST_CASEPAT|PST_ALEXPNEXT|PST_SUBSHELL|PST_REDIRLIST);
/* State flags we want to set for this run through the parser. */
parser_state |= PST_CMDSUBST|PST_EOFTOKEN|PST_NOEXPAND;
+ if (open == '{') /* } */
+ parser_state |= PST_FUNSUBST;
/* leave pushed_string_list alone, since we might need to consume characters
from it to satisfy this command substitution (in some perverse case). */
#endif
current_token = '\n'; /* XXX */
- token_to_read = DOLPAREN; /* let's trick the parser */
+ token_to_read = (open == '(') ? DOLPAREN : DOLBRACE; /* let's trick the parser ) */
r = yyparse ();
tcmd = print_comsub (parsed_command); /* returns static memory */
retlen = strlen (tcmd);
- if (tcmd[0] == '(') /* ) need a space to prevent arithmetic expansion */
- retlen++;
- ret = xmalloc (retlen + 2);
- if (tcmd[0] == '(') /* ) */
+ if (open == '(') /* ) */
{
- ret[0] = ' ';
- strcpy (ret + 1, tcmd);
+ if (tcmd[0] == '(') /* ) need a space to prevent arithmetic expansion */
+ retlen++;
+ ret = xmalloc (retlen + 2);
+ if (tcmd[0] == '(') /* ) */
+ {
+ ret[0] = ' ';
+ strcpy (ret + 1, tcmd);
+ }
+ else
+ strcpy (ret, tcmd);
}
- else
- strcpy (ret, tcmd);
- ret[retlen++] = ')';
+ else /* open == '{' } */
+ {
+ int lastc;
+
+ lastc = tcmd[retlen - 1];
+ retlen++;
+ ret = xmalloc (retlen + 4);
+ ret[0] = (dolbrace_spec == '|') ? '|' : ' ';
+ strcpy (ret + 1, tcmd); /* ( */
+ if (lastc != '\n' && lastc != ';' && lastc != '&' && lastc != ')')
+ ret[retlen++] = ';';
+ ret[retlen++] = ' ';
+ }
+ ret[retlen++] = close;
ret[retlen] = '\0';
dispose_command (parsed_command);
{
sh_parser_state_t ps;
sh_input_line_state_t ls;
- int orig_ind, nc, sflags, start_lineno, local_extglob;
+ int orig_ind, nc, sflags, start_lineno, local_extglob, funsub, closer;
char *ret, *ep, *ostring;
/*debug_parser(1);*/
/*itrace("xparse_dolparen: size = %d shell_input_line = `%s' string=`%s'", shell_input_line_size, shell_input_line, string);*/
+ funsub = flags & SX_FUNSUB;
+
sflags = SEVAL_NONINT|SEVAL_NOHIST|SEVAL_NOFREE;
if (flags & SX_NOLONGJMP)
sflags |= SEVAL_NOLONGJMP;
pushed_string_list = (STRING_SAVER *)NULL;
#endif
/*(*/
- parser_state |= PST_CMDSUBST|PST_EOFTOKEN; /* allow instant ')' */ /*(*/
- shell_eof_token = ')';
+ parser_state |= PST_CMDSUBST|PST_EOFTOKEN; /* allow instant ')' */ /*{(*/
+ closer = shell_eof_token = funsub ? '}' : ')';
if (flags & SX_COMPLETE)
parser_state |= PST_NOERROR;
+ if (funsub)
+ parser_state |= PST_FUNSUBST;
/* Don't expand aliases on this pass at all. Either parse_comsub() does it
at parse time, in which case this string already has aliases expanded,
local_extglob = extended_glob;
#endif
- token_to_read = DOLPAREN; /* let's trick the parser */
+ if (funsub && FUNSUB_CHAR (*string))
+ string++;
+
+ token_to_read = funsub ? DOLBRACE : DOLPAREN; /* let's trick the parser */
nc = parse_string (string, "command substitution", sflags, (COMMAND **)NULL, &ep);
{
clear_shell_input_line (); /* XXX */
if (bash_input.type != st_string) /* paranoia */
- parser_state &= ~(PST_CMDSUBST|PST_EOFTOKEN);
+ parser_state &= ~(PST_CMDSUBST|PST_EOFTOKEN|PST_FUNSUBST);
if ((flags & SX_NOLONGJMP) == 0)
jump_to_top_level (-nc); /* XXX */
}
and return it. If flags & 1 (SX_NOALLOC) we can return NULL. */
/*(*/
- if (ep[-1] != ')')
+ if (ep[-1] != closer)
{
#if 0
if (ep[-1] != '\n')
itrace("xparse_dolparen:%d: *indp (%d) < orig_ind (%d), orig_string = `%s'", line_number, *indp, orig_ind, ostring);
#endif
- if (base[*indp] != ')' && (flags & SX_NOLONGJMP) == 0)
+ if (base[*indp] != closer && (flags & SX_NOLONGJMP) == 0)
{
/*(*/
if ((flags & SX_NOERROR) == 0)
}
}
+ /* Just an awful special case. We want '}' to delimit the foreground
+ command substitution construct ${Ccommand; } but since it's a word
+ expansion, we need to join it with any potential following
+ characters. We fake things out here and treat a word beginning with
+ a close brace as the '}' reserved word, treat it as a separate
+ token, terminate the command substitution, and go on reading
+ characters into the same upper-layer token. */
+ if ((parser_state & PST_FUNSUBST) && token_index == 0 && quoted == 0 &&
+ reserved_word_acceptable (last_read_token) &&
+ MBTEST(character == '}'))
+ {
+ RESIZE_MALLOCED_BUFFER (token, token_index, 2,
+ token_buffer_size, TOKEN_DEFAULT_GROW_SIZE);
+ token[token_index++] = character;
+ all_digit_token = dollar_present = 0;
+ goto got_token;
+ }
+
/* Parse a matched pair of quote characters. */
if MBTEST(shellquote (character))
{
((peek_char == '{' || peek_char == '[') && character == '$')) /* ) ] } */
{
if (peek_char == '{') /* } */
- ttok = parse_matched_pair (cd, '{', '}', &ttoklen, P_FIRSTCLOSE|P_DOLBRACE);
+ {
+ int npeek;
+ npeek = shell_getc (1);
+ shell_ungetc (npeek);
+ if (FUNSUB_CHAR (npeek))
+ {
+ push_delimiter (dstack, peek_char);
+ ttok = parse_comsub (cd, '{', '}', &ttoklen, P_COMMAND);
+ pop_delimiter (dstack);
+ }
+ else
+ ttok = parse_matched_pair (cd, '{', '}', &ttoklen, P_FIRSTCLOSE|P_DOLBRACE);
+ }
else if (peek_char == '(') /* ) */
{
/* XXX - push and pop the `(' as a delimiter for use by
case WHILE:
case 0:
case DOLPAREN:
+ case DOLBRACE:
return 1;
default:
#if defined (COPROCESS_SUPPORT)
static const int no_semi_successors[] = {
'\n', '{', '(', ')', ';', '&', '|',
CASE, DO, ELSE, IF, SEMI_SEMI, SEMI_AND, SEMI_SEMI_AND, THEN, UNTIL,
- WHILE, AND_AND, OR_OR, IN, DOLPAREN,
+ WHILE, AND_AND, OR_OR, IN, DOLPAREN, DOLBRACE,
0
};
#define PST_NOERROR 0x800000 /* don't print error messages in yyerror */
#define PST_STRING 0x1000000 /* parsing a string to a command or word list */
#define PST_CMDBLTIN 0x2000000 /* last token was the `command' builtin */
+#define PST_FUNSUBST 0x4000000 /* parsing a foreground command substitution */
/* Definition of the delimiter stack. Needed by parse.y and bashhist.c. */
struct dstack {
#define DOLBRACE_QUOTE 0x40 /* single quote is special in double quotes */
#define DOLBRACE_QUOTE2 0x80 /* single quote is semi-special in double quotes */
+/* characters that can appear following ${ to introduce a function or value
+ substitution (this is mksh terminology and needs to be changed). */
+#define FUNSUB_CHAR(n) ((n) == ' ' || (n) == '\t' || (n) == '\n' || (n) == '|')
+
/* variable declarations from parse.y */
extern struct dstack dstack;
si = i + 2;
if (string[i + 1] == LPAREN)
ret = extract_command_subst (string, &si, (flags & SX_COMPLETE));
+ else if (string[i + 1] == LBRACE && FUNSUB_CHAR (string[si]))
+ ret = extract_function_subst (string, &si, Q_DOUBLE_QUOTES, (flags & SX_COMPLETE));
else
ret = extract_dollar_brace_string (string, &si, Q_DOUBLE_QUOTES, 0);
si = i + 2;
if (string[i + 1] == LPAREN)
ret = extract_command_subst (string, &si, SX_NOALLOC|(flags&SX_COMPLETE));
+ else if (string[i + 1] == LBRACE && FUNSUB_CHAR (string[si]))
+ ret = extract_function_subst (string, &si, Q_DOUBLE_QUOTES, (flags & SX_COMPLETE));
else
ret = extract_dollar_brace_string (string, &si, Q_DOUBLE_QUOTES, SX_NOALLOC);
}
}
+/* Take a ${Ccommand} where C is a character that introduces a function
+ substitution and extract the string. */
+char *
+extract_function_subst (const char *string, int *sindex, int quoted, int xflags)
+{
+ char *ret;
+ char *xstr;
+
+ if (string[*sindex] == LBRACE || (xflags & SX_COMPLETE))
+ return (extract_dollar_brace_string (string, sindex, quoted, xflags|SX_COMMAND));
+ else
+ {
+ xflags |= (no_longjmp_on_fatal_error ? SX_NOLONGJMP : 0);
+ xstr = (char *)string + *sindex;
+ ret = xparse_dolparen (string, xstr, sindex, xflags|SX_FUNSUB);
+ return ret;
+ }
+}
+
/* Extract the $[ construct in STRING, and return a new string. (])
Start extracting at (SINDEX) as if we had just seen "$[".
Make (SINDEX) get the position of the matching "]". */
continue;
}
+ /* Process alternate form of nested command substitution. */
+ if ((flags & SX_COMMAND) && string[i] == '$' && string[i+1] == LBRACE && FUNSUB_CHAR (string[i+2]))
+ {
+ si = i + 2;
+ t = extract_function_subst (string, &si, 0, flags|SX_NOALLOC);
+ CHECK_STRING_OVERRUN (i, si, slen, c);
+ i = si + 1;
+ continue;
+ }
+
/* Process a nested OPENER. */
if (STREQN (string + i, opener, len_opener))
{
/* Pass the contents of new-style command substitutions and
arithmetic substitutions through verbatim. */
- if (string[i] == '$' && string[i+1] == LPAREN)
+ if (string[i] == '$' && (string[i+1] == LPAREN || (string[i+1] == LBRACE && FUNSUB_CHAR (string[i+2]))))
{
+ int open;
+
si = i + 2;
- t = extract_command_subst (string, &si, flags);
+ open = string[i+1];
+ if (open == LPAREN)
+ t = extract_command_subst (string, &si, flags);
+ else
+ t = extract_function_subst (string, &si, quoted, flags);
CHECK_STRING_OVERRUN (i, si, slen, c);
tlen = si - i - 2;
RESIZE_MALLOCED_BUFFER (result, result_index, tlen + 4, result_size, 64);
result[result_index++] = c;
- result[result_index++] = LPAREN;
+ result[result_index++] = open;
strncpy (result + result_index, t, tlen);
result_index += tlen;
result[result_index++] = string[si];
continue;
}
+ /* Pass the contents of foreground command substitutions (funsub/valsub)
+ through verbatim. */
+ if (string[i] == '$' && string[i+1] == LBRACE && FUNSUB_CHAR (string[i+2]))
+ {
+ si = i + 2;
+ t = extract_function_subst (string, &si, quoted, flags|SX_NOALLOC);
+
+ CHECK_STRING_OVERRUN (i, si, slen, c);
+
+ i = si + 1;
+ continue;
+ }
+
#if defined (PROCESS_SUBSTITUTION)
/* Technically this should only work at the start of a word */
if ((string[i] == '<' || string[i] == '>') && string[i+1] == LPAREN)
if (string[si] == '\0')
CQ_RETURN(si);
- /* XXX - extract_command_subst here? */
+ /* XXX - extract_command_subst/extract_function_subst here? */
if (string[i+1] == LPAREN)
temp = extract_delimited_string (string, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND); /* ) */
else
return istring;
}
+WORD_DESC *
+function_substitute (char *string, int quoted, int flags)
+{
+ pid_t old_pipeline_pgrp;
+ char *istring, *s;
+ int fd;
+ int result, fildes[2], function_value, pflags, rc, tflag, fork_flags;
+ int valsub;
+ WORD_DESC *ret;
+ sigset_t set, oset;
+
+ istring = (char *)NULL;
+
+ /* In the case of no command to run, just return NULL. */
+ for (s = string; s && *s && (shellblank (*s) || *s == '\n'); s++)
+ ;
+ if (s == 0 || *s == 0)
+ return ((WORD_DESC *)NULL);
+
+ if (valsub = (*string == '|'))
+ string++;
+
+ /* Flags to pass to parse_and_execute() */
+ pflags = (interactive && sourcelevel == 0) ? SEVAL_RESETLINE : 0;
+
+#if defined (JOB_CONTROL)
+ old_pipeline_pgrp = pipeline_pgrp;
+ /* Don't reset the pipeline pgrp if we're already a subshell in a pipeline or
+ we've already forked to run a disk command (and are expanding redirections,
+ for example). */
+ if ((subshell_environment & (SUBSHELL_FORK|SUBSHELL_PIPE)) == 0)
+ pipeline_pgrp = shell_pgrp;
+ save_pipeline (1);
+ stop_making_children ();
+#endif /* JOB_CONTROL */
+
+ last_command_exit_value = EXECUTION_FAILURE;
+ report_error ("bad substitution: %s", string);
+
+ pipeline_pgrp = old_pipeline_pgrp;
+ restore_pipeline (1);
+
+ /* exp_jump_to_top_level (DISCARD); */
+ return ((WORD_DESC *)NULL);
+}
+
/* Perform command substitution on STRING. This returns a WORD_DESC * with the
contained string possibly quoted. */
WORD_DESC *
break;
case LBRACE:
+ /* Foreground command substitution. */
+ if (FUNSUB_CHAR (string[zindex + 1]))
+ {
+ /* We have to extract the contents of this command substitution. */
+ t_index = zindex + 1;
+ temp = extract_function_subst (string, &t_index, quoted, (pflags&PF_COMPLETE) ? SX_COMPLETE : 0);
+ zindex = t_index;
+
+ /* This is basically the same as the comsub code. */
+ if (pflags & PF_NOCOMSUB)
+ /* we need zindex+1 because string[zindex] == RPAREN */
+ temp1 = substring (string, *sindex, zindex+1);
+ else
+ {
+ tdesc = function_substitute (temp, quoted, pflags&PF_ASSIGNRHS);
+ temp1 = tdesc ? tdesc->word : (char *)NULL;
+ if (tdesc)
+ dispose_word_desc (tdesc);
+ }
+ FREE (temp);
+ temp = temp1;
+ break;
+ }
+
tdesc = parameter_brace_expand (string, &zindex, quoted, pflags,
quoted_dollar_at_p,
contains_dollar_at);
#define SX_NOCTLESC 0x0010 /* don't honor CTLESC quoting */
#define SX_NOESCCTLNUL 0x0020 /* don't let CTLESC quote CTLNUL */
#define SX_NOLONGJMP 0x0040 /* don't longjmp on fatal error */
-#define SX_ARITHSUB 0x0080 /* extracting $(( ... )) (currently unused) */
+#define SX_FUNSUB 0x0080 /* extracting ${ command; }; passed to xparse_dolparen */
#define SX_POSIXEXP 0x0100 /* extracting new Posix pattern removal expansions in extract_dollar_brace_string */
#define SX_WORD 0x0200 /* extracting word in ${param op word} */
#define SX_COMPLETE 0x0400 /* extracting word for completion */
XFLAGS is additional flags to pass to other extraction functions, */
extern char *extract_command_subst (const char *, int *, int);
+/* Placeholder */
+extern char *extract_function_subst (const char *, int *, int, int);
+
/* Extract the $[ construct in STRING, and return a new string.
Start extracting at (SINDEX) as if we had just seen "$[".
Make (SINDEX) get the position just after the matching "]". */
extern WORD_LIST *expand_words_shellexp (WORD_LIST *);
extern WORD_DESC *command_substitute (char *, int, int);
+extern WORD_DESC *function_substitute (char *, int, int);
extern char *pat_subst (char *, char *, char *, int);
#if defined (PROCESS_SUBSTITUTION)
--- /dev/null
+FROM alpine:3.17
+RUN apk add --no-cache bison coreutils gcc libc-dev make
+ncurses-dev libncurses5-dev libncursesw5-dev
+WORKDIR /tmp/bash
+COPY . .
+RUN ./configure --enable-readline --with-curses
+RUN make
+RUN make tests
env BASH_FUNC_x%%=$'() { _;}>_[$($())]\n{ echo vuln;}' ${THIS_SH} -c : 2>/dev/null
eval 'x() { _;}>_[$($())] { echo vuln;}' 2>/dev/null
+echo this will fail now that '${ ' has syntactic meaning
+
eval 'foo() { _; } >_[${ $() }] ;{ echo eval ok; }'
# other tests fixed in bash43-030 concerning function name transformation