]> git.ipfire.org Git - thirdparty/bash.git/blobdiff - subst.c
Bash-4.3 patch 32
[thirdparty/bash.git] / subst.c
diff --git a/subst.c b/subst.c
index 78f217cabb2a045f6444d07ff56f9deddd1ab5a3..9e8447a0c836e9070389c4a28ff0439a0272c0dd 100644 (file)
--- a/subst.c
+++ b/subst.c
@@ -4,7 +4,7 @@
 /* ``Have a little faith, there's magic in the night.  You ain't a
      beauty, but, hey, you're alright.'' */
 
-/* Copyright (C) 1987-2009 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2013 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
@@ -42,6 +42,7 @@
 #include "bashintl.h"
 
 #include "shell.h"
+#include "parser.h"
 #include "flags.h"
 #include "jobs.h"
 #include "execute_cmd.h"
@@ -51,6 +52,7 @@
 #include "mailcheck.h"
 
 #include "shmbutil.h"
+#include "typemax.h"
 
 #include "builtins/getopt.h"
 #include "builtins/common.h"
@@ -86,12 +88,21 @@ extern int errno;
 /* Flags for the `pflags' argument to param_expand() */
 #define PF_NOCOMSUB    0x01    /* Do not perform command substitution */
 #define PF_IGNUNBOUND  0x02    /* ignore unbound vars even if -u set */
+#define PF_NOSPLIT2    0x04    /* same as W_NOSPLIT2 */
+#define PF_ASSIGNRHS   0x08    /* same as W_ASSIGNRHS */
 
 /* These defs make it easier to use the editor. */
 #define LBRACE         '{'
 #define RBRACE         '}'
 #define LPAREN         '('
 #define RPAREN         ')'
+#define LBRACK         '['
+#define RBRACK         ']'
+
+#if defined (HANDLE_MULTIBYTE)
+#define WLPAREN                L'('
+#define WRPAREN                L')'
+#endif
 
 /* Evaluates to 1 if C is one of the shell's special parameters whose length
    can be taken, but is also one of the special expansion characters. */
@@ -101,7 +112,7 @@ extern int errno;
 /* Evaluates to 1 if C is one of the shell's special parameters for which an
    indirect variable reference may be made. */
 #define VALID_INDIR_PARAM(c) \
-  ((c) == '#' || (c) == '?' || (c) == '@' || (c) == '*')
+  ((posixly_correct == 0 && (c) == '#') || (posixly_correct == 0 && (c) == '?') || (c) == '@' || (c) == '*')
 
 /* Evaluates to 1 if C is one of the OP characters that follows the parameter
    in ${parameter[:]OPword}. */
@@ -126,6 +137,7 @@ pid_t current_command_subst_pid = NO_PID;
 SHELL_VAR *ifs_var;
 char *ifs_value;
 unsigned char ifs_cmap[UCHAR_MAX + 1];
+int ifs_is_set, ifs_is_null;
 
 #if defined (HANDLE_MULTIBYTE)
 unsigned char ifs_firstc[MB_LEN_MAX];
@@ -134,12 +146,20 @@ size_t ifs_firstc_len;
 unsigned char ifs_firstc;
 #endif
 
+/* Sentinel to tell when we are performing variable assignments preceding a
+   command name and putting them into the environment.  Used to make sure
+   we use the temporary environment when looking up variable values. */
 int assigning_in_environment;
 
+/* Used to hold a list of variable assignments preceding a command.  Global
+   so the SIGCHLD handler in jobs.c can unwind-protect it when it runs a
+   SIGCHLD trap and so it can be saved and restored by the trap handlers. */
+WORD_LIST *subst_assign_varlist = (WORD_LIST *)NULL;
+
 /* Extern functions and variables from different files. */
 extern int last_command_exit_value, last_command_exit_signal;
-extern int subshell_environment;
-extern int subshell_level, parse_and_execute_level;
+extern int subshell_environment, line_number;
+extern int subshell_level, parse_and_execute_level, sourcelevel;
 extern int eof_encountered;
 extern int return_catch_flag, return_catch_value;
 extern pid_t dollar_dollar_pid;
@@ -149,6 +169,7 @@ extern struct fd_bitmap *current_fds_to_close;
 extern int wordexp_only;
 extern int expanding_redir;
 extern int tempenv_assign_error;
+extern int builtin_ignoring_errexit;
 
 #if !defined (HAVE_WCSDUP) && defined (HANDLE_MULTIBYTE)
 extern wchar_t *wcsdup __P((const wchar_t *));
@@ -183,11 +204,6 @@ static int no_longjmp_on_fatal_error = 0;
    $* on $IFS, primarily when doing assignment statements. */
 static int expand_no_split_dollar_star = 0;
 
-/* Used to hold a list of variable assignments preceding a command.  Global
-   so the SIGCHLD handler in jobs.c can unwind-protect it when it runs a
-   SIGCHLD trap. */
-WORD_LIST *subst_assign_varlist = (WORD_LIST *)NULL;
-
 /* A WORD_LIST of words to be expanded by expand_word_list_internal,
    without any leading variable assignments. */
 static WORD_LIST *garglist = (WORD_LIST *)NULL;
@@ -204,6 +220,8 @@ static WORD_LIST *expand_string_leave_quoted __P((char *, int));
 static WORD_LIST *expand_string_for_rhs __P((char *, int, int *, int *));
 
 static WORD_LIST *list_quote_escapes __P((WORD_LIST *));
+static WORD_LIST *list_dequote_escapes __P((WORD_LIST *));
+
 static char *make_quoted_char __P((int));
 static WORD_LIST *quote_list __P((WORD_LIST *));
 
@@ -235,10 +253,8 @@ static wchar_t *remove_wpattern __P((wchar_t *, size_t, wchar_t *, int));
 #endif
 static char *remove_pattern __P((char *, char *, int));
 
-static int match_pattern_char __P((char *, char *));
 static int match_upattern __P((char *, char *, int, char **, char **));
 #if defined (HANDLE_MULTIBYTE)
-static int match_pattern_wchar __P((wchar_t *, wchar_t *));
 static int match_wpattern __P((wchar_t *, char **, size_t, wchar_t *, int, char **, char **));
 #endif
 static int match_pattern __P((char *, char *, int, char **, char **));
@@ -250,7 +266,7 @@ static char *parameter_list_remove_pattern __P((int, char *, int, int));
 #ifdef ARRAY_VARS
 static char *array_remove_pattern __P((SHELL_VAR *, char *, int, char *, int));
 #endif
-static char *parameter_brace_remove_pattern __P((char *, char *, char *, int, int));
+static char *parameter_brace_remove_pattern __P((char *, char *, int, char *, int, int, int));
 
 static char *process_substitute __P((char *, int));
 
@@ -264,7 +280,8 @@ static int valid_brace_expansion_word __P((char *, int));
 static int chk_atstar __P((char *, int, int *, int *));
 static int chk_arithsub __P((const char *, int));
 
-static WORD_DESC *parameter_brace_expand_word __P((char *, int, int, int));
+static WORD_DESC *parameter_brace_expand_word __P((char *, int, int, int, arrayind_t *));
+static char *parameter_brace_find_indir __P((char *, int, int, int));
 static WORD_DESC *parameter_brace_expand_indir __P((char *, int, int, int *, int *));
 static WORD_DESC *parameter_brace_expand_rhs __P((char *, char *, int, int, int *, int *));
 static void parameter_brace_expand_error __P((char *, char *));
@@ -274,18 +291,20 @@ static intmax_t parameter_brace_expand_length __P((char *));
 
 static char *skiparith __P((char *, int));
 static int verify_substring_values __P((SHELL_VAR *, char *, char *, int, intmax_t *, intmax_t *));
-static int get_var_and_type __P((char *, char *, int, SHELL_VAR **, char **));
+static int get_var_and_type __P((char *, char *, arrayind_t, int, int, SHELL_VAR **, char **));
 static char *mb_substring __P((char *, int, int));
-static char *parameter_brace_substring __P((char *, char *, char *, int));
+static char *parameter_brace_substring __P((char *, char *, int, char *, int, int));
+
+static int shouldexp_replacement __P((char *));
 
 static char *pos_params_pat_subst __P((char *, char *, char *, int));
 
-static char *parameter_brace_patsub __P((char *, char *, char *, int));
+static char *parameter_brace_patsub __P((char *, char *, int, char *, int, int));
 
 static char *pos_params_casemod __P((char *, char *, int, int));
-static char *parameter_brace_casemod __P((char *, char *, int, char *, int));
+static char *parameter_brace_casemod __P((char *, char *, int, int, char *, int, int));
 
-static WORD_DESC *parameter_brace_expand __P((char *, int *, int, int *, int *));
+static WORD_DESC *parameter_brace_expand __P((char *, int *, int, int, int *, int *));
 static WORD_DESC *param_expand __P((char *, int *, int, int *, int *, int *, int *, int));
 
 static WORD_LIST *expand_word_internal __P((WORD_DESC *, int, int, int *, int *));
@@ -311,6 +330,155 @@ static WORD_LIST *expand_word_list_internal __P((WORD_LIST *, int));
 /*                                                                 */
 /* **************************************************************** */
 
+#if defined (DEBUG)
+void
+dump_word_flags (flags)
+     int flags;
+{
+  int f;
+
+  f = flags;
+  fprintf (stderr, "%d -> ", f);
+  if (f & W_ASSIGNASSOC)
+    {
+      f &= ~W_ASSIGNASSOC;
+      fprintf (stderr, "W_ASSIGNASSOC%s", f ? "|" : "");
+    }
+  if (f & W_ASSIGNARRAY)
+    {
+      f &= ~W_ASSIGNARRAY;
+      fprintf (stderr, "W_ASSIGNARRAY%s", f ? "|" : "");
+    }
+  if (f & W_HASCTLESC)
+    {
+      f &= ~W_HASCTLESC;
+      fprintf (stderr, "W_HASCTLESC%s", f ? "|" : "");
+    }
+  if (f & W_NOPROCSUB)
+    {
+      f &= ~W_NOPROCSUB;
+      fprintf (stderr, "W_NOPROCSUB%s", f ? "|" : "");
+    }
+  if (f & W_DQUOTE)
+    {
+      f &= ~W_DQUOTE;
+      fprintf (stderr, "W_DQUOTE%s", f ? "|" : "");
+    }
+  if (f & W_HASQUOTEDNULL)
+    {
+      f &= ~W_HASQUOTEDNULL;
+      fprintf (stderr, "W_HASQUOTEDNULL%s", f ? "|" : "");
+    }
+  if (f & W_ASSIGNARG)
+    {
+      f &= ~W_ASSIGNARG;
+      fprintf (stderr, "W_ASSIGNARG%s", f ? "|" : "");
+    }
+  if (f & W_ASSNBLTIN)
+    {
+      f &= ~W_ASSNBLTIN;
+      fprintf (stderr, "W_ASSNBLTIN%s", f ? "|" : "");
+    }
+  if (f & W_ASSNGLOBAL)
+    {
+      f &= ~W_ASSNGLOBAL;
+      fprintf (stderr, "W_ASSNGLOBAL%s", f ? "|" : "");
+    }
+  if (f & W_ASSIGNINT)
+    {
+      f &= ~W_ASSIGNINT;
+      fprintf (stderr, "W_ASSIGNINT%s", f ? "|" : "");
+    }
+  if (f & W_COMPASSIGN)
+    {
+      f &= ~W_COMPASSIGN;
+      fprintf (stderr, "W_COMPASSIGN%s", f ? "|" : "");
+    }
+  if (f & W_NOEXPAND)
+    {
+      f &= ~W_NOEXPAND;
+      fprintf (stderr, "W_NOEXPAND%s", f ? "|" : "");
+    }
+  if (f & W_ITILDE)
+    {
+      f &= ~W_ITILDE;
+      fprintf (stderr, "W_ITILDE%s", f ? "|" : "");
+    }
+  if (f & W_NOTILDE)
+    {
+      f &= ~W_NOTILDE;
+      fprintf (stderr, "W_NOTILDE%s", f ? "|" : "");
+    }
+  if (f & W_ASSIGNRHS)
+    {
+      f &= ~W_ASSIGNRHS;
+      fprintf (stderr, "W_ASSIGNRHS%s", f ? "|" : "");
+    }
+  if (f & W_NOCOMSUB)
+    {
+      f &= ~W_NOCOMSUB;
+      fprintf (stderr, "W_NOCOMSUB%s", f ? "|" : "");
+    }
+  if (f & W_DOLLARSTAR)
+    {
+      f &= ~W_DOLLARSTAR;
+      fprintf (stderr, "W_DOLLARSTAR%s", f ? "|" : "");
+    }
+  if (f & W_DOLLARAT)
+    {
+      f &= ~W_DOLLARAT;
+      fprintf (stderr, "W_DOLLARAT%s", f ? "|" : "");
+    }
+  if (f & W_TILDEEXP)
+    {
+      f &= ~W_TILDEEXP;
+      fprintf (stderr, "W_TILDEEXP%s", f ? "|" : "");
+    }
+  if (f & W_NOSPLIT2)
+    {
+      f &= ~W_NOSPLIT2;
+      fprintf (stderr, "W_NOSPLIT2%s", f ? "|" : "");
+    }
+  if (f & W_NOSPLIT)
+    {
+      f &= ~W_NOSPLIT;
+      fprintf (stderr, "W_NOSPLIT%s", f ? "|" : "");
+    }
+  if (f & W_NOBRACE)
+    {
+      f &= ~W_NOBRACE;
+      fprintf (stderr, "W_NOBRACE%s", f ? "|" : "");
+    }
+  if (f & W_NOGLOB)
+    {
+      f &= ~W_NOGLOB;
+      fprintf (stderr, "W_NOGLOB%s", f ? "|" : "");
+    }
+  if (f & W_SPLITSPACE)
+    {
+      f &= ~W_SPLITSPACE;
+      fprintf (stderr, "W_SPLITSPACE%s", f ? "|" : "");
+    }
+  if (f & W_ASSIGNMENT)
+    {
+      f &= ~W_ASSIGNMENT;
+      fprintf (stderr, "W_ASSIGNMENT%s", f ? "|" : "");
+    }
+  if (f & W_QUOTED)
+    {
+      f &= ~W_QUOTED;
+      fprintf (stderr, "W_QUOTED%s", f ? "|" : "");
+    }
+  if (f & W_HASDOLLAR)
+    {
+      f &= ~W_HASDOLLAR;
+      fprintf (stderr, "W_HASDOLLAR%s", f ? "|" : "");
+    }
+  fprintf (stderr, "\n");
+  fflush (stderr);
+}
+#endif
+
 #ifdef INCLUDE_UNUSED
 static char *
 quoted_substring (string, start, end)
