]> git.ipfire.org Git - thirdparty/bash.git/commitdiff
fixed a bug with expanding unquoted $* when the separator is not whitespace and one... devel
authorChet Ramey <chet.ramey@case.edu>
Wed, 20 May 2026 14:18:32 +0000 (10:18 -0400)
committerChet Ramey <chet.ramey@case.edu>
Wed, 20 May 2026 14:18:32 +0000 (10:18 -0400)
15 files changed:
CWRU/CWRU.chlog
MANIFEST
arrayfunc.c
command.h
examples/loadables/Makefile.in
examples/loadables/cut.c
execute_cmd.c
subst.c
subst.h
tests/dollar-at-star
tests/dollar-at-star12.sub [new file with mode: 0644]
tests/dollar-at-star13.sub [new file with mode: 0644]
tests/dollar.right
tests/run-test
variables.c

index db0e4d06f1afb1b998aecb9b428b3127f23ea88e..eb8ab19ae24e46b032202df79f1ae46e43bd34e3 100644 (file)
@@ -12877,3 +12877,64 @@ lib/sh/stringvec.c,lib/sh/stringlist.c
          of list_append/list_length, since they're always called with
          WORD_LIST * arguments
 
+                                   5/8
+                                   ---
+command.h
+       - PF_STRINGEND,PF_STRINGBEG: new flags for the parameter expansion
+         functions; set if there are no characters following or preceding the
+         expansion, respectively
+
+subst.c
+       - string_list_dollar_atstar: new function, expands unquoted $* and $@
+         when word splitting will take place and the separator is not
+         whitespace, and the positional parameters can potentially contain
+         the separator. It splits the positional parameters before separating
+         them with the separator, taking care to handle null positional
+         parameters and proper merging with text immediately preceding or
+         following the expansion. Fixes bug that added a spurious empty
+         argument if one of the positional parameters ended with the
+         separator
+       - string_list_internal: take a new flag value that says to add an
+         additional separator at the end of the returned string if the
+         last word is a null word and it will be joined with text following
+         the expansion
+       - string_list_pos_params: call string_list_dollar_atstar as
+         appropriate
+       - list_string: take a new word flag: W_SPLITONLY. This means not to
+         perform quoted null character removal while splitting the word
+         and don't allow CTLESC to quote anything, and to return "" results
+         of splitting verbatim, but marked as W_QUOTED to preserve null
+         arguments
+       - expand_word_internal,param_expand: set PF_STRINGEND and PF_STRINGBEG
+         appropriately
+       - word_split: pass W_SPLITONLY through to list_string()
+       These changes fix the bug with adding an extra empty word if one of
+       the positional parameters ends with the first character of $IFS and
+       it's not whitespace. Changes tests/dollar-at-star6.sub output.
+
+arrayfunc.c
+       - array_value_internal: if we have an unquoted array expansion
+         subscripted with * or @, call string_list_dollar_atstar if
+         appropriate
+
+                                  5/13
+                                  ----
+execute_cmd.c
+       - execute_disk_command: if we update SHLVL, do it after (maybe)
+         making the export environment and then update the export env in
+         place, so we don't have to rebuild it twice or rebuild it more
+         times than necessary
+         Report and fix from Gao Xiang <gaoxiang@kylinos.cn>
+
+                                  5/14
+                                  ----
+variables.c
+       - adjust_shell_level,set_pwd: make sure variable binding works before
+         trying to set the variable as exported
+         Report and patch from Grisha Levit <grishalevit@gmail.com>
+
+subst.c
+       - function_substitute: make sure funsubs don't leave `-v' unset
+         after they return by adding an unwind-protect function
+         uw_restore_verbose()
+         Report and patch from Grisha Levit <grishalevit@gmail.com>
index 40a8739f81bf9572b8e48445027ac5528f674095..7a2d3aa681295fffa761a7db8749bb979c23ef5c 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -1135,6 +1135,8 @@ tests/dollar-at-star8.sub f
 tests/dollar-at-star9.sub      f
 tests/dollar-at-star10.sub     f
 tests/dollar-at-star11.sub     f
+tests/dollar-at-star12.sub     f
+tests/dollar-at-star13.sub     f
 tests/dollar-at1.sub   f
 tests/dollar-at2.sub   f
 tests/dollar-at3.sub   f
