]> git.ipfire.org Git - thirdparty/bash.git/blobdiff - subst.c
Imported from ../bash-2.05.tar.gz.
[thirdparty/bash.git] / subst.c
diff --git a/subst.c b/subst.c
index d57867512be136dd3927c719a0a4ac2511ec271b..bba1eb2456b4cffe8aecf0c88703c088280b89fd 100644 (file)
--- a/subst.c
+++ b/subst.c
@@ -80,11 +80,26 @@ extern int errno;
 #define LPAREN         '('
 #define RPAREN         ')'
 
+/* Evaluates to 1 if C is one of the shell's special parameters whose length
+   can be taken, but is also one of the special expansion characters. */
+#define VALID_SPECIAL_LENGTH_PARAM(c) \
+  ((c) == '-' || (c) == '?' || (c) == '#')
+
+/* Evaluates to 1 if C is one of the shell's special parameters for which an
+   indirect variable reference may be made. */
+#define VALID_INDIR_PARAM(c) \
+  ((c) == '#' || (c) == '?' || (c) == '@' || (c) == '*')
+
+/* Evaluates to 1 if C is one of the OP characters that follows the parameter
+   in ${parameter[:]OPword}. */
+#define VALID_PARAM_EXPAND_CHAR(c) \
+  ((c) == '-' || (c) == '=' || (c) == '?' || (c) == '+')
+
 /* Evaluates to 1 if this is one of the shell's special variables. */
 #define SPECIAL_VAR(name, wi) \
- ((digit (*name) && all_digits (name)) || \
-      (name[1] == '\0' && member (*name, "#-?$!@*")) || \
-      (wi && name[2] == '\0' && member (name[1], "#?@*")))
+ ((isdigit (*name) && all_digits (name)) || \
+      (name[1] == '\0' && (sh_syntaxtab[*name] & CSPECVAR)) || \
+      (wi && name[2] == '\0' && VALID_INDIR_PARAM (name[1])))
 
 /* Process ID of the last command executed within command substitution. */
 pid_t last_command_subst_pid = NO_PID;
@@ -115,13 +130,19 @@ static int glob_argv_flags_size;
 static WORD_LIST expand_word_error, expand_word_fatal;
 static char expand_param_error, expand_param_fatal;
 
-static int doing_completion = 0;
-static int doing_prompt_expansion = 0;
+/* Tell the expansion functions to not longjmp back to top_level on fatal
+   errors.  Enabled when doing completion and prompt string expansion. */
+static int no_longjmp_on_fatal_error = 0;
+
+/* Set by expand_word_unsplit; used to inhibit splitting and re-joining
+   $* on $IFS, primarily when doing assignment statements. */
+static int expand_no_split_dollar_star = 0;
 
 /* Used to hold a list of variable assignments preceding a command.  Global
    so the SIGCHLD handler in jobs.c can unwind-protect it when it runs a
    SIGCHLD trap. */
 WORD_LIST *subst_assign_varlist = (WORD_LIST *)NULL;
+
 /* A WORD_LIST of words to be expanded by expand_word_list_internal,
    without any leading variable assignments. */
 static WORD_LIST *garglist = (WORD_LIST *)NULL;
@@ -187,12 +208,12 @@ quoted_substring (string, start, end)
     {
       if (*s == CTLESC)
        {
-          s++;
-          continue;
+         s++;
+         continue;
        }
       l++;
       if (*s == 0)
-        break;
+       break;
     }
 
   r = result = xmalloc (2*len + 1);      /* save room for quotes */
@@ -202,11 +223,11 @@ quoted_substring (string, start, end)
   for (l = 0; l < len; s++)
     {
       if (*s == CTLESC)
-        *r++ = *s++;
+       *r++ = *s++;
       *r++ = *s;
       l++;
       if (*s == 0)
-        break;
+       break;
     }
   *r = '\0';
   return result;
@@ -460,7 +481,7 @@ string_extract_double_quoted (string, sindex, stripdq)
                The returned string will be run through expansion as if
                it were double-quoted. */
          if ((stripdq == 0 && c != '"') ||
-             (stripdq && ((dquote && strchr (slashify_in_quotes, c)) || dquote == 0)))
+             (stripdq && ((dquote && (sh_syntaxtab[c] & CBSDQUOTE)) || dquote == 0)))
            temp[j++] = '\\';
          temp[j++] = c;
          pass_next = 0;
@@ -518,7 +539,7 @@ string_extract_double_quoted (string, sindex, stripdq)
        }
 
       /* Add any character but a double quote to the quoted string we're
-         accumulating. */
+        accumulating. */
       if (c != '"')
        {
          temp[j++] = c;
@@ -632,13 +653,13 @@ skip_single_quoted (string, sind)
      char *string;
      int sind;
 {
-  register int i;
+  register int c;
 
-  for (i = sind; string[i] && string[i] != '\''; i++)
+  for (c = sind; string[c] && string[c] != '\''; c++)
     ;
-  if (string[i])
-    i++;
-  return i;
+  if (string[c])
+    c++;
+  return c;
 }
 
 /* Just like string_extract, but doesn't hack backslashes or any of
@@ -688,7 +709,7 @@ extract_command_subst (string, sindex)
   return (extract_delimited_string (string, sindex, "$(", "(", ")"));
 }
 
-/* Extract the $[ construct in STRING, and return a new string.
+/* Extract the $[ construct in STRING, and return a new string. (])
    Start extracting at (SINDEX) as if we had just seen "$[".
    Make (SINDEX) get the position of the matching "]". */
 char *
@@ -696,7 +717,7 @@ extract_arithmetic_subst (string, sindex)
      char *string;
      int *sindex;
 {
-  return (extract_delimited_string (string, sindex, "$[", "[", "]"));
+  return (extract_delimited_string (string, sindex, "$[", "[", "]")); /*]*/
 }
 
 #if defined (PROCESS_SUBSTITUTION)
@@ -756,7 +777,7 @@ extract_delimited_string (string, sindex, opener, alt_opener, closer)
       c = string[i];
 
       if (c == 0)
-        break;
+       break;
 
       if (pass_character)      /* previous char was backslash */
        {
@@ -772,12 +793,7 @@ extract_delimited_string (string, sindex, opener, alt_opener, closer)
          continue;
        }
 
-#if 0
-      if (c == '\\' && delimiter == '"' &&
-             (member (string[i], slashify_in_quotes)))
-#else
       if (c == '\\')
-#endif
        {
          pass_character++;
          i++;
@@ -816,29 +832,29 @@ extract_delimited_string (string, sindex, opener, alt_opener, closer)
 
       /* Pass old-style command substitution through verbatim. */
       if (c == '`')
-        {
-          si = i + 1;
-          t = string_extract (string, &si, "`", 0);
-          i = si + 1;
-          FREE (t);
-          continue;
-        }
+       {
+         si = i + 1;
+         t = string_extract (string, &si, "`", 0);
+         i = si + 1;
+         FREE (t);
+         continue;
+       }
 
       /* Pass single-quoted strings through verbatim. */
       if (c == '\'')
-        {
-          si = i + 1;
-          i = skip_single_quoted (string, si);
-          continue;
-        }
+       {
+         si = i + 1;
+         i = skip_single_quoted (string, si);
+         continue;
+       }
 
       /* Pass embedded double-quoted strings through verbatim as well. */
       if (c == '"')
-        {
-          si = i + 1;
-          i = skip_double_quoted (string, si);
-          continue;
-        }
+       {
+         si = i + 1;
+         i = skip_double_quoted (string, si);
+         continue;
+       }
 
       i++;     /* move past this character, which was not special. */
     }
@@ -846,10 +862,11 @@ extract_delimited_string (string, sindex, opener, alt_opener, closer)
 #if 0
   if (c == 0 && nesting_level)
 #else
-  if (c == 0 && nesting_level && doing_completion == 0)
+  if (c == 0 && nesting_level && no_longjmp_on_fatal_error == 0)
 #endif
     {
       report_error ("bad substitution: no `%s' in %s", closer, string);
+      last_command_exit_value = EXECUTION_FAILURE;
       jump_to_top_level (DISCARD);
     }
 
@@ -948,9 +965,10 @@ extract_dollar_brace_string (string, sindex, quoted)
        }
     }
 