@@ -470,7 +638,6 @@ unquoted_substring (substr, string)
        {
        case '\\':
          sindex++;
-
          if (string[sindex])
            ADVANCE_CHAR (string, slen, sindex);
          break;
@@ -585,7 +752,7 @@ string_extract (string, sindex, charlist, flags)
        {
          int ni;
          /* If this is an array subscript, skip over it and continue. */
-         ni = skipsubscript (string, i);
+         ni = skipsubscript (string, i, 0);
          if (string[ni] == ']')
            i = ni;
        }
@@ -647,6 +814,7 @@ string_extract_double_quoted (string, sindex, stripdq)
       /* Process a character that was quoted by a backslash. */
       if (pass_next)
        {
+         /* XXX - take another look at this in light of Interp 221 */
          /* Posix.2 sez:
 
             ``The backslash shall retain its special meaning as an escape
@@ -720,7 +888,7 @@ add_one_character:
          if (string[i + 1] == LPAREN)
            ret = extract_command_subst (string, &si, 0);
          else
-           ret = extract_dollar_brace_string (string, &si, 1, 0);
+           ret = extract_dollar_brace_string (string, &si, Q_DOUBLE_QUOTES, 0);
 
          temp[j++] = '$';
          temp[j++] = string[i + 1];
@@ -822,7 +990,7 @@ skip_double_quoted (string, slen, sind)
          if (string[i + 1] == LPAREN)
            ret = extract_command_subst (string, &si, SX_NOALLOC);
          else
-           ret = extract_dollar_brace_string (string, &si, 1, SX_NOALLOC);
+           ret = extract_dollar_brace_string (string, &si, Q_DOUBLE_QUOTES, SX_NOALLOC);
 
          i = si + 1;
          continue;
@@ -899,7 +1067,7 @@ string_extract_verbatim (string, slen, sindex, charlist, flags)
      char *charlist;
      int flags;
 {
-  register int i = *sindex;
+  register int i;
 #if defined (HANDLE_MULTIBYTE)
   size_t clen;
   wchar_t *wcharlist;
@@ -992,14 +1160,14 @@ string_extract_verbatim (string, slen, sindex, charlist, flags)
 /* 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 ")". )
-   XFLAGS is additional flags to pass to other extraction functions, */
+   XFLAGS is additional flags to pass to other extraction functions. */
 char *
 extract_command_subst (string, sindex, xflags)
      char *string;
      int *sindex;
      int xflags;
 {
-  if (string[*sindex] == '(')  /*)*/
+  if (string[*sindex] == LPAREN)
     return (extract_delimited_string (string, sindex, "$(", "(", ")", xflags|SX_COMMAND)); /*)*/
   else
     {
@@ -1024,12 +1192,18 @@ extract_arithmetic_subst (string, sindex)
    Start extracting at (SINDEX) as if we had just seen "<(".
    Make (SINDEX) get the position of the matching ")". */ /*))*/
 char *
-extract_process_subst (string, starter, sindex)
+extract_process_subst (string, starter, sindex, xflags)
      char *string;
      char *starter;
      int *sindex;
+     int xflags;
 {
-  return (extract_delimited_string (string, sindex, starter, "(", ")", 0));
+#if 0
+  return (extract_delimited_string (string, sindex, starter, "(", ")", SX_COMMAND));
+#else
+  xflags |= (no_longjmp_on_fatal_error ? SX_NOLONGJMP : 0);
+  return (xparse_dolparen (string, string+*sindex, sindex, xflags));
+#endif
 }
 #endif /* PROCESS_SUBSTITUTION */
 
@@ -1111,7 +1285,7 @@ extract_delimited_string (string, sindex, opener, alt_opener, closer, flags)
        }
 
       /* Not exactly right yet; should handle shell metacharacters and
-        multibyte characters, too. */
+        multibyte characters, too.  See COMMENT_BEGIN define in parse.y */
       if ((flags & SX_COMMAND) && c == '#' && (i == 0 || string[i - 1] == '\n' || shellblank (string[i - 1])))
        {
           in_comment = 1;
@@ -1126,6 +1300,16 @@ extract_delimited_string (string, sindex, opener, alt_opener, closer, flags)
          continue;
        }
 
+      /* Process a nested command substitution, but only if we're parsing an
+        arithmetic substitution. */
+      if ((flags & SX_COMMAND) && string[i] == '$' && string[i+1] == LPAREN)
+        {
+          si = i + 2;
+          t = extract_command_subst (string, &si, flags|SX_NOALLOC);
+          i = si + 1;
+          continue;
+        }
+
       /* Process a nested OPENER. */
       if (STREQN (string + i, opener, len_opener))
        {
@@ -1180,8 +1364,8 @@ extract_delimited_string (string, sindex, opener, alt_opener, closer, flags)
     {
       if (no_longjmp_on_fatal_error == 0)
        {
-         report_error (_("bad substitution: no closing `%s' in %s"), closer, string);
          last_command_exit_value = EXECUTION_FAILURE;
+         report_error (_("bad substitution: no closing `%s' in %s"), closer, string);
          exp_jump_to_top_level (DISCARD);
        }
       else
@@ -1220,7 +1404,7 @@ extract_dollar_brace_string (string, sindex, quoted, flags)
 {
   register int i, c;
   size_t slen;
-  int pass_character, nesting_level, si;
+  int pass_character, nesting_level, si, dolbrace_state;
   char *result, *t;
   DECLARE_MBSTATE;
 
@@ -1228,6 +1412,14 @@ extract_dollar_brace_string (string, sindex, quoted, flags)
   nesting_level = 1;
   slen = strlen (string + *sindex) + *sindex;
 
+  /* The handling of dolbrace_state needs to agree with the code in parse.y:
+     parse_matched_pair().  The different initial value is to handle the
+     case where this function is called to parse the word in
+     ${param op word} (SX_WORD). */
+  dolbrace_state = (flags & SX_WORD) ? DOLBRACE_WORD : DOLBRACE_PARAM;
+  if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && (flags & SX_POSIXEXP))
+    dolbrace_state = DOLBRACE_QUOTE;
+
   i = *sindex;
   while (c = string[i])
     {
@@ -1282,27 +1474,56 @@ extract_dollar_brace_string (string, sindex, quoted, flags)
          continue;
        }
 
-      /* Pass the contents of single-quoted and double-quoted strings
-        through verbatim. */
-      if (c == '\'' || c == '"')
+      /* Pass the contents of double-quoted strings through verbatim. */
+      if (c == '"')
        {
          si = i + 1;
-         i = (c == '\'') ? skip_single_quoted (string, slen, si)
-                         : skip_double_quoted (string, slen, si);
+         i = skip_double_quoted (string, slen, si);
          /* skip_XXX_quoted leaves index one past close quote */
          continue;
        }
 
+      if (c == '\'')
+       {
+/*itrace("extract_dollar_brace_string: c == single quote flags = %d quoted = %d dolbrace_state = %d", flags, quoted, dolbrace_state);*/
+         if (posixly_correct && shell_compatibility_level > 42 && dolbrace_state != DOLBRACE_QUOTE && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+           ADVANCE_CHAR (string, slen, i);
+         else
+           {
+             si = i + 1;
+             i = skip_single_quoted (string, slen, si);
+           }
+
+          continue;
+       }
+
       /* move past this character, which was not special. */
       ADVANCE_CHAR (string, slen, i);
+
+      /* This logic must agree with parse.y:parse_matched_pair, since they
+        share the same defines. */
+      if (dolbrace_state == DOLBRACE_PARAM && c == '%' && (i - *sindex) > 1)
+       dolbrace_state = DOLBRACE_QUOTE;
+      else if (dolbrace_state == DOLBRACE_PARAM && c == '#' && (i - *sindex) > 1)
+        dolbrace_state = DOLBRACE_QUOTE;
+      else if (dolbrace_state == DOLBRACE_PARAM && c == '/' && (i - *sindex) > 1)
+        dolbrace_state = DOLBRACE_QUOTE2;      /* XXX */
+      else if (dolbrace_state == DOLBRACE_PARAM && c == '^' && (i - *sindex) > 1)
+        dolbrace_state = DOLBRACE_QUOTE;
+      else if (dolbrace_state == DOLBRACE_PARAM && c == ',' && (i - *sindex) > 1)
+        dolbrace_state = DOLBRACE_QUOTE;
+      else if (dolbrace_state == DOLBRACE_PARAM && strchr ("#%^,~:-=?+/", c) != 0)
+       dolbrace_state = DOLBRACE_OP;
+      else if (dolbrace_state == DOLBRACE_OP && strchr ("#%^,~:-=?+/", c) == 0)
+       dolbrace_state = DOLBRACE_WORD;
     }
 
   if (c == 0 && nesting_level)
     {
       if (no_longjmp_on_fatal_error == 0)
        {                       /* { */
-         report_error (_("bad substitution: no closing `%s' in %s"), "}", string);
          last_command_exit_value = EXECUTION_FAILURE;
+         report_error (_("bad substitution: no closing `%s' in %s"), "}", string);
          exp_jump_to_top_level (DISCARD);
        }
       else
@@ -1377,7 +1598,10 @@ unquote_bang (string)
 #define CQ_RETURN(x) do { no_longjmp_on_fatal_error = 0; return (x); } while (0)
 
 /* This function assumes s[i] == open; returns with s[ret] == close; used to
-   parse array subscripts.  FLAGS currently unused. */
+   parse array subscripts.  FLAGS & 1 means to not attempt to skip over
+   matched pairs of quotes or backquotes, or skip word expansions; it is
+   intended to be used after expansion has been performed and during final
+   assignment parsing (see arrayfunc.c:assign_compound_array_list()). */
 static int
 skip_matched_pair (string, start, open, close, flags)
      const char *string;
@@ -1418,13 +1642,13 @@ skip_matched_pair (string, start, open, close, flags)
          ADVANCE_CHAR (string, slen, i);
          continue;
        }
-      else if (c == '`')
+      else if ((flags & 1) == 0 && c == '`')
        {
          backq = 1;
          i++;
          continue;
        }
-      else if (c == open)
+      else if ((flags & 1) == 0 && c == open)
        {
          count++;
          i++;
@@ -1438,13 +1662,13 @@ skip_matched_pair (string, start, open, close, flags)
          i++;
          continue;
        }
-      else if (c == '\'' || c == '"')
+      else if ((flags & 1) == 0 && (c == '\'' || c == '"'))
        {
          i = (c == '\'') ? skip_single_quoted (ss, slen, ++i)
                          : skip_double_quoted (ss, slen, ++i);
          /* no increment, the skip functions increment past the closing quote. */
        }
-      else if (c == '$' && (string[i+1] == LPAREN || string[i+1] == LBRACE))
+      else if ((flags&1) == 0 && c == '$' && (string[i+1] == LPAREN || string[i+1] == LBRACE))
        {
          si = i + 2;
          if (string[si] == '\0')
@@ -1469,11 +1693,11 @@ skip_matched_pair (string, start, open, close, flags)
 
 #if defined (ARRAY_VARS)
 int
-skipsubscript (string, start)
+skipsubscript (string, start, flags)
      const char *string;
-     int start;
+     int start, flags;
 {
-  return (skip_matched_pair (string, start, '[', ']', 0));
+  return (skip_matched_pair (string, start, '[', ']', flags));
 }
 #endif
 
@@ -1490,20 +1714,26 @@ skip_to_delim (string, start, delims, flags)
      char *delims;
      int flags;
 {
-  int i, pass_next, backq, si, c, invert;
+  int i, pass_next, backq, si, c, invert, skipquote, skipcmd;
   size_t slen;
-  char *temp;
+  char *temp, open[3];
   DECLARE_MBSTATE;
 
   slen = strlen (string + start) + start;
   if (flags & SD_NOJMP)
     no_longjmp_on_fatal_error = 1;
   invert = (flags & SD_INVERT);
+  skipcmd = (flags & SD_NOSKIPCMD) == 0;
 
   i = start;
   pass_next = backq = 0;
   while (c = string[i])
     {
+      /* If this is non-zero, we should not let quote characters be delimiters
+        and the current character is a single or double quote.  We should not
+        test whether or not it's a delimiter until after we skip single- or
+        double-quoted strings. */
+      skipquote = ((flags & SD_NOQUOTEDELIM) && (c == '\'' || c =='"'));
       if (pass_next)
        {
          pass_next = 0;
@@ -1531,7 +1761,7 @@ skip_to_delim (string, start, delims, flags)
          i++;
          continue;
        }
-      else if (invert == 0 && member (c, delims))
+      else if (skipquote == 0 && invert == 0 && member (c, delims))
        break;
       else if (c == '\'' || c == '"')
        {
@@ -1539,7 +1769,7 @@ skip_to_delim (string, start, delims, flags)
                          : skip_double_quoted (string, slen, ++i);
          /* no increment, the skip functions increment past the closing quote. */
        }
-      else if (c == '$' && (string[i+1] == LPAREN || string[i+1] == LBRACE))
+      else if (c == '$' && ((skipcmd && string[i+1] == LPAREN) || string[i+1] == LBRACE))
        {
          si = i + 2;
          if (string[si] == '\0')
@@ -1555,7 +1785,55 @@ skip_to_delim (string, start, delims, flags)
          i++;
          continue;
        }
-      else if (invert && (member (c, delims) == 0))
+#if defined (PROCESS_SUBSTITUTION)
+      else if (skipcmd && (c == '<' || c == '>') && string[i+1] == LPAREN)
+       {
+         si = i + 2;
+         if (string[si] == '\0')
+           CQ_RETURN(si);
+         temp = extract_process_subst (string, (c == '<') ? "<(" : ">(", &si, 0);
+         free (temp);          /* no SX_ALLOC here */
+         i = si;
+         if (string[i] == '\0')
+           break;
+         i++;
+         continue;
+       }
+#endif /* PROCESS_SUBSTITUTION */
+#if defined (EXTENDED_GLOB)
+      else if ((flags & SD_EXTGLOB) && extended_glob && string[i+1] == LPAREN && member (c, "?*+!@"))
+       {
+         si = i + 2;
+         if (string[si] == '\0')
+           CQ_RETURN(si);
+
+         open[0] = c;
+         open[1] = LPAREN;
+         open[2] = '\0';
+         temp = extract_delimited_string (string, &si, open, "(", ")", SX_NOALLOC); /* ) */
+
+         i = si;
+         if (string[i] == '\0')        /* don't increment i past EOS in loop */
+           break;
+         i++;
+         continue;
+       }
+#endif
+      else if ((flags & SD_GLOB) && c == LBRACK)
+       {
+         si = i + 1;
+         if (string[si] == '\0')
+           CQ_RETURN(si);
+
+         temp = extract_delimited_string (string, &si, "[", "[", "]", SX_NOALLOC); /* ] */
+
+         i = si;
+         if (string[i] == '\0')        /* don't increment i past EOS in loop */
+           break;
+         i++;
+         continue;
+       }
+      else if ((skipquote || invert) && (member (c, delims) == 0))
        break;
       else
        ADVANCE_CHAR (string, slen, i);
@@ -1673,14 +1951,14 @@ unclosed_pair (string, eindex, openstr)
    the index of the word containing SENTINEL.  Non-whitespace chars in
    DELIMS delimit separate fields. */
 WORD_LIST *
-split_at_delims (string, slen, delims, sentinel, nwp, cwp)
+split_at_delims (string, slen, delims, sentinel, flags, nwp, cwp)
      char *string;
      int slen;
      char *delims;
-     int sentinel;
+     int sentinel, flags;
      int *nwp, *cwp;
 {
-  int ts, te, i, nw, cw, ifs_split;
+  int ts, te, i, nw, cw, ifs_split, dflags;
   char *token, *d, *d2;
   WORD_LIST *ret, *tl;
 
@@ -1737,7 +2015,7 @@ split_at_delims (string, slen, delims, sentinel, nwp, cwp)
 
   ret = (WORD_LIST *)NULL;
 
-  /* Remove sequences of whitspace characters at the start of the string, as
+  /* Remove sequences of whitespace characters at the start of the string, as
      long as those characters are delimiters. */
   for (i = 0; member (string[i], d) && spctabnl (string[i]); i++)
     ;
@@ -1747,9 +2025,10 @@ split_at_delims (string, slen, delims, sentinel, nwp, cwp)
   ts = i;
   nw = 0;
   cw = -1;
+  dflags = flags|SD_NOJMP;
   while (1)
     {
-      te = skip_to_delim (string, ts, d, SD_NOJMP);
+      te = skip_to_delim (string, ts, d, dflags);
 
       /* If we have a non-whitespace delimiter character, use it to make a
         separate field.  This is just about what $IFS splitting does and
@@ -1807,9 +2086,10 @@ split_at_delims (string, slen, delims, sentinel, nwp, cwp)
 
   /* Special case for SENTINEL at the end of STRING.  If we haven't found
      the word containing SENTINEL yet, and the index we're looking for is at
-     the end of STRING, add an additional null argument and set the current
-     word pointer to that. */
-  if (cwp && cw == -1 && sentinel >= slen)
+     the end of STRING (or past the end of the previously-found token,
+     possible if the end of the line is composed solely of IFS whitespace)
+     add an additional null argument and set the current word pointer to that. */
+  if (cwp && cw == -1 && (sentinel >= slen || sentinel >= te))
     {
       if (whitespace (string[sentinel - 1]))
        {
@@ -1825,6 +2105,8 @@ split_at_delims (string, slen, delims, sentinel, nwp, cwp)
   if (cwp)
     *cwp = cw;
 
+  FREE (d2);
+
   return (REVERSE_LIST (ret, WORD_LIST *));
 }
 #endif /* READLINE */
@@ -2055,11 +2337,7 @@ string_list_dollar_at (list, quoted)
 
   /* XXX -- why call quote_list if ifs == 0?  we can get away without doing
      it now that quote_escapes quotes spaces */
-#if 0
-  tlist = ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (ifs && *ifs == 0))
-#else
-  tlist = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
-#endif
+  tlist = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES|Q_PATQUOTE))
                ? quote_list (list)
                : list_quote_escapes (list);
 
@@ -2070,7 +2348,7 @@ string_list_dollar_at (list, quoted)
   return ret;
 }
 
-/* Turn the positional paramters into a string, understanding quoting and
+/* Turn the positional parameters into a string, understanding quoting and
    the various subtleties of using the first character of $IFS as the
    separator.  Calls string_list_dollar_at, string_list_dollar_star, and
    string_list as appropriate. */
@@ -2453,11 +2731,12 @@ do_compound_assignment (name, value, flags)
      int flags;
 {
   SHELL_VAR *v;
-  int mklocal, mkassoc;
+  int mklocal, mkassoc, mkglobal;
   WORD_LIST *list;
 
   mklocal = flags & ASS_MKLOCAL;
   mkassoc = flags & ASS_MKASSOC;
+  mkglobal = flags & ASS_MKGLOBAL;
 
   if (mklocal && variable_context)
     {
@@ -2466,8 +2745,25 @@ do_compound_assignment (name, value, flags)
       if (mkassoc)
        v = make_local_assoc_variable (name);
       else if (v == 0 || (array_p (v) == 0 && assoc_p (v) == 0) || v->context != variable_context)
-        v = make_local_array_variable (name);
-      assign_compound_array_list (v, list, flags);
+        v = make_local_array_variable (name, 0);
+      if (v)
+       assign_compound_array_list (v, list, flags);
+    }
+  /* In a function but forcing assignment in global context */
+  else if (mkglobal && variable_context)
+    {
+      v = find_global_variable (name);
+      list = expand_compound_array_assignment (v, value, flags);
+      if (v == 0 && mkassoc)
+       v = make_new_assoc_variable (name);
+      else if (v && mkassoc && assoc_p (v) == 0)
+       v = convert_var_to_assoc (v);
+      else if (v == 0)
+       v = make_new_array_variable (name);
+      else if (v && mkassoc == 0 && array_p (v) == 0)
+       v = convert_var_to_array (v);
+      if (v)
+       assign_compound_array_list (v, list, flags);
     }
   else
     v = assign_array_from_string (name, value, flags);
@@ -2486,8 +2782,8 @@ do_assignment_internal (word, expand)
      const WORD_DESC *word;
      int expand;
 {
-  int offset, tlen, appendop, assign_list, aflags, retval;
-  char *name, *value;
+  int offset, appendop, assign_list, aflags, retval;
+  char *name, *value, *temp;
   SHELL_VAR *entry;
 #if defined (ARRAY_VARS)
   char *t;
@@ -2506,8 +2802,6 @@ do_assignment_internal (word, expand)
 
   if (name[offset] == '=')
     {
-      char *temp;
-
       if (name[offset - 1] == '+')
        {
          appendop = 1;
@@ -2516,7 +2810,6 @@ do_assignment_internal (word, expand)
 
       name[offset] = 0;                /* might need this set later */
       temp = name + offset + 1;
-      tlen = STRLEN (temp);
 
 #if defined (ARRAY_VARS)
       if (expand && (word->flags & W_COMPASSIGN))
@@ -2526,7 +2819,6 @@ do_assignment_internal (word, expand)
        }
       else
 #endif
-
       if (expand && temp[0])
        value = expand_string_if_necessary (temp, 0, expand_string_assignment);
       else
@@ -2554,7 +2846,7 @@ do_assignment_internal (word, expand)
     aflags |= ASS_APPEND;
 
 #if defined (ARRAY_VARS)
-  if (t = xstrchr (name, '[')) /*]*/
+  if (t = mbschr (name, '['))  /*]*/
     {
       if (assign_list)
        {
@@ -2567,8 +2859,10 @@ do_assignment_internal (word, expand)
     }
   else if (assign_list)
     {
-      if (word->flags & W_ASSIGNARG)
+      if ((word->flags & W_ASSIGNARG) && (word->flags & W_ASSNGLOBAL) == 0)
        aflags |= ASS_MKLOCAL;
+      if ((word->flags & W_ASSIGNARG) && (word->flags & W_ASSNGLOBAL))
+       aflags |= ASS_MKGLOBAL;
       if (word->flags & W_ASSIGNASSOC)
        aflags |= ASS_MKASSOC;
       entry = do_compound_assignment (name, value, aflags);
@@ -2579,7 +2873,6 @@ do_assignment_internal (word, expand)
 
   stupidly_hack_special_variables (name);
 
-#if 1
   /* Return 1 if the assignment seems to have been performed correctly. */
   if (entry == 0 || readonly_p (entry))
     retval = 0;                /* assignment failure */
@@ -2595,12 +2888,6 @@ do_assignment_internal (word, expand)
     VUNSETATTR (entry, att_invisible);
 
   ASSIGN_RETURN (retval);
-#else
-  if (entry)
-    VUNSETATTR (entry, att_invisible);
-
-  ASSIGN_RETURN (entry ? ((readonly_p (entry) == 0) && noassign_p (entry) == 0) : 0);
-#endif
 }
 
 /* Perform the assignment statement in STRING, and expand the
@@ -2618,8 +2905,9 @@ do_assignment (string)
 }
 
 int
-do_word_assignment (word)
+do_word_assignment (word, flags)
      WORD_DESC *word;
+     int flags;
 {
   return do_assignment_internal (word, 1);
 }
@@ -2739,7 +3027,7 @@ pos_params (string, start, end, quoted)
       save = params = t;
     }
 
-  for (i = 1; params && i < start; i++)
+  for (i = start ? 1 : 0; params && i < start; i++)
     params = params->next;
   if (params == 0)
     return ((char *)NULL);
@@ -2871,7 +3159,58 @@ expand_arith_string (string, quoted)
      char *string;
      int quoted;
 {
-  return (expand_string_if_necessary (string, quoted, expand_string));
+  WORD_DESC td;
+  WORD_LIST *list, *tlist;
+  size_t slen;
+  int i, saw_quote;
+  char *ret;
+  DECLARE_MBSTATE;
+
+  /* Don't need string length for ADVANCE_CHAR unless multibyte chars possible. */
+  slen = (MB_CUR_MAX > 1) ? strlen (string) : 0;
+  i = saw_quote = 0;
+  while (string[i])
+    {
+      if (EXP_CHAR (string[i]))
+       break;
+      else if (string[i] == '\'' || string[i] == '\\' || string[i] == '"')
+       saw_quote = 1;
+      ADVANCE_CHAR (string, slen, i);
+    }
+
+  if (string[i])
+    {
+      /* This is expanded version of expand_string_internal as it's called by
+        expand_string_leave_quoted  */
+      td.flags = W_NOPROCSUB;  /* don't want process substitution */
+      td.word = savestring (string);
+      list = call_expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL);
+      /* This takes care of the calls from expand_string_leave_quoted and
+        expand_string */
+      if (list)
+       {
+         tlist = word_list_split (list);
+         dispose_words (list);
+         list = tlist;
+         if (list)
+           dequote_list (list);
+       }
+      /* This comes from expand_string_if_necessary */
+      if (list)
+       {
+         ret = string_list (list);
+         dispose_words (list);
+       }
+      else
+       ret = (char *)NULL;
+      FREE (td.word);
+    }
+  else if (saw_quote && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0))
+    ret = string_quote_removal (string, quoted);
+  else
+    ret = savestring (string);
+
+  return ret;
 }
 
 #if defined (COND_COMMAND)
@@ -2915,16 +3254,22 @@ cond_expand_word (w, special)
   if (w->word == 0 || w->word[0] == '\0')
     return ((char *)NULL);
 
+  expand_no_split_dollar_star = 1;
+  w->flags |= W_NOSPLIT2;
   l = call_expand_word_internal (w, 0, 0, (int *)0, (int *)0);
+  expand_no_split_dollar_star = 0;
   if (l)
     {
-      if (special == 0)
+      if (special == 0)                        /* LHS */
        {
          dequote_list (l);
          r = string_list (l);
        }
       else
        {
+         /* Need to figure out whether or not we should call dequote_escapes
+            or a new dequote_ctlnul function here, and under what
+            circumstances. */
          qflags = QGLOB_CVTNULL;
          if (special == 2)
            qflags |= QGLOB_REGEXP;
@@ -2962,13 +3307,16 @@ call_expand_word_internal (w, q, i, c, e)
       last_command_exit_value = EXECUTION_FAILURE;
       exp_jump_to_top_level ((result == &expand_word_error) ? DISCARD : FORCE_EOF);
       /* NOTREACHED */
+      return (NULL);
     }
   else
     return (result);
 }
 
 /* Perform parameter expansion, command substitution, and arithmetic
-   expansion on STRING, as if it were a word.  Leave the result quoted. */
+   expansion on STRING, as if it were a word.  Leave the result quoted.
+   Since this does not perform word splitting, it leaves quoted nulls
+   in the result.  */
 static WORD_LIST *
 expand_string_internal (string, quoted)
      char *string;
@@ -3135,7 +3483,7 @@ expand_string_for_rhs (string, quoted, dollar_at_p, has_dollar_at)
   if (string == 0 || *string == '\0')
     return (WORD_LIST *)NULL;
 
-  td.flags = 0;
+  td.flags = W_NOSPLIT2;               /* no splitting, remove "" and '' */
   td.word = string;
   tresult = call_expand_word_internal (&td, quoted, 1, dollar_at_p, has_dollar_at);
   return (tresult);
@@ -3209,6 +3557,7 @@ quote_escapes (string)
       COPY_CHAR_P (t, s, send);
     }
   *t = '\0';
+
   return (result);
 }
 
@@ -3274,9 +3623,26 @@ dequote_escapes (string)
       COPY_CHAR_P (t, s, send);
     }
   *t = '\0';
+
   return result;
 }
 
+static WORD_LIST *
+list_dequote_escapes (list)
+     WORD_LIST *list;
+{
+  register WORD_LIST *w;
+  char *t;
+
+  for (w = list; w; w = w->next)
+    {
+      t = w->word->word;
+      w->word->word = dequote_escapes (t);
+      free (t);
+    }
+  return list;
+}
+
 /* Return a new string with the quoted representation of character C.
    This turns "" into QUOTED_NULL, so the W_HASQUOTEDNULL flag needs to be
    set in any resultant WORD_DESC where this value is the word. */
@@ -3468,7 +3834,10 @@ remove_quoted_nulls (string)
            break;
        }
       else if (string[i] == CTLNUL)
-       i++;
+       {
+         i++;
+         continue;
+       }
 
       prev_i = i;
       ADVANCE_CHAR (string, slen, i);
@@ -3545,6 +3914,7 @@ mb_getcharlens (string, len)
 #define RP_LONG_RIGHT  3
 #define RP_SHORT_RIGHT 4
 
+/* Returns its first argument if nothing matched; new memory otherwise */
 static char *
 remove_upattern (param, pattern, op)
      char *param, *pattern;
@@ -3613,10 +3983,11 @@ remove_upattern (param, pattern, op)
        break;
     }
 
-  return (savestring (param)); /* no match, return original string */
+  return (param);      /* no match, return original string */
 }
 
 #if defined (HANDLE_MULTIBYTE)
+/* Returns its first argument if nothing matched; new memory otherwise */
 static wchar_t *
 remove_wpattern (wparam, wstrlen, wpattern, op)
      wchar_t *wparam;
@@ -3682,7 +4053,7 @@ remove_wpattern (wparam, wstrlen, wpattern, op)
        break;
     }
 