index 3f34370ee12b36c154433c86dd99d39f365d597c..db70eb0c5c9051204cb6a4958678d7b2d2506cc3 100644 (file)
@@ -1581,6 +1581,10 @@ array_value_internal (const char *s, int quoted, int flags, array_eltstate_t *es
          retval = quote_nosplit (temp);
          free (temp);
        }
+      else if (quoted == 0 && (flags & AV_ASSIGNRHS) == 0 &&
+              ifs_is_set && ifs_is_null == 0 &&
+              spctabnl (ifs_firstc[0]) == 0)
+       retval = string_list_dollar_atstar (l, quoted, 0);
       else     /* ${name[@]} or unquoted ${name[*]} */
        retval = string_list_dollar_at (l, quoted, (flags & AV_ASSIGNRHS) ? PF_ASSIGNRHS : 0);
 
index 1c565cf796daa7bb2cb6b2c9b98e3673777d40c9..8ed115c300690ca1bda0b7a3e46d0daa3e8b6a70 100644 (file)
--- a/command.h
+++ b/command.h
@@ -116,6 +116,8 @@ enum command_type { cm_for, cm_case, cm_while, cm_if, cm_simple, cm_select,
 #define PF_ALLINDS     0x40    /* array, act as if [@] was supplied */
 #define PF_BACKQUOTE   0x80    /* differentiate `` from $() for command_substitute */
 #define PF_COMSUBNLS   0x100   /* for ${; ...; } and read_comsub() to not strip trailing newlines */
+#define PF_STRINGEND   0x200   /* set if there are no chars following this expansion */
+#define PF_STRINGBEG   0x400   /* set if there are no chars before this expansion */
 
 /* Possible values for subshell_environment */
 #define SUBSHELL_ASYNC 0x01    /* subshell caused by `command &' */
index c01b50c6e47ab1629817588526bebe16ef0cb7ba..d23dc69faa7e0f995698dca31152d0b9f59697b2 100644 (file)
@@ -103,7 +103,7 @@ INC = -I. -I.. -I$(topdir) -I$(topdir)/lib -I$(topdir)/builtins -I${srcdir} \
 ALLPROG = print truefalse sleep finfo logname basename dirname fdflags \
          tty pathchk tee head mkdir rmdir mkfifo mktemp printenv id whoami \
          uname sync push ln unlink realpath strftime mypid setpgid seq rm \
-         accept csv dsv cut stat getconf kv strptime chmod fltexpr jobid
+         accept csv dsv cut stat getconf kv strptime chmod fltexpr jobid rev
 OTHERPROG = necho hello cat pushd asort
 
 SUBDIRS = perl
index 2b9cce5cdac6b7fbd7b9d454d9f9937b08f00249..b69e7bd0d50902d21d85cc819babfc356dad4423 100644 (file)
@@ -364,7 +364,7 @@ cutline (SHELL_VAR *v, arrayind_t ind, char *line, struct cutop *ops)
 static int
 cutfile (SHELL_VAR *v, WORD_LIST *list, struct cutop *ops)
 {
-  int fd, unbuffered_read, r;
+  int fd, unbuffered_read, r, closefd;
   char *line, *b;
   size_t llen;
   WORD_LIST *l;
@@ -378,11 +378,16 @@ cutfile (SHELL_VAR *v, WORD_LIST *list, struct cutop *ops)
   l = list;
   do
     {
+      closefd = 0;
+
       /* for each file */
       if (l == 0 || (l->word->word[0] == '-' && l->word->word[1] == '\0'))
        fd = 0;
       else
-       fd = open (l->word->word, O_RDONLY);
+       {
+         fd = open (l->word->word, O_RDONLY);
+         closefd = fd != -1;
+       }
       if (fd < 0)
        {
          file_error (l->word->word);
@@ -403,7 +408,8 @@ cutfile (SHELL_VAR *v, WORD_LIST *list, struct cutop *ops)
          r = cutline (v, ind, line, ops);      /* can modify line */
          ind += r;
        }
-      if (fd > 0)
+
+      if (closefd)
        close (fd);
 
       QUIT;
index ea361106b718a798d11fd09bba576d8cded7fe38..9cfe5b35939aa0ebb7491d555f1a8dc993a281d7 100644 (file)
@@ -5937,17 +5937,26 @@ execute_disk_command (WORD_LIST *words, REDIRECT *redirects, char *command_line,
 
   if (command)
     {
-      /* If we're optimizing out the fork (implicit `exec'), decrement the
-        shell level like `exec' would do. Don't do this if we are already
-        in a pipeline environment, assuming it's already been done. */
-      if (nofork && pipe_in == NO_PIPE && pipe_out == NO_PIPE && (subshell_environment & SUBSHELL_PIPE) == 0)
-       adjust_shell_level (-1);
-
 #if defined (STRICT_POSIX)
       if (posixly_correct == 0 || subst_assign_varlist == 0) /* Done below. */
 #endif
        {
          maybe_make_export_env ();
+         /* If we're optimizing out the fork (implicit `exec'), decrement the
+            shell level like `exec' would do. Don't do this if we are already
+            in a pipeline environment, assuming it's already been done.
+            Since we've just (possibly) remade the export environment, it's
+            marked as not dirty, so we update SHLVL in place rather than
+            remake the whole thing again or force a remake we would not
+            otherwise have to do. This mostly hits command substitutions with
+            a lot of exported variables, since command_substitute already
+            calls maybe_make_export_env (). */
+         if (nofork && pipe_in == NO_PIPE && pipe_out == NO_PIPE && (subshell_environment & SUBSHELL_PIPE) == 0)
+           {
+             adjust_shell_level (-1);
+             update_export_env_inplace ("SHLVL=", 6, get_string_value ("SHLVL"));
+             array_needs_making = 0;
+           }
          put_command_name_into_env (command);
        }
     }
@@ -6035,6 +6044,13 @@ execute_disk_command (WORD_LIST *words, REDIRECT *redirects, char *command_line,
          expand_assignment_statements (command, 0);
 
          maybe_make_export_env ();
+         /* See above for why we do this here. */
+         if (nofork && pipe_in == NO_PIPE && pipe_out == NO_PIPE && (subshell_environment & SUBSHELL_PIPE) == 0)
+           {
+             adjust_shell_level (-1);
+             update_export_env_inplace ("SHLVL=", 6, get_string_value ("SHLVL"));
+             array_needs_making = 0;
+           }
          put_command_name_into_env (command);
        }
 #endif
diff --git a/subst.c b/subst.c
index 121a7a3c4c791ca11c90eb0c4c8991b2ffd39303..e4dffd34e022879759e709ab2c34cb6fd66d6ec5 100644 (file)
--- a/subst.c
+++ b/subst.c
@@ -2875,6 +2875,9 @@ string_list_internal (WORD_LIST *list, char *sep, int flags)
       result_size += strlen (t->word->word);
     }
 
+  if (flags & 1)
+    result_size += sep_len;
+
   r = result = (char *)xmalloc (result_size + 1);
 
   for (t = list; t; t = t->next)
@@ -2893,6 +2896,20 @@ string_list_internal (WORD_LIST *list, char *sep, int flags)
       word_len = strlen (t->word->word);
       FASTCOPY (t->word->word, r, word_len);
       r += word_len;
+
+      /* FLAGS & 1 means to add an additional SEP to the end of the returned
+        string if and only if the last word is a null word, since it will be
+        joined with text following the expansion. */
+      if ((flags & 1) && t->next == 0 && (t->word->word == 0 || t->word->word[0] == '\0') && (t->word->flags & W_QUOTED))
+       {
+         if (sep_len > 1)
+           {
+             FASTCOPY (sep, r, sep_len);
+             r += sep_len;
+           }
+         else if (sep_len)
+           *r++ = sep[0];
+       }
     }
 
   *r = '\0';
@@ -3071,6 +3088,107 @@ string_list_dollar_at (WORD_LIST *list, int quoted, int flags)
   return ret;
 }
 
+/* Expand $* or $@ in a context where word splitting will be performed. We
+   separate the words in LIST with the first character of IFS and assume
+   that a later split will recreate the list.
+   To avoid issues with a non-whitespace separator producing spurious empty
+   words if the last character of one of the words in LIST is that same
+   separator, we split LIST and join each resultant word with SEP. We remove
+   quoted nulls from the result because the callers expect them not to
+   be present before another word splitting pass.
+   This does not use FLAGS yet, but could. */
+char *
+string_list_dollar_atstar (WORD_LIST *list, int quoted, int flags)
+{
+  char *ret;
+  WORD_LIST *l, *l2, *tl;
+#if defined (HANDLE_MULTIBYTE)
+#  if defined (__GNUC__)
+  char sep[MB_CUR_MAX + 1];
+#  else
+  char *sep = 0;
+#  endif
+#else
+  char sep[2];
+#endif
+
+  if (list == 0)
+    return (NULL);
+
+#if defined (HANDLE_MULTIBYTE)
+#  if !defined (__GNUC__)
+  sep = (char *)xmalloc (locale_mb_cur_max + 1);
+#  endif /* !__GNUC__ */
+  if (ifs_firstc_len == 1)
+    {
+      sep[0] = ifs_firstc[0];
+      sep[1] = '\0';
+    }
+  else
+    {
+      memcpy (sep, ifs_firstc, ifs_firstc_len);
+      sep[ifs_firstc_len] = '\0';
+    }
+#else
+  sep[0] = ifs_firstc;
+  sep[1] = '\0';
+#endif
+
+  /* We want to split non-empty arguments, but preserve null arguments.
+     Preserving the null positional parameters and following them with SEP
+     is backwards compatible. */
+  /* If we want to remove (unquoted) null arguments, so $* is like $1 $2 ...,
+     then use PF_STRINGBEG and PF_STRINGEND to inhibit separators after the
+     first and last arguments so they are joined to whatever comes before
+     and after the $* and simply skip over them otherwise. */
+
+  /* Pre-process the list */
+  /* We arrange for null arguments to be preserved. */
+  for (l2 = tl = NULL, l = list; l; l = l->next)
+    {
+      char *new_string;
+      WORD_DESC *new_word;
+
+      new_word = copy_word (l->word);
+      new_word->flags |= W_SPLITONLY;
+      if (l->word->word[0] == '\0')
+       new_word->flags |= W_SAWQUOTEDNULL;
+
+      if (l2 == NULL)
+       l2 = tl = make_word_list (new_word, l2);
+      else
+       {
+         tl->next = make_word_list (new_word, (WORD_LIST *)NULL);
+         tl = tl->next;
+       }
+    }
+
+  /* XXX - this turns words that look like quoted nulls into "", which we
+     don't want here. */
+  l = word_list_split (l2);    /* pre-split, preserving empty arguments */
+
+  /* We want to turn words that are QUOTED_NULL with W_HASQUOTEDNULL set in
+     the word flags back into "" but leave every other $'\177' alone. */
+  for (l2 = l; l2; l2 = l2->next)
+    if (QUOTED_NULL (l2->word->word) && (l2->word->flags & W_HASQUOTEDNULL))
+      {
+       l2->word->word[0] = '\0';
+       l2->word->flags &= ~W_HASQUOTEDNULL;
+      }
+   
+  list_quote_escapes (l);
+  
+  ret = string_list_internal (l, sep, (flags & PF_STRINGEND) ? 1 : 0);
+#if defined (HANDLE_MULTIBYTE) && !defined (__GNUC__)
+  free (sep);
+#endif
+
+  dispose_words (l2);
+  dispose_words (l);
+
+  return ret;
+}
+
 /* Turn the positional parameters into a string, understanding quoting and
    the various subtleties of using the first character of $IFS as the
    separator.  Calls string_list_dollar_at, string_list_dollar_star, and
@@ -3100,6 +3218,14 @@ string_list_pos_params (int pchar, WORD_LIST *list, int quoted, int pflags)
     ret = expand_no_split_dollar_star ? string_list_dollar_star (list, quoted, 0) : string_list_dollar_at (list, quoted, 0);   /* Posix interp 888 */
   else if (pchar == '*' && quoted == 0 && (pflags & PF_ASSIGNRHS))     /* XXX */
     ret = expand_no_split_dollar_star ? string_list_dollar_star (list, quoted, 0) : string_list_dollar_at (list, quoted, 0);   /* Posix interp 888 */
+  else if (pchar == '*' && quoted == 0 && (pflags & PF_ASSIGNRHS) == 0 &&
+          ifs_is_set && ifs_is_null == 0 &&
+#if defined (HANDLE_MULTIBYTE)
+          spctabnl (ifs_firstc[0]) == 0)
+#else
+          spctabnl (ifs_firstc) == 0)
+#endif
+    ret = string_list_dollar_atstar (list, quoted, 0);
   else if (pchar == '*')
     {
       /* Even when unquoted, string_list_dollar_star does the right thing
@@ -3123,14 +3249,16 @@ string_list_pos_params (int pchar, WORD_LIST *list, int quoted, int pflags)
        that quotes the escapes. We could use string_list_internal with " "
        as the second argument. */
     ret = string_list_dollar_at (list, quoted, pflags);        /* Posix interp 888 */
-  else if (pchar == '@')
-#if 0
-    /* XXX - param_expand uses string_list_dollar_at() for this case. */
-    /* string_list_dollar_at quotes CTLESC, even if quoted == 0 */
-    ret = string_list_dollar_at (list, quoted, 0);
+  else if (pchar == '@' && quoted == 0 && (pflags & PF_ASSIGNRHS) == 0 &&
+          ifs_is_set && ifs_is_null == 0 &&
+#if defined (HANDLE_MULTIBYTE)
+          spctabnl (ifs_firstc[0]) == 0)       /* separate these cases for now */
 #else
-    ret = string_list_dollar_star (list, quoted, 0);
+          spctabnl (ifs_firstc) == 0)  /* separate these cases for now */
 #endif