-  if (c == 0 && nesting_level && doing_completion == 0)
+  if (c == 0 && nesting_level && no_longjmp_on_fatal_error == 0)
     {
       report_error ("bad substitution: no ending `}' in %s", string);
+      last_command_exit_value = EXECUTION_FAILURE;
       jump_to_top_level (DISCARD);
     }
 
@@ -1003,11 +1021,11 @@ unquote_bang (string)
 #if defined (READLINE)
 /* Return 1 if the portion of STRING ending at EINDEX is quoted (there is
    an unclosed quoted string), or if the character at EINDEX is quoted
-   by a backslash. DOING_COMPLETION is used to flag that the various
+   by a backslash. NO_LONGJMP_ON_FATAL_ERROR is used to flag that the various
    single and double-quoted string parsing functions should not return an
    error if there are unclosed quotes or braces. */
 
-#define CQ_RETURN(x) do { doing_completion = 0; return (x); } while (0)
+#define CQ_RETURN(x) do { no_longjmp_on_fatal_error = 0; return (x); } while (0)
 
 int
 char_is_quoted (string, eindex)
@@ -1016,7 +1034,7 @@ char_is_quoted (string, eindex)
 {
   int i, pass_next, quoted;
 
-  doing_completion = 1;
+  no_longjmp_on_fatal_error = 1;
   for (i = pass_next = quoted = 0; i <= eindex; i++)
     {
       if (pass_next)
@@ -1097,7 +1115,7 @@ skip_to_delim (string, start, delims)
   int i, pass_next, backq, si;
   char *temp;
 
-  doing_completion = 1;
+  no_longjmp_on_fatal_error = 1;
   for (i = start, pass_next = backq = 0; string[i]; i++)
     {
       if (pass_next)
@@ -1143,7 +1161,7 @@ skip_to_delim (string, start, delims)
          continue;
        }
       else if (member (string[i], delims))
-        break;
+       break;
     }
   CQ_RETURN(i);
 }
@@ -1192,7 +1210,7 @@ split_at_delims (string, slen, delims, sentinel, nwp, cwp)
 
   ret = (WORD_LIST *)NULL;
 
-  for (i = 0; member (string[i], d) && whitespace(string[i]); i++)
+  for (i = 0; member (string[i], d) && (whitespace(string[i]) || string[i] == '\n'); i++)
     ;
   if (string[i] == '\0')
     return (ret);
@@ -1207,10 +1225,10 @@ split_at_delims (string, slen, delims, sentinel, nwp, cwp)
       /* If we have a non-whitespace delimiter character, use it to make a
         separate field.  This is just about what $IFS splitting does and
         is closer to the behavior of the shell parser. */
-      if (ts == te && member(string[ts], d2))
+      if (ts == te && d2 && member (string[ts], d2))
        {
          te = ts + 1;
-         while (member(string[te], d2))
+         while (member (string[te], d2))
            te++;
        }
 
@@ -1224,27 +1242,27 @@ split_at_delims (string, slen, delims, sentinel, nwp, cwp)
        cw = nw;
 
       /* If the cursor is at whitespace just before word start, set the
-         sentinel word to the current word. */
+        sentinel word to the current word. */
       if (cwp && cw == -1 && sentinel == ts-1)
        cw = nw;
 
       /* If the cursor is at whitespace between two words, make a new, empty
-         word, add it before (well, after, since the list is in reverse order)
-         the word we just added, and set the current word to that one. */
+        word, add it before (well, after, since the list is in reverse order)
+        the word we just added, and set the current word to that one. */
       if (cwp && cw == -1 && sentinel < ts)
-        {
-          tl = (WORD_LIST *)xmalloc (sizeof (WORD_LIST));
-          tl->word = make_word ("");
-          tl->next = ret->next;
-          ret->next = tl;
-          cw = nw;
-          nw++;
-        }
+       {
+         tl = (WORD_LIST *)xmalloc (sizeof (WORD_LIST));
+         tl->word = make_word ("");
+         tl->next = ret->next;
+         ret->next = tl;
+         cw = nw;
+         nw++;
+       }
 
       if (string[te] == 0)
        break;
 
-      i = te + member(string[te], d);
+      i = te + member (string[te], d);
       while (member (string[i], d) && whitespace(string[i]))
        i++;
 
@@ -1261,11 +1279,11 @@ split_at_delims (string, slen, delims, sentinel, nwp, cwp)
   if (cwp && cw == -1 && sentinel >= slen)
     {
       if (whitespace (string[sentinel - 1]))
-        {
-          token = "";
-          ret = add_string_to_list (token, ret);
-          nw++;
-        }
+       {
+         token = "";
+         ret = add_string_to_list (token, ret);
+         nw++;
+       }
       cw = nw;
     }
 
@@ -1444,7 +1462,7 @@ list_string (string, separators, quoted)
   WORD_LIST *result;
   WORD_DESC *t;
   char *current_word, *s;