-  return (wcsdup (wparam));    /* no match, return original string */
+  return (wparam);     /* no match, return original string */
 }
 #endif /* HANDLE_MULTIBYTE */
 
@@ -3691,6 +4062,8 @@ remove_pattern (param, pattern, op)
      char *param, *pattern;
      int op;
 {
+  char *xret;
+
   if (param == NULL)
     return (param);
   if (*param == '\0' || pattern == NULL || *pattern == '\0')   /* minor optimization */
@@ -3703,18 +4076,30 @@ remove_pattern (param, pattern, op)
       size_t n;
       wchar_t *wparam, *wpattern;
       mbstate_t ps;
-      char *xret;
 
       n = xdupmbstowcs (&wpattern, NULL, pattern);
       if (n == (size_t)-1)
-       return (remove_upattern (param, pattern, op));
+       {
+         xret = remove_upattern (param, pattern, op);
+         return ((xret == param) ? savestring (param) : xret);
+       }
       n = xdupmbstowcs (&wparam, NULL, param);
+
       if (n == (size_t)-1)
        {
          free (wpattern);
-         return (remove_upattern (param, pattern, op));
+         xret = remove_upattern (param, pattern, op);
+         return ((xret == param) ? savestring (param) : xret);
        }
       oret = ret = remove_wpattern (wparam, n, wpattern, op);
+      /* Don't bother to convert wparam back to multibyte string if nothing
+        matched; just return copy of original string */
+      if (ret == wparam)
+        {
+          free (wparam);
+          free (wpattern);
+          return (savestring (param));
+        }
 
       free (wparam);
       free (wpattern);
@@ -3729,36 +4114,9 @@ remove_pattern (param, pattern, op)
     }
   else
 #endif
-    return (remove_upattern (param, pattern, op));
-}
-
-/* Return 1 of the first character of STRING could match the first
-   character of pattern PAT.  Used to avoid n2 calls to strmatch(). */
-static int
-match_pattern_char (pat, string)
-     char *pat, *string;
-{
-  char c;
-
-  if (*string == 0)
-    return (0);
-
-  switch (c = *pat++)
     {
-    default:
-      return (*string == c);
-    case '\\':
-      return (*string == *pat);
-    case '?':
-      return (*pat == LPAREN ? 1 : (*string != '\0'));
-    case '*':
-      return (1);
-    case '+':
-    case '!':
-    case '@':
-      return (*pat == LPAREN ? 1 : (*string == c));
-    case '[':
-      return (*string != '\0');
+      xret = remove_upattern (param, pattern, op);
+      return ((xret == param) ? savestring (param) : xret);
     }
 }
 
@@ -3774,9 +4132,10 @@ match_upattern (string, pat, mtype, sp, ep)
      int mtype;
      char **sp, **ep;
 {
-  int c, len;
+  int c, len, mlen;
   register char *p, *p1, *npat;
   char *end;
+  int n1;
 
   /* If the pattern doesn't match anywhere in the string, go ahead and
      short-circuit right away.  A minor optimization, saves a bunch of
@@ -3787,11 +4146,11 @@ match_upattern (string, pat, mtype, sp, ep)
   /* XXX - check this later if I ever implement `**' with special meaning,
      since this will potentially result in `**' at the beginning or end */
   len = STRLEN (pat);
-  if (pat[0] != '*' || (pat[0] == '*' && pat[1] == '(' && extended_glob) || pat[len - 1] != '*')       /*)*/
+  if (pat[0] != '*' || (pat[0] == '*' && pat[1] == LPAREN && extended_glob) || pat[len - 1] != '*')
     {
       p = npat = (char *)xmalloc (len + 3);
       p1 = pat;
-      if (*p1 != '*' || (*p1 == '*' && p1[1] == '(' && extended_glob)) /*)*/
+      if (*p1 != '*' || (*p1 == '*' && p1[1] == LPAREN && extended_glob))
        *p++ = '*';
       while (*p1)
        *p++ = *p1++;
@@ -3810,6 +4169,8 @@ match_upattern (string, pat, mtype, sp, ep)
   len = STRLEN (string);
   end = string + len;
 
+  mlen = umatchlen (pat, len);
+
   switch (mtype)
     {
     case MATCH_ANY:
@@ -3817,7 +4178,18 @@ match_upattern (string, pat, mtype, sp, ep)
        {
          if (match_pattern_char (pat, p))
            {
-             for (p1 = end; p1 >= p; p1--)
+             p1 = (mlen == -1) ? end : p + mlen;
+             /* p1 - p = length of portion of string to be considered
+                p = current position in string
+                mlen = number of characters consumed by match (-1 for entire string)
+                end = end of string
+                we want to break immediately if the potential match len
+                is greater than the number of characters remaining in the
+                string
+             */
+             if (p1 > end)
+               break;
+             for ( ; p1 >= p; p1--)
                {
                  c = *p1; *p1 = '\0';
                  if (strmatch (pat, p, FNMATCH_EXTFLAG) == 0)
@@ -3828,6 +4200,11 @@ match_upattern (string, pat, mtype, sp, ep)
                      return 1;
                    }
                  *p1 = c;
+#if 1
+                 /* If MLEN != -1, we have a fixed length pattern. */
+                 if (mlen != -1)
+                   break;
+#endif
                }
            }
        }
@@ -3838,7 +4215,7 @@ match_upattern (string, pat, mtype, sp, ep)
       if (match_pattern_char (pat, string) == 0)
        return (0);
 
-      for (p = end; p >= string; p--)
+      for (p = (mlen == -1) ? end : string + mlen; p >= string; p--)
        {
          c = *p; *p = '\0';
          if (strmatch (pat, string, FNMATCH_EXTFLAG) == 0)
@@ -3849,12 +4226,15 @@ match_upattern (string, pat, mtype, sp, ep)
              return 1;
            }
          *p = c;
+         /* If MLEN != -1, we have a fixed length pattern. */
+         if (mlen != -1)
+           break;
        }
 
       return (0);
 
     case MATCH_END:
-      for (p = string; p <= end; p++)
+      for (p = end - ((mlen == -1) ? len : mlen); p <= end; p++)
        {
          if (strmatch (pat, p, FNMATCH_EXTFLAG) == 0)
            {
@@ -3862,7 +4242,9 @@ match_upattern (string, pat, mtype, sp, ep)
              *ep = end;
              return 1;
            }
-
+         /* If MLEN != -1, we have a fixed length pattern. */
+         if (mlen != -1)
+           break;
        }
 
       return (0);
@@ -3872,39 +4254,9 @@ match_upattern (string, pat, mtype, sp, ep)
 }
 
 #if defined (HANDLE_MULTIBYTE)
-/* Return 1 of the first character of WSTRING could match the first
-   character of pattern WPAT.  Wide character version. */
-static int
-match_pattern_wchar (wpat, wstring)
-     wchar_t *wpat, *wstring;
-{
-  wchar_t wc;
-
-  if (*wstring == 0)
-    return (0);
-
-  switch (wc = *wpat++)
-    {
-    default:
-      return (*wstring == wc);
-    case L'\\':
-      return (*wstring == *wpat);
-    case L'?':
-      return (*wpat == LPAREN ? 1 : (*wstring != L'\0'));
-    case L'*':
-      return (1);
-    case L'+':
-    case L'!':
-    case L'@':
-      return (*wpat == LPAREN ? 1 : (*wstring == wc));
-    case L'[':
-      return (*wstring != L'\0');
-    }
-}
-
-/* Match WPAT anywhere in WSTRING and return the match boundaries.
-   This returns 1 in case of a successful match, 0 otherwise.  Wide
-   character version. */
+/* Match WPAT anywhere in WSTRING and return the match boundaries.
+   This returns 1 in case of a successful match, 0 otherwise.  Wide
+   character version. */
 static int
 match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
      wchar_t *wstring;
