]> git.ipfire.org Git - thirdparty/bash.git/commitdiff
round seconds in command timing information if precision is >= 0; fix for readline...
authorChet Ramey <chet.ramey@case.edu>
Thu, 23 Oct 2025 20:35:55 +0000 (16:35 -0400)
committerChet Ramey <chet.ramey@case.edu>
Thu, 23 Oct 2025 20:35:55 +0000 (16:35 -0400)
12 files changed:
CWRU/CWRU.chlog
arrayfunc.c
bashline.c
builtins/mkbuiltins.c
builtins/read.def
execute_cmd.c
subst.c
subst.h
tests/RUN-TEST-SCRIPT
tests/builtins.right
tests/conv-warnings [deleted file]
variables.c

index 34efc0dfd55b9bece620adc50a094ec4c2658dec..17786954d2f43a39b5135ee6668d6ad42d25299e 100644 (file)
@@ -13,7 +13,8 @@ Makefile.in
                                   -----
 builtins/wait.def
        - wait_builtin: don't assign the variable given with -p if there are no
-         jobs to wait for. Report and fix from OÄuz <oguzismailuysal@gmail.com>
+         jobs to wait for.
+         Report and fix from OÄuz <oguzismailuysal@gmail.com>
 
 arrayfunc.c
        - kvpair_assignment_p: return non-zero if argument L appears to be a
@@ -28,8 +29,8 @@ arrayfunc.h
 subst.c
        - expand_oneword: detect whether VALUE appears to be a key-value
          pair compound assignment and call the appropriate function to expand
-         each word in the resulting list. Fixes inconsistency reported by
-         oguzismailuysal@gmail.com
+         each word in the resulting list.
+         Fixes inconsistency reported by oguzismailuysal@gmail.com
 
                                   12/12
                                   -----
@@ -152,8 +153,8 @@ variables.c
          array variable that was the value of a nameref (used in the original
          assignment), don't call make_variable_value on the value, since that
          messes up +=. Just call assign_array_element and let that take care
-         of calling make_variable_value appropriately. Fixes bug reported by
-         Oguz <oguzismailuysal@gmail.com>
+         of calling make_variable_value appropriately.
+         Fixes bug reported by Oguz <oguzismailuysal@gmail.com>
 
                                   1/14
                                   ----
@@ -206,7 +207,8 @@ Makefile.in
 subst.c
        - param_expand: if a nameref expands to array[@] or array[*], make sure
          to call chk_atstar so the right variables are set to split the
-         result. Report from Oguz <oguzismailuysal@gmail.com>
+         result.
+         Report from Oguz <oguzismailuysal@gmail.com>
 
                                   1/22
                                   ----
