read this new ${...} string
- extract_heredoc_dolbrace_string: new function, variant of
extract_dollar_brace_string, to process the WORD in ${PARAM OP WORD}
- while processing here-document data. It's complicated by the
+ while expanding lines of here-document data. It's complicated by the
requirement to add to the result string as we go along, since we
need to change the contents of the input string with ansi expansion
or locale translation.
- string_extract_single_quoted: take a new third argument: ALLOWESC.
This allows backslash to escape an embedded single quote, needed by
extract_heredoc_dolbrace_string to process $'...'; changed callers
+
+ 1/25
+ ----
+parse.y
+ - parse_matched_pair: ansi-expand $'...' in WORD for ${PARAM OP WORD}
+ and single-quote the result if dolbrace_state == DOLBRACE_QUOTE
+ (posix pattern removal operators) even if extended_quote == 0
+
+subst.c
+ - extract_heredoc_dolbrace_string: add logic to align with parse.y:
+ parse_matched_pair and its $'...' expansion, including handling
+ extended_quote
+
+ 1/27
+ ----
+builtins/evalstring.c
+ - should_optimize_fork: broke conditions for optimizing away the fork
+ for a simple command out of optimize_fork into new function, call
+ from should_suppress_fork and optimize_subshell_command. Call from
+ optimize_fork if (subshell_environment & SUBSHELL_PAREN), relying
+ on fact that CMD_TRY_OPTIMIZING is only set in a couple of specific
+ conditions
+ - optimize_fork: call should_suppress_fork only if startup_state == 2;
+ it does the extra checks for that specific case
+ - optimize_fork: call should_optimize_fork if we're in a (list)
+ subshell (subshell_environment & SUBSHELL_PAREN)
+ - optimize_subshell_command: set CMD_TRY_OPTIMIZING on the right side
+ of a `&&', `||', or `;' list as long as it's a simple command so
+ we can check with optimize_fork() when it's time to execute it
+
+execute_cmd.c
+ - execute_in_subshell: call optimize_subshell_command for (list)
+ subshells to either set CMD_NO_FORK for simple commands or set
+ CMD_TRY_OPTIMIZING for likely candidates for later optimization
+
+builtins/common.h,builtins/evalstring.c
+ - optimize_fork: renamed to optimize_connection_fork; changed callers
+
lib/sh/strtoull.c f
lib/sh/strtoumax.c f
lib/sh/strtrans.c f
+lib/sh/strvis.c f
lib/sh/timers.c f
lib/sh/times.c f
lib/sh/timeval.c f
${SH_LIBSRC}/wcswidth.c ${SH_LIBSRC}/wcsnwidth.c \
${SH_LIBSRC}/shmbchar.c ${SH_LIBSRC}/utf8.c \
${SH_LIBSRC}/random.c ${SH_LIBSRC}/gettimeofday.c \
- ${SH_LIBSRC}/timers.c
+ ${SH_LIBSRC}/timers.c ${SH_LIBSRC}/strvis.c
SHLIB_LIB = -lsh
SHLIB_LIBNAME = libsh.a
/* common.h -- extern declarations for functions defined in common.c. */
-/* Copyright (C) 1993-2021 Free Software Foundation, Inc.
+/* Copyright (C) 1993-2022 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
extern int should_suppress_fork PARAMS((COMMAND *));
extern int can_optimize_connection PARAMS((COMMAND *));
extern int can_optimize_cat_file PARAMS((COMMAND *));
-extern void optimize_fork PARAMS((COMMAND *));
+extern void optimize_connection_fork PARAMS((COMMAND *));
extern void optimize_subshell_command PARAMS((COMMAND *));
extern void optimize_shell_function PARAMS((COMMAND *));
/* evalstring.c - evaluate a string as one or more shell commands. */
-/* Copyright (C) 1996-2021 Free Software Foundation, Inc.
+/* Copyright (C) 1996-2022 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
the_printed_command_except_trap = x;
}
+int
+should_optimize_fork (command, subshell)
+ COMMAND *command;
+ int subshell;
+{
+ return (running_trap == 0 &&
+ command->type == cm_simple &&
+ signal_is_trapped (EXIT_TRAP) == 0 &&
+ signal_is_trapped (ERROR_TRAP) == 0 &&
+ any_signals_trapped () < 0 &&
+ (subshell || (command->redirects == 0 && command->value.Simple->redirects == 0)) &&
+ ((command->flags & CMD_TIME_PIPELINE) == 0) &&
+ ((command->flags & CMD_INVERT_RETURN) == 0));
+}
+
+/* This has extra tests to account for STARTUP_STATE == 2, which is for
+ -c command but has been extended to command and process substitution
+ (basically any time you call parse_and_execute in a subshell). */
int
should_suppress_fork (command)
COMMAND *command;
return (startup_state == 2 && parse_and_execute_level == 1 &&
*bash_input.location.string == '\0' &&
parser_expanding_alias () == 0 &&
- running_trap == 0 &&
- command->type == cm_simple &&
- signal_is_trapped (EXIT_TRAP) == 0 &&
- signal_is_trapped (ERROR_TRAP) == 0 &&
- any_signals_trapped () < 0 &&
- (subshell || (command->redirects == 0 && command->value.Simple->redirects == 0)) &&
- ((command->flags & CMD_TIME_PIPELINE) == 0) &&
- ((command->flags & CMD_INVERT_RETURN) == 0));
+ should_optimize_fork (command, subshell));
}
int
}
void
-optimize_fork (command)
+optimize_connection_fork (command)
COMMAND *command;
{
if (command->type == cm_connection &&
(command->value.Connection->connector == AND_AND || command->value.Connection->connector == OR_OR || command->value.Connection->connector == ';') &&
(command->value.Connection->second->flags & CMD_TRY_OPTIMIZING) &&
- should_suppress_fork (command->value.Connection->second))
+ ((startup_state == 2 && should_suppress_fork (command->value.Connection->second)) ||
+ ((subshell_environment & SUBSHELL_PAREN) && should_optimize_fork (command->value.Connection->second, 0))))
{
command->value.Connection->second->flags |= CMD_NO_FORK;
command->value.Connection->second->value.Simple->flags |= CMD_NO_FORK;
optimize_subshell_command (command)
COMMAND *command;
{
- if (running_trap == 0 &&
- command->type == cm_simple &&
- signal_is_trapped (EXIT_TRAP) == 0 &&
- signal_is_trapped (ERROR_TRAP) == 0 &&
- any_signals_trapped () < 0 &&
- command->redirects == 0 && command->value.Simple->redirects == 0 &&
- ((command->flags & CMD_TIME_PIPELINE) == 0) &&
- ((command->flags & CMD_INVERT_RETURN) == 0))
+ if (should_optimize_fork (command, 0))
{
command->flags |= CMD_NO_FORK;
command->value.Simple->flags |= CMD_NO_FORK;
}
else if (command->type == cm_connection &&
- (command->value.Connection->connector == AND_AND || command->value.Connection->connector == OR_OR))
- optimize_subshell_command (command->value.Connection->second);
+ (command->value.Connection->connector == AND_AND || command->value.Connection->connector == OR_OR || command->value.Connection->connector == ';') &&
+ command->value.Connection->second->type == cm_simple &&
+ parser_expanding_alias () == 0)
+ {
+ command->value.Connection->second->flags |= CMD_TRY_OPTIMIZING;
+ command->value.Connection->second->value.Simple->flags |= CMD_TRY_OPTIMIZING;
+ }
}
void
/* execute_cmd.c -- Execute a COMMAND structure. */
-/* Copyright (C) 1987-2021 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2022 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
default_buffered_input = -1;
#endif
-#if 0
- /* We can't optimize if one of the commands executed by the subshell sets
- an exit trap. */
+ /* We can't optimize away forks if one of the commands executed by the
+ subshell sets an exit trap, so we set CMD_NO_FORK for simple commands
+ and set CMD_TRY_OPTIMIZING for simple commands on the right side of an
+ and-or or `;' list to test for optimizing forks when they are executed. */
if (user_subshell && command->type == cm_subshell)
optimize_subshell_command (command->value.Subshell->command);
-#endif
/* Do redirections, then dispose of them before recursive call. */
if (command->redirects)
#endif
QUIT;
- optimize_fork (command); /* XXX */
+ optimize_connection_fork (command); /* XXX */
exec_result = execute_command_internal (command->value.Connection->second,
asynchronous, pipe_in, pipe_out,
fds_to_close);
((command->value.Connection->connector == OR_OR) &&
(exec_result != EXECUTION_SUCCESS)))
{
- optimize_fork (command);
+ optimize_connection_fork (command);
second = command->value.Connection->second;
if (ignore_return && second)
extern int ansic_shouldquote PARAMS((const char *));
extern char *ansiexpand PARAMS((char *, int, int, int *));
+/* declarations for functions defined in lib/sh/strvis.c */
+extern int charvis PARAMS((const char *, size_t *, char *, size_t *));
+extern char *sh_strvis PARAMS((const char *));
+
/* declarations for functions defined in lib/sh/timeval.c. No prototypes
so we don't have to count on having a definition of struct timeval in
scope when this file is included. */
#endif
#ifndef UNCTRL
/* control char to letter -- ASCII */
-# define UNCTRL(x) (TOUPPER(x) ^ 0x40)
+# define UNCTRL(x) (TOUPPER(x ^ 0x40))
#endif
#endif /* _SH_CHARTYPES_H */
@deftypefun int rl_timeout_remaining (unsigned int *secs, unsigned int *usecs)
Return the number of seconds and microseconds remaining in the current
-timeout duration in @code{*secs} and @code{*usecs}, respectively.
-Returns -1 on error or when there is no timeout set, 0 when the timeout has
-expired (leaving @code{*secs} and @code{*usecs} unchanged), and 1 if the
-timeout has not expired. If @code{secs} and @code{usecs} are @code{NULL},
+timeout duration in @var{*secs} and @var{*usecs}, respectively.
+Both @var{*secs} and @var{*usecs} must be non-NULL to return any values.
+The return value is -1 on error or when there is no timeout set,
+0 when the timeout has expired (leaving @var{*secs} and @var{*usecs}
+unchanged),
+and 1 if the timeout has not expired.
+If either of @var{secs} and @var{usecs} is @code{NULL},
the return value indicates whether the timeout has expired.
@end deftypefun
/* Let's not step on anyone else's define for now, since we don't use this yet. */
#ifndef HS_HISTORY_VERSION
-# define HS_HISTORY_VERSION 0x0801 /* History 8.1 */
+# define HS_HISTORY_VERSION 0x0802 /* History 8.2 */
#endif
/* The structure used to store a history entry. */
#endif
/* Hex-encoded Readline version number. */
-#define RL_READLINE_VERSION 0x0801 /* Readline 8.1 */
+#define RL_READLINE_VERSION 0x0802 /* Readline 8.2 */
#define RL_VERSION_MAJOR 8
-#define RL_VERSION_MINOR 1
+#define RL_VERSION_MINOR 2
/* Readline data structures. */
# Makefile for the Bash library
#
#
-# Copyright (C) 1998-2020 Free Software Foundation, Inc.
+# Copyright (C) 1998-2022 Free Software Foundation, Inc.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
wcsdup.c fpurge.c zgetline.c mbscmp.c uconvert.c ufuncs.c \
casemod.c dprintf.c input_avail.c mbscasecmp.c fnxform.c \
strchrnul.c unicode.c wcswidth.c wcsnwidth.c shmbchar.c strdup.c \
- utf8.c random.c gettimeofday.c timers.c
+ strvis.c utf8.c random.c gettimeofday.c timers.c
# The header files for this library.
HSOURCES =
strtrans.o snprintf.o mailstat.o fmtulong.o \
fmtullong.o fmtumax.o zcatfd.o zmapfd.o winsize.o wcsdup.o \
fpurge.o zgetline.o mbscmp.o uconvert.o ufuncs.o casemod.o \
- input_avail.o mbscasecmp.o fnxform.o unicode.o shmbchar.o \
+ input_avail.o mbscasecmp.o fnxform.o unicode.o shmbchar.o strvis.o \
utf8.o random.o gettimeofday.o timers.o wcsnwidth.o ${LIBOBJS}
SUPPORT = Makefile
strtoull.o: strtoull.c
strtoumax.o: strtoumax.c
strtrans.o: strtrans.c
+strvis.o: strvis.c
timers.o: timers.c
times.o: times.c
timeval.o: timeval.c
strtoull.o: ${BUILD_DIR}/config.h
strtoumax.o: ${BUILD_DIR}/config.h
strtrans.o: ${BUILD_DIR}/config.h
+strvis.o: ${BUILD_DIR}/config.h
timers.o: ${BUILD_DIR}/config.h
times.o: ${BUILD_DIR}/config.h
timeval.o: ${BUILD_DIR}/config.h
strtrans.o: ${BASHINCDIR}/shmbutil.h ${BASHINCDIR}/shmbchar.h
#strtrans.o: ${BUILD_DIR}/version.h
+strvis.o: ${topdir}/bashansi.h
+strvis.o: ${BASHINCDIR}/ansi_stdlib.h ${BASHINCDIR}/chartypes.h
+strvis.o: ${BASHINCDIR}/shmbutil.h ${BASHINCDIR}/shmbchar.h
+strvis.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+
times.o: ${BASHINCDIR}/systimes.h
times.o: ${BASHINCDIR}/posixtime.h
You should have received a copy of the GNU General Public License
along with Bash. If not, see <http://www.gnu.org/licenses/>.
*/
-
#include <config.h>
--- /dev/null
+/* strvis.c - make unsafe graphical characters in a string visible. */
+
+/* Copyright (C) 2022 Free Software Foundation, Inc.
+
+ This file is part of GNU Bash, the Bourne Again SHell.
+
+ Bash is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bash is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bash. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* This is a stripped-down version suitable for the shell's use. */
+#include <config.h>
+
+#include <unistd.h>
+
+#include "bashansi.h"
+#include <stdio.h>
+
+#include "chartypes.h"
+#include "bashintl.h"
+#include "shmbutil.h"
+
+#define SAFECHAR(c) ((c) == ' ' || (c) == '\t')
+
+#ifndef RUBOUT
+#define RUBOUT 0x7f
+#endif
+
+#ifndef CTRL_CHAR
+#define CTRL_CHAR(c) ((c) < 0x20)
+#endif
+
+#ifndef META_CHAR
+#define META_CHAR(c) ((c) > 0x7f && (c) <= UCHAR_MAX)
+#endif
+
+#ifndef UNCTRL
+#define UNCTRL(c) (TOUPPER ((c) | 0x40))
+#endif
+
+#ifndef UNMETA
+#define UNMETA(c) ((c) & 0x7f)
+#endif
+
+static int
+charvis (s, sindp, ret, rindp)
+ const char *s;
+ size_t *sindp;
+ char *ret;
+ size_t *rindp;
+{
+ unsigned char c;
+ size_t si, ri;
+ const char *send;
+ DECLARE_MBSTATE;
+
+ si = *sindp;
+ ri = *rindp;
+ c = s[*sindp];
+
+ send = (locale_mb_cur_max > 1) ? s + strlen (s) : 0;
+
+ if (SAFECHAR (c))
+ {
+ ret[ri++] = c;
+ si++;
+ }
+ else if (c == RUBOUT)
+ {
+ ret[ri++] = '^';
+ ret[ri++] = '?';
+ si++;
+ }
+ else if (CTRL_CHAR (c))
+ {
+ ret[ri++] = '^';
+ ret[ri++] = UNCTRL (c);
+ si++;
+ }
+ else if (locale_mb_cur_max > 1)
+ COPY_CHAR_I (ret, ri, s, send, si);
+ else if (META_CHAR (c))
+ {
+ ret[ri++] = 'M';
+ ret[ri++] = '-';
+ ret[ri++] = UNMETA (c);
+ si++;
+ }
+ else
+ ret[ri++] = s[si++];
+
+ *sindp = si;
+ *rindp = ri;
+
+ return si;
+}
+
+/* Return a new string with `unsafe' non-graphical characters in S rendered
+ in a visible way. */
+char *
+sh_strvis (string)
+ const char *string;
+{
+ size_t slen, sind;
+ char *ret;
+ size_t retind, retsize;
+ unsigned char c;
+ DECLARE_MBSTATE;
+
+ if (string == 0)
+ return 0;
+ if (*string == '\0')
+ {
+ if ((ret = (char *)malloc (1)) == 0)
+ return 0;
+ ret[0] = '\0';
+ return ret;
+ }
+
+ slen = strlen (string);
+ retsize = 3 * slen + 1;
+
+ ret = (char *)malloc (retsize);
+ if (ret == 0)
+ return 0;
+
+ retind = 0;
+ sind = 0;
+
+ while (string[sind])
+ sind = charvis (string, &sind, ret, &retind);
+
+ ret[retind] = '\0';
+ return ret;
+}
pop_delimiter (dstack);
CHECK_NESTRET_ERROR ();
- if MBTEST((tflags & LEX_WASDOL) && ch == '\'' && (extended_quote || (rflags & P_DQUOTE) == 0))
+ if MBTEST((tflags & LEX_WASDOL) && ch == '\'' && (extended_quote || (rflags & P_DQUOTE) == 0 || dolbrace_state == DOLBRACE_QUOTE || dolbrace_state == DOLBRACE_QUOTE2))
{
/* Translate $'...' here. */
/* PST_NOEXPAND */
make sure we single-quote the results of the ansi
expansion because quote removal should remove them later */
/* FLAG POSIX INTERP 221 */
- if ((shell_compatibility_level > 42) && (rflags & P_DQUOTE) && (dolbrace_state == DOLBRACE_QUOTE2) && (flags & P_DOLBRACE))
+ if ((shell_compatibility_level > 42) && (rflags & P_DQUOTE) && (dolbrace_state == DOLBRACE_QUOTE2 || dolbrace_state == DOLBRACE_QUOTE) && (flags & P_DOLBRACE))
{
nestret = sh_single_quote (ttrans);
free (ttrans);
nestlen = strlen (nestret);
}
+#if 0 /* TAG:bash-5.3 */
+ /* This single-quotes PARAM in ${PARAM OP WORD} when PARAM
+ contains a $'...' even when extended_quote is set. */
+ else if ((rflags & P_DQUOTE) && (dolbrace_state == DOLBRACE_PARAM) && (flags & P_DOLBRACE))
+ {
+ nestret = sh_single_quote (ttrans);
+ free (ttrans);
+ nestlen = strlen (nestret);
+ }
+#endif
else if ((rflags & P_DQUOTE) == 0)
{
nestret = sh_single_quote (ttrans);
extern struct fd_bitmap *current_fds_to_close;
extern int wordexp_only;
extern int singlequote_translations;
+extern int extended_quote;
#if defined (JOB_CONTROL) && defined (PROCESS_SUBSTITUTION)
extern PROCESS *last_procsub_child;
path doesn't. It's separate because we don't want to mess with the fast
common path. We already know we're going to allocate and return a new
string and quoted == Q_HERE_DOCUMENT. We might be able to cut it down
- some more, but extracting strings and adding them as we go adds complexity. */
+ some more, but extracting strings and adding them as we go adds complexity.
+ This needs to match the logic in parse.y:parse_matched_pair so we get
+ consistent behavior between here-documents and double-quoted strings. */
static char *
extract_heredoc_dolbrace_string (string, sindex, quoted, flags)
char *string;
char *ttrans;
int ttranslen;
+ if ((posixly_correct || extended_quote == 0) && dolbrace_state != DOLBRACE_QUOTE && dolbrace_state != DOLBRACE_QUOTE2)
+ {
+ RESIZE_MALLOCED_BUFFER (result, result_index, 3, result_size, 64);
+ result[result_index++] = '$';
+ result[result_index++] = '\'';
+ i += 2;
+ continue;
+ }
+
si = i + 2;
t = string_extract_single_quoted (string, &si, 1); /* XXX */
CHECK_STRING_OVERRUN (i, si, slen, c);
free (t);
/* needed to correctly quote any embedded single quotes. */
- t = sh_single_quote (ttrans);
- tlen = strlen (t);
- free (ttrans);
+ if (dolbrace_state == DOLBRACE_QUOTE || dolbrace_state == DOLBRACE_QUOTE2)
+ {
+ t = sh_single_quote (ttrans);
+ tlen = strlen (t);
+ free (ttrans);
+ }
+ else if (extended_quote) /* dolbrace_state == DOLBRACE_PARAM */
+ {
+ /* This matches what parse.y:parse_matched_pair() does */
+ t = ttrans;
+ tlen = strlen (t);
+ }
RESIZE_MALLOCED_BUFFER (result, result_index, tlen + 1, result_size, 64);
strncpy (result + result_index, t, tlen);
result[result_index++] = c;
result[result_index++] = string[i+1];
i += 2;
- if (dolbrace_state == DOLBRACE_QUOTE || dolbrace_state == DOLBRACE_WORD)
+ if (dolbrace_state == DOLBRACE_QUOTE || dolbrace_state == DOLBRACE_QUOTE2 || dolbrace_state == DOLBRACE_WORD)
dolbrace_state = DOLBRACE_PARAM;
continue;
}
x
y
z
+=====
+WORKS
+done
+WORKS
+a
+b
+c
+d
+a
+b
+c
+d
+e
+A
+B
+c
+d
+c
+d
+e
+x
+y
+z
+WORKS
+w
+x
+y
+z
$THIS_SH -c 'trap "echo WORKS" EXIT ; $binecho x ; $binecho y ; $binecho z'
${THIS_SH} -c 'echo w ; { echo x ; $binecho y; }; $binecho z'
+
+echo =====
+
+( trap "echo WORKS && rm $TMPDIR/x$$" EXIT && touch $TMPDIR/x$$ )
+( trap "echo WORKS && rm $TMPDIR/x$$" EXIT && touch $TMPDIR/x$$ ; $binecho done )
+
+( echo a && { $binecho b && $binecho c ; } && echo d )
+( echo a && { $binecho b && $binecho c ; } && echo d ; $binecho e )
+
+( echo A && $binecho B )
+( $binecho c && echo d )
+
+( $binecho c && $binecho d && echo e )
+
+( trap "echo WORKS" EXIT ; $binecho x ; $binecho y ; $binecho z )
+
+( echo w ; { echo x ; $binecho y; }; $binecho z )
[ abc def ghi jkl / abc def ghi jkl ]
[ abc def ghi jkl ]
[ abc def ghi jkl / abc def ghi jkl / abc def ghi jkl ]
-5: OK
+1: OK
+2: $'not'
+3: OK
+4: OK
+5: tOK
OK
OK
-5: $'not\ttoo\nbad'
+$'not'
+OK
+tOK
+6: $'not\ttoo\nbad'
+OKa ' b
+OKa ' b
+7: OK
+8: OKa ' b
+9: OKa " b
+10: OKa " b
+tOK
+tOK
+tOK
+tOK
+./posixexp7.sub: line 69: ${'x1'%'t'}: bad substitution
+./posixexp7.sub: line 70: ${'x1'%'t'}: bad substitution
+./posixexp7.sub: line 73: ${'x1'%'t'}: bad substitution
+./posixexp7.sub: line 74: ${'x1'%'t'}: bad substitution
"A"
A
argv[1] = <"A">
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
# test the effect of quotes on the WORD in the posix pattern removal operators
-# a here document does not behave the same as double quotes
+#
x=notOK
+x1=not
+
cat <<EOF
-5: ${x#$'not'}
+1: ${x#$'not'}
+2: $'not'
+3: ${x#"not"}
+4: ${x#'not'}
+5: ${x#${x1%'t'}}
EOF
echo "${x#'not'}"
echo "${x#$'not'}"
+echo "$'not'"
+echo "${x#"not"}"
+echo "${x#${x1%'t'}}"
+
+cat <<EOF
+6: $'not\ttoo\nbad'
+EOF
+
+x=OK$'a\t\'\tb'
+echo OK$'a\t\'\tb'
+echo "$x"
+
+cat <<EOF
+7: ${x%$'a\t\'\tb'}
+8: ${x#$'a\t\'\tb'}
+EOF
+
+x=OK'a " b'
+
+cat <<EOF
+9: ${x#'a " b'}
+10: ${x#$'a " b'}
+EOF
+
+x=notOK
+x1=not
+
+# extquote makes these work
+echo "${x#${$'x1'%$'t'}}"
+cat <<EOF
+${x#${$'x1'%$'t'}}
+EOF
+echo "${x#${$'x1'%'t'}}"
+cat <<EOF
+${x#${$'x1'%'t'}}
+EOF
+
+# syntax errors
+
+echo "${x#${'x1'%'t'}}"
+cat <<EOF
+${x#${'x1'%'t'}}
+EOF
+echo "${x#${'x1'%$'t'}}"
cat <<EOF
-5: $'not\ttoo\nbad'
+${x#${'x1'%$'t'}}
EOF