-  int sindex, sh_style_split;
+  int sindex, sh_style_split, whitesep;
 
   if (!string || !*string)
     return ((WORD_LIST *)NULL);
@@ -1511,6 +1529,9 @@ list_string (string, separators, quoted)
 
       free (current_word);
 
+      /* Note whether or not the separator is IFS whitespace, used later. */
+      whitesep = string[sindex] && spctabnl (string[sindex]);
+
       /* Move past the current separator character. */
       if (string[sindex])
        sindex++;
@@ -1519,6 +1540,13 @@ list_string (string, separators, quoted)
         in the list of separators. */
       while (string[sindex] && spctabnl (string[sindex]) && issep (string[sindex]))
        sindex++;
+
+      /* If the first separator was IFS whitespace and the current character is
+        a non-whitespace IFS character, it should be part of the current field
+        delimiter, not a separate delimiter that would result in an empty field.
+        Look at POSIX.2, 3.6.5, (3)(b). */
+      if (string[sindex] && whitesep && issep (string[sindex]) && !spctabnl (string[sindex]))
+       sindex++;
     }
   return (REVERSE_LIST (result, WORD_LIST *));
 }
@@ -1534,7 +1562,7 @@ get_word_from_string (stringp, separators, endptr)
 {
   register char *s;
   char *current_word;
-  int sindex, sh_style_split;
+  int sindex, sh_style_split, whitesep;
 
   if (!stringp || !*stringp || !**stringp)
     return ((char *)NULL);
@@ -1573,6 +1601,9 @@ get_word_from_string (stringp, separators, endptr)
   if (endptr)
     *endptr = s + sindex;
 
+  /* Note whether or not the separator is IFS whitespace, used later. */
+  whitesep = s[sindex] && spctabnl (s[sindex]);
+
   /* Move past the current separator character. */
   if (s[sindex])
     sindex++;
@@ -1582,6 +1613,13 @@ get_word_from_string (stringp, separators, endptr)
   while (s[sindex] && spctabnl (s[sindex]) && issep (s[sindex]))
     sindex++;
 
+  /* If the first separator was IFS whitespace and the current character is
+     a non-whitespace IFS character, it should be part of the current field
+     delimiter, not a separate delimiter that would result in an empty field.
+     Look at POSIX.2, 3.6.5, (3)(b). */
+  if (s[sindex] && whitesep && issep (s[sindex]) && !spctabnl (s[sindex]))
+    sindex++;
+
   /* Update STRING to point to the next field. */
   *stringp = s + sindex;
   return (current_word);
@@ -1636,7 +1674,7 @@ list_string_with_quotes (string)
            i++;
        }
       else if (c == '\'')
-        i = skip_single_quoted (s, ++i);
+       i = skip_single_quoted (s, ++i);
       else if (c == '"')
        i = skip_double_quoted (s, ++i);
       else if (c == 0 || spctabnl (c))
@@ -1689,12 +1727,12 @@ do_array_element_assignment (name, value)
   ind = array_expand_index (t, ni - ind);
   if (ind < 0)
     {
-      t[-1] = '[';             /* restore original name */
+      t[-1] = '[';             /* restore original name */
       report_error ("%s: bad array subscript", name);
       return ((SHELL_VAR *)NULL);
     }
   entry = bind_array_variable (name, ind, value);
-  t[-1] = '[';         /* restore original name */
+  t[-1] = '[';         /* restore original name */
   return (entry);
 }
 #endif /* ARRAY_VARS */
@@ -1739,7 +1777,7 @@ do_assignment_internal (string, expand)
 
       /* Perform tilde expansion. */
       if (expand && temp[0])
-        {
+       {
          temp = (strchr (temp, '~') && unquoted_member ('~', temp))
                        ? bash_tilde_expand (temp)
                        : savestring (temp);
@@ -1768,7 +1806,7 @@ do_assignment_internal (string, expand)
 #define ASSIGN_RETURN(r)       do { FREE (value); free (name); return (r); } while (0)
 
 #if defined (ARRAY_VARS)
-  if (t = strchr (name, '['))
+  if (t = strchr (name, '['))  /*]*/
     {
       if (assign_list)
        {
@@ -1777,7 +1815,7 @@ do_assignment_internal (string, expand)
        }
       entry = do_array_element_assignment (name, value);
       if (entry == 0)
-        ASSIGN_RETURN (0);
+       ASSIGN_RETURN (0);
     }
   else if (assign_list)
     entry = assign_array_from_string (name, value);
@@ -1791,7 +1829,7 @@ do_assignment_internal (string, expand)
     VUNSETATTR (entry, att_invisible);
 
   /* Return 1 if the assignment seems to have been performed correctly. */
-  ASSIGN_RETURN (entry ? (readonly_p (entry) == 0) : 0);
+  ASSIGN_RETURN (entry ? ((readonly_p (entry) == 0) && noassign_p (entry) == 0) : 0);
 }
 
 /* Perform the assignment statement in STRING, and expand the
@@ -1863,7 +1901,7 @@ get_dollar_var_value (ind)
     {
       ind -= 10;
       for (p = rest_of_args; p && ind--; p = p->next)
-        ;
+       ;
       temp = p ? savestring (p->word->word) : (char *)NULL;
     }
   return (temp);
@@ -2014,9 +2052,9 @@ remove_backslashes (string)
   for (s = string; s && *s; )
     {
       if (*s == '\\')
-        s++;
+       s++;
       if (*s == 0)
-        break;
+       break;
       *r++ = *s++;
     }
   *r = '\0';
@@ -2041,6 +2079,13 @@ cond_expand_word (w, special)
   if (w->word == 0 || w->word[0] == '\0')
     return ((char *)NULL);
 
+  if (strchr (w->word, '~') && unquoted_member ('~', w->word))
+    {
+      p = bash_tilde_expand (w->word);
+      free (w->word);
+      w->word = p;
+    }
+
   l = call_expand_word_internal (w, 0, 0, (int *)0, (int *)0);
   if (l)
     {
@@ -2050,11 +2095,11 @@ cond_expand_word (w, special)
          r = string_list (l);
        }
       else
-        {
-          p = string_list (l);
-          r = quote_string_for_globbing (p, QGLOB_CVTNULL);
-          free (p);
-        }
+       {
+         p = string_list (l);
+         r = quote_string_for_globbing (p, QGLOB_CVTNULL);
+         free (p);
+       }
       dispose_words (l);
     }
   else
@@ -2077,11 +2122,13 @@ call_expand_word_internal (w, q, i, c, e)
   result = expand_word_internal (w, q, i, c, e);
   if (result == &expand_word_error || result == &expand_word_fatal)
     {
+      expand_no_split_dollar_star = 0; /* XXX */
       /* By convention, each time this error is returned, w->word has
         already been freed (it sometimes may not be in the fatal case,
         but that doesn't result in a memory leak because we're going
         to exit in most cases). */
       w->word = (char *)NULL;
+      last_command_exit_value = EXECUTION_FAILURE;
       jump_to_top_level ((result == &expand_word_error) ? DISCARD : FORCE_EOF);
       /* NOTREACHED */
     }
