Imported from ../bash-2.02.tar.gz.
[platform/upstream/bash.git] / subst.c
diff --git a/subst.c b/subst.c
index a2640f7..cdc37d6 100644 (file)
--- a/subst.c
+++ b/subst.c
 #include "builtins/getopt.h"
 #include "builtins/common.h"
 
-#if defined (READLINE)
-#  include "bashline.h"
-#  include <readline/readline.h>
-#else
-#  include <tilde/tilde.h>
-#endif
-
-#if defined (HISTORY)
-#  include "bashhist.h"
-#  include <readline/history.h>
-#endif
-
+#include <tilde/tilde.h>
 #include <glob/fnmatch.h>
 
 #if !defined (errno)
@@ -82,8 +71,11 @@ extern int errno;
 #define ST_BACKSL      0x01
 #define ST_CTLESC      0x02
 
-/* How to quote character C. */
-static char *make_quoted_char ();
+/* These defs make it easier to use the editor. */
+#define LBRACE         '{'
+#define RBRACE         '}'
+#define LPAREN         '('
+#define RPAREN         ')'
 
 /* Process ID of the last command executed within command substitution. */
 pid_t last_command_subst_pid = NO_PID;
@@ -93,13 +85,9 @@ extern int last_command_exit_value, interactive, interactive_shell;
 extern int subshell_environment, startup_state;
 extern int dollar_dollar_pid;
 extern int posixly_correct;
-extern int eof_encountered, eof_encountered_limit, ignoreeof;
 extern char *this_command_name;
 extern struct fd_bitmap *current_fds_to_close;
-#if defined (READLINE)
-extern int no_line_editing;
-extern int hostname_list_initialized;
-#endif
+extern int wordexp_only;
 
 extern void getopts_reset ();
 
@@ -116,17 +104,25 @@ static int glob_argv_flags_size;
 static WORD_LIST expand_word_error, expand_word_fatal;
 static char expand_param_error, expand_param_fatal;
 
+static char *make_quoted_char ();
+static void remove_quoted_nulls ();
+static char *param_expand ();
+static char *maybe_expand_string ();
+static WORD_LIST *call_expand_word_internal ();
 static WORD_LIST *expand_string_internal ();
 static WORD_LIST *expand_word_internal (), *expand_word_list_internal ();
 static WORD_LIST *expand_string_leave_quoted ();
 static WORD_LIST *expand_string_for_rhs ();
 static WORD_LIST *word_list_split ();
 static WORD_LIST *quote_list (), *dequote_list ();
+static char *quote_escapes ();
+static WORD_LIST *list_quote_escapes ();
 static int unquoted_substring (), unquoted_member ();
 static int do_assignment_internal ();
 static char *string_extract_verbatim (), *string_extract ();
 static char *string_extract_double_quoted (), *string_extract_single_quoted ();
-static int skip_single_quoted (), skip_double_quoted ();
+static char *string_list_dollar_at (), *string_list_dollar_star ();
+static inline int skip_single_quoted (), skip_double_quoted ();
 static char *extract_delimited_string ();
 static char *extract_dollar_brace_string ();
 
@@ -220,87 +216,136 @@ quoted_strchr (s, c, flags)
   return ((char *)NULL);
 }
 
-/* Conventions:
-
-     A string with s[0] == CTLNUL && s[1] == 0 is a quoted null string.
-     The parser passes CTLNUL as CTLESC CTLNUL. */
-
-/* The parser passes us CTLESC as CTLESC CTLESC and CTLNUL as CTLESC CTLNUL.
-   This is necessary to make unquoted CTLESC and CTLNUL characters in the
-   data stream pass through properly.
-   Here we remove doubled CTLESC characters inside quoted strings before
-   quoting the entire string, so we do not double the number of CTLESC
-   characters. */
-static char *
-remove_quoted_escapes (string)
+/* Return 1 if CHARACTER appears in an unquoted portion of
+   STRING.  Return 0 otherwise. */
+static int
+unquoted_member (character, string)
+     int character;
      char *string;
 {
-  register char *s;
-  int docopy;
-  char *t, *t1;
-
-  if (string == NULL)
-    return (string);
+  int sindex, c;
 
-  t1 = t = xmalloc (strlen (string) + 1);
-  for (docopy = 0, s = string; *s; s++, t1++)
+  for (sindex = 0; c = string[sindex]; )
     {
-      if (*s == CTLESC && (s[1] == CTLESC || s[1] == CTLNUL))
+      if (c == character)
+       return (1);
+
+      switch (c)
        {
-         s++;
-         docopy = 1;
+       default:
+         sindex++;
+         break;
+
+       case '\\':
+         sindex++;
+         if (string[sindex])
+           sindex++;
+         break;
+
+       case '\'':
+         sindex = skip_single_quoted (string, ++sindex);
+         break;
+
+       case '"':
+         sindex = skip_double_quoted (string, ++sindex);
+         break;
        }
-      *t1 = *s;
     }
-  *t1 = '\0';
-  if (docopy)
-    strcpy (string, t);
-  free (t);
-  return (string);
+  return (0);
 }
 
-/* Quote escape characters in string s, but no other characters.  This is
-   used to protect CTLESC and CTLNUL in variable values from the rest of
-   the word expansion process after the variable is expanded. */
-static char *
-quote_escapes (string)
-     char *string;
+/* Return 1 if SUBSTR appears in an unquoted portion of STRING. */
+static int
+unquoted_substring (substr, string)
+     char *substr, *string;
 {
-  register char *s, *t;
-  char *result;
+  int sindex, c, sublen;
 
-  result = xmalloc ((strlen (string) * 2) + 1);
-  for (s = string, t = result; *s; )
+  if (substr == 0 || *substr == '\0')
+    return (0);
+
+  sublen = strlen (substr);
+  for (sindex = 0; c = string[sindex]; )
     {
-      if (*s == CTLESC || *s == CTLNUL)
-       *t++ = CTLESC;
-      *t++ = *s++;
+      if (STREQN (string + sindex, substr, sublen))
+       return (1);
+
+      switch (c)
+       {
+       case '\\':
+         sindex++;
+
+         if (string[sindex])
+           sindex++;
+         break;
+
+       case '\'':
+         sindex = skip_single_quoted (string, ++sindex);
+         break;
+
+       case '"':
+         sindex = skip_double_quoted (string, ++sindex);
+         break;
+
+       default:
+         sindex++;
+         break;
+       }
     }
-  *t = '\0';
-  return (result);
+  return (0);
 }
 
-#ifdef INCLUDE_UNUSED
-static char *
-dequote_escapes (string)
-     char *string;
-{
-  register char *s, *t;
-  char *result;
+/* Most of the substitutions must be done in parallel.  In order
+   to avoid using tons of unclear goto's, I have some functions
+   for manipulating malloc'ed strings.  They all take INDX, a
+   pointer to an integer which is the offset into the string
+   where manipulation is taking place.  They also take SIZE, a
+   pointer to an integer which is the current length of the
+   character array for this string. */
 
-  result = xmalloc (strlen (string) + 1);
-  for (s = string, t = result; *s; )
+/* Append SOURCE to TARGET at INDEX.  SIZE is the current amount
+   of space allocated to TARGET.  SOURCE can be NULL, in which
+   case nothing happens.  Gets rid of SOURCE by freeing it.
+   Returns TARGET in case the location has changed. */
+inline char *
+sub_append_string (source, target, indx, size)
+     char *source, *target;
+     int *indx, *size;
+{
+  if (source)
     {
-      if (*s == CTLESC && (s[1] == CTLESC || s[1] == CTLNUL))
+      int srclen, n;
+
+      srclen = STRLEN (source);
+      if (srclen >= (int)(*size - *indx))
        {
-         s++;
-         if (*s == '\0')
-           break;
+         n = srclen + *indx;
+         n = (n + DEFAULT_ARRAY_SIZE) - (n % DEFAULT_ARRAY_SIZE);
+         target = xrealloc (target, (*size = n));
        }
-      *t++ = *s++;
+
+      FASTCOPY (source, target + *indx, srclen);
+      *indx += srclen;
+      target[*indx] = '\0';
+
+      free (source);
     }
-  *t = '\0';
-  return result;
+  return (target);
+}
+
+#if 0
+/* UNUSED */
+/* Append the textual representation of NUMBER to TARGET.
+   INDX and SIZE are as in SUB_APPEND_STRING. */
+char *
+sub_append_number (number, target, indx, size)
+     int number, *indx, *size;
+     char *target;
+{
+  char *temp;
+
+  temp = itos (number);
+  return (sub_append_string (temp, target, indx, size));
 }
 #endif
 
@@ -431,11 +476,11 @@ string_extract_double_quoted (string, sindex, stripdq)
 
       /* Pass everything between `$(' and the matching `)' or a quoted
         ${ ... } pair through according to the Posix.2 specification. */
-      if (c == '$' && ((string[i + 1] == '(') || (string[i + 1] == '{')))
+      if (c == '$' && ((string[i + 1] == LPAREN) || (string[i + 1] == LBRACE)))
        {
          si = i + 2;
-         if (string[i + 1] == '(')
-           ret = extract_delimited_string (string, &si, "$(", "(", ")");
+         if (string[i + 1] == LPAREN)
+           ret = extract_delimited_string (string, &si, "$(", "(", ")"); /*)*/
          else
            ret = extract_dollar_brace_string (string, &si, 1);
 
@@ -513,10 +558,10 @@ skip_double_quoted (string, sind)
          backquote++;
          continue;
        }
-      else if (c == '$' && ((string[i + 1] == '(') || (string[i + 1] == '{')))
+      else if (c == '$' && ((string[i + 1] == LPAREN) || (string[i + 1] == LBRACE)))
        {
          si = i + 2;
-         if (string[i + 1] == '(')
+         if (string[i + 1] == LPAREN)
            ret = extract_delimited_string (string, &si, "$(", "(", ")");
          else
            ret = extract_dollar_brace_string (string, &si, 0);
@@ -642,7 +687,7 @@ extract_arithmetic_subst (string, sindex)
 #if defined (PROCESS_SUBSTITUTION)
 /* Extract the <( or >( 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 ")". */
+   Make (SINDEX) get the position of the matching ")". */ /*))*/
 char *
 extract_process_subst (string, starter, sindex)
      char *string;
@@ -783,19 +828,18 @@ extract_delimited_string (string, sindex, opener, alt_opener, closer)
       i++;     /* move past this character, which was not special. */
     }
 
-  si = i - *sindex - len_closer + 1;
-  result = xmalloc (1 + si);
-  strncpy (result, string + *sindex, si);
-  result[si] = '\0';
-  *sindex = i;
-
   if (c == 0 && nesting_level)
     {
       report_error ("bad substitution: no `%s' in %s", closer, string);
-      free (result);
       jump_to_top_level (DISCARD);
     }
 
+  si = i - *sindex - len_closer + 1;
+  result = xmalloc (1 + si);
+  strncpy (result, string + *sindex, si);
+  result[si] = '\0';
+  *sindex = i;
+
   return (result);
 }
 
@@ -828,27 +872,21 @@ extract_dollar_brace_string (string, sindex, quoted)
          continue;
        }
 
-      if (c == CTLESC)
-       {
-         pass_character++;
-         continue;
-       }
-
-      /* Backslashes quote the next character. */
-      if (c == '\\')
+      /* CTLESCs and backslashes quote the next character. */
+      if (c == CTLESC || c == '\\')
        {
          pass_character++;
          continue;
        }
 
-      if (string[i] == '$' && string[i+1] == '{')
+      if (string[i] == '$' && string[i+1] == LBRACE)
        {
          nesting_level++;
          i++;
          continue;
        }
 
-      if (c == '}')
+      if (c == RBRACE)
        {
          nesting_level--;
          if (nesting_level == 0)
@@ -867,36 +905,34 @@ extract_dollar_brace_string (string, sindex, quoted)
          continue;
        }
 
-      /* Pass the contents of new-style command substitutions through
-        verbatim. */
-      if (string[i] == '$' && string[i+1] == '(')
+      /* Pass the contents of new-style command substitutions and
+        arithmetic substitutions through verbatim. */
+      if (string[i] == '$' && string[i+1] == LPAREN)
        {
          si = i + 2;
-         t = extract_delimited_string (string, &si, "$(", "(", ")");
+         t = extract_delimited_string (string, &si, "$(", "(", ")"); /*)*/
          i = si;
          free (t);
          continue;
        }
 
-      /* Pass the contents of single-quoted strings through verbatim. */
-      if (c == '\'')
+      /* Pass the contents of single-quoted and double-quoted strings
+        through verbatim. */
+      if (c == '\'' || c == '"')
        {
          si = i + 1;
-         i = skip_single_quoted (string, si);
-         /* skip_single_quoted leaves index one past close quote */
+         i = (c == '\'') ? skip_single_quoted (string, si)
+                         : skip_double_quoted (string, si);
+         /* skip_XXX_quoted leaves index one past close quote */
          i--;
          continue;
        }
+    }
 
-      /* Pass embedded double-quoted strings through verbatim as well. */
-      if (c == '"')
-       {
-         si = i + 1;
-         /* skip_double_quoted leaves index one past close quote */
-         i = skip_double_quoted (string, si);
-         i--;
-         continue;
-       }
+  if (c == 0 && nesting_level)
+    {
+      report_error ("bad substitution: no ending `}' in %s", string);
+      jump_to_top_level (DISCARD);
     }
 
   l = i - *sindex;
@@ -905,13 +941,6 @@ extract_dollar_brace_string (string, sindex, quoted)
   result[l] = '\0';
   *sindex = i;
 
-  if (c == 0 && nesting_level)
-    {
-      report_error ("bad substitution: no ending `}' in %s", string);
-      free (result);
-      jump_to_top_level (DISCARD);
-    }
-
   return (result);
 }
 
@@ -931,6 +960,7 @@ de_backslash (string)
 }
 
 #if 0
+/*UNUSED*/
 /* Replace instances of \! in a string with !. */
 void
 unquote_bang (string)
@@ -1051,6 +1081,12 @@ assignment_name (string)
 }
 #endif
 
+/* **************************************************************** */
+/*                                                                 */
+/*     Functions to convert strings to WORD_LISTs and vice versa    */
+/*                                                                 */
+/* **************************************************************** */
+
 /* Return a single string of all the words in LIST.  SEP is the separator
    to put between individual elements of LIST in the output string. */
 static char *
@@ -1114,25 +1150,48 @@ string_list (list)
    expansion [of $*] appears within a double quoted string, it expands
    to a single field with the value of each parameter separated by the
    first character of the IFS variable, or by a <space> if IFS is unset." */
-char *
+static char *
 string_list_dollar_star (list)
      WORD_LIST *list;
 {
   char *ifs, sep[2];
 
   ifs = get_string_value ("IFS");
-  if (ifs == 0)
-    sep[0] = ' ';
-  else if (*ifs == '\0')
-    sep[0] = '\0';
-  else
-    sep[0] = *ifs;
 
+  sep[0] = (ifs == 0) ? ' ' : *ifs;
   sep[1] = '\0';
 
   return (string_list_internal (list, sep));
 }
 
+/* Turn $@ into a string.  If (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+   is non-zero, the $@ appears within double quotes, and we should quote
+   the list before converting it into a string.  If IFS is unset, and the
+   word is not quoted, we just need to quote CTLESC and CTLNUL characters
+   in the words in the list, because the default value of $IFS is
+   <space><tab><newline>, IFS characters in the words in the list should
+   also be split.  If IFS is null, and the word is not quoted, we need
+   to quote the words in the list to preserve the positional parameters
+   exactly. */
+static char *
+string_list_dollar_at (list, quoted)
+     WORD_LIST *list;
+     int quoted;
+{
+  char *ifs, sep[2];
+  WORD_LIST *tlist;
+
+  ifs = get_string_value ("IFS");
+
+  sep[0] = (ifs == 0 || *ifs == 0) ? ' ' : *ifs;
+  sep[1] = '\0';
+
+  tlist = ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (ifs && *ifs == 0))
+               ? quote_list (list)
+               : list_quote_escapes (list);
+  return (string_list_internal (tlist, sep));
+}
+
 /* Return the list of words present in STRING.  Separate the string into
    words at any of the characters found in SEPARATORS.  If QUOTED is
    non-zero then word in the list will have its quoted flag set, otherwise
@@ -1158,54 +1217,6 @@ string_list_dollar_star (list)
 /* BEWARE!  list_string strips null arguments.  Don't call it twice and
    expect to have "" preserved! */
 