@@ -12024,3 +12026,66 @@ lib/glob/sm_loop.c
 
 oslib.c
        - bcopy: update function declaration to add `const'
+
+execute_cmd.c
+       - mkfmt: fix rounding of fractional part of the printed value to
+         increment the seconds value if necessary (e.g., 0.9848 -> 1.0 if
+         precision == 1)
+       - mkfmt: compute the seconds and seconds fraction before writing
+         anything into the buffer in case we increment the seconds
+         Report and patch from Grisha Levit <grishalevit@gmail.com>
+
+subst.h
+       - SD_QUOTEDSTR: tell skip_to_delim that we're skipping over a quoted
+         string; *delims is the close quote
+
+subst.c
+       - skip_to_delim: understand SD_QUOTEDSTR and call skip_single_quoted
+         or skip_double_quoted immediately as appropriate
+
+bashline.c
+       - bash_forward_shellword: use SD_QUOTEDSTR in the call to skip_to_delim
+         Fixes bug reported by Grisha Levit <grishalevit@gmail.com>
+
+                                  10/20
+                                  -----
+execute_cmd.c
+       - mkfmt: round when the precision is 0 like with other precision values
+         Report and patch from Grisha Levit <grishalevit@gmail.com>
+
+variables.c
+       - make_local_array_variable,make_local_assoc_variable: if we're
+         converting an unset nameref into an array, warn about it and turn
+         off the nameref attribute
+         Report from Grisha Levit <grishalevit@gmail.com>
+
+builtins/mkbuiltins.c
+       - write_documentation: change to use modern C-style strings, where
+         adjacent strings are effectively concatenated
+       - write_documentation: don't add indent to otherwise blank lines
+         Original patch from Martin D Kealey <martin@kurahaupo.gen.nz>
+
+                                  10/21
+                                  -----
+builtins/read.def
+       - read_builtin: since referring to a pointer after realloc may have
+         freed it is, in theory, undefined behavior, redo the unwind-protect
+         every time we increase the size of input_string. This updates the
+         CHERI-only change of 10/6
+         From https://savannah.gnu.org/bugs/?67586
+
+                                  10/22
+                                  -----
+arrayfunc.c
+       - make_array_variable_value,assign_array_element_internal: set
+         att_assigning while evaluating the value to be assigned so the rest
+         of the shell knows not to unset the variable
+
+variables.c
+       - bind_variable_internal,bind_variable_value: set att_assigning
+         around calls to make_variable_value to avoid unsetting the variable
+       - makunbound: if we are assigning to the variable being unset, just
+         empty it out and set it to invisible, assuming the variable will be
+         marked as visible after assignment
+         Report from Oguz <oguzismailuysal@gmail.com> back on 8/8/2025
+
index 72ec83b3af51b1c56ddc283fa0dc295e9fef744a..fd3ebb1aad91b0a550f12f0f95d1fa665041ae4f 100644 (file)
@@ -203,7 +203,13 @@ make_array_variable_value (SHELL_VAR *entry, arrayind_t ind, const char *key, co
       dispose_variable (dentry);
     }
   else
-    newval = make_variable_value (entry, value, flags);
+    {
+      if (entry)
+       VSETATTR (entry, att_assigning);
+      newval = make_variable_value (entry, value, flags);
+      if (entry)
+       VUNSETATTR (entry, att_assigning);
+    }
 
   return newval;
 }
@@ -227,7 +233,7 @@ bind_assoc_var_internal (SHELL_VAR *entry, HASH_TABLE *hash, char *key, const ch
       (*entry->assign_func) (entry, newval, 0, key);
       FREE (key);
     }
-  else
+  else if (assoc_p (entry))
     assoc_insert (hash, key, newval);
 
   FREE (newval);
@@ -251,7 +257,7 @@ bind_array_var_internal (SHELL_VAR *entry, arrayind_t ind, char *key, const char
     (*entry->assign_func) (entry, newval, ind, key);
   else if (assoc_p (entry))
     assoc_insert (assoc_cell (entry), key, newval);
-  else
+  else if (array_p (entry))
     array_insert (array_cell (entry), ind, newval);
   FREE (newval);
 
@@ -432,7 +438,11 @@ assign_array_element_internal (SHELL_VAR *entry, const char *name, char *vname,
       int avflags;
 
       avflags = convert_assign_flags_to_arrayval_flags (flags);
+      if (entry)
+       VSETATTR (entry, att_assigning);
       ind = array_expand_index (entry, sub, sublen, avflags);
+      if (entry)
+       VUNSETATTR (entry, att_assigning);
       /* negative subscripts to indexed arrays count back from end */
       if (entry && ind < 0)
        ind = (array_p (entry) ? array_max_index (array_cell (entry)) : 0) + 1 + ind;
index e4f9975b454781456e95408a7592edeef833b487..bd5a25429346dc438ce17ce03599754b6b18e7d1 100644 (file)
@@ -1114,10 +1114,10 @@ bash_forward_shellword (int count, int key)
                ADVANCE_CHAR (rl_line_buffer, slen, p);
              break;
            case '\'':
-             p = skip_to_delim (rl_line_buffer, ++p, "'", SD_NOJMP);
+             p = skip_to_delim (rl_line_buffer, ++p, "'", SD_NOJMP|SD_QUOTEDSTR|SD_COMPLETE);
              break;
            case '"':
-             p = skip_to_delim (rl_line_buffer, ++p, "\"", SD_NOJMP);
+             p = skip_to_delim (rl_line_buffer, ++p, "\"", SD_NOJMP|SD_QUOTEDSTR|SD_COMPLETE);
              break;
            }
 
@@ -1145,10 +1145,10 @@ bash_forward_shellword (int count, int key)
                ADVANCE_CHAR (rl_line_buffer, slen, p);
              break;
            case '\'':
-             p = skip_to_delim (rl_line_buffer, ++p, "'", SD_NOJMP);
+             p = skip_to_delim (rl_line_buffer, ++p, "'", SD_NOJMP|SD_QUOTEDSTR|SD_COMPLETE);
              break;
            case '"':
-             p = skip_to_delim (rl_line_buffer, ++p, "\"", SD_NOJMP);
+             p = skip_to_delim (rl_line_buffer, ++p, "\"", SD_NOJMP|SD_QUOTEDSTR|SD_COMPLETE);
              break;
            }
 
index 2a27826fe2f189d7693db8f03bb021379019cd18..04e1bf0cfd1ce32d735c18e78f3a6675c3606bfc 100644 (file)
@@ -44,6 +44,7 @@
 #include "filecntl.h"
 
 #include "../bashansi.h"
+#include <stdbool.h>
 #include <stdio.h>
 #include <errno.h>
 
@@ -1391,68 +1392,53 @@ write_documentation (FILE *stream, char **documentation, int indentation, int fl
 {
   int i, j;
   char *line;
-  int string_array, texinfo, base_indent, filename_p;
+  int string_array, texinfo, filename_p;
+  int full_indent, base_indent;
 
   if (stream == 0)
     return;
 
   string_array = flags & STRING_ARRAY;
   filename_p = flags & HELPFILE;
+  texinfo = flags & TEXINFO;
+
+  base_indent = (string_array && single_longdoc_strings && filename_p == 0) ? BASE_INDENT : 0;
+  full_indent = indentation + base_indent;
 
   if (string_array)
     {
       fprintf (stream, " {\n#if defined (HELP_BUILTIN)\n");    /* } */
-      if (single_longdoc_strings)
-       {
-         if (filename_p == 0)
-           {
-             if (documentation && documentation[0] && documentation[0][0])
-               fprintf (stream,  "N_(\"");
-             else
-               fprintf (stream, "N_(\" ");             /* the empty string translates specially. */
-           }
-         else
-           fprintf (stream, "\"");
-       }
+      if (filename_p == 0 && single_longdoc_strings)
+       fprintf (stream, "N_(");
     }
 
-  base_indent = (string_array && single_longdoc_strings && filename_p == 0) ? BASE_INDENT : 0;
-
-  for (i = 0, texinfo = (flags & TEXINFO); documentation && (line = documentation[i]); i++)
+  for (i = 0; documentation && (line = documentation[i]); i++)
     {
+      bool first_line = i == 0;
+      bool last_line = documentation[i+1] == 0;
+      
       /* Allow #ifdef's to be written out verbatim, but don't put them into
         separate help files. */
       if (*line == '#')
        {
-         if (string_array && filename_p == 0 && single_longdoc_strings == 0)
+         if (string_array)
            fprintf (stream, "%s\n", line);
          continue;
        }
 
       /* prefix with N_( for gettext */
-      if (string_array && single_longdoc_strings == 0)
-       {
-         if (filename_p == 0)
-           {
-             if (line[0])            
-               fprintf (stream, "  N_(\"");
-             else
-               fprintf (stream, "  N_(\" ");           /* the empty string translates specially. */
-           }
-         else
-           fprintf (stream, "  \"");
-       }
-
-      if (indentation)
-       for (j = 0; j < indentation; j++)
-         fprintf (stream, " ");
-
-      /* Don't indent the first line, because of how the help builtin works. */
-      if (i == 0)
-       indentation += base_indent;
-
       if (string_array)
        {
+         if (filename_p == 0 && single_longdoc_strings == 0)
+           fprintf (stream,  "N_(");
+         else if (first_line == 0)
+           fputc ('\t', stream);
+         fputc ('"', stream);
+         if (filename_p == 0 && *line == 0 && last_line && first_line && indentation == 0)
+           line = " ";         /* the empty string translates specially. */
+         if (indentation && *line)
+           fprintf (stream, "%*.0s", indentation, "");
+
          for (j = 0; line[j]; j++)
            {
              if (line[j] == '\\' || line[j] == '"')
@@ -1460,20 +1446,18 @@ write_documentation (FILE *stream, char **documentation, int indentation, int fl
              fputc (line[j], stream);
            }
 
-         /* closing right paren for gettext */
-         if (single_longdoc_strings == 0)
-           {
-             if (filename_p == 0)
-               fprintf (stream, "\"),\n");
-             else
-               fprintf (stream, "\",\n");
-           }
-         else if (documentation[i+1])
-           /* don't add extra newline after last line */
-           fprintf (stream, "\\n\\\n");
+         if (last_line == 0)
+           fprintf (stream, "\\n");
+         fputc ('"', stream);
+         if (filename_p == 0 && single_longdoc_strings == 0)
+           fprintf (stream,  "),");
+         if (last_line == 0)
+           fprintf (stream, "\n");
        }
       else if (texinfo)
-       {
+       {          
+         if (indentation && *line)
+           fprintf (stream, "%*.0s", indentation, "");
          for (j = 0; line[j]; j++)
            {
              switch (line[j])
@@ -1489,20 +1473,23 @@ write_documentation (FILE *stream, char **documentation, int indentation, int fl
          fputc ('\n', stream);
        }
       else
-       fprintf (stream, "%s\n", line);
+       fprintf (stream, "%*.0s%s\n", indentation, "", line);
+
+      /* Don't indent the first line, because of how the help builtin works. */
+      indentation = full_indent;
     }
 
   /* closing right paren for gettext */
-  if (string_array && single_longdoc_strings)
+  if (string_array)
     {
-      if (filename_p == 0)
-       fprintf (stream, "\"),\n");
-      else
-       fprintf (stream, "\",\n");
+      if (single_longdoc_strings)
+       {
+         if (filename_p == 0)
+           fputc (')', stream);
+         fputc (',', stream);
+       }
+      fprintf (stream, "\n#endif /* HELP_BUILTIN */\n\t(char *)NULL\n};\n");
     }
-
-  if (string_array)
-    fprintf (stream, "#endif /* HELP_BUILTIN */\n  (char *)NULL\n};\n");
 }
 
 int
index 6b62d0beaf8e0abb94c45c94b7a20708dd8e9e9b..c6ab8b2d0deba48fab32176aa5560f34e4992ba9 100644 (file)
@@ -816,8 +816,9 @@ read_builtin (WORD_LIST *list)
          char *x;
          x = (char *)xrealloc (input_string, size += 128);
 
-         /* Only need to change unwind-protect if input_string changes */
-#ifndef __CHERI_PURE_CAPABILITY__
+#if 0
+         /* This is, in theory, undefined behavior, since input_string may
+            have been freed. */
          if (x != input_string)
 #endif
            {
index 436f37ba4ef8b12a7d830542f574207689e5b041..8d4e9e676cee892ae08e2ea2735696e406ae1007 100644 (file)
@@ -1273,18 +1273,48 @@ extern int timeval_to_cpu (struct timeval *, struct timeval *, struct timeval *)
 #define BASH_TIMEFORMAT  "\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS"
 
 static const int precs[] = { 0, 100000, 10000, 1000, 100, 10, 1 };
-static const int maxvals[] = { 1, 10, 100, 1000, 10000, 100000, 10000000 };
+static const int maxvals[] = { 1, 10, 100, 1000, 10000, 100000, 1000000 };
 
 /* Expand one `%'-prefixed escape sequence from a time format string. */
 /* SEC_FRACTION is in usecs. We normalize and round that based on the
   precision. */