@@ -2102,9 +2149,12 @@ expand_string_internal (string, quoted)
   if (string == 0 || *string == 0)
     return ((WORD_LIST *)NULL);
 
-  bzero ((char *)&td, sizeof (td));
-  td.word = string;
+  td.flags = 0;
+  td.word = savestring (string);
+
   tresult = call_expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL);
+
+  FREE (td.word);
   return (tresult);
 }
 
@@ -2120,10 +2170,13 @@ expand_string_unsplit (string, quoted)
 {
   WORD_LIST *value;
 
-  if (!string || !*string)
+  if (string == 0 || *string == '\0')
     return ((WORD_LIST *)NULL);
 
+  expand_no_split_dollar_star = 1;
   value = expand_string_internal (string, quoted);
+  expand_no_split_dollar_star = 0;
+
   if (value)
     {
       if (value->word)
@@ -2149,11 +2202,13 @@ expand_prompt_string (string, quoted)
   if (string == 0 || *string == 0)
     return ((WORD_LIST *)NULL);
 
-  bzero ((char *)&td, sizeof (td));
+  td.flags = 0;
   td.word = savestring (string);
-  doing_prompt_expansion = 1;
+
+  no_longjmp_on_fatal_error = 1;
   value = expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL);
-  doing_prompt_expansion = 0;
+  no_longjmp_on_fatal_error = 0;
+
   if (value == &expand_word_error || value == &expand_word_fatal)
     {
       value = make_word_list (make_bare_word (string), (WORD_LIST *)NULL);
@@ -2209,7 +2264,7 @@ expand_string_for_rhs (string, quoted, dollar_at_p, has_dollar_at)
   if (string == 0 || *string == '\0')
     return (WORD_LIST *)NULL;
 
-  bzero ((char *)&td, sizeof (td));
+  td.flags = 0;
   td.word = string;
   tresult = call_expand_word_internal (&td, quoted, 1, dollar_at_p, has_dollar_at);
   return (tresult);
@@ -2227,7 +2282,7 @@ expand_string (string, quoted)
 {
   WORD_LIST *result;
 
-  if (!string || !*string)
+  if (string == 0 || *string == '\0')
     return ((WORD_LIST *)NULL);
 
   result = expand_string_leave_quoted (string, quoted);
@@ -2494,7 +2549,7 @@ remove_quoted_nulls (string)
          continue;
        }
       if (*s == CTLNUL)
-        continue;
+       continue;
       *p++ = *s;
     }
   *p = '\0';
@@ -2625,13 +2680,13 @@ match_pattern_char (pat, string)
     case '\\':
       return (*string == *pat);
     case '?':
-      return (*string == LPAREN ? 1 : (*string != '\0'));
+      return (*pat == LPAREN ? 1 : (*string != '\0'));
     case '*':
       return (1);
     case '+':
     case '!':
     case '@':
-      return (*string == LPAREN ? 1 : (*string == c));
+      return (*pat == LPAREN ? 1 : (*string == c));
     case '[':
       return (*string != '\0');
     }
@@ -2683,7 +2738,7 @@ match_pattern (string, pat, mtype, sp, ep)
 
     case MATCH_BEG:
       if (match_pattern_char (pat, string) == 0)
-        return (0);
+       return (0);
       for (p = end; p >= string; p--)
        {
          c = *p; *p = '\0';
@@ -2870,14 +2925,14 @@ array_remove_pattern (value, aspec, aval, c, quoted)
   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);
-        }
+       {
+         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);
+       return ((char *)NULL);
       ret = list_remove_pattern (l, pattern, patspec, t[0], quoted);
       dispose_words (l);
     }
@@ -2924,13 +2979,16 @@ expand_word (word, quoted)
    does parameter expansion, command substitution, arithmetic expansion,
    and quote removal. */
 WORD_LIST *
-expand_word_no_split (word, quoted)
+expand_word_unsplit (word, quoted)
      WORD_DESC *word;
      int quoted;
 {
   WORD_LIST *result;
 
+  expand_no_split_dollar_star = 1;
   result = call_expand_word_internal (word, quoted, 0, (int *)NULL, (int *)NULL);
+  expand_no_split_dollar_star = 0;
+  
   return (result ? dequote_list (result) : result);
 }
 
@@ -2958,7 +3016,6 @@ expand_word_leave_quoted (word, quoted)
    unlink all of them. add_fifo_list adds the name of an open FIFO to the
    list.  NFIFO is a count of the number of FIFOs in the list. */
 #define FIFO_INCR 20
-extern char *mktemp ();
 
 static char **fifo_list = (char **)NULL;
 static int nfifo;