-/* Perform quoted null character removal on STRING.  We don't allow any
-   quoted null characters in the middle or at the ends of strings because
-   of how expand_word_internal works.  remove_quoted_nulls () turns
-   STRING into an empty string iff it only consists of a quoted null,
-   and removes all unquoted CTLNUL characters. */
-/*
-#define remove_quoted_nulls(string) \
-  do { if (QUOTED_NULL (string)) string[0] ='\0'; } while (0)
-*/
-static void
-remove_quoted_nulls (string)
-     char *string;
-{
-  char *nstr, *s, *p;
-
-  nstr = savestring (string);
-  nstr[0] = '\0';
-  for (p = nstr, s = string; *s; s++)
-    {
-      if (*s == CTLESC)
-       {
-         *p++ = *s++;  /* CTLESC */
-         if (*s == 0)
-           break;
-         *p++ = *s;    /* quoted char */
-         continue;
-       }
-      if (*s == CTLNUL)
-        continue;
-      *p++ = *s;
-    }
-  *p = '\0';
-  strcpy (string, nstr);
-  free (nstr);
-}
-
-/* Perform quoted null character removal on each element of LIST.
-   This modifies LIST. */
-void
-word_list_remove_quoted_nulls (list)
-     WORD_LIST *list;
-{
-  register WORD_LIST *t;
-
-  for (t = list; t; t = t->next)
-    remove_quoted_nulls (t->word->word);
-}
-
 /* This performs word splitting and quoted null character removal on
    STRING. */
 #define issep(c)       (member ((c), separators))
@@ -1267,7 +1278,11 @@ list_string (string, separators, quoted)
          /* If we have something, then add it regardless.  However,
             perform quoted null character removal on the current word. */
          remove_quoted_nulls (current_word);
+#if 0
          result = make_word_list (make_word (current_word), result);
+#else
+         result = add_string_to_list (current_word, result);
+#endif   
          if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))
            result->word->flags |= W_QUOTED;
        }
@@ -1381,7 +1396,6 @@ strip_trailing_ifs_whitespace (string, separators, saw_escape)
   return string;
 }
 
-#if 0
 #if defined (ARRAY_VARS)
 WORD_LIST *
 list_string_with_quotes (string)
@@ -1419,7 +1433,11 @@ list_string_with_quotes (string)
          token = xmalloc (len + 1);
          strncpy (token, s + tokstart, len);
          token[len] = '\0';
+#if 0
          list = make_word_list (make_word (token), list);
+#else
+         list = add_string_to_list (token, list);
+#endif
          free (token);
          while (spctabnl (s[i]))
            i++;
@@ -1434,76 +1452,12 @@ list_string_with_quotes (string)
   return (REVERSE_LIST (list, WORD_LIST *));
 }
 #endif /* ARRAY_VARS */
-#endif /* 0 */
-
-#if defined (PROCESS_SUBSTITUTION)
-#define EXP_CHAR(s) (s == '$' || s == '`' || s == '<' || s == '>' || s == CTLESC)
-#else
-#define EXP_CHAR(s) (s == '$' || s == '`' || s == CTLESC)
-#endif
-
-/* If there are any characters in STRING that require full expansion,
-   then call FUNC to expand STRING; otherwise just perform quote
-   removal if necessary.  This returns a new string. */
-static char *
-maybe_expand_string (string, quoted, func)
-     char *string;
-     int quoted;
-     WORD_LIST *(*func)();
-{
-  WORD_LIST *list;
-  int i, saw_quote;
-  char *ret;
-
-  for (i = saw_quote = 0; string[i]; i++)
-    {
-      if (EXP_CHAR (string[i]))
-       break;
-      else if (string[i] == '\'' || string[i] == '\\' || string[i] == '"')
-       saw_quote = 1;
-    }
-
-  if (string[i])
-    {
-      list = (*func) (string, quoted);
-      if (list)
-       {
-         ret = string_list (list);
-         dispose_words (list);
-       }
-      else
-       ret = (char *)NULL;
-    }
-  else if (saw_quote && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0))
-    ret = string_quote_removal (string, quoted);
-  else
-    ret = savestring (string);
-  return ret;
-}
-
-static inline char *
-expand_string_to_string (string, quoted, func)
-     char *string;
-     int quoted;
-     WORD_LIST *(*func)();
-{
-  WORD_LIST *list;
-  char *ret;
-
-  if (string == 0 || *string == '\0')
-    return ((char *)NULL);
-
-  list = (*func) (string, quoted);
-  if (list)
-    {
-      ret = string_list (list);
-      dispose_words (list);
-    }
-  else
-    ret = (char *)NULL;
 
-  return (ret);
-}
+/********************************************************/
+/*                                                     */
+/*     Functions to perform assignment statements      */
+/*                                                     */
+/********************************************************/
 
 #if defined (ARRAY_VARS)
 SHELL_VAR *
@@ -1568,7 +1522,7 @@ do_assignment_internal (string, expand)
       temp = name + offset + 1;
 
 #if defined (ARRAY_VARS)
-      if (expand && temp[0] == '(' && strchr (temp, ')'))
+      if (expand && temp[0] == LPAREN && strchr (temp, RPAREN))
        {
          assign_list = ni = 1;
          value = extract_delimited_string (temp, &ni, "(", (char *)NULL, ")");
@@ -1652,59 +1606,11 @@ do_assignment_no_expand (string)
   return do_assignment_internal (string, 0);
 }
 
-/* Most of the substitutions must be done in parallel.  In order
-   to avoid using tons of unclear goto's, I have some functions
-   for manipulating malloc'ed strings.  They all take INDX, a
-   pointer to an integer which is the offset into the string
-   where manipulation is taking place.  They also take SIZE, a
-   pointer to an integer which is the current length of the
-   character array for this string. */
-
-/* Append SOURCE to TARGET at INDEX.  SIZE is the current amount
-   of space allocated to TARGET.  SOURCE can be NULL, in which
-   case nothing happens.  Gets rid of SOURCE by freeing it.
-   Returns TARGET in case the location has changed. */
-inline char *
-sub_append_string (source, target, indx, size)
-     char *source, *target;
-     int *indx, *size;
-{
-  if (source)
-    {
-      int srclen, n;
-
-      srclen = STRLEN (source);
-      if (srclen >= (int)(*size - *indx))
-       {
-         n = srclen + *indx;
-         n = (n + DEFAULT_ARRAY_SIZE) - (n % DEFAULT_ARRAY_SIZE);
-         target = xrealloc (target, (*size = n));
-       }
-
-      FASTCOPY (source, target + *indx, srclen);
-      *indx += srclen;
-      target[*indx] = '\0';
-
-      free (source);
-    }
-  return (target);
-}
-
-#if 0
-/* UNUSED */
-/* Append the textual representation of NUMBER to TARGET.
-   INDX and SIZE are as in SUB_APPEND_STRING. */
-char *
-sub_append_number (number, target, indx, size)
-     int number, *indx, *size;
-     char *target;
-{
-  char *temp;
-
-  temp = itos (number);
-  return (sub_append_string (temp, target, indx, size));
-}
-#endif
+/***************************************************
+ *                                                *
+ *  Functions to manage the positional parameters  *
+ *                                                *
+ ***************************************************/
 
 /* Return the word list that corresponds to `$*'. */
 WORD_LIST *
@@ -1736,6 +1642,26 @@ number_of_args ()
   return n;
 }
 
+/* Return the value of a positional parameter.  This handles values > 10. */
+char *
+get_dollar_var_value (ind)
+     int ind;
+{
+  char *temp;
+  WORD_LIST *p;
+
+  if (ind < 10)
+    temp = dollar_vars[ind] ? savestring (dollar_vars[ind]) : (char *)NULL;
+  else /* We want something like ${11} */
+    {
+      ind -= 10;
+      for (p = rest_of_args; p && ind--; p = p->next)
+        ;
+      temp = p ? savestring (p->word->word) : (char *)NULL;
+    }
+  return (temp);
+}
+
 /* Make a single large string out of the dollar digit variables,
    and the rest_of_args.  If DOLLAR_STAR is 1, then obey the special
    case of "$*" with respect to IFS. */
@@ -1752,54 +1678,223 @@ string_rest_of_args (dollar_star)
   return (string);
 }
 
-/***************************************************
- *                                                *
- *        Functions to Expand a String            *
- *                                                *
- ***************************************************/
-/* Call expand_word_internal to expand W and handle error returns.
-   A convenience function for functions that don't want to handle
-   any errors or free any memory before aborting. */
-static WORD_LIST *
-call_expand_word_internal (w, q, c, e)
-     WORD_DESC *w;
-     int q, *c, *e;
+/* Return a string containing the positional parameters from START to
+   END, inclusive.  If STRING[0] == '*', we obey the rules for $*,
+   which only makes a difference if QUOTED is non-zero. */
+static char *
+pos_params (string, start, end, quoted)
+     char *string;
+     int start, end, quoted;
 {
-  WORD_LIST *result;
+  WORD_LIST *save, *params, *h, *t;
+  char *ret;
+  int i;
 
-  result = expand_word_internal (w, q, c, e);
-  if (result == &expand_word_error)
+  save = params = list_rest_of_args ();
+  if (save == 0)
+    return ((char *)NULL);
+
+  for (i = 1; params && i < start; i++)
+    params = params->next;
+  if (params == 0)
+    return ((char *)NULL);
+  for (h = t = params; params && i < end; i++)
     {
-      /* By convention, each time this error is returned, w->word has
-        already been freed. */
-      w->word = (char *)NULL;
-      jump_to_top_level (DISCARD);
-      /* NOTREACHED */
+      t = params;
+      params = params->next;
     }
-  else if (result == &expand_word_fatal)
-    jump_to_top_level (FORCE_EOF);
-    /* NOTREACHED */
-  else
-    return (result);
-}
-
-/* Perform parameter expansion, command substitution, and arithmetic
-   expansion on STRING, as if it were a word.  Leave the result quoted. */
-static WORD_LIST *
-expand_string_internal (string, quoted)
-     char *string;
-     int quoted;
-{
-  WORD_DESC td;
-  WORD_LIST *tresult;
 
-  if (string == 0 || *string == 0)
-    return ((WORD_LIST *)NULL);
+  t->next = (WORD_LIST *)NULL;
+  if (string[0] == '*')
+    ret = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? string_list_dollar_star (h) : string_list (h);
+  else
+    ret = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (h) : h);
+  t->next = params;
 
-  bzero ((char *)&td, sizeof (td));
-  td.word = string;
-  tresult = call_expand_word_internal (&td, quoted, (int *)NULL, (int *)NULL);
-  return (tresult);
+  dispose_words (save);
+  return (ret);
+}
+
+/******************************************************************/
+/*                                                               */
+/*     Functions to expand strings to strings or WORD_LISTs      */
+/*                                                               */
+/******************************************************************/
+
+#if defined (PROCESS_SUBSTITUTION)
+#define EXP_CHAR(s) (s == '$' || s == '`' || s == '<' || s == '>' || s == CTLESC)
+#else
+#define EXP_CHAR(s) (s == '$' || s == '`' || s == CTLESC)
+#endif
+
+/* If there are any characters in STRING that require full expansion,
+   then call FUNC to expand STRING; otherwise just perform quote
+   removal if necessary.  This returns a new string. */
+static char *
+maybe_expand_string (string, quoted, func)
+     char *string;
+     int quoted;
+     WORD_LIST *(*func)();
+{
+  WORD_LIST *list;
+  int i, saw_quote;
+  char *ret;
+
+  for (i = saw_quote = 0; string[i]; i++)
+    {
+      if (EXP_CHAR (string[i]))
+       break;
+      else if (string[i] == '\'' || string[i] == '\\' || string[i] == '"')
+       saw_quote = 1;
+    }
+
+  if (string[i])
+    {
+      list = (*func) (string, quoted);
+      if (list)
+       {
+         ret = string_list (list);
+         dispose_words (list);
+       }
+      else
+       ret = (char *)NULL;
+    }
+  else if (saw_quote && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0))
+    ret = string_quote_removal (string, quoted);
+  else
+    ret = savestring (string);
+  return ret;
+}
+
+static inline char *
+expand_string_to_string (string, quoted, func)
+     char *string;
+     int quoted;
+     WORD_LIST *(*func)();
+{
+  WORD_LIST *list;
+  char *ret;
+
+  if (string == 0 || *string == '\0')
+    return ((char *)NULL);
+
+  list = (*func) (string, quoted);
+  if (list)
+    {
+      ret = string_list (list);
+      dispose_words (list);
+    }
+  else
+    ret = (char *)NULL;
+
+  return (ret);
+}
+
+#if defined (COND_COMMAND)
+/* Just remove backslashes in STRING.  Returns a new string. */
+char *
+remove_backslashes (string)
+     char *string;
+{
+  char *r, *ret, *s;
+
+  r = ret = xmalloc (strlen (string) + 1);
+  for (s = string; s && *s; )
+    {
+      if (*s == '\\')
+        s++;
+      if (*s == 0)
+        break;
+      *r++ = *s++;
+    }
+  *r = '\0';
+  return ret;
+}
+
+/* This needs better error handling. */
+/* Expand W for use as an argument to a unary or binary operator in a
+   [[...]] expression.  If SPECIAL is nonzero, this is the rhs argument
+   to the != or == operator, and should be treated as a pattern.  In
+   this case, we quote the string specially for the globbing code.  The
+   caller is responsible for removing the backslashes if the unquoted
+   words is needed later. */   
+char *
+cond_expand_word (w, special)
+     WORD_DESC *w;
+     int special;
+{
+  char *r, *p;
+  WORD_LIST *l;
+
+  if (w->word == 0 || w->word[0] == '\0')
+    return ((char *)NULL);
+
+  l = call_expand_word_internal (w, 0, (int *)0, (int *)0);
+  if (l)
+    {
+      if (special == 0)
+       {
+         dequote_list (l);
+         r = string_list (l);
+       }
+      else
+        {
+          p = string_list (l);
+          r = quote_string_for_globbing (p, QGLOB_CVTNULL);
+          free (p);
+        }
+      dispose_words (l);
+    }
+  else
+    r = (char *)NULL;
+
+  return r;
+}
+#endif
+
+/* Call expand_word_internal to expand W and handle error returns.
+   A convenience function for functions that don't want to handle
+   any errors or free any memory before aborting. */
+static WORD_LIST *
+call_expand_word_internal (w, q, c, e)
+     WORD_DESC *w;
+     int q, *c, *e;
+{
+  WORD_LIST *result;
+
+  result = expand_word_internal (w, q, c, e);
+  if (result == &expand_word_error)
+    {
+      /* By convention, each time this error is returned, w->word has
+        already been freed. */
+      w->word = (char *)NULL;
+      jump_to_top_level (DISCARD);
+      /* NOTREACHED */
+    }
+  else if (result == &expand_word_fatal)
+    jump_to_top_level (FORCE_EOF);
+    /* NOTREACHED */
+  else
+    return (result);
+}
+
+/* Perform parameter expansion, command substitution, and arithmetic
+   expansion on STRING, as if it were a word.  Leave the result quoted. */
+static WORD_LIST *
+expand_string_internal (string, quoted)
+     char *string;
+     int quoted;
+{
+  WORD_DESC td;
+  WORD_LIST *tresult;
+
+  if (string == 0 || *string == 0)
+    return ((WORD_LIST *)NULL);
+
+  bzero ((char *)&td, sizeof (td));
+  td.word = string;
+  tresult = call_expand_word_internal (&td, quoted, (int *)NULL, (int *)NULL);
+  return (tresult);
 }
 
 /* Expand STRING by performing parameter expansion, command substitution,
@@ -1898,6 +1993,106 @@ expand_string (string, quoted)
  *                                                *
  ***************************************************/
 