+    ret = string_list_dollar_atstar (list, quoted, 0);
+  else if (pchar == '@')
+    ret = string_list_dollar_star (list, quoted, 0);
   else
     ret = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (list) : list);
 
@@ -3202,6 +3330,9 @@ list_string (char *string, char *separators, int flags)
       else if (*s == CTLNUL) xflags |= SX_NOESCCTLNUL;
     }
 
+  if (flags & W_SPLITONLY)
+    xflags |= SX_NOCTLESC|SX_NOESCCTLNUL;
+
   slen = 0;
   /* Remove sequences of whitespace at the beginning of STRING, as
      long as those characters appear in IFS.  Do not do this if
@@ -3242,7 +3373,7 @@ list_string (char *string, char *separators, int flags)
         want to preserve the quoted null character iff this is a quoted
         empty string; otherwise the quoted null characters are removed
         below. */
-      if (QUOTED_NULL (current_word))
+      if ((flags & W_SPLITONLY) == 0 && QUOTED_NULL (current_word))
        {
          t = alloc_word_desc ();
          t->word = make_quoted_char ('\0');
@@ -3253,7 +3384,8 @@ list_string (char *string, char *separators, int flags)
        {
          /* If we have something, then add it regardless.  However,
             perform quoted null character removal on the current word. */
-         remove_quoted_nulls (current_word);
+         if ((flags & W_SPLITONLY) == 0)
+           remove_quoted_nulls (current_word);
 
          /* We don't want to set the word flags based on the string contents
             here -- that's mostly for the parser -- so we just allocate a
@@ -3271,6 +3403,15 @@ list_string (char *string, char *separators, int flags)
          if (current_word == 0 || current_word[0] == '\0')
            result->word->flags |= W_SAWQUOTEDNULL;
        }
+      else if ((flags & W_SPLITONLY) && current_word[0] == '\0')
+       {
+         t = alloc_word_desc ();
+         t->word = current_word;
+         /* W_QUOTED for string_list_internal () */
+         t->flags |= W_QUOTED|W_HASQUOTEDNULL;
+         result = make_word_list (t, result);
+         free_word = 0;
+       }
 
       /* If we're not doing sequences of separators in the traditional
         Bourne shell style, then add a quoted null argument. */
@@ -7008,6 +7149,13 @@ uw_restore_errexit (void *eflag)
   set_shellopts ();
 }
 