@@ -2998,7 +3055,7 @@ make_named_pipe ()
 {
   char *tname;
 
-  tname = mktemp (savestring ("/tmp/sh-np-XXXXXX"));
+  tname = sh_mktmpname ("sh-np", MT_USERANDOM);
   if (mkfifo (tname, 0600) < 0)
     {
       free (tname);
@@ -3171,7 +3228,7 @@ process_substitute (string, open_for_read_in_child)
       /* Cancel traps, in trap.c. */
       restore_original_signals ();
       setup_async_signals ();
-      subshell_environment = SUBSHELL_COMSUB;
+      subshell_environment |= SUBSHELL_COMSUB;
     }
 
 #if defined (JOB_CONTROL)
@@ -3227,7 +3284,7 @@ process_substitute (string, open_for_read_in_child)
     }
   if (open_for_read_in_child)
     {
-      if (unset_nodelay_mode (fd) < 0)
+      if (sh_unset_nodelay_mode (fd) < 0)
        {
          sys_error ("cannout reset nodelay mode for fd %d", fd);
          exit (127);
@@ -3290,11 +3347,15 @@ read_comsub (fd, quoted)
   istring = (char *)NULL;
   istring_index = istring_size = bufn = 0;
 
+#ifdef __CYGWIN__
+  setmode (fd, O_TEXT);                /* we don't want CR/LF, we want Unix-style */
+#endif
+
   /* Read the output of the command through the pipe. */
   while (1)
     {
       if (fd < 0)
-        break;
+       break;
       if (--bufn <= 0)
        {
          bufn = zread (fd, buf, sizeof (buf));
@@ -3304,6 +3365,14 @@ read_comsub (fd, quoted)
        }
       c = *bufp++;
 
+      if (c == 0)
+       {
+#if 0
+         internal_warning ("read_comsub: ignored null byte in input");
+#endif
+         continue;
+       }
+
       /* Add the character to ISTRING, possibly after resizing it. */
       RESIZE_MALLOCED_BUFFER (istring, istring_index, 2, istring_size, DEFAULT_ARRAY_SIZE);
 
@@ -3311,6 +3380,16 @@ read_comsub (fd, quoted)
        istring[istring_index++] = CTLESC;
 
       istring[istring_index++] = c;
+
+#if 0
+#if defined (__CYGWIN__)
+      if (c == '\n' && istring_index > 1 && istring[istring_index - 2] == '\r')
+       {
+         istring_index--;
+         istring[istring_index - 1] = '\n';
+       }
+#endif
+#endif
     }
 
   if (istring)
@@ -3394,7 +3473,9 @@ command_substitute (string, quoted)
   old_pid = last_made_pid;
 #if defined (JOB_CONTROL)
   old_pipeline_pgrp = pipeline_pgrp;
-  pipeline_pgrp = shell_pgrp;
+  /* Don't reset the pipeline pgrp if we're already a subshell in a pipeline. */
+  if ((subshell_environment & SUBSHELL_PIPE) == 0)
+    pipeline_pgrp = shell_pgrp;
   cleanup_the_pipeline ();
 #endif
 
@@ -3424,11 +3505,7 @@ command_substitute (string, quoted)
   if (pid == 0)
     {
       set_sigint_handler ();   /* XXX */
-#if 0
-#if defined (JOB_CONTROL)
-      set_job_control (0);
-#endif
-#endif
+
       if (dup2 (fildes[1], 1) < 0)
        {
          sys_error ("command_substitute: cannot duplicate pipe as fd 1");
@@ -3455,10 +3532,12 @@ command_substitute (string, quoted)
       interactive = 0;
 
       /* This is a subshell environment. */
-      subshell_environment = SUBSHELL_COMSUB;
+      subshell_environment |= SUBSHELL_COMSUB;
 
-      /* Command substitution does not inherit the -e flag. */
-      exit_immediately_on_error = 0;
+      /* When not in POSIX mode, command substitution does not inherit
+         the -e flag. */
+      if (posixly_correct == 0)
+       exit_immediately_on_error = 0;
 
       remove_quoted_escapes (string);
 
@@ -3517,9 +3596,9 @@ command_substitute (string, quoted)
 #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)
+      if (interactive && pipeline_pgrp != (pid_t)0 && (subshell_environment & SUBSHELL_ASYNC) == 0)
 #endif
-       give_terminal_to (pipeline_pgrp);
+       give_terminal_to (pipeline_pgrp, 0);
 #endif /* JOB_CONTROL */
 
       return (istring);
@@ -3542,7 +3621,7 @@ valid_array_reference (name)
   char *t;
   int r, len;
 
-  t = strchr (name, '[');
+  t = strchr (name, '[');      /* ] */
   if (t)
     {
       *t = '\0';
@@ -3580,7 +3659,10 @@ array_expand_index (s, len)
   free (t);
   free (exp);
   if (expok == 0)
-    jump_to_top_level (DISCARD);
+    {
+      last_command_exit_value = EXECUTION_FAILURE;
+      jump_to_top_level (DISCARD);
+    }
   return val;
 }
 
@@ -3607,7 +3689,7 @@ array_variable_part (s, subp, lenp)
 
   *t = '\0';
   var = find_variable (s);
-  *t++ = '[';
+  *t++ = '[';          /* ] */
 
   if (subp)
     *subp = t;
@@ -3631,23 +3713,26 @@ array_value_internal (s, quoted, allow_all)
   if (var == 0)
     return (char *)NULL;
 
+  /* [ */
   if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']')
     {
-      if (array_p (var) == 0 || allow_all == 0)
-        {
-          report_error ("%s: bad array subscript", s);
-          return ((char *)NULL);
-        }
-      l = array_to_word_list (array_cell (var));
-      if (l == (WORD_LIST *)NULL)
-       return ((char *) NULL);
+      if (allow_all == 0)
+       {
+         report_error ("%s: bad array subscript", s);
+         return ((char *)NULL);
+       }
+      else if (array_p (var) == 0)
+       {
+         l = (WORD_LIST *)NULL;
+         l = add_string_to_list (value_cell (var), l);
+       }
+      else
+       {
+         l = array_to_word_list (array_cell (var));
+         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);
@@ -3656,7 +3741,6 @@ array_value_internal (s, quoted, allow_all)
        }
       else     /* ${name[@]} or unquoted ${name[*]} */
        retval = string_list_dollar_at (l, quoted);
-#endif
 
       dispose_words (l);
     }
@@ -3669,7 +3753,7 @@ array_value_internal (s, quoted, allow_all)
          return ((char *)NULL);
        }
       if (array_p (var) == 0)
-        return (ind == 0 ? savestring (value_cell (var)) : (char *)NULL);
+       return (ind == 0 ? savestring (value_cell (var)) : (char *)NULL);
       retval = array_reference (array_cell (var), ind);
       if (retval)
        retval = quote_escapes (retval);
@@ -3721,13 +3805,15 @@ array_length_reference (s)
     }
   else if (var == 0)
     return 0;
-  else if (array_p (var) == 0)
-    return 1;
 
-  array = array_cell (var);
+  /* We support a couple of expansions for variables that are not arrays.
+     We'll return the length of the value for v[0], and 1 for v[@] or
+     v[*].  Return 0 for everything else. */
+
+  array = array_p (var) ? array_cell (var) : (ARRAY *)NULL;
 
   if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']')
-    return (array_num_elements (array));
+    return (array_p (var) ? array_num_elements (array) : 1);
 
   ind = array_expand_index (t, len);
   if (ind < 0)
@@ -3735,9 +3821,13 @@ array_length_reference (s)
       report_error ("%s: bad array subscript", t);
       return (-1);
     }
-  t = array_reference (array, ind);
-  len = STRLEN (t);
 
+  if (array_p (var))
+    t = array_reference (array, ind);
+  else
+    t = (ind == 0) ? value_cell (var) : (char *)NULL;
+
+  len = STRLEN (t);
   return (len);
 }
 #endif /* ARRAY_VARS */