+/* Conventions:
+
+     A string with s[0] == CTLNUL && s[1] == 0 is a quoted null string.
+     The parser passes CTLNUL as CTLESC CTLNUL. */
+
+/* The parser passes us CTLESC as CTLESC CTLESC and CTLNUL as CTLESC CTLNUL.
+   This is necessary to make unquoted CTLESC and CTLNUL characters in the
+   data stream pass through properly.
+   Here we remove doubled CTLESC characters inside quoted strings before
+   quoting the entire string, so we do not double the number of CTLESC
+   characters. */
+static char *
+remove_quoted_escapes (string)
+     char *string;
+{
+  register char *s;
+  int docopy;
+  char *t, *t1;
+
+  if (string == NULL)
+    return (string);
+
+  t1 = t = xmalloc (strlen (string) + 1);
+  for (docopy = 0, s = string; *s; s++, t1++)
+    {
+      if (*s == CTLESC && (s[1] == CTLESC || s[1] == CTLNUL))
+       {
+         s++;
+         docopy = 1;
+       }
+      *t1 = *s;
+    }
+  *t1 = '\0';
+  if (docopy)
+    strcpy (string, t);
+  free (t);
+  return (string);
+}
+
+/* Quote escape characters in string s, but no other characters.  This is
+   used to protect CTLESC and CTLNUL in variable values from the rest of
+   the word expansion process after the variable is expanded. */
+static char *
+quote_escapes (string)
+     char *string;
+{
+  register char *s, *t;
+  char *result;
+
+  result = xmalloc ((strlen (string) * 2) + 1);
+  for (s = string, t = result; *s; )
+    {
+      if (*s == CTLESC || *s == CTLNUL)
+       *t++ = CTLESC;
+      *t++ = *s++;
+    }
+  *t = '\0';
+  return (result);
+}
+
+static WORD_LIST *
+list_quote_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 = quote_escapes (t);
+      free (t);
+    }
+  return list;
+}
+
+#ifdef INCLUDE_UNUSED
+static char *
+dequote_escapes (string)
+     char *string;
+{
+  register char *s, *t;
+  char *result;
+
+  result = xmalloc (strlen (string) + 1);
+  for (s = string, t = result; *s; )
+    {
+      if (*s == CTLESC && (s[1] == CTLESC || s[1] == CTLNUL))
+       {
+         s++;
+         if (*s == '\0')
+           break;
+       }
+      *t++ = *s++;
+    }
+  *t = '\0';
+  return result;
+}
+#endif
+
 static WORD_LIST *
 dequote_list (list)
      WORD_LIST *list;
@@ -2022,15 +2217,63 @@ quote_list (list)
   return list;
 }
 