+static void
+uw_restore_verbose (void *vflag)
+{
+  change_flag ('v', (intptr_t) vflag ? FLAG_ON : FLAG_OFF);
+  set_shellopts ();
+}
+
 /* Quote the output of nofork varsub command substitution in the way that the
    caller of function_substitute expects. The caller guarantees that STRING
    is non-null. This is equivalent to what read_comsub does to the output it
@@ -7146,7 +7294,8 @@ function_substitute (char *string, int quoted, int flags)
   push_context (lambdafunc.name, 1, temporary_env);            /* make local variables work */
   this_shell_function = &lambdafunc;
 
-  unwind_protect_int (verbose_flag);
+
+  add_unwind_protect (uw_restore_verbose, (void *) (intptr_t) verbose_flag);
   change_flag ('v', FLAG_OFF);
 
   /* When inherit_errexit option is not enabled, command substitution does
@@ -10821,6 +10970,20 @@ param_expand (char *string, size_t *sindex, int quoted,
 #  endif
            /* Posix interp 888: not RHS, no splitting, IFS set to '' */
            temp = string_list_dollar_star (list, quoted, 0);
+         else if (expand_no_split_dollar_star == 0 && (pflags & PF_ASSIGNRHS) == 0 &&
+                  ifs_is_set && ifs_is_null == 0 &&
+#  if defined (HANDLE_MULTIBYTE)
+                  spctabnl (ifs_firstc[0]) == 0)
+#  else
+                  spctabnl (ifs_firstc) == 0)
+#  endif
+           {
+             int nflags;
+             /* XXX - only if $# > 1? */
+             nflags = (string[zindex+1] == '\0') ? PF_STRINGEND : 0;
+
+             temp = string_list_dollar_atstar (list, quoted, nflags);
+           }
          else
            {
              temp = string_list_dollar_at (list, quoted, 0);
@@ -10927,6 +11090,20 @@ param_expand (char *string, size_t *sindex, int quoted,
          else
            temp = string_list_dollar_at (list, quoted, pflags);
        }
+      else if (quoted == 0 &&
+              ifs_is_set && ifs_is_null == 0 &&
+#if defined (HANDLE_MULTIBYTE)
+              spctabnl (ifs_firstc[0]) == 0)
+#else
+              spctabnl (ifs_firstc) == 0)
+#endif
+       {
+         int nflags;
+         /* XXX - only if $# > 1? */
+         nflags = (string[zindex+1] == '\0') ? PF_STRINGEND : 0;
+
+         temp = string_list_dollar_atstar (list, quoted, pflags|nflags);
+       }
       else
        temp = string_list_dollar_at (list, quoted, pflags);
 