-int
+static int
 mkfmt (char *buf, int prec, int lng, time_t sec, long sec_fraction)
 {
   time_t min;
   char abuf[INT_STRLEN_BOUND(time_t) + 1];
   int ind, aind;
 
+  /* We want to add a decimal point and PREC places after it if PREC is
+     nonzero.  PREC is not greater than 6.  SEC_FRACTION is between 0
+     and 999999 (microseconds). */
+  if (prec < 6)
+    {
+      /* We round here because we changed timeval_to_secs to return
+        microseconds and normalized clock_t_to_secs's fractional return
+        value to microseconds, deferring the work to be done to now.
+
+        sec_fraction is in microseconds. Take the value, cut off what we
+        don't want, round up if necessary, then convert back to
+        microseconds. */
+      int frac, rest, maxval;
+
+      maxval = maxvals[6 - prec];
+      frac = sec_fraction / maxval;
+      rest = sec_fraction % maxval;
+
+      if (rest >= maxval/2)
+       frac++;
+
+      if (frac == maxvals[prec])
+       {
+         sec++;
+         sec_fraction = 0;
+       }
+      else
+       sec_fraction = frac * (1000000 / maxvals[prec]);
+    }
+  
   ind = 0;
   abuf[sizeof(abuf) - 1] = '\0';
 
@@ -1312,32 +1342,8 @@ mkfmt (char *buf, int prec, int lng, time_t sec, long sec_fraction)
   while (abuf[aind])
     buf[ind++] = abuf[aind++];
 
-  /* We want to add a decimal point and PREC places after it if PREC is
-     nonzero.  PREC is not greater than 6.  SEC_FRACTION is between 0
-     and 999999 (microseconds). */
-  if (prec != 0)
+  if (prec > 0)
     {
-      /* We round here because we changed timeval_to_secs to return
-        microseconds and normalized clock_t_to_secs's fractional return
-        value to microseconds, deferring the work to be done to now.
-
-        sec_fraction is in microseconds. Take the value, cut off what we
-        don't want, round up if necessary, then convert back to
-        microseconds. */
-      if (prec != 6)
-       {
-         int frac, rest, maxval;
-
-         maxval = maxvals[6 - prec];
-         frac = sec_fraction / maxval;
-         rest = sec_fraction % maxval;
-
-         if (rest >= maxval/2)
-         frac++;
-
-         sec_fraction = frac * (1000000 / maxvals[prec]);
-       }
-  
       buf[ind++] = locale_decpoint ();
       for (aind = 1; aind <= prec; aind++)
        {
diff --git a/subst.c b/subst.c
index fd7262a4243a4c163801994d02cabe70c5fd4fd3..0e804b763abb761bdb2b93a27356cd5825b45513 100644 (file)
--- a/subst.c
+++ b/subst.c
@@ -2217,6 +2217,18 @@ skip_to_delim (const char *string, int start, const char *delims, int flags)
   arithexp = (flags & SD_ARITHEXP);
   skipcol = 0;
 
+  /* If the caller tells us we're skipping a quoted string after the opening
+     quote, and the delimiter is a single- or double-quote, skip all the logic
+     here and just call the same functions as word expansion. */
+  if ((flags & SD_QUOTEDSTR) && (*delims == '\'' || *delims == '"') && delims[1] == '\0')
+    {
+      if (*delims == '\'')
+       i = skip_single_quoted (string, slen, start, 0);
+      else
+       i = skip_double_quoted (string, slen, start, completeflag);
+      CQ_RETURN (i);
+    }
+
   i = start;
   pass_next = backq = dquote = 0;
   while (c = string[i])
diff --git a/subst.h b/subst.h
index c9f155a8b62938a3e1eadd0f452ba2849dcaa854..59f09065cc2487dc8ae824fbc5691e6595c91cfb 100644 (file)
--- a/subst.h
+++ b/subst.h
@@ -336,6 +336,7 @@ extern char *cond_expand_word (WORD_DESC *, int);
 #define SD_HISTEXP     0x200   /* skip_to_delim during history expansion */
 #define SD_ARITHEXP    0x400   /* skip_to_delim during arithmetic expansion */
 #define SD_NOERROR     0x800   /* don't print error messages */
+#define SD_QUOTEDSTR   0x1000  /* skipping a part of a single- or double-quoted string */
 
 extern int skip_to_delim (const char *, int, const char *, int);
 
index 2aff0e09997d2380aac7fe5fc1a614b530614a16..5d962abd5d44458bbe2dd8a3e50fb16c09a1c758 100755 (executable)
@@ -1,10 +1,22 @@
-: ${BUILD_DIR:=/usr/local/build/chet/bash/bash-current}
+: ${BUILD_DIR:=$PWD}
 THIS_SH=$BUILD_DIR/bash
 PATH=$PATH:$BUILD_DIR
 
-export THIS_SH PATH
+export THIS_SH PATH BUILD_DIR
+
+: ${TMPDIR:=/tmp}
+export TMPDIR
 
 export BASH_TSTOUT=/tmp/xx
 rm -f ${BASH_TSTOUT}
 
+if [ -t 1 ]; then
+        # can't rely on having $'...' or printf understanding \e
+        # bright red background, white foreground text
+        export CSTART=$(printf '\033[01;101;37m') CEND=$(printf '\033[0m')
+else
+        export CSTART= CEND=
+fi
+export TAB='   '
+
 ${THIS_SH} "$@"
index db0f686400e889def68c30037a1b703a32891a78..c0194b86d01ad835b1555a92a357a20e463eb289 100644 (file)
@@ -406,10 +406,10 @@ help: help [-dms] [pattern ...]
 shift - Shift positional parameters.
 shift: shift [n]
     Shift positional parameters.
-    
+
     Rename the positional parameters $N+1,$N+2 ... to $1,$2 ...  If N is
     not given, it is assumed to be 1.
-    
+
     Exit Status:
     Returns success unless N is negative or greater than $#.
 builtin: builtin [shell-builtin [arg ...]]
@@ -424,9 +424,9 @@ readarray: readarray [-d delim] [-n count] [-O origin] [-s count] [-t] [-u fd] [
 readonly: readonly [-aAf] [name[=value] ...] or readonly -p
 :: :
     Null command.
-    
+
     No effect; the command does nothing.
-    
+
     Exit Status:
     Always succeeds.
 NAME
@@ -437,9 +437,9 @@ SYNOPSIS
 
 DESCRIPTION
     Null command.
-    
+
     No effect; the command does nothing.
-    
+
     Exit Status:
     Always succeeds.
 
diff --git a/tests/conv-warnings b/tests/conv-warnings
deleted file mode 100644 (file)
index 2cdd25a..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-for f ; do
-       echo $f
-       sed 's|warning:|${CSTART}warning${CEND}:|' < $f > zzz && mv zzz $f
-done
index 7f1449d14ddc7c70f71bb4f7d6f7fb9b39a4519b..543b4bf7932676fe928413f3c7f7610f329eafed 100644 (file)
@@ -2824,6 +2824,13 @@ make_local_array_variable (const char *name, int flags)
   if (var == 0 || array_p (var) || (assoc_ok && assoc_p (var)))
     return var;
 
+  /* array variables cannot be namerefs */
+  if (var && nameref_p (var) && invisible_p (var))
+    {
+      internal_warning (_("%s: removing nameref attribute"), name);
+      VUNSETATTR (var, att_nameref);
+    }
+
   /* Validate any value we inherited from a variable instance at a previous
      scope and discard anything that's invalid. */
   if (localvar_inherit && assoc_p (var))
@@ -2880,6 +2887,13 @@ make_local_assoc_variable (const char *name, int flags)
   if (var == 0 || assoc_p (var) || (array_ok && array_p (var)))
     return var;
 
+  /* assoc variables cannot be namerefs */
+  if (var && nameref_p (var) && invisible_p (var))
+    {
+      internal_warning (_("%s: removing nameref attribute"), name);
+      VUNSETATTR (var, att_nameref);
+    }
+
   /* Validate any value we inherited from a variable instance at a previous
      scope and discard anything that's invalid. */
   if (localvar_inherit && array_p (var))
@@ -3123,13 +3137,19 @@ bind_variable_internal (const char *name, const char *value, HASH_TABLE *table,
 #endif
        {
          entry = make_new_variable (newval, table);
-         var_setvalue (entry, make_variable_value (entry, value, aflags));
+         VSETATTR (entry, att_assigning);
+         newval = make_variable_value (entry, value, aflags);
+         VUNSETATTR (entry, att_assigning);
+         var_setvalue (entry, newval);
        }
     }
   else if (entry == 0)
     {
       entry = make_new_variable (name, table);
-      var_setvalue (entry, make_variable_value (entry, value, aflags)); /* XXX */
+      VSETATTR (entry, att_assigning);
+      newval = make_variable_value (entry, value, aflags); /* XXX */
+      VUNSETATTR (entry, att_assigning);
+      var_setvalue (entry, newval);
     }
   else if (entry->assign_func) /* array vars have assign functions now */
     {
@@ -3141,7 +3161,9 @@ bind_variable_internal (const char *name, const char *value, HASH_TABLE *table,
        }
 
       INVALIDATE_EXPORTSTR (entry);
+      VSETATTR (entry, att_assigning);
       newval = (aflags & ASS_APPEND) ? make_variable_value (entry, value, aflags) : (char *)value;
+      VUNSETATTR (entry, att_assigning);
       if (assoc_p (entry))
        entry = (*(entry->assign_func)) (entry, newval, -1, savestring ("0"));
       else if (array_p (entry))
@@ -3162,9 +3184,6 @@ assign_value:
          return (entry);
        }
 
-      /* Variables which are bound are visible. */
-      VUNSETATTR (entry, att_invisible);
-
       /* If we can optimize the assignment, do so and return.  Right now, we
         optimize appends to string variables. */
       if (can_optimize_assignment (entry, value, aflags))
@@ -3178,15 +3197,23 @@ assign_value:
          if (exported_p (entry))
            array_needs_making = 1;
 
+         /* Variables which are bound are visible. */
+         VUNSETATTR (entry, att_invisible);
+
          return (entry);
        }
 
+      VSETATTR (entry, att_assigning);
 #if defined (ARRAY_VARS)
       if (assoc_p (entry) || array_p (entry))
         newval = make_array_variable_value (entry, 0, "0", value, aflags);
       else
 #endif
       newval = make_variable_value (entry, value, aflags);     /* XXX */
+      VUNSETATTR (entry, att_assigning);
+
+      /* Variables which are bound are visible. */
+      VUNSETATTR (entry, att_invisible);
 
       /* Invalidate any cached export string */
       INVALIDATE_EXPORTSTR (entry);
@@ -3326,23 +3353,24 @@ SHELL_VAR *
 bind_variable_value (SHELL_VAR *var, char *value, int aflags)
 {
   char *t;
-  int invis;
-
-  invis = invisible_p (var);
-  VUNSETATTR (var, att_invisible);
 
   if (var->assign_func)
     {
       /* If we're appending, we need the old value, so use
         make_variable_value */
+      VSETATTR (var, att_assigning);
       t = (aflags & ASS_APPEND) ? make_variable_value (var, value, aflags) : value;
+      VUNSETATTR (var, att_assigning);
+      VUNSETATTR (var, att_invisible);
       (*(var->assign_func)) (var, t, -1, 0);
       if (t != value && t)
        free (t);      
     }
   else
     {
+      VSETATTR (var, att_assigning);
       t = make_variable_value (var, value, aflags);
+      VUNSETATTR (var, att_assigning);
       if ((aflags & (ASS_NAMEREF|ASS_FORCE)) == ASS_NAMEREF && check_selfref (name_cell (var), t, 0))
        {
          if (variable_context)
@@ -3351,20 +3379,17 @@ bind_variable_value (SHELL_VAR *var, char *value, int aflags)
            {
              internal_error (_("%s: nameref variable self references not allowed"), name_cell (var));
              free (t);
-             if (invis)
-               VSETATTR (var, att_invisible);  /* XXX */
              return ((SHELL_VAR *)NULL);
            }
        }
       if ((aflags & ASS_NAMEREF) && (valid_nameref_value (t, 0) == 0))
        {
          free (t);
-         if (invis)
-           VSETATTR (var, att_invisible);      /* XXX */
          return ((SHELL_VAR *)NULL);
        }
       FREE (value_cell (var));
       var_setvalue (var, t);
+      VUNSETATTR (var, att_invisible);
     }
 
   INVALIDATE_EXPORTSTR (var);
@@ -4010,6 +4035,36 @@ makunbound (const char *name, VAR_CONTEXT *vc)
       return (0);
     }
 
+  /* If we are currently assigning this variable, but the evaluation of the
+     value causes it to be unset, we need to make sure pointers to the
+     variable struct remain valid, and insert a cleared-out version of the
+     variable back into the correct hash table. */
+  if (old_var && assigning_p (old_var))
+    {
+      dispose_variable_value (old_var);
+      var_setvalue (old_var, (char *)NULL);
+
+      old_var->attributes = 0;
+      VSETATTR (old_var, att_assigning);
+      VSETATTR (old_var, att_invisible);
+
+      INVALIDATE_EXPORTSTR (old_var);
+
+      old_var->dynamic_value = NULL;
+      old_var->assign_func = NULL;
+
+      /* leave the context unchanged if we're going to be assigning it */
+      /* old_var->context = 0; */
+
+      new_elt = hash_insert (savestring (old_var->name), v->table, 0);
+      new_elt->data = (PTR_T)old_var;
+      stupidly_hack_special_variables (old_var->name);
+
+      free (elt->key);
+      free (elt);
+      return (0);
+    }
+
   /* Have to save a copy of name here, because it might refer to
      old_var->name.  If so, stupidly_hack_special_variables will
      reference freed memory. */