-/* **************************************************************** */
-/*                                                                 */
-/*                 Functions for Removing Patterns                 */
-/*                                                                 */
-/* **************************************************************** */
-
-/* Remove the portion of PARAM matched by PATTERN according to OP, where OP
-   can have one of 4 values:
-       RP_LONG_LEFT    remove longest matching portion at start of PARAM
+/* Perform quoted null character removal on STRING.  We don't allow any
+   quoted null characters in the middle or at the ends of strings because
+   of how expand_word_internal works.  remove_quoted_nulls () turns
+   STRING into an empty string iff it only consists of a quoted null,
+   and removes all unquoted CTLNUL characters. */
+/*
+#define remove_quoted_nulls(string) \
+  do { if (QUOTED_NULL (string)) string[0] ='\0'; } while (0)
+*/
+static void
+remove_quoted_nulls (string)
+     char *string;
+{
+  char *nstr, *s, *p;
+
+  nstr = savestring (string);
+  nstr[0] = '\0';
+  for (p = nstr, s = string; *s; s++)
+    {
+      if (*s == CTLESC)
+       {
+         *p++ = *s++;  /* CTLESC */
+         if (*s == 0)
+           break;
+         *p++ = *s;    /* quoted char */
+         continue;
+       }
+      if (*s == CTLNUL)
+        continue;
+      *p++ = *s;
+    }
+  *p = '\0';
+  strcpy (string, nstr);
+  free (nstr);
+}
+
+/* Perform quoted null character removal on each element of LIST.
+   This modifies LIST. */
+void
+word_list_remove_quoted_nulls (list)
+     WORD_LIST *list;
+{
+  register WORD_LIST *t;
+
+  for (t = list; t; t = t->next)
+    remove_quoted_nulls (t->word->word);
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*        Functions for Matching and Removing Patterns             */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Remove the portion of PARAM matched by PATTERN according to OP, where OP
+   can have one of 4 values:
+       RP_LONG_LEFT    remove longest matching portion at start of PARAM
        RP_SHORT_LEFT   remove shortest matching portion at start of PARAM
        RP_LONG_RIGHT   remove longest matching portion at end of PARAM
        RP_SHORT_RIGHT  remove shortest matching portion at end of PARAM
@@ -2064,7 +2307,7 @@ remove_pattern (param, pattern, op)
        for (p = end; p >= param; p--)
          {
            c = *p; *p = '\0';
-           if (fnmatch (pattern, param, 0) != FNM_NOMATCH)
+           if (fnmatch (pattern, param, FNMATCH_EXTFLAG) != FNM_NOMATCH)
              {
                *p = c;
                return (savestring (p));
@@ -2077,7 +2320,7 @@ remove_pattern (param, pattern, op)
        for (p = param; p <= end; p++)
          {
            c = *p; *p = '\0';
-           if (fnmatch (pattern, param, 0) != FNM_NOMATCH)
+           if (fnmatch (pattern, param, FNMATCH_EXTFLAG) != FNM_NOMATCH)
              {
                *p = c;
                return (savestring (p));
@@ -2089,7 +2332,7 @@ remove_pattern (param, pattern, op)
       case RP_LONG_RIGHT:      /* remove longest match at end */
        for (p = param; p <= end; p++)
          {
-           if (fnmatch (pattern, p, 0) != FNM_NOMATCH)
+           if (fnmatch (pattern, p, FNMATCH_EXTFLAG) != FNM_NOMATCH)
              {
                c = *p; *p = '\0';
                ret = savestring (param);
@@ -2102,7 +2345,7 @@ remove_pattern (param, pattern, op)
       case RP_SHORT_RIGHT:     /* remove shortest match at end */
        for (p = end; p >= param; p--)
          {
-           if (fnmatch (pattern, p, 0) != FNM_NOMATCH)
+           if (fnmatch (pattern, p, FNMATCH_EXTFLAG) != FNM_NOMATCH)
              {
                c = *p; *p = '\0';
                ret = savestring (param);
@@ -2121,11 +2364,6 @@ static int
 match_pattern_char (pat, string)
      char *pat, *string;
 {
-#if 0
-  register char *np;
-  int neg;
-  char c1;
-#endif
   char c;
 
   if (*string == 0)
@@ -2138,31 +2376,15 @@ match_pattern_char (pat, string)
     case '\\':
       return (*string == *pat);
     case '?':
-      return (*string != '\0');
+      return (*string == LPAREN ? 1 : (*string != '\0'));
     case '*':
       return (1);
+    case '+':
+    case '!':
+    case '@':
+      return (*string == LPAREN ? 1 : (*string == c));
     case '[':
-#if 0
-      for (np = pat; *np != ']'; np++);
-      if (*np == 0)
-        return (*string == '[');
-      if (neg = (*pat == '!' || *pat == '^'))
-       pat++;
-      for ( ; (c1 = *pat++) != ']'; )
-       {
-         if (c1 == '\\')
-           c1 = *pat++;
-         if (c1 == 0)
-           return (0);
-         if (*pat != '-' || pat[1] == '\0' || pat[1] == ']')
-           return (neg ? *string != c1 : *string == c1);
-         if (c1 <= *string && *string <= pat[1])
-           return (1);
-         pat += 2;
-       }
-#else
       return (*string != '\0');
-#endif
     }
 }
 
@@ -2197,7 +2419,7 @@ match_pattern (string, pat, mtype, sp, ep)
              for (p1 = end; p1 >= p; p1--)
                {
                  c = *p1; *p1 = '\0';
-                 if (fnmatch (pat, p, 0) == 0)
+                 if (fnmatch (pat, p, FNMATCH_EXTFLAG) == 0)
                    {
                      *p1 = c;
                      *sp = p;
@@ -2216,7 +2438,7 @@ match_pattern (string, pat, mtype, sp, ep)
       for (p = end; p >= string; p--)
        {
          c = *p; *p = '\0';
-         if (fnmatch (pat, string, 0) == 0)
+         if (fnmatch (pat, string, FNMATCH_EXTFLAG) == 0)
            {
              *p = c;
              *sp = string;
@@ -2229,7 +2451,7 @@ match_pattern (string, pat, mtype, sp, ep)
 
     case MATCH_END:
       for (p = string; p <= end; p++)
-       if (fnmatch (pat, p, 0) == 0)
+       if (fnmatch (pat, p, FNMATCH_EXTFLAG) == 0)
          {
            *sp = p;
            *ep = end;
@@ -2241,6 +2463,191 @@ match_pattern (string, pat, mtype, sp, ep)
   return (0);
 }
 
+static int
+getpatspec (c, value)
+     int c;
+     char *value;
+{
+  if (c == '#')
+    return ((*value == '#') ? RP_LONG_LEFT : RP_SHORT_LEFT);
+  else /* c == '%' */
+    return ((*value == '%') ? RP_LONG_RIGHT : RP_SHORT_RIGHT);
+}
+
+/* Posix.2 says that the WORD should be run through tilde expansion,
+   parameter expansion, command substitution and arithmetic expansion.
+   This leaves the result quoted, so quote_string_for_globbing () has
+   to be called to fix it up for fnmatch ().  If QUOTED is non-zero,
+   it means that the entire expression was enclosed in double quotes.
+   This means that quoting characters in the pattern do not make any
+   special pattern characters quoted.  For example, the `*' in the
+   following retains its special meaning: "${foo#'*'}". */
+static char *
+getpattern (value, quoted, expandpat)
+     char *value;
+     int quoted, expandpat;
+{
+  char *pat, *tword;
+  WORD_LIST *l;
+  int i;
+
+  tword = strchr (value, '~') ? bash_tilde_expand (value) : savestring (value);
+
+  /* expand_string_internal () leaves WORD quoted and does not perform
+     word splitting. */
+  if (expandpat && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && *tword)
+    {
+      i = 0;
+      pat = string_extract_double_quoted (tword, &i, 1);
+      free (tword);
+      tword = pat;
+    }
+
+  /* There is a problem here:  how to handle single or double quotes in the
+     pattern string when the whole expression is between double quotes? */
+#if 0
+  l = *tword ? expand_string_for_rhs (tword, quoted, (int *)NULL, (int *)NULL)
+#else
+  l = *tword ? expand_string_for_rhs (tword,
+                                     (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? Q_NOQUOTE : quoted,
+                                     (int *)NULL, (int *)NULL)
+#endif
+            : (WORD_LIST *)0;
+  free (tword);
+  pat = string_list (l);
+  dispose_words (l);
+  if (pat)
+    {
+      tword = quote_string_for_globbing (pat, QGLOB_CVTNULL);
+      free (pat);
+      pat = tword;
+    }
+  return (pat);
+}
+
+/* Handle removing a pattern from a string as a result of ${name%[%]value}
+   or ${name#[#]value}. */
+static char *
+parameter_brace_remove_pattern (value, temp, c, quoted)
+     char *value, *temp;
+     int c, quoted;
+{
+  int patspec;
+  char *pattern, *tword;
+
+  patspec = getpatspec (c, value);
+  if (patspec == RP_LONG_LEFT || patspec == RP_LONG_RIGHT)
+    value++;
+
+  pattern = getpattern (value, quoted, 1);
+
+  tword = remove_pattern (temp, pattern, patspec);
+
+  FREE (pattern);
+  return (tword);
+}
+
+static char *
+list_remove_pattern (list, pattern, patspec, type, quoted)
+     WORD_LIST *list;
+     char *pattern;
+     int patspec, type, quoted;
+{
+  WORD_LIST *new, *l;
+  WORD_DESC *w;
+  char *tword;
+
+  for (new = (WORD_LIST *)NULL, l = list; l; l = l->next)
+    {
+      tword = remove_pattern (l->word->word, pattern, patspec);
+      w = make_bare_word (tword);
+      free (tword);
+      new = make_word_list (w, new);
+    }
+
+  l = REVERSE_LIST (new, WORD_LIST *);
+  if (type == '*')
+    tword = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? string_list_dollar_star (l) : string_list (l);
+  else
+    tword = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (l) : l);
+
+  dispose_words (l);
+  return (tword);
+}
+
+static char *
+parameter_list_remove_pattern (value, type, c, quoted)
+     char *value;
+     int type, c, quoted;
+{
+  int patspec;
+  char *pattern, *ret;
+  WORD_LIST *list;
+
+  patspec = getpatspec (c, value);
+  if (patspec == RP_LONG_LEFT || patspec == RP_LONG_RIGHT)
+    value++;
+
+  pattern = getpattern (value, quoted, 1);
+
+  list = list_rest_of_args ();
+  ret = list_remove_pattern (list, pattern, patspec, type, quoted);
+  dispose_words (list);
+  FREE (pattern);
+  return (ret);
+}
+
+#if defined (ARRAY_VARS)
+static char *
+array_remove_pattern (value, aspec, aval, c, quoted)
+     char *value, *aspec, *aval;       /* AVAL == evaluated ASPEC */
+     int c, quoted;
+{
+  SHELL_VAR *var;
+  int len, patspec;
+  char *ret, *t, *pattern;
+  WORD_LIST *l;
+
+  var = array_variable_part (aspec, &t, &len);
+  if (var == 0)
+    return ((char *)NULL);
+
+  patspec = getpatspec (c, value);
+  if (patspec == RP_LONG_LEFT || patspec == RP_LONG_RIGHT)
+    value++;
+
+  pattern = getpattern (value, quoted, 1);
+
+  if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']')
+    {
+      if (array_p (var) == 0)
+        {
+          report_error ("%s: bad array subscript", aspec);
+          FREE (pattern);
+          return ((char *)NULL);
+        }
+      l = array_to_word_list (array_cell (var));
+      if (l == 0)
+        return ((char *)NULL);
+      ret = list_remove_pattern (l, pattern, patspec, t[0], quoted);
+      dispose_words (l);
+    }
+  else
+    {
+      ret = remove_pattern (aval, pattern, patspec);
+      if (ret)
+       {
+         t = quote_escapes (ret);
+         free (ret);
+         ret = t;
+       }
+    }
+
+  FREE (pattern);
+  return ret;
+}
+#endif /* ARRAY_VARS */
+
 /*******************************************
  *                                        *
  *     Functions to expand WORD_DESCs     *
@@ -2288,33 +2695,13 @@ expand_word_leave_quoted (word, quoted)
   return (call_expand_word_internal (word, quoted, (int *)NULL, (int *)NULL));
 }
 
-/* Return the value of a positional parameter.  This handles values > 10. */
-char *
-get_dollar_var_value (ind)
-     int ind;
-{
-  char *temp;
-  WORD_LIST *p;
-
-  if (ind < 10)
-    temp = dollar_vars[ind] ? savestring (dollar_vars[ind]) : (char *)NULL;
-  else /* We want something like ${11} */
-    {
-      ind -= 10;
-      for (p = rest_of_args; p && ind--; p = p->next)
-        ;
-      temp = p ? savestring (p->word->word) : (char *)NULL;
-    }
-  return (temp);
-}
-
 #if defined (PROCESS_SUBSTITUTION)
 
-/* **************************************************************** */
-/*                                                               */
-/*                 Hacking Process Substitution                    */
-/*                                                               */
-/* **************************************************************** */
+/*****************************************************************/
+/*                                                              */
+/*                 Hacking Process Substitution                 */
+/*                                                              */
+/*****************************************************************/
 
 #if !defined (HAVE_DEV_FD)
 /* Named pipes must be removed explicitly with `unlink'.  This keeps a list
@@ -2484,7 +2871,7 @@ process_substitute (string, open_for_read_in_child)
   pid_t old_pipeline_pgrp;
 #endif
 
-  if (!string || !*string)
+  if (!string || !*string || wordexp_only)
     return ((char *)NULL);
 
 #if !defined (HAVE_DEV_FD)
@@ -2626,6 +3013,12 @@ process_substitute (string, open_for_read_in_child)
 }
 #endif /* PROCESS_SUBSTITUTION */
 
+/***********************************/
+/*                                */
+/*     Command Substitution       */
+/*                                */
+/***********************************/
+
 static char *
 read_comsub (fd, quoted)
      int fd, quoted;
@@ -2713,6 +3106,12 @@ command_substitute (string, quoted)
   if (!string || !*string || (string[0] == '\n' && !string[1]))
     return ((char *)NULL);
 
+  if (wordexp_only && read_but_dont_execute)
+    {
+      last_command_exit_value = 125;
+      jump_to_top_level (EXITPROG);
+    }
+
   /* Pipe the output of executing STRING into the current shell. */
   if (pipe (fildes) < 0)
     {
@@ -2825,8 +3224,16 @@ command_substitute (string, quoted)
        kill (getpid (), SIGINT);
 
       /* wait_for gives the terminal back to shell_pgrp.  If some other
-        process group should have it, give it away to that group here. */
-      if (interactive && pipeline_pgrp != (pid_t)0)
+        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 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)
+#endif
        give_terminal_to (pipeline_pgrp);
 #endif /* JOB_CONTROL */
 
@@ -2840,208 +3247,9 @@ command_substitute (string, quoted)
  *                                                     *
  ********************************************************/
 
-static int
-getpatspec (c, value)
-     int c;
-     char *value;
-{
-  if (c == '#')
-    return ((*value == '#') ? RP_LONG_LEFT : RP_SHORT_LEFT);
-  else /* c == '%' */
-    return ((*value == '%') ? RP_LONG_RIGHT : RP_SHORT_RIGHT);
-}
-
-/* Posix.2 says that the WORD should be run through tilde expansion,
-   parameter expansion, command substitution and arithmetic expansion.
-   This leaves the result quoted, so quote_string_for_globbing () has
-   to be called to fix it up for fnmatch ().  If QUOTED is non-zero,
-   it means that the entire expression was enclosed in double quotes.
-   This means that quoting characters in the pattern do not make any
-   special pattern characters quoted.  For example, the `*' in the
-   following retains its special meaning: "${foo#'*'}". */
-static char *
-getpattern (value, quoted, expandpat)
-     char *value;
-     int quoted, expandpat;
-{
-  char *pat, *tword;
-  WORD_LIST *l;
-  int i;
-
-  tword = strchr (value, '~') ? bash_tilde_expand (value) : savestring (value);
-
-  /* expand_string_internal () leaves WORD quoted and does not perform
-     word splitting. */
-  if (expandpat && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && *tword)
-    {
-      i = 0;
-      pat = string_extract_double_quoted (tword, &i, 1);
-      free (tword);
-      tword = pat;
-    }
-
-  /* There is a problem here:  how to handle single or double quotes in the
-     pattern string when the whole expression is between double quotes? */
-#if 0
-  l = *tword ? expand_string_for_rhs (tword, quoted, (int *)NULL, (int *)NULL)
-#else
-  l = *tword ? expand_string_for_rhs (tword,
-                                     (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? Q_NOQUOTE : quoted,
-                                     (int *)NULL, (int *)NULL)
-#endif
-            : (WORD_LIST *)0;
-  free (tword);
-  pat = string_list (l);
-  dispose_words (l);
-  if (pat)
-    {
-      tword = quote_string_for_globbing (pat, 1);
-      free (pat);
-      pat = tword;
-    }
-  return (pat);
-}
-
-/* Handle removing a pattern from a string as a result of ${name%[%]value}
-   or ${name#[#]value}. */
-static char *
-parameter_brace_remove_pattern (value, temp, c, quoted)
-     char *value, *temp;
-     int c, quoted;
-{
-  int patspec;
-  char *pattern, *tword;
-
-  patspec = getpatspec (c, value);
-  if (patspec == RP_LONG_LEFT || patspec == RP_LONG_RIGHT)
-    value++;
-
-  pattern = getpattern (value, quoted, 1);
-
-  tword = remove_pattern (temp, pattern, patspec);
-
-  FREE (pattern);
-  return (tword);
-}
-
-static char *
-list_remove_pattern (list, pattern, patspec, type, quoted)
-     WORD_LIST *list;
-     char *pattern;
-     int patspec, type, quoted;
-{
-  WORD_LIST *new, *l;
-  WORD_DESC *w;
-  char *tword;
-
-  for (new = (WORD_LIST *)NULL, l = list; l; l = l->next)
-    {
-      tword = remove_pattern (l->word->word, pattern, patspec);
-      w = make_bare_word (tword);
-      free (tword);
-      new = make_word_list (w, new);
-    }
-
-  l = REVERSE_LIST (new, WORD_LIST *);
-  if (type == '*')
-    tword = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? string_list_dollar_star (l) : string_list (l);
-  else
-    tword = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (l) : l);
-
-  dispose_words (l);
-  return (tword);
-}
-
-static char *
-parameter_list_remove_pattern (value, type, c, quoted)
-     char *value;
-     int type, c, quoted;
-{
-  int patspec;
-  char *pattern, *ret;
-  WORD_LIST *list;
-
-  patspec = getpatspec (c, value);
-  if (patspec == RP_LONG_LEFT || patspec == RP_LONG_RIGHT)
-    value++;
-
-  pattern = getpattern (value, quoted, 1);
-
-  list = list_rest_of_args ();
-  ret = list_remove_pattern (list, pattern, patspec, type, quoted);
-  dispose_words (list);
-  FREE (pattern);
-  return (ret);
-}
+/* Utility functions to manage arrays and their contents for expansion */
 
 #if defined (ARRAY_VARS)
-static char *
-array_remove_pattern (value, aspec, aval, c, quoted)
-     char *value, *aspec, *aval;       /* AVAL == evaluated ASPEC */
-     int c, quoted;
-{
-  SHELL_VAR *var;
-  int len, patspec;
-#if 0
-  int ind;
-#endif
-  char *ret, *t, *pattern;
-  WORD_LIST *l;
-
-  var = array_variable_part (aspec, &t, &len);
-  if (var == 0)
-    return ((char *)NULL);
-
-  patspec = getpatspec (c, value);
-  if (patspec == RP_LONG_LEFT || patspec == RP_LONG_RIGHT)
-    value++;
-
-  pattern = getpattern (value, quoted, 1);
-
-  if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']')
-    {
-      if (array_p (var) == 0)
-        {
-          report_error ("%s: bad array subscript", aspec);
-          FREE (pattern);
-          return ((char *)NULL);
-        }
-      l = array_to_word_list (array_cell (var));
-      if (l == 0)
-        return ((char *)NULL);
-      ret = list_remove_pattern (l, pattern, patspec, t[0], quoted);
-      dispose_words (l);
-    }
-  else
-    {
-#if 0
-      ind = array_expand_index (t, len);
-      if (ind < 0)
-       {
-         report_error ("%s: bad array subscript", aspec);
-         FREE (pattern);
-         return ((char *)NULL);
-       }
-      if (array_p (var) == 0 && ind != 0)
-       return ((char *)NULL);
-
-      t = array_p (var) ? array_reference (array_cell (var), ind) : value_cell (var);
-      ret = remove_pattern (t, pattern, patspec);
-#else
-      ret = remove_pattern (aval, pattern, patspec);
-#endif
-      if (ret)
-       {
-         t = quote_escapes (ret);
-         free (ret);
-         ret = t;
-       }
-    }
-
-  FREE (pattern);
-  return ret;
-}
-
 int
 valid_array_reference (name)
      char *name;
@@ -3129,8 +3337,8 @@ array_value_internal (s, quoted, allow_all)
      int quoted, allow_all;
 {
   int len, ind;
-  char *retval, *t;
-  WORD_LIST *l;
+  char *retval, *t, *temp;
+  WORD_LIST *l, *list;
   SHELL_VAR *var;
 
   var = array_variable_part (s, &t, &len);
@@ -3149,10 +3357,21 @@ array_value_internal (s, quoted, allow_all)
       if (l == (WORD_LIST *)NULL)
        return ((char *) NULL);
 
+#if 0
       if (t[0] == '*')         /* ${name[*]} */
        retval = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? string_list_dollar_star (l) : string_list (l);
       else                     /* ${name[@]} */
        retval = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (l) : l);
+#else
+      if (t[0] == '*' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+       {
+         temp = string_list_dollar_star (l);
+         retval = quote_string (temp);
+         free (temp);
+       }
+      else     /* ${name[@]} or unquoted ${name[*]} */
+       retval = string_list_dollar_at (l, quoted);
+#endif
 
       dispose_words (l);
     }
@@ -3280,13 +3499,20 @@ parameter_brace_expand_word (name, var_is_special, quoted)
     }
   else if (var_is_special)      /* ${@} */
     {
+      int sindex;
       tt = xmalloc (2 + strlen (name));
-      tt[0] = '$';
+      tt[sindex = 0] = '$';
       strcpy (tt + 1, name);
+#if 0
       l = expand_string_leave_quoted (tt, quoted);
       free (tt);
       temp = string_list (l);
       dispose_words (l);
+#else
+      temp = param_expand (tt, &sindex, quoted, (int *)NULL, (int *)NULL,
+                          (int *)NULL, (int *)NULL, 0);
+      free (tt);
+#endif
     }
 #if defined (ARRAY_VARS)
   else if (valid_array_reference (name))
@@ -3306,6 +3532,9 @@ parameter_brace_expand_word (name, var_is_special, quoted)
 
          if (temp)
            temp = quote_escapes (temp);
+
+         if (tempvar_p (var))
+           dispose_variable (var);
         }
       else
        temp = (char *)NULL;
@@ -3440,6 +3669,7 @@ valid_length_expression (name)
 {
   return (!name[1] ||                                          /* ${#} */
          ((name[1] == '@' || name[1] == '*') && !name[2]) ||   /* ${#@}, ${#*} */
+         (member (name[1], "-?$!#") && !name[2]) ||            /* ${#-}, etc. */
          (digit (name[1]) && all_digits (name + 1)) ||         /* ${#11} */
 #if defined (ARRAY_VARS)
          valid_array_reference (name + 1) ||                   /* ${#a[7]} */
@@ -3462,11 +3692,40 @@ parameter_brace_expand_length (name)
 
   if (name[1] == '\0')                 /* ${#} */
     number = number_of_args ();
+  else if ((name[1] == '@' || name[1] == '*') && name[2] == '\0')      /* ${#@}, ${#*} */
+    number = number_of_args ();
+  else if (member (name[1], "-?$!#") && name[2] == '\0')
+    {
+      /* Take the lengths of some of the shell's special parameters. */
+      switch (name[1])
+       {
+       case '-':
+         t = which_set_flags ();
+         break;
+       case '?':
+         t = itos (last_command_exit_value);
+         break;
+       case '$':
+         t = itos (dollar_dollar_pid);
+         break;
+       case '!':
+         if (last_asynchronous_pid == NO_PID)
+           t = (char *)NULL;
+         else
+           t = itos ((int)last_asynchronous_pid);
+         break;
+       case '#':
+         t = itos (number_of_args ());
+         break;
+       }
+      number = STRLEN (t);
+      FREE (t);
+    }
 #if defined (ARRAY_VARS)
   else if (valid_array_reference (name + 1))
     number = array_length_reference (name + 1);
 #endif /* ARRAY_VARS */
-  else if (name[1] != '*' && name[1] != '@')
+  else
     {
       number = 0;
 
@@ -3497,15 +3756,13 @@ parameter_brace_expand_length (name)
          FREE (t);
        }
     }
-  else                                 /* ${#@} and ${#*} */
-    number = number_of_args ();
 
   return (number);
 }
 
 /* Verify and limit the start and end of the desired substring.  If
    VTYPE == 0, a regular shell variable is being used; if it is 1,
-   then the positional paramters are being used; if it is 2, then
+   then the positional parameters are being used; if it is 2, then
    VALUE is really a pointer to an array variable that should be used.
    Return value is 1 if both values were OK, 0 if there was a problem
    with an invalid expression, or -1 if the values were out of range. */
@@ -3576,45 +3833,8 @@ verify_substring_values (value, substr, vtype, e1p, e2p)
   return (1);
 }
 
-/* Return a string containing the positional parameters from START to
-   END, inclusive.  If STRING[0] == '*', we obey the rules for $*,
-   which only makes a difference if QUOTED is non-zero. */
-static char *
-pos_params (string, start, end, quoted)
-     char *string;
-     int start, end, quoted;
-{
-  WORD_LIST *save, *params, *h, *t;
-  char *ret;
-  int i;
-
-  save = params = list_rest_of_args ();
-  if (save == 0)
-    return ((char *)NULL);
-
-  for (i = 1; params && i < start; i++)
-    params = params->next;
-  if (params == 0)
-    return ((char *)NULL);
-  for (h = t = params; params && i < end; i++)
-    {
-      t = params;
-      params = params->next;
-    }
-
-  t->next = (WORD_LIST *)NULL;
-  if (string[0] == '*')
-    ret = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? string_list_dollar_star (h) : string_list (h);
-  else
-    ret = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (h) : h);
-  t->next = params;
-
-  dispose_words (save);
-  return (ret);
-}
-
 /* Return the type of variable specified by VARNAME (simple variable,
-   positional param, or array variable.  Also return the value specified
+   positional param, or array variable).  Also return the value specified
    by VARNAME (value of a variable or a reference to an array element). */
 static int
 get_var_and_type (varname, value, varp, valp)
@@ -3665,6 +3885,12 @@ get_var_and_type (varname, value, varp, valp)
   return vtype;
 }
 
+/******************************************************/
+/*                                                   */
+/* Functions to extract substrings of variable values */
+/*                                                   */
+/******************************************************/
+
 /* Process a variable substring expansion: ${name:e1[:e2]}.  If VARNAME
    is `@', use the positional parameters; otherwise, use the value of
    VARNAME.  If VARNAME is an array variable, use the array elements. */
@@ -3714,6 +3940,12 @@ parameter_brace_substring (varname, value, substr, quoted)
   return temp;
 }
 
+/****************************************************************/
+/*                                                             */
+/* Functions to perform pattern substitution on variable values */
+/*                                                             */
+/****************************************************************/
+
 char *
 pat_subst (string, pat, rep, mflags)
      char *string, *pat, *rep;
@@ -3794,6 +4026,10 @@ pos_params_pat_subst (string, pat, rep, mflags)
   return (ret);
 }
 
+/* Perform pattern substitution on VALUE, which is the expansion of
+   VARNAME.  PATSUB is an expression supplying the pattern to match
+   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;
@@ -3833,7 +4069,11 @@ parameter_brace_patsub (varname, value, patsub, quoted)
   /* Expand PAT and REP for command, variable and parameter, arithmetic,
      and process substitution.  Also perform quote removal.  Do not
      perform word splitting or filename generation. */
+#if 0
   pat = maybe_expand_string (patsub, quoted, expand_string_unsplit);
+#else
+  pat = maybe_expand_string (patsub, (quoted & ~Q_DOUBLE_QUOTES), expand_string_unsplit);
+#endif
   if (rep)
     {
       if ((mflags & MATCH_QUOTED) == 0)
@@ -3856,9 +4096,10 @@ parameter_brace_patsub (varname, value, patsub, quoted)
   else
     mflags |= MATCH_ANY;
 
-  /* OK, we now want to substitute REP for PAT in VAL.  If GLOBAL is 1,
-     the substitution is done everywhere, otherwise only the first
-     occurrence of PAT is replaced. */
+  /* OK, we now want to substitute REP for PAT in VAL.  If
+     flags & MATCH_GLOBREP is non-zero, the substitution is done
+     everywhere, otherwise only the first occurrence of PAT is
+     replaced. */
   switch (vtype)
     {
     case VT_VARIABLE:
@@ -3884,6 +4125,12 @@ parameter_brace_patsub (varname, value, patsub, quoted)
   return temp;
 }
 
+/****************************************************************/
+/*                                                             */
+/*     Functions to perform parameter expansion on a string    */
+/*                                                             */
+/****************************************************************/
+
 /* ${[#][!]name[[:]#[#]%[%]-=?+[word][:e1[:e2]]]} */
 static char *
 parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_dollar_at)
@@ -3895,21 +4142,26 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
   char *name, *value, *temp, *temp1;
   int t_index, sindex, c, number;
 
-  sindex = *indexp;
-  t_index = ++sindex;
-  name = string_extract (string, &t_index, "#%:-=?+/}", 1);
   value = (char *)NULL;
   var_is_set = var_is_null = var_is_special = check_nullness = 0;
   want_substring = want_indir = want_patsub = 0;
 
-  /* If the name really consists of a special variable, then
-     make sure that we have the entire name.  Handle indirect
-     references to special variables here, too. */
-  if ((sindex == t_index ||
-      ((sindex == t_index - 1) && string[sindex] == '!')) &&
+  sindex = *indexp;
+  t_index = ++sindex;
+  name = string_extract (string, &t_index, "#%:-=?+/}", 1);
+
+  /* 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] == '#'))
+        string[t_index] == '#')) ||
+      (sindex == t_index - 1 && string[sindex] == '!' &&
+        (string[t_index] == '#' ||
+         string[t_index] == '?' ||
+         string[t_index] == '@' ||
+         string[t_index] == '*')))
     {
       t_index++;
       free (name);
@@ -3918,11 +4170,11 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
       *name = string[sindex];
       if (string[sindex] == '!')
        {
-         /* indirect ref. of special variable */
-         name[1] = string[sindex + 1];
-         strcpy (name + 2, temp1);
+          /* indirect reference of $#, $?, $@, or $* */
+          name[1] = string[sindex + 1];
+          strcpy (name + 2, temp1);
        }
-      else
+      else     
        strcpy (name + 1, temp1);
       free (temp1);
     }
@@ -3943,69 +4195,55 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
       if (c = string[sindex])
        sindex++;
     }
-  else if (c == ':')
+  else if (c == ':' && string[sindex] != RBRACE)
     want_substring = 1;
-  else if (c == '/')
+  else if (c == '/' && string[sindex] != RBRACE)
     want_patsub = 1;
 
-  want_indir = *name == '!';
+  /* Catch the valid and invalid brace expressions that made it through the
+     tests above. */
+  /* ${#-} is a valid expansion and means to take the length of $-.
+     Similarly for ${#?} and ${##}... */
+  if (name[0] == '#' && name[1] == '\0' && check_nullness == 0 &&
+       member (c, "-?#") && string[sindex] == RBRACE)
+    {
+      name = xrealloc (name, 3);
+      name[1] = c;
+      name[2] = '\0';
+      c = string[sindex++];
+    }
+
+  /* ...but ${#%}, ${#:}, ${#=}, ${#+}, and ${#/} are errors. */
+  if (name[0] == '#' && name[1] == '\0' && check_nullness == 0 &&
+       member (c, "%:=+/") && string[sindex] == RBRACE)
+    {
+      temp = (char *)NULL;
+      goto bad_substitution;
+    }
+
+  /* Indirect expansion begins with a `!'.  A valid indirect expansion is
+     either a variable name, one of the positional parameters or a special
+     variable that expands to one of the positional parameters. */
+  want_indir = *name == '!' &&
+    (legal_variable_starter (name[1]) || digit (name[1]) || member (name[1], "#?@*"));
 
   /* Determine the value of this variable. */
 
-  /* Check for special variables, directly and indirectly
-     referenced. */
+  /* Check for special variables, directly referenced. */
   if ((digit (*name) && all_digits (name)) ||
       (name[1] == '\0' && member (*name, "#-?$!@*")) ||
-      (want_indir && name[2] == '\0' && member (name[1], "#-?$!@*")))
+      (want_indir && name[2] == '\0' && member (name[1], "#?@*")))
     var_is_special++;
 