@@ -11520,6 +11697,8 @@ add_string:
          if (temp)
            {
              istring = sub_append_string (temp, istring, &istring_index, &istring_size);
+             if (istring_index > 0)
+               pflags &= ~PF_STRINGBEG;
              temp = (char *)0;
            }
 
@@ -11694,6 +11873,8 @@ add_string:
            pflags |= PF_ASSIGNRHS;
          if (word->flags & W_COMPLETE)
            pflags |= PF_COMPLETE;
+         if (istring_index == 0)
+           pflags |= PF_STRINGBEG;
 
          tword = param_expand (string, &sindex, quoted, &local_expanded,
                               &temp_has_dollar_at, &quoted_dollar_at,
@@ -12554,7 +12735,7 @@ word_split (WORD_DESC *w, char *ifs_chars)
       char *xifs;
 
       xifs = ((w->flags & W_QUOTED) || ifs_chars == 0) ? "" : ifs_chars;
-      result = list_string (w->word, xifs, w->flags & W_QUOTED);
+      result = list_string (w->word, xifs, w->flags & (W_QUOTED|W_SPLITONLY));
     }
   else
     result = (WORD_LIST *)NULL;
diff --git a/subst.h b/subst.h
index b6f0df9d2e898549b9ea6259e2fa2893d135a863..158fd8a774f4476d69db864b3d2616c076b82e4b 100644 (file)
--- a/subst.h
+++ b/subst.h
@@ -130,6 +130,11 @@ extern char *string_list_dollar_star (WORD_LIST *, int, int);
 /* Expand $@ into a single string, obeying POSIX rules. */
 extern char *string_list_dollar_at (WORD_LIST *, int, int);
 