@@ -3747,7 +3837,7 @@ valid_brace_expansion_word (name, var_is_special)
      char *name;
      int var_is_special;
 {
-  if (digit (*name) && all_digits (name))
+  if (isdigit (*name) && all_digits (name))
     return 1;
   else if (var_is_special)
     return 1;
@@ -3777,7 +3867,7 @@ parameter_brace_expand_word (name, var_is_special, quoted)
   WORD_LIST *l;
 
   /* Handle multiple digit arguments, as in ${11}. */
-  if (digit (*name))
+  if (isdigit (*name))
     {
       arg_index = atoi (name);
       temp = get_dollar_var_value (arg_index);
@@ -3808,7 +3898,7 @@ parameter_brace_expand_word (name, var_is_special, quoted)
   else if (var = find_variable (name))
     {
       if (var && invisible_p (var) == 0)
-        {
+       {
 #if defined (ARRAY_VARS)
          temp = array_p (var) ? array_reference (array_cell (var), 0) : value_cell (var);
 #else
@@ -3820,7 +3910,7 @@ parameter_brace_expand_word (name, var_is_special, quoted)
 
          if (tempvar_p (var))
            dispose_variable (var);
-        }
+       }
       else
        temp = (char *)NULL;
     }
@@ -3865,7 +3955,7 @@ parameter_brace_expand_rhs (name, value, c, quoted, qdollaratp, hasdollarat)
   int hasdol;
 
   temp = (*value == '~' || (strchr (value, '~') && unquoted_substring ("=~", value)))
-        ? bash_tilde_expand (value)
+       ? bash_tilde_expand (value)
        : savestring (value);
 
   /* If the entire expression is between double quotes, we want to treat
@@ -3935,7 +4025,12 @@ parameter_brace_expand_error (name, value)
 
   if (value && *value)
     {
-      l = expand_string (value, 0);
+      temp = (*value == '~' || (strchr (value, '~') && unquoted_substring ("=~", value)))
+               ? bash_tilde_expand (value)
+               : savestring (value);
+
+      l = expand_string (temp, 0);
+      FREE (temp);
       temp =  string_list (l);
       report_error ("%s: %s", name, temp ? temp : ""); /* XXX was value not "" */
       FREE (temp);
@@ -3956,10 +4051,9 @@ static int
 valid_length_expression (name)
      char *name;
 {
-  return (!name[1] ||                                          /* ${#} */
-         ((name[1] == '@' || name[1] == '*') && !name[2]) ||   /* ${#@}, ${#*} */
-         (member (name[1], "-?$!#") && !name[2]) ||            /* ${#-}, etc. */
-         (digit (name[1]) && all_digits (name + 1)) ||         /* ${#11} */
+  return (name[1] == '\0' ||                                   /* ${#} */
+         ((sh_syntaxtab[name[1]] & CSPECVAR) && name[2] == '\0') ||  /* special param */
+         (isdigit (name[1]) && all_digits (name + 1)) ||       /* ${#11} */
 #if defined (ARRAY_VARS)
          valid_array_reference (name + 1) ||                   /* ${#a[7]} */
 #endif
@@ -3983,7 +4077,7 @@ parameter_brace_expand_length (name)
     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')
+  else if ((sh_syntaxtab[name[1]] & CSPECVAR) && name[2] == '\0')
     {
       /* Take the lengths of some of the shell's special parameters. */
       switch (name[1])
@@ -4018,7 +4112,7 @@ parameter_brace_expand_length (name)
     {
       number = 0;
 
-      if (digit (name[1]))             /* ${#1} */
+      if (isdigit (name[1]))           /* ${#1} */
        {
          t = get_dollar_var_value (atoi (name + 1));
          number = STRLEN (t);
@@ -4049,6 +4143,54 @@ parameter_brace_expand_length (name)
   return (number);
 }
 
+/* Skip characters in SUBSTR until DELIM.  SUBSTR is an arithmetic expression,
+   so we do some ad-hoc parsing of an arithmetic expression to find
+   the first DELIM, instead of using strchr(3).  Two rules:
+       1.  If the substring contains a `(', read until closing `)'.
+       2.  If the substring contains a `?', read past one `:' for each `?'.
+*/
+
+static char *
+skiparith (substr, delim)
+     char *substr;
+     int delim;
+{
+  int skipcol, pcount;
+  char *t;
+
+  for (skipcol = pcount = 0, t = substr; *t; t++)
+    {
+      /* Balance parens */
+      if (*t == '(')
+       {
+         pcount++;
+         continue;
+       }
+      if (*t == ')' && pcount)
+       {
+         pcount--;
+         continue;
+       }
+      if (pcount)
+       continue;
+
+      /* Skip one `:' for each `?' */
+      if (*t == ':' && skipcol)
+       {
+         skipcol--;
+         continue;
+       }
+      if (*t == delim)
+       break;
+      if (*t == '?')
+       {
+         skipcol++;
+         continue;
+       }
+    }
+  return t;
+}
+
 /* 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 parameters are being used; if it is 2, then
@@ -4066,9 +4208,18 @@ verify_substring_values (value, substr, vtype, e1p, e2p)
  ARRAY *a;
 #endif
 
+#if 1
+  /* duplicate behavior of strchr(3) */
+  t = skiparith (substr, ':');
+  if (*t && *t == ':')
+    *t = '\0'; 
+  else
+    t = (char *)0;
+#else
   t = strchr (substr, ':');
   if (t)
     *t = '\0';
+#endif
   temp1 = maybe_expand_string (substr, Q_DOUBLE_QUOTES, expand_string);
   *e1p = evalexp (temp1, &expok);
   free (temp1);
@@ -4108,15 +4259,15 @@ verify_substring_values (value, substr, vtype, e1p, e2p)
       *e2p = evalexp (temp1, &expok);
       free (temp1);
       if (expok == 0)
-        return (0);
+       return (0);
       if (*e2p < 0)
-        {
-          internal_error ("%s: substring expression < 0", t);
+       {
+         internal_error ("%s: substring expression < 0", t);
          return (0);
-        }
+       }
       *e2p += *e1p;            /* want E2 chars starting at E1 */
       if (*e2p > len)
-        *e2p = len;
+       *e2p = len;
     }
   else
     *e2p = len;
@@ -4217,6 +4368,8 @@ parameter_brace_substring (varname, value, substr, quoted)
     case VT_VARIABLE:
     case VT_ARRAYMEMBER:
       temp = quoted ? quoted_substring (value, e1, e2) : substring (value, e1, e2);
+      if (val && vtype == VT_ARRAYMEMBER)
+       free (val);
       break;
     case VT_POSPARMS:
       temp = pos_params (varname, e1, e2, quoted);
@@ -4285,8 +4438,8 @@ pat_subst (string, pat, rep, mflags)
 
       /* OK, now copy the leading unmatched portion of the string (from
         str to s) to ret starting at rptr (the current offset).  Then copy
-         the replacement string at ret + rptr + (s - str).  Increment
-         rptr (if necessary) and str and go on. */
+        the replacement string at ret + rptr + (s - str).  Increment
+        rptr (if necessary) and str and go on. */
       if (l)
        {
          strncpy (ret + rptr, str, l);
@@ -4297,9 +4450,11 @@ pat_subst (string, pat, rep, mflags)
          strncpy (ret + rptr, rep, replen);
          rptr += replen;
        }
+      if (s == e)
+       e++;            /* avoid infinite recursion on zero-length match */
       str = e;         /* e == end of match */
       if (((mflags & MATCH_GLOBREP) == 0) || mtype != MATCH_ANY)
-        break;
+       break;
     }
 
   /* Now copy the unmatched portion of the input string */
@@ -4400,7 +4555,7 @@ parameter_brace_patsub (varname, value, patsub, quoted)
       if ((mflags & MATCH_QUOTED) == 0)
        rep = maybe_expand_string (rep, quoted, expand_string_unsplit);
       else
-        rep = expand_string_to_string (rep, quoted, expand_string_unsplit);
+       rep = expand_string_to_string (rep, quoted, expand_string_unsplit);
     }
 
   p = pat;
@@ -4480,10 +4635,10 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
         string[t_index] == '?' ||
         string[t_index] == '#')) ||
       (sindex == t_index - 1 && string[sindex] == '!' &&
-        (string[t_index] == '#' ||
-         string[t_index] == '?' ||
-         string[t_index] == '@' ||
-         string[t_index] == '*')))
+       (string[t_index] == '#' ||
+        string[t_index] == '?' ||
+        string[t_index] == '@' ||
+        string[t_index] == '*')))
     {
       t_index++;
       free (name);
@@ -4492,9 +4647,9 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
       *name = string[sindex];
       if (string[sindex] == '!')
        {
-          /* indirect reference of $#, $?, $@, or $* */
-          name[1] = string[sindex + 1];
-          strcpy (name + 2, temp1);
+         /* indirect reference of $#, $?, $@, or $* */
+         name[1] = string[sindex + 1];
+         strcpy (name + 2, temp1);
        }
       else     
        strcpy (name + 1, temp1);
@@ -4511,7 +4666,7 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
      characters, move past it as normal.  If not, assume that
      a substring specification is being given, and do not move
      past it. */
-  if (c == ':' && member (string[sindex], "-=?+"))
+  if (c == ':' && VALID_PARAM_EXPAND_CHAR (string[sindex]))
     {
       check_nullness++;
       if (c = string[sindex])
@@ -4527,7 +4682,7 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
   /* ${#-} 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)
+       VALID_SPECIAL_LENGTH_PARAM (c) && string[sindex] == RBRACE)
     {
       name = xrealloc (name, 3);
       name[1] = c;
@@ -4547,7 +4702,8 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
      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], "#?@*"));
+    (legal_variable_starter (name[1]) || isdigit (name[1])
+                                     || VALID_INDIR_PARAM (name[1]));
 
   /* Determine the value of this variable. */
 
@@ -4559,10 +4715,10 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
   if (*name == '#' && name[1])
     {
       /* 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. */
+        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;
@@ -4599,7 +4755,16 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
       temp1[number - 1] = '\0';
       x = all_variables_matching_prefix (temp1);
       xlist = argv_to_word_list (x, 1, 0);
-      temp = string_list_dollar_star (xlist, quoted);
+      if (string[sindex - 2] == '*')
+       temp = string_list_dollar_star (xlist);
+      else
+       {
+         temp = string_list_dollar_at (xlist, quoted);
+         if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp)
+           *quoted_dollar_atp = 1;
+         if (contains_dollar_at)
+           *contains_dollar_at = 1;
+       }
       free (x);
       free (xlist);
       free (temp1);
@@ -4621,15 +4786,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] == ']')
-        {
+       {
          if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp)
            *quoted_dollar_atp = 1;
          if (contains_dollar_at)
@@ -4652,10 +4813,10 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
   if (c && c != RBRACE)
     {
       /* Extract the contents of the ${ ... } expansion
-         according to the Posix.2 rules. */
+        according to the Posix.2 rules. */
       value = extract_dollar_brace_string (string, &sindex, quoted);
       if (string[sindex] == RBRACE)
-        sindex++;
+       sindex++;
       else
        goto bad_substitution;
     }
@@ -4696,7 +4857,7 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
 
     case RBRACE:
       if (var_is_set == 0 && unbound_vars_is_error)
-        {
+       {
          report_error ("%s: unbound variable", name);
          FREE (value);
          FREE (temp);
@@ -4709,10 +4870,10 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
     case '#':  /* ${param#[#]pattern} */
     case '%':  /* ${param%[%]pattern} */
       if (value == 0 || *value == '\0' || temp == 0 || *temp == '\0')
-        {
-          FREE (value);
+       {
+         FREE (value);
          break;
-        }
+       }
       if ((name[0] == '@' || name[0] == '*') && name[1] == '\0')
        temp1 = parameter_list_remove_pattern (value, name[0], c, quoted);
 #if defined (ARRAY_VARS)
@@ -4731,23 +4892,23 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
     case '?':
     case '+':
       if (var_is_set && var_is_null == 0)
-        {
-         /* We don't want the value of the named variable for
-            anything, just the value of the right hand side. */
-
-         /* XXX -- if we're double-quoted and the named variable is "$@",
-            we want to turn off any special handling of "$@" -- we're not
-            using it, so whatever is on the rhs applies. */
-         if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp)
-           *quoted_dollar_atp = 0;
-         if (contains_dollar_at)
-           *contains_dollar_at = 0;
+       {
+         /* If the operator is `+', we don't want the value of the named
+            variable for anything, just the value of the right hand side. */
 
          if (c == '+')
            {
+             /* XXX -- if we're double-quoted and the named variable is "$@",
+                       we want to turn off any special handling of "$@" --
+                       we're not using it, so whatever is on the rhs applies. */
+             if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp)
+               *quoted_dollar_atp = 0;
+             if (contains_dollar_at)
+               *contains_dollar_at = 0;
+
              FREE (temp);
              if (value)
-               {
+               {
                  temp = parameter_brace_expand_rhs (name, value, c,
                                                     quoted,
                                                     quoted_dollar_atp,
@@ -4755,7 +4916,7 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
                  free (value);
                }
              else
-               temp = (char *)NULL;
+               temp = (char *)NULL;
            }
          else
            {
@@ -4764,7 +4925,7 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
          /* Otherwise do nothing; just use the value in TEMP. */
        }
       else     /* VAR not set or VAR is NULL. */
-        {
+       {
          FREE (temp);
          temp = (char *)NULL;
          if (c == '=' && var_is_special)
@@ -4777,14 +4938,25 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
          else if (c == '?')
            {
              parameter_brace_expand_error (name, value);
-             return (interactive ? &expand_param_error : &expand_param_fatal);
+             return (interactive_shell ? &expand_param_error : &expand_param_fatal);
            }
          else if (c != '+')
-           temp = parameter_brace_expand_rhs (name, value, c, quoted,
-                                              quoted_dollar_atp,
-                                              contains_dollar_at);
+           {
+             /* XXX -- if we're double-quoted and the named variable is "$@",
+                       we want to turn off any special handling of "$@" --
+                       we're not using it, so whatever is on the rhs applies. */
+             if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp)
+               *quoted_dollar_atp = 0;
+             if (contains_dollar_at)
+               *contains_dollar_at = 0;
+
+             temp = parameter_brace_expand_rhs (name, value, c, quoted,
+                                                quoted_dollar_atp,
+                                                contains_dollar_at);
+           }
          free (value);
        }
+
       break;
     }
   free (name);
@@ -4899,12 +5071,12 @@ param_expand (string, sindex, quoted, expanded_something,
          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;
-        }
+       {
+         /* If the $* is not quoted it is identical to $@ */
+         temp = string_list_dollar_at (list, quoted);
+         if (expand_no_split_dollar_star == 0 && contains_dollar_at)
+           *contains_dollar_at = 1;
+       }
 
       dispose_words (list);
       break;
@@ -4948,13 +5120,19 @@ param_expand (string, sindex, quoted, expanded_something,
         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. */
+        in the string, discard TEMP, and go on.  The exception to
+        this is when we have "${@}" and $1 is '', since $@ needs
+        special handling. */
       if (temp && QUOTED_NULL (temp))
        {
          if (had_quoted_null_p)
            *had_quoted_null_p = 1;
-         free (temp);
-         temp = (char *)NULL;
+         if (*quoted_dollar_at_p == 0)
+           {
+             free (temp);
+             temp = (char *)NULL;
+           }
+           
        }
 
       goto return0;
@@ -4967,7 +5145,7 @@ param_expand (string, sindex, quoted, expanded_something,
       zindex = t_index;
 
       /* For Posix.2-style `$(( ))' arithmetic substitution,
-         extract the expression and pass it to the evaluator. */
+        extract the expression and pass it to the evaluator. */
       if (temp && *temp == LPAREN)
        {
          char *temp2;
@@ -5161,6 +5339,7 @@ expand_word_internal (word, quoted, isexp, contains_dollar_at, expanded_somethin
 
   int had_quoted_null;
   int has_dollar_at;
+  int tflag;
 
   register int c;              /* Current character. */
   int number;                  /* Temporary number value. */
@@ -5300,13 +5479,14 @@ add_string:
          c = string[++sindex];
 
          if (quoted & Q_HERE_DOCUMENT)
-           temp1 = slashify_in_here_document;
+           tflag = CBSHDOC;
          else if (quoted & Q_DOUBLE_QUOTES)
-           temp1 = slashify_in_quotes;
+           tflag = CBSDQUOTE;
          else
-           temp1 = "";
+           tflag = 0;
+
 
-         if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && member (c, temp1) == 0)
+         if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && ((sh_syntaxtab[c] & tflag) == 0))
            {
              twochars[0] = '\\';
              twochars[1] = c;
@@ -5601,7 +5781,7 @@ finished_with_string:
       if (word->flags & W_NOGLOB)
        tword->flags |= W_NOGLOB;       /* XXX */
       if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
-        tword->flags |= W_QUOTED;
+       tword->flags |= W_QUOTED;
     }
   else
     {
@@ -5659,7 +5839,7 @@ string_quote_removal (string, quoted)
        {
        case '\\':
          c = string[++sindex];
-         if (((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || dquote) && member (c, slashify_in_quotes) == 0)
+         if (((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || dquote) && (sh_syntaxtab[c] & CBSDQUOTE) == 0)
            *r++ = '\\';
          /* FALLTHROUGH */
 
@@ -5976,12 +6156,8 @@ glob_expand_word_list (tlist, eflags)
       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_ASSIGNMENT) == 0 &&
-#else
+        pattern matching character, then glob it. */
       if ((tlist->word->flags & W_NOGLOB) == 0 &&
-#endif
          unquoted_glob_pattern_p (tlist->word->word))
        {
          glob_array = shell_glob_filename (tlist->word->word);
@@ -6159,6 +6335,7 @@ shell_expand_word_list (tlist, eflags)
          /* Dispose the new list we're building. */
          dispose_words (new_list);
 
+         last_command_exit_value = EXECUTION_FAILURE;
          if (expanded == &expand_word_error)
            jump_to_top_level (DISCARD);
          else
@@ -6232,10 +6409,13 @@ expand_word_list_internal (list, eflags)
                  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)
+                 if (tint == 0)
                    {
                      last_command_exit_value = EXECUTION_FAILURE;
-                     jump_to_top_level (FORCE_EOF);
+                     if (interactive_shell == 0 && posixly_correct)
+                       jump_to_top_level (FORCE_EOF);
+                     else
+                       jump_to_top_level (DISCARD);
                    }
                }
              dispose_words (subst_assign_varlist);
@@ -6287,11 +6467,13 @@ expand_word_list_internal (list, eflags)
          tint = (*assign_func) (temp_list->word->word);
          /* Variable assignment errors in non-interactive shells running
             in Posix.2 mode cause the shell to exit. */
-         if (tint == 0 && assign_func == do_assignment &&
-               interactive_shell == 0 && posixly_correct)
+         if (tint == 0 && assign_func == do_assignment)
            {
              last_command_exit_value = EXECUTION_FAILURE;
-             jump_to_top_level (FORCE_EOF);
+             if (interactive_shell == 0 && posixly_correct)
+               jump_to_top_level (FORCE_EOF);
+             else
+               jump_to_top_level (DISCARD);
            }
        }