-  /* Check for special expansion things. */
-  if (*name == '#')    /* length of a parameter */
+  /* Check for special expansion things, like the length of a parameter */
+  if (*name == '#' && name[1])
     {
-      /* Take the lengths of some of the shell's special
-         parameters. */
-      if (string[sindex] == '}' && name[1] == '\0' &&
-         check_nullness == 0 && member (c, "-?$!#"))
-       {
-         free (name);
-         switch (c)
-           {
-           case '-':
-             temp1 = which_set_flags ();
-             break;
-           case '?':
-             temp1 = itos (last_command_exit_value);
-             break;
-           case '$':
-             temp1 = itos (dollar_dollar_pid);
-             break;
-           case '!':
-             if (last_asynchronous_pid == NO_PID)
-               temp1 = (char *)NULL;
-             else
-               temp1 = itos ((int)last_asynchronous_pid);
-             break;
-           case '#':
-             temp1 = itos (number_of_args ());
-             break;
-           }
-         number = STRLEN (temp1);
-         FREE (temp1);
-         *indexp = ++sindex;   /* { string[sindex] == '}' */
-         return (itos (number));
-       }
-
-      /* Don't allow things like ${#:-foo} to go by; they are
-         errors.  If we are not pointing at the character just
-         after the closing brace, then we haven't gotten all of
-         the name.  Since it begins with a special character,
-         this is a bad substitution.  Explicitly check for ${#:},
-         which the rules do not catch.  Also check NAME for
-         validity before trying to go on. */
-      if (string[sindex - 1] != '}' ||
-         member (c, "?-=+") ||
-         (name[1] == '\0' && c == '}' && check_nullness) ||
-         (valid_length_expression (name) == 0))
+      /* If we are not pointing at the character just after the
+         closing brace, then we haven't gotten all of the name.
+         Since it begins with a special character, this is a bad
+         substitution.  Also check NAME for validity before trying
+         to go on. */
+      if (string[sindex - 1] != RBRACE || (valid_length_expression (name) == 0))
        {
          temp = (char *)NULL;
          goto bad_substitution;
@@ -4042,7 +4280,11 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
     temp = parameter_brace_expand_word (name, var_is_special, quoted);
 
 #if defined (ARRAY_VARS)
+#if 0
   if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && valid_array_reference (name))
+#else
+  if (valid_array_reference (name))
+#endif
     {
       temp1 = strchr (name, '[');
       if (temp1 && temp1[1] == '@' && temp1[2] == ']')
@@ -4051,6 +4293,13 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
            *quoted_dollar_atp = 1;
          if (contains_dollar_at)
            *contains_dollar_at = 1;
+       }       /* [ */
+      /* ${array[*]}, when unquoted, should be treated like ${array[@]},
+        which should result in separate words even when IFS is unset. */
+      if (temp1 && temp1[1] == '*' && temp1[2] == ']' && quoted == 0)
+       {
+         if (contains_dollar_at)
+           *contains_dollar_at = 1;
        }
     }
 #endif
@@ -4059,13 +4308,12 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
   var_is_null = check_nullness && (var_is_set == 0 || *temp == 0);
 
   /* Get the rest of the stuff inside the braces. */