+/* A special function for expanding unquoted $* and $@ in a context where
+   word splitting will be performed using a non-whitespace separator and
+   the positional parameters potentially contain that separator. */
+extern char *string_list_dollar_atstar (WORD_LIST *, int, int);
+
 /* Turn the positional parameters into a string, understanding quoting and
    the various subtleties of using the first character of $IFS as the
    separator.  Calls string_list_dollar_at, string_list_dollar_star, and
index ff575895637f4b7195f2e5022b6524f21d2a02ef..efcd9ab7794a5fa2181e091afd64f7454ac981ff 100755 (executable)
@@ -266,6 +266,11 @@ test_runsub ./dollar-at-star9.sub
 test_runsub ./dollar-at-star10.sub
 test_runsub ./dollar-at-star11.sub
 
+# tests for unquoted expansions of $* and $@ when word splitting will take
+# place and the separator is not whitespace
+test_runsub ./dollar-at-star12.sub
+test_runsub ./dollar-at-star13.sub
+
 # tests for special expansion of "$*" and "${array[*]}" when used with other
 # expansions -- bugs through bash-2.05b
 test_runsub ./dollar-star1.sub
diff --git a/tests/dollar-at-star12.sub b/tests/dollar-at-star12.sub
new file mode 100644 (file)
index 0000000..dfebef9
--- /dev/null
@@ -0,0 +1,60 @@
+OIFS=$IFS
+
+set -- a b+ c +d e; IFS=+;
+
+# these should be the same
+printf '<%s> ' $1 $2 $3 $4 $5; echo
+printf '<%s> ' $* ; echo
+printf '<%s> ' $@ ; echo
+
+set -- 'b+' 'c'
+
+# these should be the same
+echo =====
+printf '<%s> ' $1 $2 $3; echo
+printf '<%s> ' $* ; echo
+printf '<%s> ' $@ ; echo
+printf '<%s> ' ${*} ; echo
+printf '<%s> ' ${@} ; echo
+
+# these should be the same
+echo =====
+set -- a 'b+c' '+d+e' 'f+g+' h
+
+printf '<%s> ' $* ; echo
+printf '<%s> ' $@ ; echo
+
+echo =====
+array=(a 'b+' c '+d' e)
+printf '<%s> ' ${array[0]} ${array[1]} ${array[2]} ${array[3]} ${array[4]}; echo
+printf '<%s> ' ${array[*]} ; echo
+printf '<%s> ' ${array[@]} ; echo
+unset array
+
+array=(a 'b+c' '+d+e' 'f+g+' h)
+printf '<%s> ' ${array[0]} ${array[1]} ${array[2]} ${array[3]} ${array[4]}; echo
+printf '<%s> ' ${array[*]} ; echo
+printf '<%s> ' ${array[@]} ; echo
+unset array
+
+echo =====
+set -- $'\001\177+' 'b+' c; IFS=+;
+
+recho $1 $2 $3
+recho $*
+
+set -- $'\177+' 'b+' c; IFS=+;
+recho $1 $2 $3
+recho $*
+
+set -- $'a\177+' 'b+' c; IFS=+;
+recho $1 $2 $3
+recho $*
+
+set -- $'\001+' b+ c; IFS=+;
+recho $1 $2 $3
+recho $*
+
+set -- $'a\001+' b+ c; IFS=+;
+recho $1 $2 $3
+recho $*
diff --git a/tests/dollar-at-star13.sub b/tests/dollar-at-star13.sub
new file mode 100644 (file)
index 0000000..b024c65
--- /dev/null
@@ -0,0 +1,41 @@
+# from back in 2018 on dash@vger.kernel.org
+
+# IFS the default, these arguments should not be split
+set -- , ,
+IFS=,
+recho $@
+recho $*
+recho ${@}
+recho ${*}
+
+set -- $@
+recho $#
+
+# IFS already set, these arguments should be split
+set -- , ,
+recho $@
+recho $*
+recho ${@}
+recho ${*}
+
+set -- $@
+recho $#
+
+# need the space to be preserved
+set -- , ' ,'
+IFS=,
+recho $@
+recho $*
+
+set -- $@
+recho $#
+
+# need the , to preserve an empty argument before the trailing char
+# from back in 2018 on dash@vger.kernel.org
+set -- ',' ','
+
+recho ${@}a
+recho ${*}b
+
+recho a${@}
+recho b${*}
index b3b142d9997a2277edfdefd7be4f49c7110b1320..2c7c01f9f462043944da3f0b1403c4270b5baceb 100644 (file)
@@ -247,22 +247,18 @@ argv[3] = <'c'>
 argv[1] = <>
 argv[2] = <a>
 argv[3] = <>
-argv[4] = <>
-argv[5] = <b>
-argv[6] = <>
-argv[7] = <>
-argv[8] = <c>
+argv[4] = <b>
+argv[5] = <>
+argv[6] = <c>
 argv[1] = <'a'>
 argv[2] = <'b'>
 argv[3] = <'c'>
 argv[1] = <>
 argv[2] = <a>
 argv[3] = <>
-argv[4] = <>
-argv[5] = <b>
-argv[6] = <>
-argv[7] = <>
-argv[8] = <c>
+argv[4] = <b>
+argv[5] = <>
+argv[6] = <c>
 argv[1] = <'a'>
 argv[2] = <'b'>
 argv[3] = <'c'>
@@ -477,6 +473,91 @@ argv[1] = <>
 argv[1] = <>
 argv[1] = </>
 1:1
+       dollar-at-star12.sub
+<a> <b> <c> <> <d> <e> 
+<a> <b> <c> <> <d> <e> 
+<a> <b> <c> <> <d> <e> 
+=====
+<b> <c> 
+<b> <c> 
+<b> <c> 
+<b> <c> 
+<b> <c> 
+=====
+<a> <b> <c> <> <d> <e> <f> <g> <h> 
+<a> <b> <c> <> <d> <e> <f> <g> <h> 
+=====
+<a> <b> <c> <> <d> <e> 
+<a> <b> <c> <> <d> <e> 
+<a> <b> <c> <> <d> <e> 
+<a> <b> <c> <> <d> <e> <f> <g> <h> 
+<a> <b> <c> <> <d> <e> <f> <g> <h> 
+<a> <b> <c> <> <d> <e> <f> <g> <h> 
+=====
+argv[1] = <^A^?>
+argv[2] = <b>
+argv[3] = <c>
+argv[1] = <^A^?>
+argv[2] = <b>
+argv[3] = <c>
+argv[1] = <^?>
+argv[2] = <b>
+argv[3] = <c>
+argv[1] = <^?>
+argv[2] = <b>
+argv[3] = <c>
+argv[1] = <a^?>
+argv[2] = <b>
+argv[3] = <c>
+argv[1] = <a^?>
+argv[2] = <b>
+argv[3] = <c>
+argv[1] = <^A>
+argv[2] = <b>
+argv[3] = <c>
+argv[1] = <^A>
+argv[2] = <b>
+argv[3] = <c>
+argv[1] = <a^A>
+argv[2] = <b>
+argv[3] = <c>
+argv[1] = <a^A>
+argv[2] = <b>
+argv[3] = <c>
+       dollar-at-star13.sub
+argv[1] = <>
+argv[2] = <>
+argv[1] = <>
+argv[2] = <>
+argv[1] = <>
+argv[2] = <>
+argv[1] = <>
+argv[2] = <>
+argv[1] = <2>
+argv[1] = <>
+argv[2] = <>
+argv[1] = <>
+argv[2] = <>
+argv[1] = <>
+argv[2] = <>
+argv[1] = <>
+argv[2] = <>
+argv[1] = <2>
+argv[1] = <>
+argv[2] = < >
+argv[1] = <>
+argv[2] = < >
+argv[1] = <2>
+argv[1] = <>
+argv[2] = <>
+argv[3] = <a>
+argv[1] = <>
+argv[2] = <>
+argv[3] = <b>
+argv[1] = <a>
+argv[2] = <>
+argv[1] = <b>
+argv[2] = <>
        dollar-star1.sub
 xa|xb|xc
 xa|xb|xc
index d68791ca26d46f94ef4724cfd6d95d1b11eed5cb..03c9bd2901f59f07895c966af299c2db3f23c8be 100644 (file)
@@ -1,4 +1,4 @@
-unset GROUPS UID 2>/dev/null
+unset GROUPS 2>/dev/null
 
 ${THIS_SH} ./test.tests >${BASH_TSTOUT} 2>&1
 diff ${BASH_TSTOUT} test.right && rm -f ${BASH_TSTOUT}
index dc74855ba0e6c971fd0b2e908bc9242b1378eb6f..1d316a1e67133bb80b7d8e7903b2a2160e559145 100644 (file)
@@ -888,8 +888,8 @@ adjust_shell_level (int change)
       new_level[3] = '\0';
     }
 
-  temp_var = bind_variable ("SHLVL", new_level, 0);
-  set_auto_export (temp_var);
+  if (temp_var = bind_variable ("SHLVL", new_level, 0))
+    set_auto_export (temp_var);
 }
 
 static void
@@ -929,7 +929,8 @@ set_pwd (void)
       if (posixly_correct && current_dir)
        {
          temp_var = bind_variable ("PWD", current_dir, 0);
-         set_auto_export (temp_var);
+         if (temp_var)
+           set_auto_export (temp_var);
        }  
       free (current_dir);
     }
@@ -938,7 +939,8 @@ set_pwd (void)
     {
       set_working_directory (home_string);
       temp_var = bind_variable ("PWD", home_string, 0);
-      set_auto_export (temp_var);
+      if (temp_var)
+       set_auto_export (temp_var);
     }
   else
     {
@@ -946,7 +948,8 @@ set_pwd (void)
       if (temp_string)
        {
          temp_var = bind_variable ("PWD", temp_string, 0);
-         set_auto_export (temp_var);
+         if (temp_var)
+           set_auto_export (temp_var);
          free (temp_string);
        }
     }