@@ -3915,11 +4267,14 @@ match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
      char **sp, **ep;
 {
   wchar_t wc, *wp, *nwpat, *wp1;
-  int len;
-#if 0
-  size_t n, n1;        /* Apple's gcc seems to miscompile this badly */
-#else
-  int n, n1;
+  size_t len;
+  int mlen;
+  int n, n1, n2, simple;
+
+  simple = (wpat[0] != L'\\' && wpat[0] != L'*' && wpat[0] != L'?' && wpat[0] != L'[');
+#if defined (EXTENDED_GLOB)
+  if (extended_glob)
+    simple &= (wpat[1] != L'(' || (wpat[0] != L'*' && wpat[0] != L'?' && wpat[0] != L'+' && wpat[0] != L'!' && wpat[0] != L'@')); /*)*/
 #endif
 
   /* If the pattern doesn't match anywhere in the string, go ahead and
@@ -3928,14 +4283,12 @@ match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
      characters) if the match is unsuccessful.  To preserve the semantics
      of the substring matches below, we make sure that the pattern has
      `*' as first and last character, making a new pattern if necessary. */
-  /* XXX - check this later if I ever implement `**' with special meaning,
-     since this will potentially result in `**' at the beginning or end */
   len = wcslen (wpat);
-  if (wpat[0] != L'*' || (wpat[0] == L'*' && wpat[1] == L'(' && extended_glob) || wpat[len - 1] != L'*')       /*)*/
+  if (wpat[0] != L'*' || (wpat[0] == L'*' && wpat[1] == WLPAREN && extended_glob) || wpat[len - 1] != L'*')
     {
       wp = nwpat = (wchar_t *)xmalloc ((len + 3) * sizeof (wchar_t));
       wp1 = wpat;
-      if (*wp1 != L'*' || (*wp1 == '*' && wp1[1] == '(' && extended_glob))     /*)*/
+      if (*wp1 != L'*' || (*wp1 == '*' && wp1[1] == WLPAREN && extended_glob))
        *wp++ = L'*';
       while (*wp1 != L'\0')
        *wp++ = *wp1++;
@@ -3951,14 +4304,22 @@ match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
   if (len == FNM_NOMATCH)
     return (0);
 
+  mlen = wmatchlen (wpat, wstrlen);
+
+/* itrace("wmatchlen (%ls) -> %d", wpat, mlen); */
   switch (mtype)
     {
     case MATCH_ANY:
       for (n = 0; n <= wstrlen; n++)
        {
-         if (match_pattern_wchar (wpat, wstring + n))
+         n2 = simple ? (*wpat == wstring[n]) : match_pattern_wchar (wpat, wstring + n);
+         if (n2)
            {
-             for (n1 = wstrlen; n1 >= n; n1--)
+             n1 = (mlen == -1) ? wstrlen : n + mlen;
+             if (n1 > wstrlen)
+               break;
+
+             for ( ; n1 >= n; n1--)
                {
                  wc = wstring[n1]; wstring[n1] = L'\0';
                  if (wcsmatch (wpat, wstring + n, FNMATCH_EXTFLAG) == 0)
@@ -3969,6 +4330,9 @@ match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
                      return 1;
                    }
                  wstring[n1] = wc;
+                 /* If MLEN != -1, we have a fixed length pattern. */
+                 if (mlen != -1)
+                   break;
                }
            }
        }
@@ -3979,7 +4343,7 @@ match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
       if (match_pattern_wchar (wpat, wstring) == 0)
        return (0);
 
-      for (n = wstrlen; n >= 0; n--)
+      for (n = (mlen == -1) ? wstrlen : mlen; n >= 0; n--)
        {
          wc = wstring[n]; wstring[n] = L'\0';
          if (wcsmatch (wpat, wstring, FNMATCH_EXTFLAG) == 0)
@@ -3990,12 +4354,15 @@ match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
              return 1;
            }
          wstring[n] = wc;
+         /* If MLEN != -1, we have a fixed length pattern. */
+         if (mlen != -1)
+           break;
        }
 
       return (0);
 
     case MATCH_END:
-      for (n = 0; n <= wstrlen; n++)
+      for (n = wstrlen - ((mlen == -1) ? wstrlen : mlen); n <= wstrlen; n++)
        {
          if (wcsmatch (wpat, wstring + n, FNMATCH_EXTFLAG) == 0)
            {
@@ -4003,6 +4370,9 @@ match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
              *ep = indices[wstrlen];
              return 1;
            }
+         /* If MLEN != -1, we have a fixed length pattern. */
+         if (mlen != -1)
+           break;
        }
 
       return (0);
@@ -4023,6 +4393,7 @@ match_pattern (string, pat, mtype, sp, ep)
   size_t n;
   wchar_t *wstring, *wpat;
   char **indices;
+  size_t slen, plen, mslen, mplen;
 #endif
 
   if (string == 0 || *string == 0 || pat == 0 || *pat == 0)
@@ -4031,6 +4402,9 @@ match_pattern (string, pat, mtype, sp, ep)
 #if defined (HANDLE_MULTIBYTE)
   if (MB_CUR_MAX > 1)
     {
+      if (mbsmbchar (string) == 0 && mbsmbchar (pat) == 0)
+        return (match_upattern (string, pat, mtype, sp, ep));
+
       n = xdupmbstowcs (&wpat, NULL, pat);
       if (n == (size_t)-1)
        return (match_upattern (string, pat, mtype, sp, ep));
@@ -4190,6 +4564,11 @@ array_remove_pattern (var, pattern, patspec, varname, quoted)
 
   /* compute itype from varname here */
   v = array_variable_part (varname, &ret, 0);
+
+  /* XXX */
+  if (v && invisible_p (var))
+    return ((char *)NULL);
+
   itype = ret[0];
 
   a = (v && array_p (v)) ? array_cell (v) : 0;
@@ -4206,9 +4585,11 @@ array_remove_pattern (var, pattern, patspec, varname, quoted)
 #endif /* ARRAY_VARS */
 
 static char *
-parameter_brace_remove_pattern (varname, value, patstr, rtype, quoted)
-     char *varname, *value, *patstr;
-     int rtype, quoted;
+parameter_brace_remove_pattern (varname, value, ind, patstr, rtype, quoted, flags)
+     char *varname, *value;
+     int ind;
+     char *patstr;
+     int rtype, quoted, flags;
 {
   int vtype, patspec, starsub;
   char *temp1, *val, *pattern;
@@ -4219,7 +4600,7 @@ parameter_brace_remove_pattern (varname, value, patstr, rtype, quoted)
 
   this_command_name = varname;
 
-  vtype = get_var_and_type (varname, value, quoted, &v, &val);
+  vtype = get_var_and_type (varname, value, ind, quoted, flags, &v, &val);
   if (vtype == -1)
     return ((char *)NULL);
 
@@ -4313,6 +4694,13 @@ expand_word_unsplit (word, quoted)
   WORD_LIST *result;
 
   expand_no_split_dollar_star = 1;
+#if defined (HANDLE_MULTIBYTE)
+  if (ifs_firstc[0] == 0)
+#else
+  if (ifs_firstc == 0)
+#endif
+    word->flags |= W_NOSPLIT;
+  word->flags |= W_NOSPLIT2;
   result = call_expand_word_internal (word, quoted, 0, (int *)NULL, (int *)NULL);
   expand_no_split_dollar_star = 0;
 
@@ -4320,13 +4708,27 @@ expand_word_unsplit (word, quoted)
 }
 
 /* Perform shell expansions on WORD, but do not perform word splitting or
-   quote removal on the result. */
+   quote removal on the result.  Virtually identical to expand_word_unsplit;
+   could be combined if implementations don't diverge. */
 WORD_LIST *
 expand_word_leave_quoted (word, quoted)
      WORD_DESC *word;
      int quoted;
 {
-  return (call_expand_word_internal (word, quoted, 0, (int *)NULL, (int *)NULL));
+  WORD_LIST *result;
+
+  expand_no_split_dollar_star = 1;
+#if defined (HANDLE_MULTIBYTE)
+  if (ifs_firstc[0] == 0)
+#else
+  if (ifs_firstc == 0)
+#endif
+    word->flags |= W_NOSPLIT;
+  word->flags |= W_NOSPLIT2;
+  result = call_expand_word_internal (word, quoted, 0, (int *)NULL, (int *)NULL);
+  expand_no_split_dollar_star = 0;
+
+  return result;
 }
 
 #if defined (PROCESS_SUBSTITUTION)
@@ -4353,6 +4755,15 @@ static struct temp_fifo *fifo_list = (struct temp_fifo *)NULL;
 static int nfifo;
 static int fifo_list_size;
 
+char *
+copy_fifo_list (sizep)
+     int *sizep;
+{
+  if (sizep)
+    *sizep = 0;
+  return (char *)NULL;
+}
+
 static void
 add_fifo_list (pathname)
      char *pathname;
@@ -4368,6 +4779,19 @@ add_fifo_list (pathname)
   nfifo++;
 }
 
+void
+unlink_fifo (i)
+     int i;
+{
+  if ((fifo_list[i].proc == -1) || (kill(fifo_list[i].proc, 0) == -1))
+    {
+      unlink (fifo_list[i].file);
+      free (fifo_list[i].file);
+      fifo_list[i].file = (char *)NULL;
+      fifo_list[i].proc = -1;
+    }
+}
+
 void
 unlink_fifo_list ()
 {
@@ -4405,12 +4829,44 @@ unlink_fifo_list ()
     nfifo = 0;
 }
 
+/* Take LIST, which is a bitmap denoting active FIFOs in fifo_list
+   from some point in the past, and close all open FIFOs in fifo_list
+   that are not marked as active in LIST.  If LIST is NULL, close
+   everything in fifo_list. LSIZE is the number of elements in LIST, in
+   case it's larger than fifo_list_size (size of fifo_list). */
+void
+close_new_fifos (list, lsize)
+     char *list;
+     int lsize;
+{
+  int i;
+
+  if (list == 0)
+    {
+      unlink_fifo_list ();
+      return;
+    }
+
+  for (i = 0; i < lsize; i++)
+    if (list[i] == 0 && i < fifo_list_size && fifo_list[i].proc != -1)
+      unlink_fifo (i);
+
+  for (i = lsize; i < fifo_list_size; i++)
+    unlink_fifo (i);  
+}
+
 int
 fifos_pending ()
 {
   return nfifo;
 }
 
+int
+num_fifos ()
+{
+  return nfifo;
+}
+
 static char *
 make_named_pipe ()
 {
@@ -4437,11 +4893,30 @@ static char *dev_fd_list = (char *)NULL;
 static int nfds;
 static int totfds;     /* The highest possible number of open files. */
 
+char *
+copy_fifo_list (sizep)
+     int *sizep;
+{
+  char *ret;
+
+  if (nfds == 0 || totfds == 0)
+    {
+      if (sizep)
+       *sizep = 0;
+      return (char *)NULL;
+    }
+
+  if (sizep)
+    *sizep = totfds;
+  ret = (char *)xmalloc (totfds);
+  return (memcpy (ret, dev_fd_list, totfds));
+}
+
 static void
 add_fifo_list (fd)
      int fd;
 {
-  if (!dev_fd_list || fd >= totfds)
+  if (dev_fd_list == 0 || fd >= totfds)
     {
       int ofds;
 
@@ -4466,6 +4941,24 @@ fifos_pending ()
   return 0;    /* used for cleanup; not needed with /dev/fd */
 }
 
+int
+num_fifos ()
+{
+  return nfds;
+}
+
+void
+unlink_fifo (fd)
+     int fd;
+{
+  if (dev_fd_list[fd])
+    {
+      close (fd);
+      dev_fd_list[fd] = 0;
+      nfds--;
+    }
+}
+
 void
 unlink_fifo_list ()
 {
@@ -4475,16 +4968,37 @@ unlink_fifo_list ()
     return;
 
   for (i = 0; nfds && i < totfds; i++)
-    if (dev_fd_list[i])
-      {
-       close (i);
-       dev_fd_list[i] = 0;
-       nfds--;
-      }
+    unlink_fifo (i);
 
   nfds = 0;
 }
 
+/* Take LIST, which is a snapshot copy of dev_fd_list from some point in
+   the past, and close all open fds in dev_fd_list that are not marked
+   as open in LIST.  If LIST is NULL, close everything in dev_fd_list.
+   LSIZE is the number of elements in LIST, in case it's larger than
+   totfds (size of dev_fd_list). */
+void
+close_new_fifos (list, lsize)
+     char *list;
+     int lsize;
+{
+  int i;
+
+  if (list == 0)
+    {
+      unlink_fifo_list ();
+      return;
+    }
+
+  for (i = 0; i < lsize; i++)
+    if (list[i] == 0 && i < totfds && dev_fd_list[i])
+      unlink_fifo (i);
+
+  for (i = lsize; i < totfds; i++)
+    unlink_fifo (i);  
+}
+
 #if defined (NOTDEF)
 print_dev_fd_list ()
 {
@@ -4590,7 +5104,7 @@ process_substitute (string, open_for_read_in_child)
       reset_terminating_signals ();    /* XXX */
       free_pushed_string_input ();
       /* Cancel traps, in trap.c. */
-      restore_original_signals ();
+      restore_original_signals ();     /* XXX - what about special builtins? bash-4.2 */
       setup_async_signals ();
       subshell_environment |= SUBSHELL_COMSUB|SUBSHELL_PROCSUB;
     }
@@ -4644,7 +5158,7 @@ process_substitute (string, open_for_read_in_child)
 
 #if !defined (HAVE_DEV_FD)
   /* Open the named pipe in the child. */
-  fd = open (pathname, open_for_read_in_child ? O_RDONLY|O_NONBLOCK : O_WRONLY);
+  fd = open (pathname, open_for_read_in_child ? O_RDONLY : O_WRONLY);
   if (fd < 0)
     {
       /* Two separate strings for ease of translation. */
@@ -4693,6 +5207,10 @@ process_substitute (string, open_for_read_in_child)
   dev_fd_list[parent_pipe_fd] = 0;
 #endif /* HAVE_DEV_FD */
 
+  /* subshells shouldn't have this flag, which controls using the temporary
+     environment for variable lookups. */
+  expanding_redir = 0;
+
   result = parse_and_execute (string, "process substitution", (SEVAL_NONINT|SEVAL_NOHIST));
 
 #if !defined (HAVE_DEV_FD)
@@ -4700,6 +5218,8 @@ process_substitute (string, open_for_read_in_child)
   close (open_for_read_in_child ? 0 : 1);
 #endif /* !HAVE_DEV_FD */
 
+  last_command_exit_value = result;
+  result = run_exit_trap ();
   exit (result);
   /*NOTREACHED*/
 }
@@ -4726,10 +5246,6 @@ read_comsub (fd, quoted, rflag)
   for (skip_ctlesc = skip_ctlnul = 0, s = ifs_value; s && *s; s++)
     skip_ctlesc |= *s == CTLESC, skip_ctlnul |= *s == CTLNUL;
 
-#ifdef __CYGWIN__
-  setmode (fd, O_TEXT);                /* we don't want CR/LF, we want Unix-style */
-#endif
-
   /* Read the output of the command through the pipe.  This may need to be
      changed to understand multibyte characters in the future. */
   while (1)
@@ -4843,7 +5359,7 @@ command_substitute (string, quoted)
 
   if (wordexp_only && read_but_dont_execute)
     {
-      last_command_exit_value = 125;
+      last_command_exit_value = EX_WEXPCOMSUB;
       jump_to_top_level (EXITPROG);
     }
 
@@ -4860,7 +5376,7 @@ command_substitute (string, quoted)
     maybe_make_export_env ();  /* XXX */
 
   /* Flags to pass to parse_and_execute() */
-  pflags = interactive ? SEVAL_RESETLINE : 0;
+  pflags = (interactive && sourcelevel == 0) ? SEVAL_RESETLINE : 0;
 
   /* Pipe the output of executing STRING into the current shell. */
   if (pipe (fildes) < 0)
@@ -4883,9 +5399,13 @@ command_substitute (string, quoted)
   last_asynchronous_pid = old_async_pid;
 
   if (pid == 0)
-    /* Reset the signal handlers in the child, but don't free the
-       trap strings. */
-    reset_signal_handlers ();
+    {
+      /* Reset the signal handlers in the child, but don't free the
+        trap strings.  Set a flag noting that we have to free the
+        trap strings if we run trap to change a signal disposition. */
+      reset_signal_handlers ();
+      subshell_environment |= SUBSHELL_RESETTRAP;
+    }
 
 #if defined (JOB_CONTROL)
   /* XXX DO THIS ONLY IN PARENT ? XXX */
@@ -4902,6 +5422,8 @@ command_substitute (string, quoted)
       sys_error (_("cannot make child for command substitution"));
     error_exit:
 
+      last_made_pid = old_pid;
+
       FREE (istring);
       close (fildes[0]);
       close (fildes[1]);
@@ -4936,6 +5458,13 @@ command_substitute (string, quoted)
          (fildes[0] != fileno (stderr)))
        close (fildes[0]);
 
+#ifdef __CYGWIN__
+      /* Let stdio know the fd may have changed from text to binary mode, and
+        make sure to preserve stdout line buffering. */
+      freopen (NULL, "w", stdout);
+      sh_setlinebuf (stdout);
+#endif /* __CYGWIN__ */
+
       /* The currently executing shell is not interactive. */
       interactive = 0;
 
@@ -4945,20 +5474,24 @@ command_substitute (string, quoted)
       /* When not in POSIX mode, command substitution does not inherit
         the -e flag. */
       if (posixly_correct == 0)
-       exit_immediately_on_error = 0;
+        {
+          builtin_ignoring_errexit = 0;
+         change_flag ('e', FLAG_OFF);
+         set_shellopts ();
+        }
 
       remove_quoted_escapes (string);
 
       startup_state = 2;       /* see if we can avoid a fork */
       /* Give command substitution a place to jump back to on failure,
         so we don't go back up to main (). */
-      result = setjmp (top_level);
+      result = setjmp_nosigs (top_level);
 
       /* If we're running a command substitution inside a shell function,
         trap `return' so we don't return from the function in the subshell
         and go off to never-never land. */
       if (result == 0 && return_catch_flag)
-       function_value = setjmp (return_catch);
+       function_value = setjmp_nosigs (return_catch);
       else
        function_value = 0;
 
@@ -5012,14 +5545,10 @@ command_substitute (string, quoted)
       /* wait_for gives the terminal back to shell_pgrp.  If some other
         process group should have it, give it away to that group here.
         pipeline_pgrp is non-zero only while we are constructing a
-        pipline, so what we are concerned about is whether or not that
+        pipeline, so what we are concerned about is whether or not that
         pipeline was started in the background.  A pipeline started in
         the background should never get the tty back here. */
-#if 0
-      if (interactive && pipeline_pgrp != (pid_t)0 && pipeline_pgrp != last_asynchronous_pid)
-#else
       if (interactive && pipeline_pgrp != (pid_t)0 && (subshell_environment & SUBSHELL_ASYNC) == 0)
-#endif
        give_terminal_to (pipeline_pgrp, 0);
 #endif /* JOB_CONTROL */
 
@@ -5048,21 +5577,23 @@ array_length_reference (s)
   char *akey;
   char *t, c;
   ARRAY *array;
+  HASH_TABLE *h;
   SHELL_VAR *var;
 
   var = array_variable_part (s, &t, &len);
 
   /* If unbound variables should generate an error, report one and return
      failure. */
-  if ((var == 0 || (assoc_p (var) == 0 && array_p (var) == 0)) && unbound_vars_is_error)
+  if ((var == 0 || invisible_p (var) || (assoc_p (var) == 0 && array_p (var) == 0)) && unbound_vars_is_error)
     {
       c = *--t;
       *t = '\0';
+      last_command_exit_value = EXECUTION_FAILURE;
       err_unboundvar (s);
       *t = c;
       return (-1);
     }
-  else if (var == 0)
+  else if (var == 0 || invisible_p (var))
     return 0;
 
   /* We support a couple of expansions for variables that are not arrays.
@@ -5070,15 +5601,16 @@ array_length_reference (s)
      v[*].  Return 0 for everything else. */
 
   array = array_p (var) ? array_cell (var) : (ARRAY *)NULL;
+  h = assoc_p (var) ? assoc_cell (var) : (HASH_TABLE *)NULL;
 
   if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']')
     {
       if (assoc_p (var))
-       return (assoc_num_elements (assoc_cell (var)));
+       return (h ? assoc_num_elements (h) : 0);
       else if (array_p (var))
-       return (array_num_elements (array));
+       return (array ? array_num_elements (array) : 0);
       else
-       return 1;
+       return (var_isset (var) ? 1 : 0);
     }
 
   if (assoc_p (var))
@@ -5089,13 +5621,18 @@ array_length_reference (s)
       if (akey == 0 || *akey == 0)
        {
          err_badarraysub (t);
+         FREE (akey);
          return (-1);
        }
       t = assoc_reference (assoc_cell (var), akey);
+      free (akey);
     }
   else
     {
-      ind = array_expand_index (t, len);
+      ind = array_expand_index (var, t, len);
+      /* negative subscripts to indexed arrays count back from end */
+      if (var && array_p (var) && ind < 0)
+       ind = array_max_index (array_cell (var)) + 1 + ind;
       if (ind < 0)
        {
          err_badarraysub (t);
@@ -5168,7 +5705,7 @@ chk_atstar (name, quoted, quoted_dollar_atp, contains_dollar_at)
 #if defined (ARRAY_VARS)
   else if (valid_array_reference (name))
     {
-      temp1 = xstrchr (name, '[');
+      temp1 = mbschr (name, '[');
       if (temp1 && temp1[1] == '@' && temp1[2] == ']')
        {
          if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp)
@@ -5196,20 +5733,25 @@ chk_atstar (name, quoted, quoted_dollar_atp, contains_dollar_at)
    the shell, e.g., "@", "$", "*", etc.  QUOTED, if non-zero, means that
    NAME was found inside of a double-quoted expression. */
 static WORD_DESC *
-parameter_brace_expand_word (name, var_is_special, quoted, pflags)
+parameter_brace_expand_word (name, var_is_special, quoted, pflags, indp)
      char *name;
      int var_is_special, quoted, pflags;
+     arrayind_t *indp;
 {
   WORD_DESC *ret;
   char *temp, *tt;
   intmax_t arg_index;
   SHELL_VAR *var;
   int atype, rflags;
+  arrayind_t ind;
 
   ret = 0;
   temp = 0;
   rflags = 0;
 
+  if (indp)
+    *indp = INTMAX_MIN;
+
   /* Handle multiple digit arguments, as in ${11}. */  
   if (legal_number (name, &arg_index))
     {
@@ -5236,11 +5778,27 @@ parameter_brace_expand_word (name, var_is_special, quoted, pflags)
 #if defined (ARRAY_VARS)
   else if (valid_array_reference (name))
     {
-      temp = array_value (name, quoted, &atype);
+expand_arrayref:
+      /* XXX - does this leak if name[@] or name[*]? */
+      if (pflags & PF_ASSIGNRHS)
+        {
+          temp = array_variable_name (name, &tt, (int *)0);
+          if (ALL_ELEMENT_SUB (tt[0]) && tt[1] == ']')
+           temp = array_value (name, quoted|Q_DOUBLE_QUOTES, 0, &atype, &ind);
+         else
+           temp = array_value (name, quoted, 0, &atype, &ind);
+        }
+      else
+       temp = array_value (name, quoted, 0, &atype, &ind);
       if (atype == 0 && temp)
-       temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
-                 ? quote_string (temp)
-                 : quote_escapes (temp);
+       {
+         temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+                   ? quote_string (temp)
+                   : quote_escapes (temp);
+         rflags |= W_ARRAYIND;
+         if (indp)
+           *indp = ind;
+       }                 
       else if (atype == 1 && temp && QUOTED_NULL (temp) && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
        rflags |= W_HASQUOTEDNULL;
     }
@@ -5268,6 +5826,28 @@ parameter_brace_expand_word (name, var_is_special, quoted, pflags)
       else
        temp = (char *)NULL;
     }
+  else if (var = find_variable_last_nameref (name))
+    {
+      temp = nameref_cell (var);
+#if defined (ARRAY_VARS)
+      /* Handle expanding nameref whose value is x[n] */
+      if (temp && *temp && valid_array_reference (temp))
+       {
+         name = temp;
+         goto expand_arrayref;
+       }
+      else
+#endif
+      /* y=2 ; typeset -n x=y; echo ${x} is not the same as echo ${2} in ksh */
+      if (temp && *temp && legal_identifier (temp) == 0)
+        {
+         last_command_exit_value = EXECUTION_FAILURE;
+         report_error (_("%s: invalid variable name for name reference"), temp);
+         temp = &expand_param_error;
+        }
+      else
+       temp = (char *)NULL;
+    }
   else
     temp = (char *)NULL;
 
@@ -5280,18 +5860,23 @@ parameter_brace_expand_word (name, var_is_special, quoted, pflags)
   return ret;
 }
 
-/* Expand an indirect reference to a variable: ${!NAME} expands to the
-   value of the variable whose name is the value of NAME. */
-static WORD_DESC *
-parameter_brace_expand_indir (name, var_is_special, quoted, quoted_dollar_atp, contains_dollar_at)
+static char *
+parameter_brace_find_indir (name, var_is_special, quoted, find_nameref)
      char *name;
-     int var_is_special, quoted;
-     int *quoted_dollar_atp, *contains_dollar_at;
+     int var_is_special, quoted, find_nameref;
 {
   char *temp, *t;
   WORD_DESC *w;
+  SHELL_VAR *v;
+
+  if (find_nameref && var_is_special == 0 && (v = find_variable_last_nameref (name)) &&
+      nameref_p (v) && (t = nameref_cell (v)) && *t)
+    return (savestring (t));
 
-  w = parameter_brace_expand_word (name, var_is_special, quoted, PF_IGNUNBOUND);
+  /* If var_is_special == 0, and name is not an array reference, this does
+     more expansion than necessary.  It should really look up the variable's
+     value and not try to expand it. */
+  w = parameter_brace_expand_word (name, var_is_special, quoted, PF_IGNUNBOUND, 0);
   t = w->word;
   /* Have to dequote here if necessary */
   if (t)
@@ -5304,11 +5889,44 @@ parameter_brace_expand_indir (name, var_is_special, quoted, quoted_dollar_atp, c
     }
   dispose_word_desc (w);
 
+  return t;
+}
+  
+/* Expand an indirect reference to a variable: ${!NAME} expands to the
+   value of the variable whose name is the value of NAME. */
+static WORD_DESC *
+parameter_brace_expand_indir (name, var_is_special, quoted, quoted_dollar_atp, contains_dollar_at)
+     char *name;
+     int var_is_special, quoted;
+     int *quoted_dollar_atp, *contains_dollar_at;
+{
+  char *temp, *t;
+  WORD_DESC *w;
+  SHELL_VAR *v;
+
+  /* See if it's a nameref first, behave in ksh93-compatible fashion.
+     There is at least one incompatibility: given ${!foo[0]} where foo=bar,
+     bash performs an indirect lookup on foo[0] and expands the result;
+     ksh93 expands bar[0].  We could do that here -- there are enough usable
+     primitives to do that -- but do not at this point. */
+  if (var_is_special == 0 && (v = find_variable_last_nameref (name)))
+    {
+      if (nameref_p (v) && (t = nameref_cell (v)) && *t)
+       {
+         w = alloc_word_desc ();
+         w->word = savestring (t);
+         w->flags = 0;
+         return w;
+       }
+    }
+
+  t = parameter_brace_find_indir (name, var_is_special, quoted, 0);
+
   chk_atstar (t, quoted, quoted_dollar_atp, contains_dollar_at);
   if (t == 0)
     return (WORD_DESC *)NULL;
 
-  w = parameter_brace_expand_word (t, SPECIAL_VAR(t, 0), quoted, 0);
+  w = parameter_brace_expand_word (t, SPECIAL_VAR(t, 0), quoted, 0, 0);
   free (t);
 
   return w;
@@ -5362,6 +5980,16 @@ parameter_brace_expand_rhs (name, value, c, quoted, qdollaratp, hasdollarat)
         is the only expansion that creates more than one word. */
       if (qdollaratp && ((hasdol && quoted) || l->next))
        *qdollaratp = 1;
+      /* If we have a quoted null result (QUOTED_NULL(temp)) and the word is
+        a quoted null (l->next == 0 && QUOTED_NULL(l->word->word)), the
+        flags indicate it (l->word->flags & W_HASQUOTEDNULL), and the
+        expansion is quoted (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+        (which is more paranoia than anything else), we need to return the
+        quoted null string and set the flags to indicate it. */
+      if (l->next == 0 && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && QUOTED_NULL (temp) && QUOTED_NULL (l->word->word) && (l->word->flags & W_HASQUOTEDNULL))
+       {
+         w->flags |= W_HASQUOTEDNULL;
+       }
       dispose_words (l);
     }
   else if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && hasdol)
@@ -5392,9 +6020,15 @@ parameter_brace_expand_rhs (name, value, c, quoted, qdollaratp, hasdollarat)
   else
 #endif /* ARRAY_VARS */
   bind_variable (name, t1, 0);
-  free (t1);
+#if 0
+  if (STREQ (name, "IFS") == 0)
+#endif
+    stupidly_hack_special_variables (name);
+
+  /* From Posix group discussion Feb-March 2010.  Issue 7 0000221 */
+  free (temp);
 
-  w->word = temp;
+  w->word = t1;
   return w;
 }
 
@@ -5409,6 +6043,7 @@ parameter_brace_expand_error (name, value)
   WORD_LIST *l;
   char *temp;
 
+  last_command_exit_value = EXECUTION_FAILURE; /* ensure it's non-zero */
   if (value && *value)
     {
       l = expand_string (value, 0);
@@ -5441,34 +6076,6 @@ valid_length_expression (name)
          legal_identifier (name + 1));                         /* ${#PS1} */
 }
 
-#if defined (HANDLE_MULTIBYTE)
-size_t
-mbstrlen (s)
-     const char *s;
-{
-  size_t clen, nc;
-  mbstate_t mbs, mbsbak;
-
-  nc = 0;
-  memset (&mbs, 0, sizeof (mbs));
-  mbsbak = mbs;
-  while ((clen = mbrlen(s, MB_CUR_MAX, &mbs)) != 0)
-    {
-      if (MB_INVALIDCH(clen))
-        {
-         clen = 1;     /* assume single byte */
-         mbs = mbsbak;
-        }
-
-      s += clen;
-      nc++;
-      mbsbak = mbs;
-    }
-  return nc;
-}
-#endif
-      
-
 /* Handle the parameter brace expansion that requires us to return the
    length of a parameter. */
 static intmax_t
@@ -5502,7 +6109,7 @@ parameter_brace_expand_length (name)
          break;
        case '!':
          if (last_asynchronous_pid == NO_PID)
-           t = (char *)NULL;
+           t = (char *)NULL;   /* XXX - error if set -u set? */
          else
            t = itos (last_asynchronous_pid);
          break;
@@ -5524,6 +6131,8 @@ parameter_brace_expand_length (name)
       if (legal_number (name + 1, &arg_index))         /* ${#1} */
        {
          t = get_dollar_var_value (arg_index);
+         if (t == 0 && unbound_vars_is_error)
+           return INTMAX_MIN;
          number = MB_STRLEN (t);
          FREE (t);
        }
@@ -5534,6 +6143,8 @@ parameter_brace_expand_length (name)
            t = assoc_reference (assoc_cell (var), "0");
          else
            t = array_reference (array_cell (var), 0);
+         if (t == 0 && unbound_vars_is_error)
+           return INTMAX_MIN;
          number = MB_STRLEN (t);
        }
 #endif