-  if (c && c != '}')
+  if (c && c != RBRACE)
     {
       /* Extract the contents of the ${ ... } expansion
          according to the Posix.2 rules. */
       value = extract_dollar_brace_string (string, &sindex, quoted);
-      /*{*/
-      if (string[sindex] == '}')
+      if (string[sindex] == RBRACE)
         sindex++;
       else
        goto bad_substitution;
@@ -4105,8 +4353,7 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
       free (name);
       return &expand_param_error;
 
-    /*{*/
-    case '}':
+    case RBRACE:
       if (var_is_set == 0 && unbound_vars_is_error)
         {
          report_error ("%s: unbound variable", name);
@@ -4194,11 +4441,316 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
   return (temp);
 }
 
-/* Make a word list which is the parameter and variable expansion,
-   command substitution, arithmetic substitution, and quote removed
-   expansion of WORD.  Return a pointer to a WORD_LIST which is the
-   result of the expansion.  If WORD contains a null word, the word
-   list returned is also null.
+/* Expand a single ${xxx} expansion.  The braces are optional.  When
+   the braces are used, parameter_brace_expand() does the work,
+   possibly calling param_expand recursively. */
+static char *
+param_expand (string, sindex, quoted, expanded_something,
+             contains_dollar_at, quoted_dollar_at_p, had_quoted_null_p,
+             pflags)
+     char *string;
+     int *sindex, quoted, *expanded_something, *contains_dollar_at;
+     int *quoted_dollar_at_p, *had_quoted_null_p, pflags;
+{
+  char *temp, *temp1;
+  int zindex, number, c, t_index, expok;
+  SHELL_VAR *var;
+  WORD_LIST *list, *tlist;
+
+  zindex = *sindex;
+  c = string[++zindex];
+
+  temp = (char *)NULL;
+
+  /* Do simple cases first. Switch on what follows '$'. */
+  switch (c)
+    {
+    /* $0 .. $9? */
+    case '0':
+    case '1':
+    case '2':
+    case '3':
+    case '4':
+    case '5':
+    case '6':
+    case '7':
+    case '8':
+    case '9':
+      temp1 = dollar_vars[digit_value (c)];
+      if (unbound_vars_is_error && temp1 == (char *)NULL)
+       {
+         report_error ("$%c: unbound variable", c);
+         last_command_exit_value = EXECUTION_FAILURE;
+         return (interactive_shell ? &expand_param_error : &expand_param_fatal);
+       }
+      temp = temp1 ? savestring (temp1) : (char *)NULL;
+      break;
+
+    /* $$ -- pid of the invoking shell. */
+    case '$':
+      temp = itos (dollar_dollar_pid);
+      break;
+
+    /* $# -- number of positional parameters. */
+    case '#':
+      temp = itos (number_of_args ());
+      break;
+
+    /* $? -- return value of the last synchronous command. */
+    case '?':
+      temp = itos (last_command_exit_value);
+      break;
+
+    /* $- -- flags supplied to the shell on invocation or by `set'. */
+    case '-':
+      temp = which_set_flags ();
+      break;
+
+      /* $! -- Pid of the last asynchronous command. */
+    case '!':
+      /* If no asynchronous pids have been created, expand to nothing.
+        If `set -u' has been executed, and no async processes have
+        been created, this is an expansion error. */
+      if (last_asynchronous_pid == NO_PID)
+       {
+         if (expanded_something)
+           *expanded_something = 0;
+         temp = (char *)NULL;
+         if (unbound_vars_is_error)
+           {
+             report_error ("$%c: unbound variable", c);
+             last_command_exit_value = EXECUTION_FAILURE;
+             return (interactive_shell ? &expand_param_error : &expand_param_fatal);
+           }
+       }
+      else
+       temp = itos ((int)last_asynchronous_pid);
+      break;
+
+    /* The only difference between this and $@ is when the arg is quoted. */
+    case '*':          /* `$*' */
+      list = list_rest_of_args ();
+
+      /* If there are no command-line arguments, this should just
+        disappear if there are other characters in the expansion,
+        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))
+       {
+         /* 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 = string_list_dollar_star (list);
+         temp1 = quote_string (temp);
+         free (temp);
+         temp = temp1;
+       }
+      else
+        {
+          /* If the $* is not quoted it is identical to $@ */
+          temp = string_list_dollar_at (list, quoted);
+          if (contains_dollar_at)
+            *contains_dollar_at = 1;
+        }
+
+      dispose_words (list);
+      break;
+
+    /* When we have "$@" what we want is "$1" "$2" "$3" ... This
+       means that we have to turn quoting off after we split into
+       the individually quoted arguments so that the final split
+       on the first character of $IFS is still done.  */
+    case '@':          /* `$@' */
+      list = list_rest_of_args ();
+
+      /* We want to flag the fact that we saw this.  We can't turn
+        off quoting entirely, because other characters in the
+        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. */
+      if (quoted_dollar_at_p && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+       *quoted_dollar_at_p = 1;
+      if (contains_dollar_at)
+       *contains_dollar_at = 1;
+
+      /* We want to separate the positional parameters with the first
+        character of $IFS in case $IFS is something other than a space.
+        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);
+
+      dispose_words (list);
+      break;
+
+    case LBRACE:
+      temp = parameter_brace_expand (string, &zindex, quoted,
+                                    quoted_dollar_at_p,
+                                    contains_dollar_at);
+      if (temp == &expand_param_error || temp == &expand_param_fatal)
+       return (temp);
+
+      /* XXX */
+      /* quoted nulls should be removed if there is anything else
+        in the string. */
+      /* Note that we saw the quoted null so we can add one back at
+        the end of this function if there are no other characters
+        in the string, discard TEMP, and go on. */
+      if (temp && QUOTED_NULL (temp))
+       {
+         if (had_quoted_null_p)
+           *had_quoted_null_p = 1;
+         free (temp);
+         temp = (char *)NULL;
+       }
+
+      goto return0;
+
+    /* Do command or arithmetic substitution. */
+    case LPAREN:
+      /* We have to extract the contents of this paren substitution. */
+      t_index = zindex + 1;
+      temp = extract_command_subst (string, &t_index);
+      zindex = t_index;
+
+      /* For Posix.2-style `$(( ))' arithmetic substitution,
+         extract the expression and pass it to the evaluator. */
+      if (temp && *temp == LPAREN)
+       {
+         char *temp2;
+         temp1 = temp + 1;
+         temp2 = savestring (temp1);
+         t_index = strlen (temp2) - 1;
+
+         if (temp2[t_index] != RPAREN)
+           {
+             free (temp2);
+             goto comsub;
+           }
+
+         /* Cut off ending `)' */
+         temp2[t_index] = '\0';
+
+         /* Expand variables found inside the expression. */
+         temp1 = maybe_expand_string (temp2, Q_DOUBLE_QUOTES, expand_string);
+         free (temp2);
+
+arithsub:
+         /* No error messages. */
+         this_command_name = (char *)NULL;
+         number = evalexp (temp1, &expok);
+         free (temp);
+         free (temp1);
+         if (expok == 0)
+           {
+             if (interactive_shell == 0 && posixly_correct)
+               {
+                 last_command_exit_value = EXECUTION_FAILURE;
+                 return (&expand_param_fatal);
+               }
+             else
+               return (&expand_param_error);
+           }
+         temp = itos (number);
+         break;
+       }
+
+comsub:
+      temp1 = command_substitute (temp, quoted);
+      FREE (temp);
+      temp = temp1;
+      break;
+
+    /* Do POSIX.2d9-style arithmetic substitution.  This will probably go
+       away in a future bash release. */
+    case '[':
+      /* We have to extract the contents of this arithmetic substitution. */
+      t_index = zindex + 1;
+      temp = extract_arithmetic_subst (string, &t_index);
+      zindex = t_index;
+
+       /* Do initial variable expansion. */
+      temp1 = maybe_expand_string (temp, Q_DOUBLE_QUOTES, expand_string);
+
+      goto arithsub;
+
+    default:
+      /* Find the variable in VARIABLE_LIST. */
+      temp = (char *)NULL;
+
+      for (t_index = zindex; (c = string[zindex]) && legal_variable_char (c); zindex++)
+       ;
+      temp1 = (zindex > t_index) ? substring (string, t_index, zindex) : (char *)NULL;
+
+      /* If this isn't a variable name, then just output the `$'. */
+      if (temp1 == 0 || *temp1 == '\0')
+       {
+         FREE (temp1);
+         temp = xmalloc (2);
+         temp[0] = '$';
+         temp[1] = '\0';
+         if (expanded_something)
+           *expanded_something = 0;
+         goto return0;
+       }
+
+      /* If the variable exists, return its value cell. */
+      var = find_variable (temp1);
+
+      if (var && invisible_p (var) == 0 && value_cell (var))
+       {
+#if defined (ARRAY_VARS)
+         if (array_p (var))
+           {
+             temp = array_reference (array_cell (var), 0);
+             if (temp)
+               temp = quote_escapes (temp);
+           }
+         else
+#endif
+         temp = quote_escapes (value_cell (var));
+         free (temp1);
+         if (tempvar_p (var))          /* XXX */
+           {
+             dispose_variable (var);   /* XXX */
+             var = (SHELL_VAR *)NULL;
+           }
+         goto return0;
+       }
+
+      temp = (char *)NULL;
+
+      if (unbound_vars_is_error)
+       report_error ("%s: unbound variable", temp1);
+      else
+       {
+         free (temp1);
+         goto return0;
+       }
+
+      free (temp1);
+      last_command_exit_value = EXECUTION_FAILURE;
+      return ((unbound_vars_is_error && interactive_shell == 0)
+               ? &expand_param_fatal
+               : &expand_param_error);
+    }
+
+  if (string[zindex])
+    zindex++;
+
+return0:
+  *sindex = zindex;
+  return (temp);
+}
+
+/* Make a word list which is the result of parameter and variable
+   expansion, command substitution, arithmetic substitution, and
+   quote removal of WORD.  Return a pointer to a WORD_LIST which is
+   the result of the expansion.  If WORD contains a null word, the
+   word list returned is also null.
 
    QUOTED contains flag values defined in shell.h.
 
@@ -4254,6 +4806,7 @@ expand_word_internal (word, quoted, contains_dollar_at, expanded_something)
   int quoted_state;
 
   int had_quoted_null;
+  int has_dollar_at;
 
   int expok;
 
@@ -4263,8 +4816,7 @@ expand_word_internal (word, quoted, contains_dollar_at, expanded_something)
 
   istring = xmalloc (istring_size = DEFAULT_INITIAL_ARRAY_SIZE);
   istring[istring_index = 0] = '\0';
-
-  quoted_dollar_at = had_quoted_null = 0;
+  quoted_dollar_at = had_quoted_null = has_dollar_at = 0;
   quoted_state = UNQUOTED;
 
   string = word->word;
@@ -4292,25 +4844,33 @@ expand_word_internal (word, quoted, contains_dollar_at, expanded_something)
          temp[1] = c = string[++sindex];
          temp[2] = '\0';
 
+dollar_add_string:
          if (string[sindex])
            sindex++;
 
-         goto add_string;
+add_string:
+         if (temp)
+           {
+             istring = sub_append_string (temp, istring, &istring_index, &istring_size);
+             temp = (char *)0;
+           }
+
+         break;
 
 #if defined (PROCESS_SUBSTITUTION)
          /* Process substitution. */
        case '<':
        case '>':
          {
-           if (string[++sindex] != '(' || (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || posixly_correct)
+           if (string[++sindex] != LPAREN || (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || posixly_correct)
              {
                sindex--;
                goto add_character;
              }
            else
-             t_index = sindex + 1; /* skip past both '<' and '(' */
+             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); /*))*/
            sindex = t_index;
 
            /* If the process substitution specification is `<()', we want to
@@ -4325,367 +4885,74 @@ expand_word_internal (word, quoted, contains_dollar_at, expanded_something)
          }
 #endif /* PROCESS_SUBSTITUTION */
 
-       /* See about breaking this into a separate function:
-           char *
-           param_expand (string, sindex, quoted, expanded_something,
-                         contains_dollar_at, quoted_dollar_at)
-           char *string;
-           int *sindex, quoted, *expanded_something, *contains_dollar_at;
-           int *quoted_dollar_at;
-       */
        case '$':
-
          if (expanded_something)
            *expanded_something = 1;
 
-         c = string[++sindex];
+         has_dollar_at = 0;
+         temp = param_expand (string, &sindex, quoted, expanded_something,
+                              &has_dollar_at, &quoted_dollar_at,
+                              &had_quoted_null, 0);
 
-         /* Do simple cases first. Switch on what follows '$'. */
-         switch (c)
+         if (temp == &expand_param_error || temp == &expand_param_fatal)
            {
-             /* $0 .. $9? */
-           case '0':
-           case '1':
-           case '2':
-           case '3':
-           case '4':
-           case '5':
-           case '6':
-           case '7':
-           case '8':
-           case '9':
-             temp1 = dollar_vars[digit_value (c)];
-             if (unbound_vars_is_error && temp1 == (char *)NULL)
-               {
-                 report_error ("$%c: unbound variable", c);
-                 free (string);
-                 free (istring);
-                 last_command_exit_value = EXECUTION_FAILURE;
-                 return (interactive_shell ? &expand_word_error : &expand_word_fatal);
-               }
-             temp = temp1 ? savestring (temp1) : (char *)NULL;
-             goto dollar_add_string;
+             free (string);
+             free (istring);
+             return ((temp == &expand_param_error) ? &expand_word_error
+                                                   : &expand_word_fatal);
+           }
+         if (contains_dollar_at && has_dollar_at)
+           *contains_dollar_at = 1;
+         goto add_string;
+         break;
 
-             /* $$ -- pid of the invoking shell. */
-           case '$':
-             number = dollar_dollar_pid;
+       case '`':               /* Backquoted command substitution. */
+         {
+           sindex++;
 
-           add_number:
-             temp = itos (number);
-           dollar_add_string:
-             if (string[sindex]) sindex++;
+           if (expanded_something)
+             *expanded_something = 1;
 
-             /* Add TEMP to ISTRING. */
-           add_string:
-             if (temp)
-               {
-                 istring = sub_append_string
-                   (temp, istring, &istring_index, &istring_size);
-                 temp = (char *)0;
-               }
+           temp = string_extract (string, &sindex, "`", 0);
+           de_backslash (temp);
+           temp1 = command_substitute (temp, quoted);
+           FREE (temp);
+           temp = temp1;
+           goto dollar_add_string;
+         }
 
-             break;
+       case '\\':
+         if (string[sindex + 1] == '\n')
+           {
+             sindex += 2;
+             continue;
+           }
 
-             /* $# -- number of positional parameters. */
-           case '#':
-             number = number_of_args ();
-             goto add_number;
+         c = string[++sindex];
 
-             /* $? -- return value of the last synchronous command. */
-           case '?':
-             number = last_command_exit_value;
-             goto add_number;
+         if (quoted & Q_HERE_DOCUMENT)
+           temp1 = slashify_in_here_document;
+         else if (quoted & Q_DOUBLE_QUOTES)
+           temp1 = slashify_in_quotes;
+         else
+           temp1 = "";
 
-             /* $- -- flags supplied to the shell on invocation or
-                by `set'. */
-           case '-':
-             temp = which_set_flags ();
-             goto dollar_add_string;
+         if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && member (c, temp1) == 0)
+           {
+             temp = xmalloc (3);
+             temp[0] = '\\'; temp[1] = c; temp[2] = '\0';
+           }
+         else
+           /* This character is quoted, so add it in quoted mode. */
+           temp = make_quoted_char (c);
 
-             /* $! -- Pid of the last asynchronous command. */
-           case '!':
-             number = (int)last_asynchronous_pid;
+         if (c)
+           sindex++;
+         goto add_string;
 
-             /* If no asynchronous pids have been created, expand
-                to nothing. */
-             if (number == (int)NO_PID)
-               {
-                 if (string[sindex])
-                   sindex++;
-                 if (expanded_something)
-                   *expanded_something = 0;
-                 break;
-               }
-             goto add_number;
-
-             /* The only difference between this and $@ is when the
-                arg is quoted. */
-           case '*':           /* `$*' */
-             temp = string_rest_of_args (quoted);
-
-             /* If there are no command-line arguments, this should just
-                disappear if there are other characters in the expansion,
-                even if it's quoted. */
-             if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && temp && *temp == '\0')
-               {
-                 free (temp);
-                 temp = (char *)NULL;
-               }
-             /* In the case of a quoted string, quote the entire arg-list.
-                "$1 $2 $3".  Otherwise quote the special escape characters. */
-             if (temp)
-               {
-                 temp1 = temp;
-                 temp = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
-                               ? quote_string (temp)
-                               : quote_escapes (temp);
-                 free (temp1);
-               }
-             goto dollar_add_string;
-
-             /* When we have "$@" what we want is "$1" "$2" "$3" ... This
-                means that we have to turn quoting off after we split into
-                the individually quoted arguments so that the final split
-                on the first character of $IFS is still done.  */
-           case '@':           /* `$@' */
-             list = list_rest_of_args ();
-
-             /* We want to flag the fact that we saw this.  We can't turn
-                off quoting entirely, because other characters in the
-                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. */
-             if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
-               quoted_dollar_at = 1;
-             if (contains_dollar_at)
-               *contains_dollar_at = 1;
-             temp = string_list (((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && list) ? quote_list (list) : list);
-             /* If the expansion is not quoted, protect any special escape
-                characters in the expansion by quoting them. */
-             if (temp && quoted == 0)
-               {
-                 temp1 = temp;
-                 temp = quote_escapes (temp);
-                 free (temp1);
-               }
-             dispose_words (list);
-             goto dollar_add_string;
-
-           case '{':   /*}*/
-             temp = parameter_brace_expand (string, &sindex, quoted,
-                                            &quoted_dollar_at,
-                                            contains_dollar_at);
-             if (temp == &expand_param_error || temp == &expand_param_fatal)
-               {
-                 free (string);
-                 free (istring);
-                 return (temp == &expand_param_error) ? &expand_word_error
-                                                      : &expand_word_fatal;
-               }
-             /* XXX */
-             /* quoted nulls should be removed if there is anything else
-                in the string. */
-             /* Note that we saw the quoted null so we can add one back at
-                the end of this function if there are no other characters
-                in the string, discard TEMP, and go on. */
-             if (temp && QUOTED_NULL (temp))
-               {
-                 had_quoted_null = 1;
-                 free (temp);
-                 break;
-               }
-
-             goto add_string;
-             /* break; */
-
-             /* Do command or arithmetic substitution. */
-           case '(':   /*)*/
-             /* We have to extract the contents of this paren substitution. */
-             t_index = sindex + 1;
-             temp = extract_command_subst (string, &t_index);
-             sindex = t_index;
-
-             /* For Posix.2-style `$(( ))' arithmetic substitution,
-                extract the expression and pass it to the evaluator. */
-             if (temp && *temp == '(')
-               {
-                 char *temp2;
-                 temp1 = temp + 1;
-                 temp2 = savestring (temp1);
-                 t_index = strlen (temp2) - 1;
-
-                 if (temp2[t_index] != ')')
-                   {
-                     free (temp2);
-#if 0
-                     report_error ("%s: bad arithmetic substitution", temp);
-                     free (temp);
-                     free (string);
-                     free (istring);
-                     return (&expand_word_error);
-#else
-                     goto comsub;
-#endif
-                   }
-
-                 /* Cut off ending `)' */
-                 temp2[t_index] = '\0';
-
-                 /* Expand variables found inside the expression. */
-                 temp1 = maybe_expand_string (temp2, Q_DOUBLE_QUOTES, expand_string);
-                 free (temp2);
-
-                 /* No error messages. */
-                 this_command_name = (char *)NULL;
-                 number = evalexp (temp1, &expok);
-                 free (temp);
-                 free (temp1);
-                 if (expok == 0)
-                   {
-                     free (string);
-                     free (istring);
-                     return (&expand_word_error);
-                   }
-                 goto add_number;
-               }
-
-       comsub:
-             temp1 = command_substitute (temp, quoted);
-             FREE (temp);
-             temp = temp1;
-             goto dollar_add_string;
-
-             /* Do straight arithmetic substitution. */
-           case '[':
-             /* We have to extract the contents of this
-                arithmetic substitution. */
-             t_index = sindex + 1;
-             temp = extract_arithmetic_subst (string, &t_index);
-             sindex = t_index;
-
-              /* Do initial variable expansion. */
-             temp1 = maybe_expand_string (temp, Q_DOUBLE_QUOTES, expand_string);
-
-             /* No error messages. */
-             this_command_name = (char *)NULL;
-             number = evalexp (temp1, &expok);
-             free (temp1);
-             free (temp);
-             if (expok == 0)
-               {
-                 free (string);
-                 free (istring);
-                 return (&expand_word_error);
-               }
-             goto add_number;
-
-           default:
-             /* Find the variable in VARIABLE_LIST. */
-             temp = (char *)NULL;
-
-             for (t_index = sindex;
-                  (c = string[sindex]) && legal_variable_char (c);
-                  sindex++);
-             temp1 = substring (string, t_index, sindex);
-
-             /* If this isn't a variable name, then just output the `$'. */
-             if (temp1 == 0 || *temp1 == '\0')
-               {
-                 FREE (temp1);
-                 temp = xmalloc (2);
-                 temp[0] = '$';
-                 temp[1] = '\0';
-                 if (expanded_something)
-                   *expanded_something = 0;
-                 goto add_string;
-               }
-
-             /* If the variable exists, return its value cell. */
-             var = find_variable (temp1);
-
-             if (var && invisible_p (var) == 0 && value_cell (var))
-               {
-#if defined (ARRAY_VARS)
-                 if (array_p (var))
-                   {
-                     temp = array_reference (array_cell (var), 0);
-                     if (temp)
-                       temp = quote_escapes (temp);
-                   }
-                 else
-#endif
-                 temp = quote_escapes (value_cell (var));
-                 free (temp1);
-                 goto add_string;
-               }
-
-             temp = (char *)NULL;
-
-             if (unbound_vars_is_error)
-               report_error ("%s: unbound variable", temp1);
-             else
-               {
-                 free (temp1);
-                 goto add_string;
-               }
-
-             free (temp1);
-             free (string);
-             last_command_exit_value = EXECUTION_FAILURE;
-             free (istring);
-             return ((unbound_vars_is_error && interactive_shell == 0)
-                       ? &expand_word_fatal
-                       : &expand_word_error);
-           }
-         break;                /* End case '$': */
-
-       case '`':               /* Backquoted command substitution. */
-         {
-           sindex++;
-
-           if (expanded_something)
-             *expanded_something = 1;
-
-           temp = string_extract (string, &sindex, "`", 0);
-           de_backslash (temp);
-           temp1 = command_substitute (temp, quoted);
-           FREE (temp);
-           temp = temp1;
-           goto dollar_add_string;
-         }
-
-       case '\\':
-         if (string[sindex + 1] == '\n')
-           {
-             sindex += 2;
-             continue;
-           }
-
-         c = string[++sindex];
-
-         if (quoted & Q_HERE_DOCUMENT)
-           temp1 = slashify_in_here_document;
-         else if (quoted & Q_DOUBLE_QUOTES)
-           temp1 = slashify_in_quotes;
-         else
-           temp1 = "";
-
-         if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && member (c, temp1) == 0)
-           {
-             temp = xmalloc (3);
-             temp[0] = '\\'; temp[1] = c; temp[2] = '\0';
-           }
-         else
-           /* This character is quoted, so add it in quoted mode. */
-           temp = make_quoted_char (c);
-
-         if (c)
-           sindex++;
-         goto add_string;
-
-       case '"':
-         if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT|Q_NOQUOTE))
-           goto add_character;
+       case '"':
+         if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT|Q_NOQUOTE))
+           goto add_character;
 
          t_index = ++sindex;
          temp = string_extract_double_quoted (string, &sindex, 0);
@@ -4698,13 +4965,12 @@ expand_word_internal (word, quoted, contains_dollar_at, expanded_something)
 
          if (temp && *temp)
            {
-             int dollar_at_flag;
-
              tword = make_word (temp);         /* XXX */
              free (temp);
              temp = (char *)NULL;
 
-             list = expand_word_internal (tword, Q_DOUBLE_QUOTES, &dollar_at_flag, (int *)NULL);
+             has_dollar_at = 0;
+             list = expand_word_internal (tword, Q_DOUBLE_QUOTES, &has_dollar_at, (int *)NULL);
 
              if (list == &expand_word_error || list == &expand_word_fatal)
                {
@@ -4722,7 +4988,7 @@ expand_word_internal (word, quoted, contains_dollar_at, expanded_something)
              /* "$@" (a double-quoted dollar-at) expands into nothing,
                 not even a NULL word, when there are no positional
                 parameters. */
-             if (list == 0 && dollar_at_flag)
+             if (list == 0 && has_dollar_at)
                {
                  quoted_dollar_at++;
                  break;
@@ -4739,7 +5005,7 @@ expand_word_internal (word, quoted, contains_dollar_at, expanded_something)
              if (list)
                dequote_list (list);
 
-             if (dollar_at_flag)
+             if (has_dollar_at)
                {
                  quoted_dollar_at++;
                  if (contains_dollar_at)
@@ -4775,6 +5041,18 @@ expand_word_internal (word, quoted, contains_dollar_at, expanded_something)
                {
                  temp = savestring (list->word->word);
                  dispose_words (list);
+#if 1
+                 /* If the string is not a quoted null string, we want
+                    to remove any embedded unquoted CTLNUL characters.
+                    We do not want to turn quoted null strings back into
+                    the empty string, though.  We do this because we
+                    want to remove any quoted nulls from expansions that
+                    contain other characters.  For example, if we have
+                    x"$*"y or "x$*y" and there are no positional parameters,
+                    the $* should expand into nothing. */                   
+                 if (QUOTED_NULL (temp) == 0)
+                   remove_quoted_nulls (temp); /* XXX */
+#endif
                }
            }
          else
@@ -4783,10 +5061,7 @@ expand_word_internal (word, quoted, contains_dollar_at, expanded_something)
          /* 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)
-           {
-             FREE (temp);
-             continue;
-           }
+           continue;
 
        add_quoted_string:
 
@@ -4921,7 +5196,7 @@ finished_with_string:
     {
       char *ifs_chars;
 
-      if (quoted_dollar_at)
+      if (quoted_dollar_at || has_dollar_at)
        {
          var = find_variable ("IFS");
          ifs_chars = var ? value_cell (var) : " \t\n";
@@ -4929,12 +5204,14 @@ finished_with_string:
       else
        ifs_chars = (char *)NULL;
 
-      /* According to Posix.2, "$@" expands to a single word if
-        IFS="" and the positional parameters are not empty. */
-      if (quoted_dollar_at && ifs_chars && *ifs_chars)
-       {
-         list = list_string (istring, " ", 1);
-       }
+      /* If we have $@, we need to split the results no matter what.  If
+        IFS is unset or NULL, string_list_dollar_at has separated the
+        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)
+       list = list_string (istring, *ifs_chars ? ifs_chars : " ", 1);
       else
        {
          tword = make_bare_word (istring);
@@ -5051,85 +5328,6 @@ word_list_quote_removal (list, quoted)
 }
 #endif
 
-/* Return 1 if CHARACTER appears in an unquoted portion of
-   STRING.  Return 0 otherwise. */
-static int
-unquoted_member (character, string)
-     int character;
-     char *string;
-{
-  int sindex, c;
-
-  for (sindex = 0; c = string[sindex]; )
-    {
-      if (c == character)
-       return (1);
-
-      switch (c)
-       {
-       default:
-         sindex++;
-         break;
-
-       case '\\':
-         sindex++;
-         if (string[sindex])
-           sindex++;
-         break;
-
-       case '\'':
-         sindex = skip_single_quoted (string, ++sindex);
-         break;
-
-       case '"':
-         sindex = skip_double_quoted (string, ++sindex);
-         break;
-       }
-    }
-  return (0);
-}
-
-/* Return 1 if SUBSTR appears in an unquoted portion of STRING. */
-static int
-unquoted_substring (substr, string)
-     char *substr, *string;
-{
-  int sindex, c, sublen;
-
-  if (substr == 0 || *substr == '\0')
-    return (0);
-
-  sublen = strlen (substr);
-  for (sindex = 0; c = string[sindex]; )
-    {
-      if (STREQN (string + sindex, substr, sublen))
-       return (1);
-
-      switch (c)
-       {
-       case '\\':
-         sindex++;
-
-         if (string[sindex])
-           sindex++;
-         break;
-
-       case '\'':
-         sindex = skip_single_quoted (string, ++sindex);
-         break;
-
-       case '"':
-         sindex = skip_double_quoted (string, ++sindex);
-         break;
-
-       default:
-         sindex++;
-         break;
-       }
-    }
-  return (0);
-}
-
 /*******************************************
  *                                        *
  *    Functions to perform word splitting  *
@@ -5157,6 +5355,9 @@ word_split (w)
        ifs_chars = "";
 
       result = list_string (w->word, ifs_chars, w->flags & W_QUOTED);
+
+      if (ifs && tempvar_p (ifs))      /* XXX */
+       dispose_variable (ifs);         /* XXX */
     }
   else
     result = (WORD_LIST *)NULL;
@@ -5182,10 +5383,15 @@ word_list_split (list)
 
 /**************************************************
  *                                               *
- *     Functions to expand an entire WORD_LIST   *
+ *    Functions to expand an entire WORD_LIST    *
  *                                               *
  **************************************************/
 
+/* Put NLIST (which is a WORD_LIST * of only one element) at the front of
+   ELIST, and set ELIST to the new list. */
+#define PREPEND_LIST(nlist, elist) \
+       do { nlist->next = elist; elist = nlist; } while (0)
+
 static WORD_LIST *varlist = (WORD_LIST *)NULL;
 
 /* Separate out any initial variable assignments from TLIST.  If set -k has
@@ -5277,6 +5483,25 @@ separate_out_assignments (tlist)
   return (tlist);
 }
 
+#define WEXP_VARASSIGN 0x001
+#define WEXP_BRACEEXP  0x002
+#define WEXP_TILDEEXP  0x004
+#define WEXP_PARAMEXP  0x008
+#define WEXP_PATHEXP   0x010
+
+/* All of the expansions, including variable assignments at the start of
+   the list. */
+#define WEXP_ALL       (WEXP_VARASSIGN|WEXP_BRACEEXP|WEXP_TILDEEXP|WEXP_PARAMEXP|WEXP_PATHEXP)
+
+/* All of the expansions except variable assignments at the start of
+   the list. */
+#define WEXP_NOVARS    (WEXP_BRACEEXP|WEXP_TILDEEXP|WEXP_PARAMEXP|WEXP_PATHEXP)
+
+/* All of the `shell expansions': brace expansion, tilde expansion, parameter
+   expansion, command substitution, arithmetic expansion, word splitting, and
+   quote removal. */
+#define WEXP_SHELLEXP  (WEXP_BRACEEXP|WEXP_TILDEEXP|WEXP_PARAMEXP)
+
 /* Take the list of words in LIST and do the various substitutions.  Return
    a new list of words which is the expanded list, and without things like
    variable assignments. */
@@ -5285,7 +5510,7 @@ WORD_LIST *
 expand_words (list)
      WORD_LIST *list;
 {
-  return (expand_word_list_internal (list, 1));
+  return (expand_word_list_internal (list, WEXP_ALL));
 }
 
 /* Same as expand_words (), but doesn't hack variable or environment
@@ -5294,126 +5519,189 @@ WORD_LIST *
 expand_words_no_vars (list)
      WORD_LIST *list;
 {
-  return (expand_word_list_internal (list, 0));
+  return (expand_word_list_internal (list, WEXP_NOVARS));
 }
 
-/* The workhorse for expand_words () and expand_words_no_vars ().
-   First arg is LIST, a WORD_LIST of words.
-   Second arg DO_VARS is non-zero if you want to do environment and
-   variable assignments, else zero.
-
-   This does all of the substitutions: brace expansion, tilde expansion,
-   parameter expansion, command substitution, arithmetic expansion,
-   process substitution, word splitting, and pathname expansion.  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_ASSIGNMENT
-   bit set do not undergo pathname expansion. */
-static WORD_LIST *
-expand_word_list_internal (list, do_vars)
+WORD_LIST *
+expand_words_shellexp (list)
      WORD_LIST *list;
-     int do_vars;
 {
-  WORD_LIST *tlist, *new_list, *next, *temp_list, *orig_list, *disposables;
-  char *temp_string;
-  int tint;
+  return (expand_word_list_internal (list, WEXP_SHELLEXP));
+}
 
-  if (list == 0)
-    return ((WORD_LIST *)NULL);
+static WORD_LIST *
+glob_expand_word_list (tlist, eflags)
+     WORD_LIST *tlist;
+     int eflags;
+{
+  char **glob_array, *temp_string;
+  register int glob_index;
+  WORD_LIST *glob_list, *output_list, *disposables, *next;
+  WORD_DESC *tword;
 
-  tlist = copy_word_list (list);
+  output_list = disposables = (WORD_LIST *)NULL;
+  glob_array = (char **)NULL;
+  while (tlist)
+    {
+      /* For each word, either globbing is attempted or the word is
+        added to orig_list.  If globbing succeeds, the results are
+        added to orig_list and the word (tlist) is added to the list
+        of disposable words.  If globbing fails and failed glob
+        expansions are left unchanged (the shell default), the
+        original word is added to orig_list.  If globbing fails and
+        failed glob expansions are removed, the original word is
+        added to the list of disposable words.  orig_list ends up
+        in reverse order and requires a call to reverse_list to
+        be set right.  After all words are examined, the disposable
+        words are freed. */
+      next = tlist->next;
 
-  if (do_vars)
-    {
-      tlist = separate_out_assignments (tlist);
-      if (tlist == 0)
+      /* If the word isn't an assignment and contains an unquoted
+         pattern matching character, then glob it. */
+      if ((tlist->word->flags & W_ASSIGNMENT) == 0 &&
+         unquoted_glob_pattern_p (tlist->word->word))
        {
-         if (varlist)
+         glob_array = shell_glob_filename (tlist->word->word);
+
+         /* Handle error cases.
+            I don't think we should report errors like "No such file
+            or directory".  However, I would like to report errors
+            like "Read failed". */
+
+         if (GLOB_FAILED (glob_array))
            {
-             /* All the words were variable assignments, so they are placed
-                into the shell's environment. */
-             for (new_list = varlist; new_list; new_list = new_list->next)
-               {
-                 this_command_name = (char *)NULL;     /* no arithmetic errors */
-                 tint = do_assignment (new_list->word->word);
-                 /* Variable assignment errors in non-interactive shells
-                    running in Posix.2 mode cause the shell to exit. */
-                 if (tint == 0 && interactive_shell == 0 && posixly_correct)
-                   {
-                     last_command_exit_value = EXECUTION_FAILURE;
-                     jump_to_top_level (FORCE_EOF);
-                   }
-               }
-             dispose_words (varlist);
-             varlist = (WORD_LIST *)NULL;
+             glob_array = (char **) xmalloc (sizeof (char *));
+             glob_array[0] = (char *)NULL;
+           }
+
+         /* Dequote the current word in case we have to use it. */
+         if (glob_array[0] == NULL)
+           {
+             temp_string = dequote_string (tlist->word->word);
+             free (tlist->word->word);
+             tlist->word->word = temp_string;
+           }
+
+         /* Make the array into a word list. */
+         glob_list = (WORD_LIST *)NULL;
+         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);
+           }
+
+         if (glob_list)
+           {
+             output_list = (WORD_LIST *)list_append (glob_list, output_list);
+             PREPEND_LIST (tlist, disposables);
+           }
+         else if (allow_null_glob_expansion == 0)
+           {
+             /* Failed glob expressions are left unchanged. */
+             PREPEND_LIST (tlist, output_list);
+           }
+         else
+           {
+             /* Failed glob expressions are removed. */
+             PREPEND_LIST (tlist, disposables);
            }
-         return ((WORD_LIST *)NULL);
        }
+      else
+       {
+         /* Dequote the string. */
+         temp_string = dequote_string (tlist->word->word);
+         free (tlist->word->word);
+         tlist->word->word = temp_string;
+         PREPEND_LIST (tlist, output_list);
+       }
+
+      free_array (glob_array);
+      glob_array = (char **)NULL;
+
+      tlist = next;
     }
 
-  /* Begin expanding the words that remain.  The expansions take place on
-     things that aren't really variable assignments. */
+  if (disposables)
+    dispose_words (disposables);
+
+  if (output_list)
+    output_list = REVERSE_LIST (output_list, WORD_LIST *);
+
+  return (output_list);
+}
 
 #if defined (BRACE_EXPANSION)
-  /* Do brace expansion on this word if there are any brace characters
-     in the string. */
-  if (brace_expansion && tlist)
+static WORD_LIST *
+brace_expand_word_list (tlist, eflags)
+     WORD_LIST *tlist;
+     int eflags;
+{
+  register char **expansions;
+  char *temp_string;
+  WORD_LIST *disposables, *output_list, *next;
+  WORD_DESC *w;
+  int eindex;
+
+  for (disposables = output_list = (WORD_LIST *)NULL; tlist; tlist = next)
     {
-      register char **expansions;
-      WORD_LIST *braces;
-      WORD_DESC *w;
-      int eindex;
+      next = tlist->next;
 
-      for (braces = disposables = (WORD_LIST *)NULL; tlist; tlist = next)
+      /* 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 `strchr', 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 (strchr (tlist->word->word, LBRACE))
        {
-         next = tlist->next;
-
-         /* 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 `strchr', 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 (strchr (tlist->word->word, '{'))
-           {
-             expansions = brace_expand (tlist->word->word);
+         expansions = brace_expand (tlist->word->word);
 
-             for (eindex = 0; temp_string = expansions[eindex]; eindex++)
-               {
-                 w = make_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;
-                 braces = make_word_list (w, braces);
-                 free (expansions[eindex]);
-               }
-             free (expansions);
-
-             /* Add TLIST to the list of words to be freed after brace
-                expansion has been performed. */
-             tlist->next = disposables;
-             disposables = tlist;
-           }
-         else
+         for (eindex = 0; temp_string = expansions[eindex]; eindex++)
            {
-             tlist->next = braces;
-             braces = tlist;
+             w = make_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;
+             output_list = make_word_list (w, output_list);
+             free (expansions[eindex]);
            }
-       }
+         free (expansions);
 
-      dispose_words (disposables);
-      tlist = REVERSE_LIST (braces, WORD_LIST *);
+         /* Add TLIST to the list of words to be freed after brace
+            expansion has been performed. */
+         PREPEND_LIST (tlist, disposables);
+       }
+      else
+       PREPEND_LIST (tlist, output_list);
     }
-#endif /* BRACE_EXPANSION */
+
+  if (disposables)
+    dispose_words (disposables);
+
+  if (output_list)
+    output_list = REVERSE_LIST (output_list, WORD_LIST *);
+
+  return (output_list);
+}
+#endif
+
+static WORD_LIST *
+shell_expand_word_list (tlist, eflags)
+     WORD_LIST *tlist;
+     int eflags;
+{
+  WORD_LIST *expanded, *orig_list, *new_list, *next, *temp_list;
+  int expanded_something, has_dollar_at;
+  char *temp_string;
 
   /* We do tilde expansion all the time.  This is what 1003.2 says. */
-  for (orig_list = tlist, new_list = (WORD_LIST *)NULL; tlist; tlist = next)
+  new_list = (WORD_LIST *)NULL;
+  for (orig_list = tlist; tlist; tlist = next)
     {
-      WORD_LIST *expanded;
-      int expanded_something, has_dollar_at;
-
       temp_string = tlist->word->word;
 
       next = tlist->next;
@@ -5470,139 +5758,99 @@ expand_word_list_internal (list, do_vars)
          temp_list = expanded;
        }
 
-      /* In the most common cases, t will be a list containing only one
-        element, so the call to reverse_list would be wasted. */
       expanded = REVERSE_LIST (temp_list, WORD_LIST *);
       new_list = (WORD_LIST *)list_append (expanded, new_list);
     }
 