@@ -5547,7 +6158,7 @@ parameter_brace_expand_length (name)
          if (list)
            dispose_words (list);
 
-         number = MB_STRLEN (t);
+         number = t ? MB_STRLEN (t) : 0;
          FREE (t);
        }
     }
@@ -5706,7 +6317,13 @@ verify_substring_values (v, value, substr, vtype, e1p, e2p)
       free (temp1);
       if (expok == 0)
        return (0);
-      if (*e2p < 0)
+#if 1
+      if ((vtype == VT_ARRAYVAR || vtype == VT_POSPARMS) && *e2p < 0)
+#else
+      /* bash-4.3: allow positional parameter length < 0 to count backwards
+        from end of positional parameters */
+      if (vtype == VT_ARRAYVAR && *e2p < 0)
+#endif
        {
          internal_error (_("%s: substring expression < 0"), t);
          return (0);
@@ -5718,7 +6335,17 @@ verify_substring_values (v, value, substr, vtype, e1p, e2p)
       if (vtype != VT_ARRAYVAR)
 #endif
        {
-         *e2p += *e1p;         /* want E2 chars starting at E1 */
+         if (*e2p < 0)
+           {
+             *e2p += len;
+             if (*e2p < 0 || *e2p < *e1p)
+               {
+                 internal_error (_("%s: substring expression < 0"), t);
+                 return (0);
+               }
+           }
+         else
+           *e2p += *e1p;               /* want E2 chars starting at E1 */
          if (*e2p > len)
            *e2p = len;
        }
@@ -5732,37 +6359,61 @@ verify_substring_values (v, value, substr, vtype, e1p, e2p)
 /* Return the type of variable specified by VARNAME (simple variable,
    positional param, or array variable).  Also return the value specified
    by VARNAME (value of a variable or a reference to an array element).
+   QUOTED is the standard description of quoting state, using Q_* defines.
+   FLAGS is currently a set of flags to pass to array_value.  If IND is
+   non-null and not INTMAX_MIN, and FLAGS includes AV_USEIND, IND is
+   passed to array_value so the array index is not computed again.
    If this returns VT_VARIABLE, the caller assumes that CTLESC and CTLNUL
    characters in the value are quoted with CTLESC and takes appropriate
    steps.  For convenience, *VALP is set to the dequoted VALUE. */
 static int
-get_var_and_type (varname, value, quoted, varp, valp)
+get_var_and_type (varname, value, ind, quoted, flags, varp, valp)
      char *varname, *value;
-     int quoted;
+     arrayind_t ind;
+     int quoted, flags;
      SHELL_VAR **varp;
      char **valp;
 {
-  int vtype;
-  char *temp;
+  int vtype, want_indir;
+  char *temp, *vname;
+  WORD_DESC *wd;
 #if defined (ARRAY_VARS)
   SHELL_VAR *v;
 #endif
+  arrayind_t lind;
 
+  want_indir = *varname == '!' &&
+    (legal_variable_starter ((unsigned char)varname[1]) || DIGIT (varname[1])
+                                       || VALID_INDIR_PARAM (varname[1]));
+  if (want_indir)
+    vname = parameter_brace_find_indir (varname+1, SPECIAL_VAR (varname, 1), quoted, 1);
+  else
+    vname = varname;
+    
   /* This sets vtype to VT_VARIABLE or VT_POSPARMS */
-  vtype = (varname[0] == '@' || varname[0] == '*') && varname[1] == '\0';
-  if (vtype == VT_POSPARMS && varname[0] == '*')
+  vtype = (vname[0] == '@' || vname[0] == '*') && vname[1] == '\0';
+  if (vtype == VT_POSPARMS && vname[0] == '*')
     vtype |= VT_STARSUB;
   *varp = (SHELL_VAR *)NULL;
 
 #if defined (ARRAY_VARS)
-  if (valid_array_reference (varname))
+  if (valid_array_reference (vname))
     {
-      v = array_variable_part (varname, &temp, (int *)0);
+      v = array_variable_part (vname, &temp, (int *)0);
+      /* If we want to signal array_value to use an already-computed index,
+        set LIND to that index */
+      lind = (ind != INTMAX_MIN && (flags & AV_USEIND)) ? ind : 0;
+      if (v && invisible_p (v))
+       {
+         vtype = VT_ARRAYMEMBER;
+         *varp = (SHELL_VAR *)NULL;
+         *valp = (char *)NULL;
+       }
       if (v && (array_p (v) || assoc_p (v)))
        { /* [ */
          if (ALL_ELEMENT_SUB (temp[0]) && temp[1] == ']')
            {
-             /* Callers have to differentiate betwen indexed and associative */
+             /* Callers have to differentiate between indexed and associative */
              vtype = VT_ARRAYVAR;
              if (temp[0] == '*')
                vtype |= VT_STARSUB;
@@ -5771,7 +6422,7 @@ get_var_and_type (varname, value, quoted, varp, valp)
          else
            {
              vtype = VT_ARRAYMEMBER;
-             *valp = array_value (varname, 1, (int *)NULL);
+             *valp = array_value (vname, Q_DOUBLE_QUOTES, flags, (int *)NULL, &lind);
            }
          *varp = v;
        }
@@ -5788,10 +6439,10 @@ get_var_and_type (varname, value, quoted, varp, valp)
        {
          vtype = VT_ARRAYMEMBER;
          *varp = v;
-         *valp = array_value (varname, 1, (int *)NULL);
+         *valp = array_value (vname, Q_DOUBLE_QUOTES, flags, (int *)NULL, &lind);
        }
     }
-  else if ((v = find_variable (varname)) && (invisible_p (v) == 0) && (assoc_p (v) || array_p (v)))
+  else if ((v = find_variable (vname)) && (invisible_p (v) == 0) && (assoc_p (v) || array_p (v)))
     {
       vtype = VT_ARRAYMEMBER;
       *varp = v;
@@ -5811,6 +6462,9 @@ get_var_and_type (varname, value, quoted, varp, valp)
        *valp = value;
     }
 
+  if (want_indir)
+    free (vname);
+
   return vtype;
 }
 
@@ -5855,9 +6509,11 @@ mb_substring (string, s, e)
    VARNAME.  If VARNAME is an array variable, use the array elements. */
 
 static char *
-parameter_brace_substring (varname, value, substr, quoted)
-     char *varname, *value, *substr;
-     int quoted;
+parameter_brace_substring (varname, value, ind, substr, quoted, flags)
+     char *varname, *value;
+     int ind;
+     char *substr;
+     int quoted, flags;
 {
   intmax_t e1, e2;
   int vtype, r, starsub;
@@ -5870,7 +6526,7 @@ parameter_brace_substring (varname, value, substr, quoted)
   oname = this_command_name;
   this_command_name = varname;
 
-  vtype = get_var_and_type (varname, value, quoted, &v, &val);
+  vtype = get_var_and_type (varname, value, ind, quoted, flags, &v, &val);
   if (vtype == -1)
     {
       this_command_name = oname;
@@ -5883,7 +6539,11 @@ parameter_brace_substring (varname, value, substr, quoted)
   r = verify_substring_values (v, val, substr, vtype, &e1, &e2);
   this_command_name = oname;
   if (r <= 0)
-    return ((r == 0) ? &expand_param_error : (char *)NULL);
+    {
+      if (vtype == VT_VARIABLE)
+       FREE (val);
+      return ((r == 0) ? &expand_param_error : (char *)NULL);
+    }
 
   switch (vtype)
     {
@@ -5942,26 +6602,52 @@ parameter_brace_substring (varname, value, substr, quoted)
 /*                                                             */
 /****************************************************************/
 
+static int
+shouldexp_replacement (s)
+     char *s;
+{
+  register char *p;
+
+  for (p = s; p && *p; p++)
+    {
+      if (*p == '\\')
+       p++;
+      else if (*p == '&')
+       return 1;
+    }
+  return 0;
+}
+
 char *
 pat_subst (string, pat, rep, mflags)
      char *string, *pat, *rep;
      int mflags;
 {
-  char *ret, *s, *e, *str;
-  int rsize, rptr, l, replen, mtype;
+  char *ret, *s, *e, *str, *rstr, *mstr;
+  int rsize, rptr, l, replen, mtype, rxpand, rslen, mlen;
+
+  if (string  == 0)
+    return (savestring (""));
 
   mtype = mflags & MATCH_TYPEMASK;
 
+#if 0  /* bash-4.2 ? */
+  rxpand = (rep && *rep) ? shouldexp_replacement (rep) : 0;
+#else
+  rxpand = 0;
+#endif
+
   /* Special cases:
    *   1.  A null pattern with mtype == MATCH_BEG means to prefix STRING
    *       with REP and return the result.
    *   2.  A null pattern with mtype == MATCH_END means to append REP to
    *       STRING and return the result.
+   * These don't understand or process `&' in the replacement string.
    */
   if ((pat == 0 || *pat == 0) && (mtype == MATCH_BEG || mtype == MATCH_END))
     {
       replen = STRLEN (rep);
-      l = strlen (string);
+      l = STRLEN (string);
       ret = (char *)xmalloc (replen + l + 2);
       if (replen == 0)
        strcpy (ret, string);
@@ -5986,7 +6672,25 @@ pat_subst (string, pat, rep, mflags)
       if (match_pattern (str, pat, mtype, &s, &e) == 0)
        break;
       l = s - str;
-      RESIZE_MALLOCED_BUFFER (ret, rptr, (l + replen), rsize, 64);
+
+      if (rxpand)
+        {
+          int x;
+          mlen = e - s;
+          mstr = xmalloc (mlen + 1);
+         for (x = 0; x < mlen; x++)
+           mstr[x] = s[x];
+          mstr[mlen] = '\0';
+          rstr = strcreplace (rep, '&', mstr, 0);
+          rslen = strlen (rstr);
+        }
+      else
+        {
+          rstr = rep;
+          rslen = replen;
+        }
+        
+      RESIZE_MALLOCED_BUFFER (ret, rptr, (l + rslen), rsize, 64);
 
       /* OK, now copy the leading unmatched portion of the string (from
         str to s) to ret starting at rptr (the current offset).  Then copy
@@ -5999,20 +6703,29 @@ pat_subst (string, pat, rep, mflags)
        }
       if (replen)
        {
-         strncpy (ret + rptr, rep, replen);
-         rptr += replen;
+         strncpy (ret + rptr, rstr, rslen);
+         rptr += rslen;
        }
       str = e;         /* e == end of match */
 
+      if (rstr != rep)
+       free (rstr);
+
       if (((mflags & MATCH_GLOBREP) == 0) || mtype != MATCH_ANY)
        break;
 
       if (s == e)
-       e++, str++;             /* avoid infinite recursion on zero-length match */
+       {
+         /* On a zero-length match, make sure we copy one character, since
+            we increment one character to avoid infinite recursion. */
+         RESIZE_MALLOCED_BUFFER (ret, rptr, 1, rsize, 64);
+         ret[rptr++] = *str++;
+         e++;          /* avoid infinite recursion on zero-length match */
+       }
     }
 
   /* Now copy the unmatched portion of the input string */
-  if (*str)
+  if (str && *str)
     {
       RESIZE_MALLOCED_BUFFER (ret, rptr, STRLEN(str) + 1, rsize, 64);
       strcpy (ret + rptr, str);
@@ -6050,18 +6763,7 @@ pos_params_pat_subst (string, pat, rep, mflags)
   pchar = (mflags & MATCH_STARSUB) == MATCH_STARSUB ? '*' : '@';
   qflags = (mflags & MATCH_QUOTED) == MATCH_QUOTED ? Q_DOUBLE_QUOTES : 0;
 
-#if 0
-  if ((mflags & (MATCH_QUOTED|MATCH_STARSUB)) == (MATCH_QUOTED|MATCH_STARSUB))
-    ret = string_list_dollar_star (quote_list (save));
-  else if ((mflags & MATCH_STARSUB) == MATCH_STARSUB)
-    ret = string_list_dollar_star (save);
-  else if ((mflags & MATCH_QUOTED) == MATCH_QUOTED)
-    ret = string_list_dollar_at (save, qflags);
-  else
-    ret = string_list_dollar_star (save);
-#else
   ret = string_list_pos_params (pchar, save, qflags);
-#endif
 
   dispose_words (save);
 
@@ -6073,9 +6775,11 @@ pos_params_pat_subst (string, pat, rep, mflags)
    and the string to substitute.  QUOTED is a flags word containing
    the type of quoting currently in effect. */
 static char *
-parameter_brace_patsub (varname, value, patsub, quoted)
-     char *varname, *value, *patsub;
-     int quoted;
+parameter_brace_patsub (varname, value, ind, patsub, quoted, flags)
+     char *varname, *value;
+     int ind;
+     char *patsub;
+     int quoted, flags;
 {
   int vtype, mflags, starsub, delim;
   char *val, *temp, *pat, *rep, *p, *lpatsub, *tt;
@@ -6086,7 +6790,7 @@ parameter_brace_patsub (varname, value, patsub, quoted)
 
   this_command_name = varname;
 
-  vtype = get_var_and_type (varname, value, quoted, &v, &val);
+  vtype = get_var_and_type (varname, value, ind, quoted, flags, &v, &val);
   if (vtype == -1)
     return ((char *)NULL);
 
@@ -6094,7 +6798,8 @@ parameter_brace_patsub (varname, value, patsub, quoted)
   vtype &= ~VT_STARSUB;
 
   mflags = 0;
-  if (patsub && *patsub == '/')
+  /* PATSUB is never NULL when this is called. */
+  if (*patsub == '/')
     {
       mflags |= MATCH_GLOBREP;
       patsub++;
@@ -6112,12 +6817,6 @@ parameter_brace_patsub (varname, value, patsub, quoted)
 
   /* If the pattern starts with a `/', make sure we skip over it when looking
      for the replacement delimiter. */
-#if 0
-  if (rep = quoted_strchr ((*patsub == '/') ? lpatsub+1 : lpatsub, '/', ST_BACKSL))
-    *rep++ = '\0';
-  else
-    rep = (char *)NULL;
-#else
   delim = skip_to_delim (lpatsub, ((*patsub == '/') ? 1 : 0), "/", 0);
   if (lpatsub[delim] == '/')
     {
@@ -6126,7 +6825,6 @@ parameter_brace_patsub (varname, value, patsub, quoted)
     }
   else
     rep = (char *)NULL;
-#endif
 
   if (rep && *rep == '\0')
     rep = (char *)NULL;
@@ -6137,7 +6835,14 @@ parameter_brace_patsub (varname, value, patsub, quoted)
 
   if (rep)
     {
-      if ((mflags & MATCH_QUOTED) == 0)
+      /* We want to perform quote removal on the expanded replacement even if
+        the entire expansion is double-quoted because the parser and string
+        extraction functions treated quotes in the replacement string as
+        special.  THIS IS NOT BACKWARDS COMPATIBLE WITH BASH-4.2. */
+      if (shell_compatibility_level > 42)
+       rep = expand_string_if_necessary (rep, quoted & ~(Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT), expand_string_unsplit);
+      /* This is the bash-4.2 code. */      
+      else if ((mflags & MATCH_QUOTED) == 0)
        rep = expand_string_if_necessary (rep, quoted, expand_string_unsplit);
       else
        rep = expand_string_to_string_internal (rep, quoted, expand_string_unsplit);
@@ -6261,11 +6966,11 @@ pos_params_modcase (string, pat, modop, mflags)
    to perform.  QUOTED is a flags word containing the type of quoting
    currently in effect. */
 static char *
-parameter_brace_casemod (varname, value, modspec, patspec, quoted)
+parameter_brace_casemod (varname, value, ind, modspec, patspec, quoted, flags)
      char *varname, *value;
-     int modspec;
+     int ind, modspec;
      char *patspec;
-     int quoted;
+     int quoted, flags;
 {
   int vtype, starsub, modop, mflags, x;
   char *val, *temp, *pat, *p, *lpat, *tt;
@@ -6276,7 +6981,7 @@ parameter_brace_casemod (varname, value, modspec, patspec, quoted)
 
   this_command_name = varname;
 
-  vtype = get_var_and_type (varname, value, quoted, &v, &val);
+  vtype = get_var_and_type (varname, value, ind, quoted, flags, &v, &val);
   if (vtype == -1)
     return ((char *)NULL);
 
@@ -6375,9 +7080,9 @@ chk_arithsub (s, len)
   i = count = 0;
   while (i < len)
     {
-      if (s[i] == '(')
+      if (s[i] == LPAREN)
        count++;
-      else if (s[i] == ')')
+      else if (s[i] == RPAREN)
        {
          count--;
          if (count < 0)
@@ -6417,9 +7122,9 @@ chk_arithsub (s, len)
 
 /* ${[#][!]name[[:][^[^]][,[,]]#[#]%[%]-=?+[word][:e1[:e2]]]} */
 static WORD_DESC *
-parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_dollar_at)
+parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, contains_dollar_at)
      char *string;
-     int *indexp, quoted, *quoted_dollar_atp, *contains_dollar_at;
+     int *indexp, quoted, *quoted_dollar_atp, *contains_dollar_at, pflags;
 {
   int check_nullness, var_is_set, var_is_null, var_is_special;
   int want_substring, want_indir, want_patsub, want_casemod;
@@ -6427,6 +7132,7 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
   WORD_DESC *tdesc, *ret;
   int t_index, sindex, c, tflag, modspec;
   intmax_t number;
+  arrayind_t ind;
 
   temp = temp1 = value = (char *)NULL;
   var_is_set = var_is_null = var_is_special = check_nullness = 0;
@@ -6453,23 +7159,17 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
   ret = 0;
   tflag = 0;
 
+  ind = INTMAX_MIN;
+
   /* If the name really consists of a special variable, then make sure
      that we have the entire name.  We don't allow indirect references
      to special variables except `#', `?', `@' and `*'. */
-  if ((sindex == t_index &&
-       (string[t_index] == '-' ||
-        string[t_index] == '?' ||
-        string[t_index] == '#')) ||
-      (sindex == t_index - 1 && string[sindex] == '!' &&
-       (string[t_index] == '#' ||
-        string[t_index] == '?' ||
-        string[t_index] == '@' ||
-        string[t_index] == '*')))
+  if ((sindex == t_index && VALID_SPECIAL_LENGTH_PARAM (string[t_index])) ||
+      (sindex == t_index - 1 && string[sindex] == '!' && VALID_INDIR_PARAM (string[t_index])))
     {
       t_index++;
-      free (name);
       temp1 = string_extract (string, &t_index, "#%:-=?+/}", 0);
-      name = (char *)xmalloc (3 + (strlen (temp1)));
+      name = (char *)xrealloc (name, 3 + (strlen (temp1)));
       *name = string[sindex];
       if (string[sindex] == '!')
        {
@@ -6500,7 +7200,7 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
     }
   else if (c == ':' && string[sindex] != RBRACE)
     want_substring = 1;
-  else if (c == '/' && string[sindex] != RBRACE)
+  else if (c == '/' /* && string[sindex] != RBRACE */) /* XXX */
     want_patsub = 1;
 #if defined (CASEMOD_EXPANSIONS)
   else if (c == '^' || c == ',' || c == '~')
@@ -6559,6 +7259,13 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
        }
 
       number = parameter_brace_expand_length (name);
+      if (number == INTMAX_MIN && unbound_vars_is_error)
+       {
+         last_command_exit_value = EXECUTION_FAILURE;
+         err_unboundvar (name+1);
+         free (name);
+         return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
+       }
       free (name);
 
       *indexp = sindex;
@@ -6580,6 +7287,8 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
 
       if (contains_dollar_at)
        *contains_dollar_at = 1;
+
+      tflag |= W_DOLLARAT;
     }
 
   /* Process ${!PREFIX*} expansion. */
@@ -6604,14 +7313,19 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
            *quoted_dollar_atp = 1;
          if (contains_dollar_at)
            *contains_dollar_at = 1;
+
+         tflag |= W_DOLLARAT;
        }
       free (x);
       dispose_words (xlist);
       free (temp1);
       *indexp = sindex;
 
+      free (name);
+
       ret = alloc_word_desc ();
       ret->word = temp;
+      ret->flags = tflag;      /* XXX */
       return ret;
     }
 
@@ -6634,6 +7348,8 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
                *quoted_dollar_atp = 1;
              if (contains_dollar_at)
                *contains_dollar_at = 1;
+
+             tflag |= W_DOLLARAT;
            }       
 
          free (temp1);
@@ -6641,6 +7357,7 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
 
          ret = alloc_word_desc ();
          ret->word = temp;
+         ret->flags = tflag;   /* XXX */
          return ret;
        }
 
@@ -6657,9 +7374,15 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
     }
 
   if (want_indir)
-    tdesc = parameter_brace_expand_indir (name + 1, var_is_special, quoted, quoted_dollar_atp, contains_dollar_at);
+    {
+      tdesc = parameter_brace_expand_indir (name + 1, var_is_special, quoted, quoted_dollar_atp, contains_dollar_at);
+      /* Turn off the W_ARRAYIND flag because there is no way for this function
+        to return the index we're supposed to be using. */
+      if (tdesc && tdesc->flags)
+       tdesc->flags &= ~W_ARRAYIND;
+    }
   else
-    tdesc = parameter_brace_expand_word (name, var_is_special, quoted, PF_IGNUNBOUND);
+    tdesc = parameter_brace_expand_word (name, var_is_special, quoted, PF_IGNUNBOUND|(pflags&(PF_NOSPLIT2|PF_ASSIGNRHS)), &ind);
 
   if (tdesc)
     {
@@ -6670,6 +7393,13 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
   else
     temp = (char  *)0;
 
+  if (temp == &expand_param_error || temp == &expand_param_fatal)
+    {
+      FREE (name);
+      FREE (value);
+      return (temp == &expand_param_error ? &expand_wdesc_error : &expand_wdesc_fatal);
+    }
+
 #if defined (ARRAY_VARS)
   if (valid_array_reference (name))
     chk_atstar (name, quoted, quoted_dollar_atp, contains_dollar_at);
@@ -6677,13 +7407,16 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
 
   var_is_set = temp != (char *)0;
   var_is_null = check_nullness && (var_is_set == 0 || *temp == 0);
+  /* XXX - this may not need to be restricted to special variables */
+  if (check_nullness)
+    var_is_null |= var_is_set && var_is_special && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && QUOTED_NULL (temp);
 
   /* Get the rest of the stuff inside the braces. */
   if (c && c != RBRACE)
     {
       /* Extract the contents of the ${ ... } expansion
         according to the Posix.2 rules. */
-      value = extract_dollar_brace_string (string, &sindex, quoted, 0);
+      value = extract_dollar_brace_string (string, &sindex, quoted, (c == '%' || c == '#' || c =='/' || c == '^' || c == ',' || c ==':') ? SX_POSIXEXP|SX_WORD : SX_WORD);
       if (string[sindex] == RBRACE)
        sindex++;
       else
@@ -6694,10 +7427,25 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
 
   *indexp = sindex;
 
+  /* All the cases where an expansion can possibly generate an unbound
+     variable error. */
+  if (want_substring || want_patsub || want_casemod || c == '#' || c == '%' || c == RBRACE)
+    {
+      if (var_is_set == 0 && unbound_vars_is_error && ((name[0] != '@' && name[0] != '*') || name[1]))
+       {
+         last_command_exit_value = EXECUTION_FAILURE;
+         err_unboundvar (name);
+         FREE (value);
+         FREE (temp);
+         free (name);
+         return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
+       }
+    }
+    
   /* If this is a substring spec, process it and add the result. */
   if (want_substring)
     {
-      temp1 = parameter_brace_substring (name, temp, value, quoted);
+      temp1 = parameter_brace_substring (name, temp, ind, value, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
       FREE (name);
       FREE (value);
       FREE (temp);
@@ -6709,13 +7457,19 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
 
       ret = alloc_word_desc ();
       ret->word = temp1;
-      if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+      /* We test quoted_dollar_atp because we want variants with double-quoted
+        "$@" to take a different code path. In fact, we make sure at the end
+        of expand_word_internal that we're only looking at these flags if
+        quoted_dollar_at == 0. */
+      if (temp1 && 
+          (quoted_dollar_atp == 0 || *quoted_dollar_atp == 0) &&
+         QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
        ret->flags |= W_QUOTED|W_HASQUOTEDNULL;
       return ret;
     }
   else if (want_patsub)
     {
-      temp1 = parameter_brace_patsub (name, temp, value, quoted);
+      temp1 = parameter_brace_patsub (name, temp, ind, value, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
       FREE (name);
       FREE (value);
       FREE (temp);
@@ -6727,16 +7481,16 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
 
       ret = alloc_word_desc ();
       ret->word = temp1;
-      ret = alloc_word_desc ();
-      ret->word = temp1;
-      if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+      if (temp1 && 
+          (quoted_dollar_atp == 0 || *quoted_dollar_atp == 0) &&
+         QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
        ret->flags |= W_QUOTED|W_HASQUOTEDNULL;
       return ret;
     }
 #if defined (CASEMOD_EXPANSIONS)
   else if (want_casemod)
     {
-      temp1 = parameter_brace_casemod (name, temp, modspec, value, quoted);
+      temp1 = parameter_brace_casemod (name, temp, ind, modspec, value, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
       FREE (name);
       FREE (value);
       FREE (temp);
@@ -6748,7 +7502,9 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
 
       ret = alloc_word_desc ();
       ret->word = temp1;
-      if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+      if (temp1 &&
+          (quoted_dollar_atp == 0 || *quoted_dollar_atp == 0) &&
+         QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
        ret->flags |= W_QUOTED|W_HASQUOTEDNULL;
       return ret;
     }
@@ -6760,6 +7516,7 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
     default:
     case '\0':
     bad_substitution:
+      last_command_exit_value = EXECUTION_FAILURE;
       report_error (_("%s: bad substitution"), string ? string : "??");
       FREE (value);
       FREE (temp);
@@ -6767,15 +7524,6 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
       return &expand_wdesc_error;
 
     case RBRACE:
-      if (var_is_set == 0 && unbound_vars_is_error && ((name[0] != '@' && name[0] != '*') || name[1]))
-       {
-         last_command_exit_value = EXECUTION_FAILURE;
-         err_unboundvar (name);
-         FREE (value);
-         FREE (temp);
-         free (name);
-         return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
-       }
       break;
 
     case '#':  /* ${param#[#]pattern} */
@@ -6785,9 +7533,10 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
          FREE (value);
          break;
        }
-      temp1 = parameter_brace_remove_pattern (name, temp, value, c, quoted);
+      temp1 = parameter_brace_remove_pattern (name, temp, ind, value, c, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
       free (temp);
       free (value);
+      free (name);
 
       ret = alloc_word_desc ();
       ret->word = temp1;
@@ -6803,7 +7552,6 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
        {
          /* If the operator is `+', we don't want the value of the named
             variable for anything, just the value of the right hand side. */
-
          if (c == '+')
            {
              /* XXX -- if we're double-quoted and the named variable is "$@",
@@ -6817,6 +7565,11 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
              FREE (temp);
              if (value)
                {
+                 /* From Posix discussion on austin-group list.  Issue 221
+                    requires that backslashes escaping `}' inside
+                    double-quoted ${...} be removed. */
+                 if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+                   quoted |= Q_DOLBRACE;
                  ret = parameter_brace_expand_rhs (name, value, c,
                                                    quoted,
                                                    quoted_dollar_atp,
@@ -6840,6 +7593,7 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
          temp = (char *)NULL;
          if (c == '=' && var_is_special)
            {
+             last_command_exit_value = EXECUTION_FAILURE;
              report_error (_("$%s: cannot assign in this way"), name);
              free (name);
              free (value);
@@ -6860,6 +7614,11 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
              if (contains_dollar_at)
                *contains_dollar_at = 0;
 
+             /* From Posix discussion on austin-group list.  Issue 221 requires
+                that backslashes escaping `}' inside double-quoted ${...} be
+                removed. */
+             if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+               quoted |= Q_DOLBRACE;
              ret = parameter_brace_expand_rhs (name, value, c, quoted,
                                                quoted_dollar_atp,
                                                contains_dollar_at);
@@ -6929,8 +7688,8 @@ param_expand (string, sindex, quoted, expanded_something,
          uerror[0] = '$';
          uerror[1] = c;
          uerror[2] = '\0';
-         err_unboundvar (uerror);
          last_command_exit_value = EXECUTION_FAILURE;
+         err_unboundvar (uerror);
          return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
        }
       if (temp1)
@@ -6977,8 +7736,8 @@ param_expand (string, sindex, quoted, expanded_something,
              uerror[0] = '$';
              uerror[1] = c;
              uerror[2] = '\0';
-             err_unboundvar (uerror);
              last_command_exit_value = EXECUTION_FAILURE;
+             err_unboundvar (uerror);
              return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
            }
        }
@@ -7015,19 +7774,22 @@ param_expand (string, sindex, quoted, expanded_something,
         even if it's quoted. */
       if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && list == 0)
        temp = (char *)NULL;
-      else if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+      else if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES|Q_PATQUOTE))
        {
          /* If we have "$*" we want to make a string of the positional
             parameters, separated by the first character of $IFS, and
             quote the whole string, including the separators.  If IFS
             is unset, the parameters are separated by ' '; if $IFS is
             null, the parameters are concatenated. */
-         temp = (quoted & Q_DOUBLE_QUOTES) ? string_list_dollar_star (list) : string_list (list);
-         temp1 = quote_string (temp);
-         if (*temp == 0)
-           tflag |= W_HASQUOTEDNULL;
-         free (temp);
-         temp = temp1;
+         temp = (quoted & (Q_DOUBLE_QUOTES|Q_PATQUOTE)) ? string_list_dollar_star (list) : string_list (list);
+         if (temp)
+           {
+             temp1 = quote_string (temp);
+             if (*temp == 0)
+               tflag |= W_HASQUOTEDNULL;
+             free (temp);
+             temp = temp1;
+           }
        }
       else
        {
@@ -7036,7 +7798,6 @@ param_expand (string, sindex, quoted, expanded_something,
             an assignment statement.  In that case, we don't separate the
             arguments at all.  Otherwise, if the $* is not quoted it is
             identical to $@ */
-#if 1
 #  if defined (HANDLE_MULTIBYTE)
          if (expand_no_split_dollar_star && ifs_firstc[0] == 0)
 #  else
@@ -7044,10 +7805,12 @@ param_expand (string, sindex, quoted, expanded_something,
 #  endif
            temp = string_list_dollar_star (list);
          else
-           temp = string_list_dollar_at (list, quoted);
-#else
-         temp = string_list_dollar_at (list, quoted);
-#endif
+           {
+             temp = string_list_dollar_at (list, quoted);
+             if (quoted == 0 && (ifs_is_set == 0 || ifs_is_null))
+               tflag |= W_SPLITSPACE;
+           }
+
          if (expand_no_split_dollar_star == 0 && contains_dollar_at)
            *contains_dollar_at = 1;
        }
@@ -7087,6 +7850,7 @@ param_expand (string, sindex, quoted, expanded_something,
         string might need it (consider "\"$@\""), but we need some
         way to signal that the final split on the first character
         of $IFS should be done, even though QUOTED is 1. */
+      /* XXX - should this test include Q_PATQUOTE? */
       if (quoted_dollar_at_p && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
        *quoted_dollar_at_p = 1;
       if (contains_dollar_at)
@@ -7097,13 +7861,18 @@ param_expand (string, sindex, quoted, expanded_something,
         We also want to make sure that splitting is done no matter what --
         according to POSIX.2, this expands to a list of the positional
         parameters no matter what IFS is set to. */
-      temp = string_list_dollar_at (list, quoted);
+      /* XXX - what to do when in a context where word splitting is not
+        performed? Even when IFS is not the default, posix seems to imply
+        that we behave like unquoted $* ?  Maybe we should use PF_NOSPLIT2
+        here. */
+      temp = string_list_dollar_at (list, (pflags & PF_ASSIGNRHS) ? (quoted|Q_DOUBLE_QUOTES) : quoted);
 
+      tflag |= W_DOLLARAT;
       dispose_words (list);
       break;
 
     case LBRACE:
-      tdesc = parameter_brace_expand (string, &zindex, quoted,
+      tdesc = parameter_brace_expand (string, &zindex, quoted, pflags,
                                      quoted_dollar_at_p,
                                      contains_dollar_at);
 
@@ -7162,6 +7931,9 @@ param_expand (string, sindex, quoted, expanded_something,
          if (chk_arithsub (temp2, t_index) == 0)
            {
              free (temp2);
+#if 0
+             internal_warning (_("future versions of the shell will force evaluation as an arithmetic substitution"));
+#endif
              goto comsub;
            }
 
@@ -7275,12 +8047,41 @@ comsub:
 
          goto return0;
        }
+      else if (var && (invisible_p (var) || var_isset (var) == 0))
+       temp = (char *)NULL;
+      else if ((var = find_variable_last_nameref (temp1)) && var_isset (var) && invisible_p (var) == 0)
+       {
+         temp = nameref_cell (var);
+#if defined (ARRAY_VARS)
+         if (temp && *temp && valid_array_reference (temp))
+           {
+             tdesc = parameter_brace_expand_word (temp, SPECIAL_VAR (temp, 0), quoted, pflags, (arrayind_t *)NULL);
+             if (tdesc == &expand_wdesc_error || tdesc == &expand_wdesc_fatal)
+               return (tdesc);
+             ret = tdesc;
+             goto return0;
+           }
+         else
+#endif
+         /* y=2 ; typeset -n x=y; echo $x is not the same as echo $2 in ksh */
+         if (temp && *temp && legal_identifier (temp) == 0)
+           {
+             last_command_exit_value = EXECUTION_FAILURE;
+             report_error (_("%s: invalid variable name for name reference"), temp);
+             return (&expand_wdesc_error);     /* XXX */
+           }
+         else
+           temp = (char *)NULL;
+       }
 
       temp = (char *)NULL;
 
 unbound_variable:
       if (unbound_vars_is_error)
-       err_unboundvar (temp1);
+       {
+         last_command_exit_value = EXECUTION_FAILURE;
+         err_unboundvar (temp1);
+       }
       else
        {
          free (temp1);
@@ -7376,8 +8177,10 @@ expand_word_internal (word, quoted, isexp, contains_dollar_at, expanded_somethin
 
   /* State flags */
   int had_quoted_null;
-  int has_dollar_at;
+  int has_dollar_at, temp_has_dollar_at;
+  int split_on_spaces;
   int tflag;
+  int pflags;                  /* flags passed to param_expand */
 
   int assignoff;               /* If assignment, offset of `=' */
 
@@ -7391,6 +8194,7 @@ expand_word_internal (word, quoted, isexp, contains_dollar_at, expanded_somethin
   istring = (char *)xmalloc (istring_size = DEFAULT_INITIAL_ARRAY_SIZE);
   istring[istring_index = 0] = '\0';
   quoted_dollar_at = had_quoted_null = has_dollar_at = 0;
+  split_on_spaces = 0;
   quoted_state = UNQUOTED;
 
   string = word->word;
@@ -7411,7 +8215,7 @@ expand_word_internal (word, quoted, isexp, contains_dollar_at, expanded_somethin
     {
       c = string[sindex];
 
-      /* Case on toplevel character. */
+      /* Case on top-level character. */
       switch (c)
        {
        case '\0':
@@ -7459,7 +8263,7 @@ add_string:
            else
              t_index = sindex + 1; /* skip past both '<' and LPAREN */
 
-           temp1 = extract_process_subst (string, (c == '<') ? "<(" : ">(", &t_index); /*))*/
+           temp1 = extract_process_subst (string, (c == '<') ? "<(" : ">(", &t_index, 0); /*))*/
            sindex = t_index;
 
            /* If the process substitution specification is `<()', we want to
@@ -7487,7 +8291,7 @@ add_string:
             even in POSIX mode. */     
          if (word->flags & (W_ASSIGNRHS|W_NOTILDE))
            {
-             if (isexp == 0 && isifs (c))
+             if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c))
                goto add_ifs_character;
              else
                goto add_character;
@@ -7507,7 +8311,7 @@ add_string:
                   string[sindex+1] == '~')
            word->flags |= W_ITILDE;
 #endif
-         if (isexp == 0 && isifs (c))
+         if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c))
            goto add_ifs_character;
          else
            goto add_character;
@@ -7515,7 +8319,7 @@ add_string:
        case ':':
          if (word->flags & W_NOTILDE)
            {
-             if (isexp == 0 && isifs (c))
+             if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c))
                goto add_ifs_character;
              else
                goto add_character;
@@ -7525,7 +8329,7 @@ add_string:
              string[sindex+1] == '~')
            word->flags |= W_ITILDE;
 
-         if (isexp == 0 && isifs (c))
+         if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c))
            goto add_ifs_character;
          else
            goto add_character;
@@ -7539,7 +8343,7 @@ add_string:
              (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
            {
              word->flags &= ~W_ITILDE;
-             if (isexp == 0 && isifs (c) && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) == 0)
+             if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c) && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) == 0)
                goto add_ifs_character;
              else
                goto add_character;
@@ -7580,11 +8384,17 @@ add_string:
          if (expanded_something)
            *expanded_something = 1;
 
-         has_dollar_at = 0;
+         temp_has_dollar_at = 0;
+         pflags = (word->flags & W_NOCOMSUB) ? PF_NOCOMSUB : 0;
+         if (word->flags & W_NOSPLIT2)
+           pflags |= PF_NOSPLIT2;
+         if (word->flags & W_ASSIGNRHS)
+           pflags |= PF_ASSIGNRHS;
          tword = param_expand (string, &sindex, quoted, expanded_something,
-                              &has_dollar_at, &quoted_dollar_at,
-                              &had_quoted_null,
-                              (word->flags & W_NOCOMSUB) ? PF_NOCOMSUB : 0);
+                              &temp_has_dollar_at, &quoted_dollar_at,
+                              &had_quoted_null, pflags);
+         has_dollar_at += temp_has_dollar_at;
+         split_on_spaces += (tword->flags & W_SPLITSPACE);
 
          if (tword == &expand_wdesc_error || tword == &expand_wdesc_fatal)
            {
@@ -7599,9 +8409,17 @@ add_string:
          if (tword && (tword->flags & W_HASQUOTEDNULL))
            had_quoted_null = 1;
 
-         temp = tword->word;
+         temp = tword ? tword->word : (char *)NULL;
          dispose_word_desc (tword);
 
+         /* Kill quoted nulls; we will add them back at the end of
+            expand_word_internal if nothing else in the string */
+         if (had_quoted_null && temp && QUOTED_NULL (temp))
+           {
+             FREE (temp);
+             temp = (char *)NULL;
+           }
+
          goto add_string;
          break;
 
@@ -7619,6 +8437,7 @@ add_string:
                    sindex = t_index;
                    goto add_character;
                  }
+               last_command_exit_value = EXECUTION_FAILURE;
                report_error (_("bad substitution: no closing \"`\" in %s") , string+t_index);
                free (string);
                free (istring);
@@ -7661,7 +8480,24 @@ add_string:
          else
            tflag = 0;
 
-         if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && ((sh_syntaxtab[c] & tflag) == 0))
+         /* From Posix discussion on austin-group list:  Backslash escaping
+            a } in ${...} is removed.  Issue 0000221 */
+         if ((quoted & Q_DOLBRACE) && c == RBRACE)
+           {
+             SCOPY_CHAR_I (twochars, CTLESC, c, string, sindex, string_size);
+           }
+         /* This is the fix for " $@\ " */
+         else if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && ((sh_syntaxtab[c] & tflag) == 0) && isexp == 0 && isifs (c))
+           {
+             RESIZE_MALLOCED_BUFFER (istring, istring_index, 2, istring_size,
+                                     DEFAULT_ARRAY_SIZE);
+             istring[istring_index++] = CTLESC;
+             istring[istring_index++] = '\\';
+             istring[istring_index] = '\0';
+
+             SCOPY_CHAR_I (twochars, CTLESC, c, string, sindex, string_size);
+           }
+         else if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && ((sh_syntaxtab[c] & tflag) == 0))
            {
              SCOPY_CHAR_I (twochars, '\\', c, string, sindex, string_size);
            }
@@ -7688,11 +8524,7 @@ add_twochars:
          break;
 
        case '"':
-#if 0
-         if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) || (word->flags & W_DQUOTE))
-#else
          if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
-#endif
            goto add_character;
 
          t_index = ++sindex;
@@ -7711,9 +8543,10 @@ add_twochars:
 
              temp = (char *)NULL;
 
-             has_dollar_at = 0;
+             temp_has_dollar_at = 0;   /* XXX */
              /* Need to get W_HASQUOTEDNULL flag through this function. */
-             list = expand_word_internal (tword, Q_DOUBLE_QUOTES, 0, &has_dollar_at, (int *)NULL);
+             list = expand_word_internal (tword, Q_DOUBLE_QUOTES, 0, &temp_has_dollar_at, (int *)NULL);
+             has_dollar_at += temp_has_dollar_at;
 
              if (list == &expand_word_error || list == &expand_word_fatal)
                {
@@ -7749,7 +8582,7 @@ add_twochars:
                dequote_list (list);
 
              if (list && list->word && (list->word->flags & W_HASQUOTEDNULL))
-               had_quoted_null = 1;
+               had_quoted_null = 1;            /* XXX */
 
              if (has_dollar_at)
                {
@@ -7814,8 +8647,11 @@ add_twochars:
            temp = (char *)NULL;
 
          /* We do not want to add quoted nulls to strings that are only
-            partially quoted; we can throw them away. */
-         if (temp == 0 && quoted_state == PARTIALLY_QUOTED)
+            partially quoted; we can throw them away.  The exception to
+            this is when we are going to be performing word splitting,
+            since we have to preserve a null argument if the next character
+            will cause word splitting. */
+         if (temp == 0 && quoted_state == PARTIALLY_QUOTED && (word->flags & (W_NOSPLIT|W_NOSPLIT2)))
            continue;
 
        add_quoted_string:
@@ -7838,11 +8674,7 @@ add_twochars:
          /* break; */
 
        case '\'':
-#if 0
-         if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) || (word->flags & W_DQUOTE))
-#else
          if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
-#endif
            goto add_character;
 
          t_index = ++sindex;
@@ -7991,17 +8823,20 @@ finished_with_string:
        tword->flags |= W_COMPASSIGN;   /* XXX */
       if (word->flags & W_NOGLOB)
        tword->flags |= W_NOGLOB;       /* XXX */
+      if (word->flags & W_NOBRACE)
+       tword->flags |= W_NOBRACE;      /* XXX */
       if (word->flags & W_NOEXPAND)
        tword->flags |= W_NOEXPAND;     /* XXX */
       if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
        tword->flags |= W_QUOTED;
-      if (had_quoted_null)
+      if (had_quoted_null && QUOTED_NULL (istring))
        tword->flags |= W_HASQUOTEDNULL;
       list = make_word_list (tword, (WORD_LIST *)NULL);
     }
   else
     {
       char *ifs_chars;
+      char *tstring;
 
       ifs_chars = (quoted_dollar_at || has_dollar_at) ? ifs_value : (char *)NULL;
 
@@ -8010,12 +8845,42 @@ finished_with_string:
         positional parameters with a space, so we split on space (we have
         set ifs_chars to " \t\n" above if ifs is unset).  If IFS is set,
         string_list_dollar_at has separated the positional parameters
-        with the first character of $IFS, so we split on $IFS. */
-      if (has_dollar_at && ifs_chars)
+        with the first character of $IFS, so we split on $IFS.  If
+        SPLIT_ON_SPACES is set, we expanded $* (unquoted) with IFS either
+        unset or null, and we want to make sure that we split on spaces
+        regardless of what else has happened to IFS since the expansion. */
+      if (split_on_spaces)
+       list = list_string (istring, " ", 1);   /* XXX quoted == 1? */
+      /* If we have $@ (has_dollar_at != 0) and we are in a context where we
+        don't want to split the result (W_NOSPLIT2), and we are not quoted,
+        we have already separated the arguments with the first character of
+        $IFS.  In this case, we want to return a list with a single word
+        with the separator possibly replaced with a space (it's what other
+        shells seem to do).
+        quoted_dollar_at is internal to this function and is set if we are
+        passed an argument that is unquoted (quoted == 0) but we encounter a
+        double-quoted $@ while expanding it. */
+      else if (has_dollar_at && quoted_dollar_at == 0 && ifs_chars && quoted == 0 && (word->flags & W_NOSPLIT2))
+       {
+         /* Only split and rejoin if we have to */
+         if (*ifs_chars && *ifs_chars != ' ')
+           {
+             list = list_string (istring, *ifs_chars ? ifs_chars : " ", 1);
+             tstring = string_list (list);
+           }
+         else
+           tstring = istring;
+         tword = make_bare_word (tstring);
+         if (tstring != istring)
+           free (tstring);
+         goto set_word_flags;
+       }
+      else if (has_dollar_at && ifs_chars)
        list = list_string (istring, *ifs_chars ? ifs_chars : " ", 1);
       else
        {
          tword = make_bare_word (istring);
+set_word_flags:
          if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) || (quoted_state == WHOLLY_QUOTED))
            tword->flags |= W_QUOTED;
          if (word->flags & W_ASSIGNMENT)
@@ -8024,9 +8889,11 @@ finished_with_string:
            tword->flags |= W_COMPASSIGN;
          if (word->flags & W_NOGLOB)
            tword->flags |= W_NOGLOB;
+         if (word->flags & W_NOBRACE)
+           tword->flags |= W_NOBRACE;
          if (word->flags & W_NOEXPAND)
            tword->flags |= W_NOEXPAND;
-         if (had_quoted_null)
+         if (had_quoted_null && QUOTED_NULL (istring))
            tword->flags |= W_HASQUOTEDNULL;    /* XXX */
          list = make_word_list (tword, (WORD_LIST *)NULL);
        }
@@ -8172,6 +9039,9 @@ setifs (v)
   ifs_var = v;
   ifs_value = (v && value_cell (v)) ? value_cell (v) : " \t\n";
 
+  ifs_is_set = ifs_var != 0;
+  ifs_is_null = ifs_is_set && (*ifs_value == 0);
+
   /* Should really merge ifs_cmap with sh_syntaxtab.  XXX - doesn't yet
      handle multibyte chars in IFS */
   memset (ifs_cmap, '\0', sizeof (ifs_cmap));
@@ -8297,7 +9167,7 @@ separate_out_assignments (tlist)
 {
   register WORD_LIST *vp, *lp;
 
-  if (!tlist)
+  if (tlist == 0)
     return ((WORD_LIST *)NULL);
 
   if (subst_assign_varlist)
@@ -8484,7 +9354,6 @@ glob_expand_word_list (tlist, eflags)
          for (glob_index = 0; glob_array[glob_index]; glob_index++)
            {
              tword = make_bare_word (glob_array[glob_index]);
-             tword->flags |= W_GLOBEXP;        /* XXX */
              glob_list = make_word_list (tword, glob_list);
            }
 
@@ -8495,6 +9364,7 @@ glob_expand_word_list (tlist, eflags)
            }
          else if (fail_glob_expansion != 0)
            {
+             last_command_exit_value = EXECUTION_FAILURE;
              report_error (_("no match: %s"), tlist->word->word);
              exp_jump_to_top_level (DISCARD);
            }
@@ -8549,27 +9419,45 @@ brace_expand_word_list (tlist, eflags)
     {
       next = tlist->next;
 
+      if (tlist->word->flags & W_NOBRACE)
+        {
+/*itrace("brace_expand_word_list: %s: W_NOBRACE", tlist->word->word);*/
+         PREPEND_LIST (tlist, output_list);
+         continue;
+        }
+
+      if ((tlist->word->flags & (W_COMPASSIGN|W_ASSIGNARG)) == (W_COMPASSIGN|W_ASSIGNARG))
+        {
+/*itrace("brace_expand_word_list: %s: W_COMPASSIGN|W_ASSIGNARG", tlist->word->word);*/
+         PREPEND_LIST (tlist, output_list);
+         continue;
+        }
+
       /* Only do brace expansion if the word has a brace character.  If
         not, just add the word list element to BRACES and continue.  In
         the common case, at least when running shell scripts, this will
-        degenerate to a bunch of calls to `xstrchr', and then what is
+        degenerate to a bunch of calls to `mbschr', and then what is
         basically a reversal of TLIST into BRACES, which is corrected
         by a call to REVERSE_LIST () on BRACES when the end of TLIST
         is reached. */
-      if (xstrchr (tlist->word->word, LBRACE))
+      if (mbschr (tlist->word->word, LBRACE))
        {
          expansions = brace_expand (tlist->word->word);
 
          for (eindex = 0; temp_string = expansions[eindex]; eindex++)
            {
-             w = make_word (temp_string);
+             w = alloc_word_desc ();
+             w->word = temp_string;
+
              /* If brace expansion didn't change the word, preserve
                 the flags.  We may want to preserve the flags
                 unconditionally someday -- XXX */
              if (STREQ (temp_string, tlist->word->word))
                w->flags = tlist->word->flags;
+             else
+               w = make_word_flags (w, temp_string);
+
              output_list = make_word_list (w, output_list);
-             free (expansions[eindex]);
            }
          free (expansions);
 
@@ -8642,11 +9530,40 @@ shell_expand_word_list (tlist, eflags)
       if ((tlist->word->flags & (W_COMPASSIGN|W_ASSIGNARG)) == (W_COMPASSIGN|W_ASSIGNARG))
        {
          int t;
+         char opts[8], opti;
 
-         if (tlist->word->flags & W_ASSIGNASSOC)
-           make_internal_declare (tlist->word->word, "-A");
+         opti = 0;
+         if (tlist->word->flags & (W_ASSIGNASSOC|W_ASSNGLOBAL|W_ASSIGNARRAY))
+           opts[opti++] = '-';
 
-         t = do_word_assignment (tlist->word);
+         if ((tlist->word->flags & (W_ASSIGNASSOC|W_ASSNGLOBAL)) == (W_ASSIGNASSOC|W_ASSNGLOBAL))
+           {
+             opts[opti++] = 'g';
+             opts[opti++] = 'A';
+           }
+         else if (tlist->word->flags & W_ASSIGNASSOC)
+           opts[opti++] = 'A';
+         else if ((tlist->word->flags & (W_ASSIGNARRAY|W_ASSNGLOBAL)) == (W_ASSIGNARRAY|W_ASSNGLOBAL))
+           {
+             opts[opti++] = 'g';
+             opts[opti++] = 'a';
+           }
+         else if (tlist->word->flags & W_ASSIGNARRAY)
+           opts[opti++] = 'a';
+         else if (tlist->word->flags & W_ASSNGLOBAL)
+           opts[opti++] = 'g';
+
+#if 0
+         /* If we have special handling note the integer attribute */
+         if (opti > 0 && (tlist->word->flags & W_ASSIGNINT))
+           opts[opti++] = 'i';
+#endif
+
+         opts[opti] = '\0';
+         if (opti > 0)
+           make_internal_declare (tlist->word->word, opts);
+
+         t = do_word_assignment (tlist->word, 0);
          if (t == 0)
            {
              last_command_exit_value = EXECUTION_FAILURE;
@@ -8656,7 +9573,7 @@ shell_expand_word_list (tlist, eflags)
          /* Now transform the word as ksh93 appears to do and go on */
          t = assignment (tlist->word->word, 0);
          tlist->word->word[t] = '\0';
-         tlist->word->flags &= ~(W_ASSIGNMENT|W_NOSPLIT|W_COMPASSIGN|W_ASSIGNARG|W_ASSIGNASSOC);
+         tlist->word->flags &= ~(W_ASSIGNMENT|W_NOSPLIT|W_COMPASSIGN|W_ASSIGNARG|W_ASSIGNASSOC|W_ASSIGNARRAY);
        }
 #endif
 
@@ -8721,7 +9638,9 @@ shell_expand_word_list (tlist, eflags)
    process substitution, word splitting, and pathname expansion, according
    to the bits set in EFLAGS.  Words with the W_QUOTED or W_NOSPLIT bits
    set, or for which no expansion is done, do not undergo word splitting.
-   Words with the W_NOGLOB bit set do not undergo pathname expansion. */
+   Words with the W_NOGLOB bit set do not undergo pathname expansion; words
+   with W_NOBRACE set do not undergo brace expansion (see
+   brace_expand_word_list above). */
 static WORD_LIST *
 expand_word_list_internal (list, eflags)
      WORD_LIST *list;
@@ -8730,6 +9649,7 @@ expand_word_list_internal (list, eflags)
   WORD_LIST *new_list, *temp_list;
   int tint;
 
+  tempenv_assign_error = 0;
   if (list == 0)
     return ((WORD_LIST *)NULL);
 
@@ -8746,7 +9666,7 @@ expand_word_list_internal (list, eflags)
              for (temp_list = subst_assign_varlist; temp_list; temp_list = temp_list->next)
                {
                  this_command_name = (char *)NULL;     /* no arithmetic errors */
-                 tint = do_word_assignment (temp_list->word);
+                 tint = do_word_assignment (temp_list->word, 0);
                  /* Variable assignment errors in non-interactive shells
                     running in Posix.2 mode cause the shell to exit. */
                  if (tint == 0)
@@ -8795,6 +9715,7 @@ expand_word_list_internal (list, eflags)
   if ((eflags & WEXP_VARASSIGN) && subst_assign_varlist)
     {
       sh_wassign_func_t *assign_func;
+      int is_special_builtin, is_builtin_or_func;
 
       /* If the remainder of the words expand to nothing, Posix.2 requires
         that the variable and environment assignments affect the shell's
@@ -8802,11 +9723,16 @@ expand_word_list_internal (list, eflags)
       assign_func = new_list ? assign_in_env : do_word_assignment;
       tempenv_assign_error = 0;
 
+      is_builtin_or_func = (new_list && new_list->word && (find_shell_builtin (new_list->word->word) || find_function (new_list->word->word)));
+      /* Posix says that special builtins exit if a variable assignment error
+        occurs in an assignment preceding it. */
+      is_special_builtin = (posixly_correct && new_list && new_list->word && find_special_builtin (new_list->word->word));
+      
       for (temp_list = subst_assign_varlist; temp_list; temp_list = temp_list->next)
        {
          this_command_name = (char *)NULL;
          assigning_in_environment = (assign_func == assign_in_env);
-         tint = (*assign_func) (temp_list->word);
+         tint = (*assign_func) (temp_list->word, is_builtin_or_func);
          assigning_in_environment = 0;
          /* Variable assignment errors in non-interactive shells running
             in Posix.2 mode cause the shell to exit. */
@@ -8815,7 +9741,7 @@ expand_word_list_internal (list, eflags)
              if (assign_func == do_word_assignment)
                {
                  last_command_exit_value = EXECUTION_FAILURE;
-                 if (interactive_shell == 0 && posixly_correct)
+                 if (interactive_shell == 0 && posixly_correct && is_special_builtin)
                    exp_jump_to_top_level (FORCE_EOF);
                  else
                    exp_jump_to_top_level (DISCARD);
@@ -8829,13 +9755,5 @@ expand_word_list_internal (list, eflags)
       subst_assign_varlist = (WORD_LIST *)NULL;
     }
 
-#if 0
-  tint = list_length (new_list) + 1;
-  RESIZE_MALLOCED_BUFFER (glob_argv_flags, 0, tint, glob_argv_flags_size, 16);
-  for (tint = 0, temp_list = new_list; temp_list; temp_list = temp_list->next)
-    glob_argv_flags[tint++] = (temp_list->word->flags & W_GLOBEXP) ? '1' : '0';
-  glob_argv_flags[tint] = '\0';
-#endif
-
   return (new_list);
 }