-  new_list = REVERSE_LIST (new_list, WORD_LIST *);
-
-  dispose_words (orig_list);
+  if (orig_list)  
+    dispose_words (orig_list);
 
-  /* Okay, we're almost done.  Now let's just do some filename
-     globbing. */
   if (new_list)
-    {
-      char **glob_array;
-      register int glob_index;
-      WORD_LIST *glob_list;
-      WORD_DESC *tword;
-
-      orig_list = disposables = (WORD_LIST *)NULL;
-      tlist = new_list;
+    new_list = REVERSE_LIST (new_list, WORD_LIST *);
 
-      /* orig_list == output list, despite the name. */
-      if (disallow_filename_globbing == 0)
-       {
-         glob_array = (char **)NULL;
-         while (tlist)
-           {
-             /* For each word, either globbing is attempted or the word is
-                added to orig_list.  If globbing succeeds, the results are
-                added to orig_list and the word (tlist) is added to the list
-                of disposable words.  If globbing fails and failed glob
-                expansions are left unchanged (the shell default), the
-                original word is added to orig_list.  If globbing fails and
-                failed glob expansions are removed, the original word is
-                added to the list of disposable words.  orig_list ends up
-                in reverse order and requires a call to reverse_list to
-                be set right.  After all words are examined, the disposable
-                words are freed. */
-             next = tlist->next;
-
-             /* If the word isn't an assignment and contains an unquoted
-                pattern matching character, then glob it. */
-#if 0
-             if ((tlist->word->flags & (W_QUOTED|W_ASSIGNMENT)) == 0 &&
-#else
-             if ((tlist->word->flags & W_ASSIGNMENT) == 0 &&
-#endif
-                 unquoted_glob_pattern_p (tlist->word->word))
-               {
-                 glob_array = shell_glob_filename (tlist->word->word);
+  return (new_list);
+}
 
-                 /* Handle error cases.
-                    I don't think we should report errors like "No such file
-                    or directory".  However, I would like to report errors
-                    like "Read failed". */
+/* The workhorse for expand_words () and expand_words_no_vars ().
+   First arg is LIST, a WORD_LIST of words.
+   Second arg DO_VARS is non-zero if you want to do environment and
+   variable assignments, else zero.
 
-                 if (GLOB_FAILED (glob_array))
-                   {
-                     glob_array = (char **) xmalloc (sizeof (char *));
-                     glob_array[0] = (char *)NULL;
-                   }
+   This does all of the substitutions: brace expansion, tilde expansion,
+   parameter expansion, command substitution, arithmetic expansion,
+   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_ASSIGNMENT bit set do not undergo pathname expansion. */
+static WORD_LIST *
+expand_word_list_internal (list, eflags)
+     WORD_LIST *list;
+     int eflags;
+{
+  WORD_LIST *new_list, *temp_list;
+  int tint;
 
-                 /* Dequote the current word in case we have to use it. */
-                 if (glob_array[0] == NULL)
-                   {
-                     temp_string = dequote_string (tlist->word->word);
-                     free (tlist->word->word);
-                     tlist->word->word = temp_string;
-                   }
+  if (list == 0)
+    return ((WORD_LIST *)NULL);
 
-                 /* Make the array into a word list. */
-                 glob_list = (WORD_LIST *)NULL;
-                 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);
-                   }
+  new_list = copy_word_list (list);
 
-                 if (glob_list)
-                   {
-                     orig_list = (WORD_LIST *)list_append (glob_list, orig_list);
-                     tlist->next = disposables;
-                     disposables = tlist;
-                   }
-                 else if (allow_null_glob_expansion == 0)
-                   {
-                     /* Failed glob expressions are left unchanged. */
-                     tlist->next = orig_list;
-                     orig_list = tlist;
-                   }
-                 else
+  if (eflags & WEXP_VARASSIGN)
+    {
+      new_list = separate_out_assignments (new_list);
+      if (new_list == 0)
+       {
+         if (varlist)
+           {
+             /* All the words were variable assignments, so they are placed
+                into the shell's environment. */
+             for (temp_list = varlist; temp_list; temp_list = temp_list->next)
+               {
+                 this_command_name = (char *)NULL;     /* no arithmetic errors */
+                 tint = do_assignment (temp_list->word->word);
+                 /* Variable assignment errors in non-interactive shells
+                    running in Posix.2 mode cause the shell to exit. */
+                 if (tint == 0 && interactive_shell == 0 && posixly_correct)
                    {
-                     /* Failed glob expressions are removed. */
-                     tlist->next = disposables;
-                     disposables = tlist;
+                     last_command_exit_value = EXECUTION_FAILURE;
+                     jump_to_top_level (FORCE_EOF);
                    }
                }
-             else
-               {
-                 /* Dequote the string. */
-                 temp_string = dequote_string (tlist->word->word);
-                 free (tlist->word->word);
-                 tlist->word->word = temp_string;
-                 tlist->next = orig_list;
-                 orig_list = tlist;
-               }
+             dispose_words (varlist);
+             varlist = (WORD_LIST *)NULL;
+           }
+         return ((WORD_LIST *)NULL);
+       }
+    }
 
-             free_array (glob_array);
-             glob_array = (char **)NULL;
+  /* Begin expanding the words that remain.  The expansions take place on
+     things that aren't really variable assignments. */
 
-             tlist = next;
-           }
+#if defined (BRACE_EXPANSION)
+  /* Do brace expansion on this word if there are any brace characters
+     in the string. */
+  if ((eflags & WEXP_BRACEEXP) && brace_expansion && new_list)
+    new_list = brace_expand_word_list (new_list, eflags);
+#endif /* BRACE_EXPANSION */
 
-         if (disposables)
-           dispose_words (disposables);
+  /* Perform the `normal' shell expansions: tilde expansion, parameter and
+     variable substitution, command substitution, arithmetic expansion,
+     and word splitting. */
+  new_list = shell_expand_word_list (new_list, eflags);
 
-         new_list = REVERSE_LIST (orig_list, WORD_LIST *);
-       }
+  /* Okay, we're almost done.  Now let's just do some filename
+     globbing. */
+  if (new_list)
+    {
+      if ((eflags & WEXP_PATHEXP) && disallow_filename_globbing == 0)
+       /* Glob expand the word list unless globbing has been disabled. */
+       new_list = glob_expand_word_list (new_list, eflags);
       else
-       {
-         /* Dequote the words, because we're not performing globbing. */
-         for (temp_list = new_list; temp_list; temp_list = temp_list->next)
-           {
-             temp_string = dequote_string (temp_list->word->word);
-             free (temp_list->word->word);
-             temp_list->word->word = temp_string;
-           }
-       }
+       /* Dequote the words, because we're not performing globbing. */
+       new_list = dequote_list (new_list);
     }
 
-  if (do_vars)
+  if ((eflags & WEXP_VARASSIGN) && varlist)
     {
       Function *assign_func;
 
@@ -5629,335 +5877,13 @@ expand_word_list_internal (list, do_vars)
       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, tlist = new_list; tlist; tlist = tlist->next)
-    glob_argv_flags[tint++] = (tlist->word->flags & W_GLOBEXP) ? '1' : '0';
+  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';
-
-  return (new_list);
-}
-
-/*************************************************
- *                                              *
- *     Functions to manage special variables    *
- *                                              *
- *************************************************/
-
-/* An alist of name.function for each special variable.  Most of the
-   functions don't do much, and in fact, this would be faster with a
-   switch statement, but by the end of this file, I am sick of switch
-   statements. */
-
-#define SET_INT_VAR(name, intvar)  intvar = find_variable (name) != 0
-
-struct name_and_function {
-  char *name;
-  VFunction *function;
-} special_vars[] = {
-  { "PATH", sv_path },
-  { "MAIL", sv_mail },
-  { "MAILPATH", sv_mail },
-  { "MAILCHECK", sv_mail },
-
-  { "POSIXLY_CORRECT", sv_strict_posix },
-  { "GLOBIGNORE", sv_globignore },
-
-  /* Variables which only do something special when READLINE is defined. */
-#if defined (READLINE)
-  { "TERM", sv_terminal },
-  { "TERMCAP", sv_terminal },
-  { "TERMINFO", sv_terminal },
-  { "HOSTFILE", sv_hostfile },
-#endif /* READLINE */
-
-  /* Variables which only do something special when HISTORY is defined. */
-#if defined (HISTORY)
-  { "HISTIGNORE", sv_histignore },
-  { "HISTSIZE", sv_histsize },
-  { "HISTFILESIZE", sv_histsize },
-  { "HISTCONTROL", sv_history_control },
-#  if defined (BANG_HISTORY)
-  { "histchars", sv_histchars },
-#  endif /* BANG_HISTORY */
-#endif /* HISTORY */
-
-  { "IGNOREEOF", sv_ignoreeof },
-  { "ignoreeof", sv_ignoreeof },
-
-  { "OPTIND", sv_optind },
-  { "OPTERR", sv_opterr },
-
-  { "TEXTDOMAIN", sv_locale },
-  { "TEXTDOMAINDIR", sv_locale },
-  { "LC_ALL", sv_locale },
-  { "LC_COLLATE", sv_locale },
-  { "LC_CTYPE", sv_locale },
-  { "LC_MESSAGES", sv_locale },
-  { "LANG", sv_locale },
-
-#if defined (HAVE_TZSET) && defined (PROMPT_STRING_DECODE)
-  { "TZ", sv_tz },
-#endif
-
-  { (char *)0, (VFunction *)0 }
-};
-
-/* The variable in NAME has just had its state changed.  Check to see if it
-   is one of the special ones where something special happens. */
-void
-stupidly_hack_special_variables (name)
-     char *name;
-{
-  int i;
-
-  for (i = 0; special_vars[i].name; i++)
-    {
-      if (STREQ (special_vars[i].name, name))
-       {
-         (*(special_vars[i].function)) (name);
-         return;
-       }
-    }
-}
-
-/* What to do just after the PATH variable has changed. */
-void
-sv_path (name)
-     char *name;
-{
-  /* hash -r */
-  flush_hashed_filenames ();
-}
-
-/* What to do just after one of the MAILxxxx variables has changed.  NAME
-   is the name of the variable.  This is called with NAME set to one of
-   MAIL, MAILCHECK, or MAILPATH.  */
-void
-sv_mail (name)
-     char *name;
-{
-  /* If the time interval for checking the files has changed, then
-     reset the mail timer.  Otherwise, one of the pathname vars
-     to the users mailbox has changed, so rebuild the array of
-     filenames. */
-  if (name[4] == 'C')  /* if (strcmp (name, "MAILCHECK") == 0) */
-    reset_mail_timer ();
-  else
-    {
-      free_mail_files ();
-      remember_mail_dates ();
-    }
-}
-
-/* What to do when GLOBIGNORE changes. */
-void
-sv_globignore (name)
-     char *name;
-{
-  setup_glob_ignore (name);
-}
-
-#if defined (READLINE)
-/* What to do just after one of the TERMxxx variables has changed.
-   If we are an interactive shell, then try to reset the terminal
-   information in readline. */
-void
-sv_terminal (name)
-     char *name;
-{
-  if (interactive_shell && no_line_editing == 0)
-    rl_reset_terminal (get_string_value ("TERM"));
-}
-
-void
-sv_hostfile (name)
-     char *name;
-{
-  hostname_list_initialized = 0;
-}
-#endif /* READLINE */
-
-#if defined (HISTORY)
-/* What to do after the HISTSIZE or HISTFILESIZE variables change.
-   If there is a value for this HISTSIZE (and it is numeric), then stifle
-   the history.  Otherwise, if there is NO value for this variable,
-   unstifle the history.  If name is HISTFILESIZE, and its value is
-   numeric, truncate the history file to hold no more than that many
-   lines. */
-void
-sv_histsize (name)
-     char *name;
-{
-  char *temp;
-  long num;
-
-  temp = get_string_value (name);
-
-  if (temp && *temp)
-    {
-      if (legal_number (temp, &num))
-        {
-         if (name[4] == 'S')
-           {
-             stifle_history (num);
-             num = where_history ();
-             if (history_lines_this_session > num)
-               history_lines_this_session = num;
-           }
-         else
-           {
-             history_truncate_file (get_string_value ("HISTFILE"), (int)num);
-             if (num <= history_lines_in_file)
-               history_lines_in_file = num;
-           }
-       }
-    }
-  else if (name[4] == 'S')
-    unstifle_history ();
-}
-
-/* What to do after the HISTIGNORE variable changes. */
-void
-sv_histignore (name)
-     char *name;
-{
-  setup_history_ignore (name);
-}
-
-/* What to do after the HISTCONTROL variable changes. */
-void
-sv_history_control (name)
-     char *name;
-{
-  char *temp;
-
-  history_control = 0;
-  temp = get_string_value (name);
-
-  if (temp && *temp && STREQN (temp, "ignore", 6))
-    {
-      if (temp[6] == 's')      /* ignorespace */
-       history_control = 1;
-      else if (temp[6] == 'd') /* ignoredups */
-       history_control = 2;
-      else if (temp[6] == 'b') /* ignoreboth */
-       history_control = 3;
-    }
-}
-
-#if defined (BANG_HISTORY)
-/* Setting/unsetting of the history expansion character. */
-void
-sv_histchars (name)
-     char *name;
-{
-  char *temp;
-
-  temp = get_string_value (name);
-  if (temp)
-    {
-      history_expansion_char = *temp;
-      if (temp[0] && temp[1])
-       {
-         history_subst_char = temp[1];
-         if (temp[2])
-             history_comment_char = temp[2];
-       }
-    }
-  else
-    {
-      history_expansion_char = '!';
-      history_subst_char = '^';
-      history_comment_char = '#';
-    }
-}
-#endif /* BANG_HISTORY */
-#endif /* HISTORY */
-
-#if defined (HAVE_TZSET) && defined (PROMPT_STRING_DECODE)
-void
-sv_tz (name)
-     char *name;
-{
-  tzset ();
-}
 #endif
 
-/* If the variable exists, then the value of it can be the number
-   of times we actually ignore the EOF.  The default is small,
-   (smaller than csh, anyway). */
-void
-sv_ignoreeof (name)
-     char *name;
-{
-  SHELL_VAR *tmp_var;
-  char *temp;
-
-  eof_encountered = 0;
-
-  tmp_var = find_variable (name);
-  ignoreeof = tmp_var != 0;
-  temp = tmp_var ? value_cell (tmp_var) : (char *)NULL;
-  if (temp)
-    eof_encountered_limit = (*temp && all_digits (temp)) ? atoi (temp) : 10;
-  set_shellopts ();    /* make sure `ignoreeof' is/is not in $SHELLOPTS */
-}
-
-void
-sv_optind (name)
-     char *name;
-{
-  char *tt;
-  int s;
-
-  tt = get_string_value ("OPTIND");
-  if (tt && *tt)
-    {
-      s = atoi (tt);
-
-      /* According to POSIX, setting OPTIND=1 resets the internal state
-        of getopt (). */
-      if (s < 0 || s == 1)
-       s = 0;
-    }
-  else
-    s = 0;
-  getopts_reset (s);
-}
-
-void
-sv_opterr (name)
-     char *name;
-{
-  char *tt;
-
-  tt = get_string_value ("OPTERR");
-  sh_opterr = (tt && *tt) ? atoi (tt) : 1;
-}
-
-void
-sv_strict_posix (name)
-     char *name;
-{
-  SET_INT_VAR (name, posixly_correct);
-  posix_initialize (posixly_correct);
-#if defined (READLINE)
-  if (interactive_shell)
-    posix_readline_initialize (posixly_correct);
-#endif /* READLINE */
-  set_shellopts ();    /* make sure `posix' is/is not in $SHELLOPTS */
-}
-
-void
-sv_locale (name)
-     char *name;
-{
-  char *v;
-
-  v = get_string_value (name);
-  if (name[0] == 'L' && name[1] == 'A')        /* LANG */
-    set_lang (name, v);
-  else
-    set_locale_var (name, v);          /* LC_*, TEXTDOMAIN* */
+  return (new_list);
 }