]> git.ipfire.org Git - thirdparty/bash.git/blobdiff - variables.c
Bash-5.0 patch 4: the wait builtin without arguments only waits for known children...
[thirdparty/bash.git] / variables.c
index 024c05fe85ac5c0e072d8ed16a99b2769dfb4453..610629abbaf8116e49a606f42629cdbff66ef6a4 100644 (file)
@@ -1,22 +1,22 @@
 /* variables.c -- Functions for hacking shell variables. */
 
-/* Copyright (C) 1987-2004 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2018 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
-   Bash is free software; you can redistribute it and/or modify it
-   under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2, or (at your option)
-   any later version.
+   Bash is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
 
-   Bash is distributed in the hope that it will be useful, but WITHOUT
-   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
-   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
-   License for more details.
+   Bash is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
-   along with Bash; see the file COPYING.  If not, write to the Free
-   Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
+   along with Bash.  If not, see <http://www.gnu.org/licenses/>.
+*/
 
 #include "config.h"
 
 #include "posixstat.h"
 #include "posixtime.h"
 
-#if defined (qnx)
-#  include <sys/vc.h>
-#endif
+#if defined (__QNX__)
+#  if defined (__QNXNTO__)
+#    include <sys/netmgr.h>
+#  else
+#    include <sys/vc.h>
+#  endif /* !__QNXNTO__ */
+#endif /* __QNX__ */
 
 #if defined (HAVE_UNISTD_H)
 #  include <unistd.h>
 
 #include <stdio.h>
 #include "chartypes.h"
-#include <pwd.h>
+#if defined (HAVE_PWD_H)
+#  include <pwd.h>
+#endif
 #include "bashansi.h"
 #include "bashintl.h"
 
+#define NEED_XTRACE_SET_DECL
+
 #include "shell.h"
+#include "parser.h"
 #include "flags.h"
 #include "execute_cmd.h"
 #include "findcmd.h"
 #include "input.h"
 #include "hashcmd.h"
 #include "pathexp.h"
+#include "alias.h"
+#include "jobs.h"
+
+#include "version.h"
 
 #include "builtins/getopt.h"
 #include "builtins/common.h"
+#include "builtins/builtext.h"
 
 #if defined (READLINE)
 #  include "bashline.h"
 #  include "pcomplete.h"
 #endif
 
+#define VARIABLES_HASH_BUCKETS 1024    /* must be power of two */
+#define FUNCTIONS_HASH_BUCKETS 512
 #define TEMPENV_HASH_BUCKETS   4       /* must be power of two */
 
-#define ifsname(s)     ((s)[0] == 'I' && (s)[1] == 'F' && (s)[2] == 'S' && (s)[3] == '\0')
+#define BASHFUNC_PREFIX                "BASH_FUNC_"
+#define BASHFUNC_PREFLEN       10      /* == strlen(BASHFUNC_PREFIX */
+#define BASHFUNC_SUFFIX                "%%"
+#define BASHFUNC_SUFFLEN       2       /* == strlen(BASHFUNC_SUFFIX) */
+
+/* flags for find_variable_internal */
+
+#define FV_FORCETEMPENV                0x01
+#define FV_SKIPINVISIBLE       0x02
+
+extern char **environ;
 
 /* Variables used here and defined in other files. */
-extern int posixly_correct;
-extern int line_number;
-extern int subshell_environment, indirection_level, subshell_level;
-extern int build_version, patch_level;
-extern int expanding_redir;
-extern char *dist_version, *release_status;
-extern char *shell_name;
-extern char *primary_prompt, *secondary_prompt;
-extern char *current_host_name;
-extern sh_builtin_func_t *this_shell_builtin;
-extern SHELL_VAR *this_shell_function;
-extern char *the_printed_command_except_trap;
-extern char *this_command_name;
-extern char *command_execution_string;
 extern time_t shell_start_time;
 
-#if defined (READLINE)
-extern int perform_hostname_completion;
-#endif
-
 /* The list of shell variables that the user has created at the global
    scope, or that came from the environment. */
 VAR_CONTEXT *global_variables = (VAR_CONTEXT *)NULL;
@@ -102,6 +110,8 @@ VAR_CONTEXT *shell_variables = (VAR_CONTEXT *)NULL;
    the environment. */
 HASH_TABLE *shell_functions = (HASH_TABLE *)NULL;
 
+HASH_TABLE *invalid_env = (HASH_TABLE *)NULL;
+
 #if defined (DEBUGGER)
 /* The table of shell function definitions that the user defined or that
    came from the environment. */
@@ -112,6 +122,15 @@ HASH_TABLE *shell_function_defs = (HASH_TABLE *)NULL;
    executing functions we are. */
 int variable_context = 0;
 
+/* If non-zero, local variables inherit values and attributes from a variable
+   with the same name at a previous scope. */
+int localvar_inherit = 0;
+
+/* If non-zero, calling `unset' on local variables in previous scopes marks
+   them as invisible so lookups find them unset. This is the same behavior
+   as local variables in the current local scope. */
+int localvar_unset = 0;
+
 /* The set of shell assignments which are made only in the environment
    for a single command. */
 HASH_TABLE *temporary_env = (HASH_TABLE *)NULL;
@@ -128,6 +147,13 @@ WORD_LIST *rest_of_args = (WORD_LIST *)NULL;
 /* The value of $$. */
 pid_t dollar_dollar_pid;
 
+/* Non-zero means that we have to remake EXPORT_ENV. */
+int array_needs_making = 1;
+
+/* The number of times BASH has been executed.  This is set
+   by initialize_variables (). */
+int shell_level = 0;
+
 /* An array which is passed to commands as their environment.  It is
    manufactured from the union of the initial environment and the
    shell variables that are marked for export. */
@@ -135,14 +161,19 @@ char **export_env = (char **)NULL;
 static int export_env_index;
 static int export_env_size;
 
-/* Non-zero means that we have to remake EXPORT_ENV. */
-int array_needs_making = 1;
+#if defined (READLINE)
+static int winsize_assignment;         /* currently assigning to LINES or COLUMNS */
+#endif
 
-/* The number of times BASH has been executed.  This is set
-   by initialize_variables (). */
-int shell_level = 0;
+SHELL_VAR nameref_invalid_value;
+static SHELL_VAR nameref_maxloop_value;
+
+static HASH_TABLE *last_table_searched;        /* hash_lookup sets this */
+static VAR_CONTEXT *last_context_searched;
 
 /* Some forward declarations. */
+static void create_variable_tables __P((void));
+
 static void set_machine_vars __P((void));
 static void set_home_var __P((void));
 static void set_shell_var __P((void));
@@ -153,42 +184,65 @@ static void uidset __P((void));
 static void make_vers_array __P((void));
 #endif
 
-static SHELL_VAR *null_assign __P((SHELL_VAR *, char *, arrayind_t));
+static SHELL_VAR *null_assign __P((SHELL_VAR *, char *, arrayind_t, char *));
 #if defined (ARRAY_VARS)
-static SHELL_VAR *null_array_assign __P((SHELL_VAR *, char *, arrayind_t));
+static SHELL_VAR *null_array_assign __P((SHELL_VAR *, char *, arrayind_t, char *));
 #endif
 static SHELL_VAR *get_self __P((SHELL_VAR *));
 
 #if defined (ARRAY_VARS)
 static SHELL_VAR *init_dynamic_array_var __P((char *, sh_var_value_func_t *, sh_var_assign_func_t *, int));
+static SHELL_VAR *init_dynamic_assoc_var __P((char *, sh_var_value_func_t *, sh_var_assign_func_t *, int));
 #endif
 
-static SHELL_VAR *assign_seconds __P((SHELL_VAR *, char *, arrayind_t));
+static SHELL_VAR *assign_seconds __P((SHELL_VAR *, char *, arrayind_t, char *));
 static SHELL_VAR *get_seconds __P((SHELL_VAR *));
 static SHELL_VAR *init_seconds_var __P((void));
 
 static int brand __P((void));
 static void sbrand __P((unsigned long));               /* set bash random number generator. */
-static SHELL_VAR *assign_random __P((SHELL_VAR *, char *, arrayind_t));
+static void seedrand __P((void));                      /* seed generator randomly */
+static SHELL_VAR *assign_random __P((SHELL_VAR *, char *, arrayind_t, char *));
 static SHELL_VAR *get_random __P((SHELL_VAR *));
 
-static SHELL_VAR *assign_lineno __P((SHELL_VAR *, char *, arrayind_t));
+static SHELL_VAR *assign_lineno __P((SHELL_VAR *, char *, arrayind_t, char *));
 static SHELL_VAR *get_lineno __P((SHELL_VAR *));
 
-static SHELL_VAR *assign_subshell __P((SHELL_VAR *, char *, arrayind_t));
+static SHELL_VAR *assign_subshell __P((SHELL_VAR *, char *, arrayind_t, char *));
 static SHELL_VAR *get_subshell __P((SHELL_VAR *));
 
+static SHELL_VAR *get_epochseconds __P((SHELL_VAR *));
+static SHELL_VAR *get_epochrealtime __P((SHELL_VAR *));
+
+static SHELL_VAR *get_bashpid __P((SHELL_VAR *));
+
 #if defined (HISTORY)
 static SHELL_VAR *get_histcmd __P((SHELL_VAR *));
 #endif
 
+#if defined (READLINE)
+static SHELL_VAR *get_comp_wordbreaks __P((SHELL_VAR *));
+static SHELL_VAR *assign_comp_wordbreaks __P((SHELL_VAR *, char *, arrayind_t, char *));
+#endif
+
 #if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS)
-static SHELL_VAR *assign_dirstack __P((SHELL_VAR *, char *, arrayind_t));
+static SHELL_VAR *assign_dirstack __P((SHELL_VAR *, char *, arrayind_t, char *));
 static SHELL_VAR *get_dirstack __P((SHELL_VAR *));
 #endif
 
 #if defined (ARRAY_VARS)
 static SHELL_VAR *get_groupset __P((SHELL_VAR *));
+#  if defined (DEBUGGER)
+static SHELL_VAR *get_bashargcv __P((SHELL_VAR *));
+#  endif
+static SHELL_VAR *build_hashcmd __P((SHELL_VAR *));
+static SHELL_VAR *get_hashcmd __P((SHELL_VAR *));
+static SHELL_VAR *assign_hashcmd __P((SHELL_VAR *,  char *, arrayind_t, char *));
+#  if defined (ALIAS)
+static SHELL_VAR *build_aliasvar __P((SHELL_VAR *));
+static SHELL_VAR *get_aliasvar __P((SHELL_VAR *));
+static SHELL_VAR *assign_aliasvar __P((SHELL_VAR *,  char *, arrayind_t, char *));
+#  endif
 #endif
 
 static SHELL_VAR *get_funcname __P((SHELL_VAR *));
@@ -196,11 +250,16 @@ static SHELL_VAR *init_funcname_var __P((void));
 
 static void initialize_dynamic_variables __P((void));
 
+static SHELL_VAR *bind_invalid_envvar __P((const char *, char *, int));
+
+static int var_sametype __P((SHELL_VAR *, SHELL_VAR *));
+
 static SHELL_VAR *hash_lookup __P((const char *, HASH_TABLE *));
 static SHELL_VAR *new_shell_variable __P((const char *));
 static SHELL_VAR *make_new_variable __P((const char *, HASH_TABLE *));
-static SHELL_VAR *bind_variable_internal __P((const char *, char *, HASH_TABLE *, int));
+static SHELL_VAR *bind_variable_internal __P((const char *, char *, HASH_TABLE *, int, int));
 
+static void dispose_variable_value __P((SHELL_VAR *));
 static void free_variable_hash_data __P((PTR_T));
 
 static VARLIST *vlist_alloc __P((int));
@@ -216,18 +275,26 @@ static SHELL_VAR **fapply __P((sh_var_map_func_t *));
 
 static int visible_var __P((SHELL_VAR *));
 static int visible_and_exported __P((SHELL_VAR *));
+static int export_environment_candidate __P((SHELL_VAR *));
 static int local_and_exported __P((SHELL_VAR *));
 static int variable_in_context __P((SHELL_VAR *));
 #if defined (ARRAY_VARS)
 static int visible_array_vars __P((SHELL_VAR *));
 #endif
 
+static SHELL_VAR *find_variable_internal __P((const char *, int));
+
+static SHELL_VAR *find_nameref_at_context __P((SHELL_VAR *, VAR_CONTEXT *));
+static SHELL_VAR *find_variable_nameref_context __P((SHELL_VAR *, VAR_CONTEXT *, VAR_CONTEXT **));
+static SHELL_VAR *find_variable_last_nameref_context __P((SHELL_VAR *, VAR_CONTEXT *, VAR_CONTEXT **));
+
 static SHELL_VAR *bind_tempenv_variable __P((const char *, char *));
+static void push_posix_temp_var __P((PTR_T));
 static void push_temp_var __P((PTR_T));
 static void propagate_temp_var __P((PTR_T));
 static void dispose_temporary_env __P((sh_free_func_t *));     
 
-static inline char *mk_env_string __P((const char *, const char *));
+static inline char *mk_env_string __P((const char *, const char *, int));
 static char **make_env_array_from_var_list __P((SHELL_VAR **));
 static char **make_var_export_array __P((VAR_CONTEXT *));
 static char **make_func_export_array __P((void));
@@ -237,38 +304,47 @@ static int n_shell_variables __P((void));
 static int set_context __P((SHELL_VAR *));
 
 static void push_func_var __P((PTR_T));
+static void push_builtin_var __P((PTR_T));
 static void push_exported_var __P((PTR_T));
 
+static inline void push_posix_tempvar_internal __P((SHELL_VAR *, int));
+
 static inline int find_special_var __P((const char *));
-              
-/* Initialize the shell variables from the current environment.
-   If PRIVMODE is nonzero, don't import functions from ENV or
-   parse $SHELLOPTS. */
-void
-initialize_shell_variables (env, privmode)
-     char **env;
-     int privmode;
-{
-  char *name, *string, *temp_string;
-  int c, char_index, string_index, string_length;
-  SHELL_VAR *temp_var;
 
+static void
+create_variable_tables ()
+{
   if (shell_variables == 0)
     {
       shell_variables = global_variables = new_var_context ((char *)NULL, 0);
       shell_variables->scope = 0;
-      shell_variables->table = hash_create (0);
+      shell_variables->table = hash_create (VARIABLES_HASH_BUCKETS);
     }
 
   if (shell_functions == 0)
-    shell_functions = hash_create (0);
+    shell_functions = hash_create (FUNCTIONS_HASH_BUCKETS);
 
 #if defined (DEBUGGER)
   if (shell_function_defs == 0)
-    shell_function_defs = hash_create (0);
+    shell_function_defs = hash_create (FUNCTIONS_HASH_BUCKETS);
 #endif
+}
+
+/* Initialize the shell variables from the current environment.
+   If PRIVMODE is nonzero, don't import functions from ENV or
+   parse $SHELLOPTS. */
+void
+initialize_shell_variables (env, privmode)
+     char **env;
+     int privmode;
+{
+  char *name, *string, *temp_string;
+  int c, char_index, string_index, string_length, ro;
+  SHELL_VAR *temp_var;
 
-  for (string_index = 0; string = env[string_index++]; )
+  create_variable_tables ();
+
+  for (string_index = 0; env && (string = env[string_index++]); )
     {
       char_index = 0;
       name = string;
@@ -287,55 +363,105 @@ initialize_shell_variables (env, privmode)
       /* Now, name = env variable name, string = env variable value, and
         char_index == strlen (name) */
 
+      temp_var = (SHELL_VAR *)NULL;
+
+#if defined (FUNCTION_IMPORT)
       /* If exported function, define it now.  Don't import functions from
         the environment in privileged mode. */
-      if (privmode == 0 && read_but_dont_execute == 0 && STREQN ("() {", string, 4))
+      if (privmode == 0 && read_but_dont_execute == 0 && 
+          STREQN (BASHFUNC_PREFIX, name, BASHFUNC_PREFLEN) &&
+          STREQ (BASHFUNC_SUFFIX, name + char_index - BASHFUNC_SUFFLEN) &&
+         STREQN ("() {", string, 4))
        {
-         string_length = strlen (string);
-         temp_string = (char *)xmalloc (3 + string_length + char_index);
+         size_t namelen;
+         char *tname;          /* desired imported function name */
+
+         namelen = char_index - BASHFUNC_PREFLEN - BASHFUNC_SUFFLEN;
 
-         strcpy (temp_string, name);
-         temp_string[char_index] = ' ';
-         strcpy (temp_string + char_index + 1, string);
+         tname = name + BASHFUNC_PREFLEN;      /* start of func name */
+         tname[namelen] = '\0';                /* now tname == func name */
 
-         parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST);
+         string_length = strlen (string);
+         temp_string = (char *)xmalloc (namelen + string_length + 2);
+
+         memcpy (temp_string, tname, namelen);
+         temp_string[namelen] = ' ';
+         memcpy (temp_string + namelen + 1, string, string_length + 1);
 
-         /* Ancient backwards compatibility.  Old versions of bash exported
-            functions like name()=() {...} */
-         if (name[char_index - 1] == ')' && name[char_index - 2] == '(')
-           name[char_index - 2] = '\0';
+         /* Don't import function names that are invalid identifiers from the
+            environment in posix mode, though we still allow them to be defined as
+            shell variables. */
+         if (absolute_program (tname) == 0 && (posixly_correct == 0 || legal_identifier (tname)))
+           parse_and_execute (temp_string, tname, SEVAL_NONINT|SEVAL_NOHIST|SEVAL_FUNCDEF|SEVAL_ONECMD);
+         else
+           free (temp_string);         /* parse_and_execute does this */
 
-         if (temp_var = find_function (name))
+         if (temp_var = find_function (tname))
            {
              VSETATTR (temp_var, (att_exported|att_imported));
              array_needs_making = 1;
            }
          else
-           report_error (_("error importing function definition for `%s'"), name);
+           {
+             if (temp_var = bind_invalid_envvar (name, string, 0))
+               {
+                 VSETATTR (temp_var, (att_exported | att_imported | att_invisible));
+                 array_needs_making = 1;
+               }
+             last_command_exit_value = 1;
+             report_error (_("error importing function definition for `%s'"), tname);
+           }
 
-         /* ( */
-         if (name[char_index - 1] == ')' && name[char_index - 2] == '\0')
-           name[char_index - 2] = '(';         /* ) */
+         /* Restore original suffix */
+         tname[namelen] = BASHFUNC_SUFFIX[0];
        }
+      else
+#endif /* FUNCTION_IMPORT */
 #if defined (ARRAY_VARS)
-#  if 0
+#  if ARRAY_EXPORT
       /* Array variables may not yet be exported. */
-      else if (*string == '(' && string[1] == '[' && xstrchr (string, ')'))
+      if (*string == '(' && string[1] == '[' && string[strlen (string) - 1] == ')')
        {
          string_length = 1;
          temp_string = extract_array_assignment_list (string, &string_length);
-         temp_var = assign_array_from_string (name, temp_string);
+         temp_var = assign_array_from_string (name, temp_string, 0);
          FREE (temp_string);
          VSETATTR (temp_var, (att_exported | att_imported));
          array_needs_making = 1;
        }
-#  endif
-#endif
       else
+#  endif /* ARRAY_EXPORT */
+#endif
        {
-         temp_var = bind_variable (name, string);
-         VSETATTR (temp_var, (att_exported | att_imported));
-         array_needs_making = 1;
+         ro = 0;
+         /* If we processed a command-line option that caused SHELLOPTS to be
+            set, it may already be set (and read-only) by the time we process
+            the shell's environment. */
+         if (/* posixly_correct &&*/ STREQ (name, "SHELLOPTS"))
+           {
+             temp_var = find_variable ("SHELLOPTS");
+             ro = temp_var && readonly_p (temp_var);
+             if (temp_var)
+               VUNSETATTR (temp_var, att_readonly);
+           }
+         if (legal_identifier (name))
+           {
+             temp_var = bind_variable (name, string, 0);
+             if (temp_var)
+               {
+                 VSETATTR (temp_var, (att_exported | att_imported));
+                 if (ro)
+                   VSETATTR (temp_var, att_readonly);
+               }
+           }
+         else
+           {
+             temp_var = bind_invalid_envvar (name, string, 0);
+             if (temp_var)
+               VSETATTR (temp_var, (att_exported | att_imported | att_invisible));
+           }
+         if (temp_var)
+           array_needs_making = 1;
        }
 
       name[char_index] = '=';
@@ -350,7 +476,7 @@ initialize_shell_variables (env, privmode)
   set_pwd ();
 
   /* Set up initial value of $_ */
-  temp_var = bind_variable ("_", dollar_vars[0]);
+  temp_var = set_if_not ("_", dollar_vars[0]);
 
   /* Remember this pid. */
   dollar_dollar_pid = getpid ();
@@ -358,22 +484,20 @@ initialize_shell_variables (env, privmode)
   /* Now make our own defaults in case the vars that we think are
      important are missing. */
   temp_var = set_if_not ("PATH", DEFAULT_PATH_VALUE);
-#if 0
-  set_auto_export (temp_var);  /* XXX */
-#endif
-
   temp_var = set_if_not ("TERM", "dumb");
-#if 0
-  set_auto_export (temp_var);  /* XXX */
-#endif
 
-#if defined (qnx)
+#if defined (__QNX__)
   /* set node id -- don't import it from the environment */
   {
     char node_name[22];
+#  if defined (__QNXNTO__)
+    netmgr_ndtostr(ND2S_LOCAL_STR, ND_LOCAL_NODE, node_name, sizeof(node_name));
+#  else
     qnx_nidtostr (getnid (), node_name, sizeof (node_name));
-    temp_var = bind_variable ("NODE", node_name);
-    set_auto_export (temp_var);
+#  endif
+    temp_var = bind_variable ("NODE", node_name, 0);
+    if (temp_var)
+      set_auto_export (temp_var);
   }
 #endif
 
@@ -389,10 +513,14 @@ initialize_shell_variables (env, privmode)
 #endif
       set_if_not ("PS2", secondary_prompt);
     }
-  set_if_not ("PS4", "+ ");
+
+  if (current_user.euid == 0)
+    bind_variable ("PS4", "+ ", 0);
+  else
+    set_if_not ("PS4", "+ ");
 
   /* Don't allow IFS to be imported from the environment. */
-  temp_var = bind_variable ("IFS", " \t\n");
+  temp_var = bind_variable ("IFS", " \t\n", 0);
   setifs (temp_var);
 
   /* Magic machine types.  Pretty convenient. */
@@ -403,7 +531,10 @@ initialize_shell_variables (env, privmode)
      names a mail file if MAILPATH is not set, and we should provide a
      default only if neither is set. */
   if (interactive_shell)
-    set_if_not ("MAILCHECK", posixly_correct ? "600" : "60");
+    {
+      temp_var = set_if_not ("MAILCHECK", posixly_correct ? "600" : "60");
+      VSETATTR (temp_var, att_integer);
+    }
 
   /* Do some things with shell level. */
   initialize_shell_level ();
@@ -411,18 +542,19 @@ initialize_shell_variables (env, privmode)
   set_ppid ();
 
   /* Initialize the `getopts' stuff. */
-  bind_variable ("OPTIND", "1");
+  temp_var = bind_variable ("OPTIND", "1", 0);
+  VSETATTR (temp_var, att_integer);
   getopts_reset (0);
-  bind_variable ("OPTERR", "1");
+  bind_variable ("OPTERR", "1", 0);
   sh_opterr = 1;
 
-  if (login_shell == 1)
+  if (login_shell == 1 && posixly_correct == 0)
     set_home_var ();
 
   /* Get the full pathname to THIS shell, and set the BASH variable
      to it. */
   name = get_bash_name ();
-  temp_var = bind_variable ("BASH", name);
+  temp_var = bind_variable ("BASH", name, 0);
   free (name);
 
   /* Make the exported environment variable SHELL be the user's login
@@ -432,13 +564,13 @@ initialize_shell_variables (env, privmode)
   set_shell_var ();
 
   /* Make a variable called BASH_VERSION which contains the version info. */
-  bind_variable ("BASH_VERSION", shell_version_string ());
+  bind_variable ("BASH_VERSION", shell_version_string (), 0);
 #if defined (ARRAY_VARS)
   make_vers_array ();
 #endif
 
   if (command_execution_string)
-    bind_variable ("BASH_EXECUTION_STRING", command_execution_string);
+    bind_variable ("BASH_EXECUTION_STRING", command_execution_string, 0);
 
   /* Find out if we're supposed to be in Posix.2 mode via an
      environment variable. */
@@ -458,14 +590,11 @@ initialize_shell_variables (env, privmode)
 
       set_if_not ("HISTFILE", name);
       free (name);
-
-      set_if_not ("HISTSIZE", "500");
-      sv_histsize ("HISTSIZE");
     }
 #endif /* HISTORY */
 
   /* Seed the random number generator. */
-  sbrand (dollar_dollar_pid + shell_start_time);
+  seedrand ();
 
   /* Handle some "special" variables that we may have inherited from a
      parent shell. */
@@ -483,9 +612,18 @@ initialize_shell_variables (env, privmode)
     {
       sv_history_control ("HISTCONTROL");
       sv_histignore ("HISTIGNORE");
+      sv_histtimefmt ("HISTTIMEFORMAT");
     }
 #endif /* HISTORY */
 
+#if defined (READLINE) && defined (STRICT_POSIX)
+  /* POSIXLY_CORRECT will be 1 here if the shell was compiled
+     -DSTRICT_POSIX or if POSIXLY_CORRECT was supplied in the shell's
+     environment */
+  if (interactive_shell && posixly_correct && no_line_editing == 0)
+    rl_prefer_env_winsize = 1;
+#endif /* READLINE && STRICT_POSIX */
+
      /*
       * 24 October 2001
       *
@@ -514,6 +652,15 @@ initialize_shell_variables (env, privmode)
   /* Get the user's real and effective user ids. */
   uidset ();
 
+  temp_var = find_variable ("BASH_XTRACEFD");
+  if (temp_var && imported_p (temp_var))
+    sv_xtracefd (temp_var->name);
+
+  sv_shcompat ("BASH_COMPAT");
+
+  /* Allow FUNCNEST to be inherited from the environment. */
+  sv_funcnest ("FUNCNEST");
+
   /* Initialize the dynamic variables, and seed their values. */
   initialize_dynamic_variables ();
 }
@@ -556,7 +703,7 @@ set_home_var ()
 
   temp_var = find_variable ("HOME");
   if (temp_var == 0)
-    temp_var = bind_variable ("HOME", sh_get_home_dir ());
+    temp_var = bind_variable ("HOME", sh_get_home_dir (), 0);
 #if 0
   VSETATTR (temp_var, att_exported);
 #endif
@@ -574,7 +721,7 @@ set_shell_var ()
     {
       if (current_user.shell == 0)
        get_current_user_info ();
-      temp_var = bind_variable ("SHELL", current_user.shell);
+      temp_var = bind_variable ("SHELL", current_user.shell, 0);
     }
 #if 0
   VSETATTR (temp_var, att_exported);
@@ -669,7 +816,7 @@ adjust_shell_level (change)
   shell_level = old_level + change;
   if (shell_level < 0)
     shell_level = 0;
-  else if (shell_level > 1000)
+  else if (shell_level >= 1000)
     {
       internal_warning (_("shell level (%d) too high, resetting to 1"), shell_level);
       shell_level = 1;
@@ -696,7 +843,7 @@ adjust_shell_level (change)
       new_level[3] = '\0';
     }
 
-  temp_var = bind_variable ("SHLVL", new_level);
+  temp_var = bind_variable ("SHLVL", new_level, 0);
   set_auto_export (temp_var);
 }
 
@@ -717,21 +864,35 @@ void
 set_pwd ()
 {
   SHELL_VAR *temp_var, *home_var;
-  char *temp_string, *home_string;
+  char *temp_string, *home_string, *current_dir;
 
   home_var = find_variable ("HOME");
   home_string = home_var ? value_cell (home_var) : (char *)NULL;
 
   temp_var = find_variable ("PWD");
+  /* Follow posix rules for importing PWD */
   if (temp_var && imported_p (temp_var) &&
       (temp_string = value_cell (temp_var)) &&
+      temp_string[0] == '/' &&
       same_file (temp_string, ".", (struct stat *)NULL, (struct stat *)NULL))
-    set_working_directory (temp_string);
+    {
+      current_dir = sh_canonpath (temp_string, PATH_CHECKDOTDOT|PATH_CHECKEXISTS);
+      if (current_dir == 0)
+       current_dir = get_working_directory ("shell_init");
+      else
+       set_working_directory (current_dir);
+      if (posixly_correct && current_dir)
+       {
+         temp_var = bind_variable ("PWD", current_dir, 0);
+         set_auto_export (temp_var);
+       }  
+      free (current_dir);
+    }
   else if (home_string && interactive_shell && login_shell &&
           same_file (home_string, ".", (struct stat *)NULL, (struct stat *)NULL))
     {
       set_working_directory (home_string);
-      temp_var = bind_variable ("PWD", home_string);
+      temp_var = bind_variable ("PWD", home_string, 0);
       set_auto_export (temp_var);
     }
   else
@@ -739,17 +900,26 @@ set_pwd ()
       temp_string = get_working_directory ("shell-init");
       if (temp_string)
        {
-         temp_var = bind_variable ("PWD", temp_string);
+         temp_var = bind_variable ("PWD", temp_string, 0);
          set_auto_export (temp_var);
          free (temp_string);
        }
     }
 
   /* According to the Single Unix Specification, v2, $OLDPWD is an
-     `environment variable' and therefore should be auto-exported.
-     Make a dummy invisible variable for OLDPWD, and mark it as exported. */
-  temp_var = bind_variable ("OLDPWD", (char *)NULL);
-  VSETATTR (temp_var, (att_exported | att_invisible));
+     `environment variable' and therefore should be auto-exported.  If we
+     don't find OLDPWD in the environment, or it doesn't name a directory,
+     make a dummy invisible variable for OLDPWD, and mark it as exported. */
+  temp_var = find_variable ("OLDPWD");
+#if defined (OLDPWD_CHECK_DIRECTORY)
+  if (temp_var == 0 || value_cell (temp_var) == 0 || file_isdir (value_cell (temp_var)) == 0)
+#else
+  if (temp_var == 0 || value_cell (temp_var) == 0)
+#endif
+    {
+      temp_var = bind_variable ("OLDPWD", (char *)NULL, 0);
+      VSETATTR (temp_var, (att_exported | att_invisible));
+    }
 }
 
 /* Make a variable $PPID, which holds the pid of the shell's parent.  */
@@ -763,7 +933,7 @@ set_ppid ()
   temp_var = find_variable ("PPID");
   if (temp_var)
     VUNSETATTR (temp_var, (att_readonly | att_exported));
-  temp_var = bind_variable ("PPID", name);
+  temp_var = bind_variable ("PPID", name, 0);
   VSETATTR (temp_var, (att_readonly | att_integer));
 }
 
@@ -777,7 +947,7 @@ uidset ()
   v = find_variable ("UID");
   if (v == 0)
     {
-      v = bind_variable ("UID", b);
+      v = bind_variable ("UID", b, 0);
       VSETATTR (v, (att_readonly | att_integer));
     }
 
@@ -787,7 +957,7 @@ uidset ()
   v = find_variable ("EUID");
   if (v == 0)
     {
-      v = bind_variable ("EUID", b);
+      v = bind_variable ("EUID", b, 0);
       VSETATTR (v, (att_readonly | att_integer));
     }
 }
@@ -800,12 +970,12 @@ make_vers_array ()
   ARRAY *av;
   char *s, d[32], b[INT_STRLEN_BOUND(int) + 1];
 
-  unbind_variable ("BASH_VERSINFO");
+  unbind_variable_noref ("BASH_VERSINFO");
 
   vv = make_new_array_variable ("BASH_VERSINFO");
   av = array_cell (vv);
   strcpy (d, dist_version);
-  s = xstrchr (d, '.');
+  s = strchr (d, '.');
   if (s)
     *s++ = '\0';
   array_insert (av, 0, d);
@@ -829,11 +999,17 @@ sh_set_lines_and_columns (lines, cols)
 {
   char val[INT_STRLEN_BOUND(int) + 1], *v;
 
+#if defined (READLINE)
+  /* If we are currently assigning to LINES or COLUMNS, don't do anything. */
+  if (winsize_assignment)
+    return;
+#endif
+
   v = inttostr (lines, val, sizeof (val));
-  bind_variable ("LINES", v);
+  bind_variable ("LINES", v, 0);
 
   v = inttostr (cols, val, sizeof (val));
-  bind_variable ("COLUMNS", v);
+  bind_variable ("COLUMNS", v, 0);
 }
 
 /* **************************************************************** */
@@ -892,6 +1068,8 @@ print_assignment (var)
 #if defined (ARRAY_VARS)
   else if (array_p (var))
     print_array_assignment (var, 0);
+  else if (assoc_p (var))
+    print_assoc_assignment (var, 0);
 #endif /* ARRAY_VARS */
   else
     {
@@ -937,8 +1115,13 @@ void
 print_var_function (var)
      SHELL_VAR *var;
 {
+  char *x;
+
   if (function_p (var) && var_isset (var))
-    printf ("%s", named_function_string ((char *)NULL, function_cell(var), 1));
+    {
+      x = named_function_string ((char *)NULL, function_cell(var), FUNC_MULTILINE|FUNC_EXTERNAL);
+      printf ("%s", x);
+    }
 }
 
 /* **************************************************************** */
@@ -983,7 +1166,7 @@ print_var_function (var)
 #define INIT_DYNAMIC_VAR(var, val, gfunc, afunc) \
   do \
     { \
-      v = bind_variable (var, (val)); \
+      v = bind_variable (var, (val), 0); \
       v->dynamic_value = gfunc; \
       v->assign_func = afunc; \
     } \
@@ -998,21 +1181,32 @@ print_var_function (var)
     } \
   while (0)
 
+#define INIT_DYNAMIC_ASSOC_VAR(var, gfunc, afunc) \
+  do \
+    { \
+      v = make_new_assoc_variable (var); \
+      v->dynamic_value = gfunc; \
+      v->assign_func = afunc; \
+    } \
+  while (0)
+
 static SHELL_VAR *
-null_assign (self, value, unused)
+null_assign (self, value, unused, key)
      SHELL_VAR *self;
      char *value;
      arrayind_t unused;
+     char *key;
 {
   return (self);
 }
 
 #if defined (ARRAY_VARS)
 static SHELL_VAR *
-null_array_assign (self, value, ind)
+null_array_assign (self, value, ind, key)
      SHELL_VAR *self;
      char *value;
      arrayind_t ind;
+     char *key;
 {
   return (self);
 }
@@ -1028,7 +1222,7 @@ get_self (self)
 }
 
 #if defined (ARRAY_VARS)
-/* A generic dynamic array variable initializer.  Intialize array variable
+/* A generic dynamic array variable initializer.  Initialize array variable
    NAME with dynamic value function GETFUNC and assignment function SETFUNC. */
 static SHELL_VAR *
 init_dynamic_array_var (name, getfunc, setfunc, attrs)
@@ -1047,8 +1241,25 @@ init_dynamic_array_var (name, getfunc, setfunc, attrs)
     VSETATTR (v, attrs);
   return v;
 }
-#endif
 
+static SHELL_VAR *
+init_dynamic_assoc_var (name, getfunc, setfunc, attrs)
+     char *name;
+     sh_var_value_func_t *getfunc;
+     sh_var_assign_func_t *setfunc;
+     int attrs;
+{
+  SHELL_VAR *v;
+
+  v = find_variable (name);
+  if (v)
+    return (v);
+  INIT_DYNAMIC_ASSOC_VAR (name, getfunc, setfunc);
+  if (attrs)
+    VSETATTR (v, attrs);
+  return v;
+}
+#endif
 
 /* The value of $SECONDS.  This is the number of seconds since shell
    invocation, or, the number of seconds since the last assignment + the
@@ -1056,10 +1267,11 @@ init_dynamic_array_var (name, getfunc, setfunc, attrs)
 static intmax_t seconds_value_assigned;
 
 static SHELL_VAR *
-assign_seconds (self, value, unused)
+assign_seconds (self, value, unused, key)
      SHELL_VAR *self;
      char *value;
      arrayind_t unused;
+     char *key;
 {
   if (legal_number (value, &seconds_value_assigned) == 0)
     seconds_value_assigned = 0;
@@ -1100,19 +1312,48 @@ init_seconds_var ()
 }
      
 /* The random number seed.  You can change this by setting RANDOM. */
-static unsigned long rseed = 1;
+static u_bits32_t rseed = 1;
 static int last_random_value;
+static int seeded_subshell = 0;
+
+#define BASH_RANDOM_16 1
 
-/* A linear congruential random number generator based on the example
-   one in the ANSI C standard.  This one isn't very good, but a more
-   complicated one is overkill. */
+#if BASH_RANDOM_16
+#  define BASH_RAND_MAX        32767           /* 0x7fff - 16 bits */
+#else
+#  define BASH_RAND_MAX        0x7fffffff      /* 32 bits */
+#endif
 
 /* Returns a pseudo-random number between 0 and 32767. */
 static int
 brand ()
 {
-  rseed = rseed * 1103515245 + 12345;
-  return ((unsigned int)((rseed >> 16) & 32767));      /* was % 32768 */
+  /* Minimal Standard generator from
+     "Random number generators: good ones are hard to find",
+     Park and Miller, Communications of the ACM, vol. 31, no. 10,
+     October 1988, p. 1195. filtered through FreeBSD.
+
+     x(n+1) = 16807 * x(n) mod (2**31 - 1).
+
+     We split up the calculations to avoid overflow.
+
+     h = rseed / q; l = x - h * q; t = a * l - h * r
+     m = 2147483647, a = 16807, q = 127773, r = 2836
+
+     There are lots of other combinations of constants to use; look at
+     https://www.gnu.org/software/gsl/manual/html_node/Other-random-number-generators.html#Other-random-number-generators */
+
+  bits32_t h, l, t;
+
+  /* Can't seed with 0. */
+  if (rseed == 0)
+    rseed = 123459876;
+  h = rseed / 127773;
+  l = rseed - (127773 * h);
+  t = 16807 * l - 2836 * h;
+  rseed = (t < 0) ? t + 0x7fffffff : t;
+
+  return ((unsigned int)(rseed & BASH_RAND_MAX));      /* was % BASH_RAND_MAX+1 */
 }
 
 /* Set the random number generator seed to SEED. */
@@ -1124,24 +1365,46 @@ sbrand (seed)
   last_random_value = 0;
 }
 
+static void
+seedrand ()
+{
+  struct timeval tv;
+  SHELL_VAR *v;
+
+  gettimeofday (&tv, NULL);
+#if 0
+  v = find_variable ("BASH_VERSION");
+  sbrand (tv.tv_sec ^ tv.tv_usec ^ getpid () ^ ((u_bits32_t)&v & 0x7fffffff));
+#else
+  sbrand (tv.tv_sec ^ tv.tv_usec ^ getpid ());
+#endif
+}
+
 static SHELL_VAR *
-assign_random (self, value, unused)
+assign_random (self, value, unused, key)
      SHELL_VAR *self;
      char *value;
      arrayind_t unused;
+     char *key;
 {
   sbrand (strtoul (value, (char **)NULL, 10));
+  if (subshell_environment)
+    seeded_subshell = getpid ();
   return (self);
 }
 
 int
 get_random_number ()
 {
-  int rv;
+  int rv, pid;
 
   /* Reset for command and process substitution. */
-  if (subshell_environment)
-    sbrand (rseed + getpid() + NOW);
+  pid = getpid ();
+  if (subshell_environment && seeded_subshell != pid)
+    {
+      seedrand ();
+      seeded_subshell = pid;
+    }
 
   do
     rv = brand ();
@@ -1168,16 +1431,17 @@ get_random (var)
 }
 
 static SHELL_VAR *
-assign_lineno (var, value, unused)
+assign_lineno (var, value, unused, key)
      SHELL_VAR *var;
      char *value;
      arrayind_t unused;
+     char *key;
 {
   intmax_t new_value;
 
   if (value == 0 || *value == '\0' || legal_number (value, &new_value) == 0)
     new_value = 0;
-  line_number = new_value;
+  line_number = line_number_base = new_value;
   return var;
 }
 
@@ -1197,10 +1461,11 @@ get_lineno (var)
 }
 
 static SHELL_VAR *
-assign_subshell (var, value, unused)
+assign_subshell (var, value, unused, key)
      SHELL_VAR *var;
      char *value;
      arrayind_t unused;
+     char *key;
 {
   intmax_t new_value;
 
@@ -1222,13 +1487,107 @@ get_subshell (var)
   return (var);
 }
 
+static SHELL_VAR *
+get_epochseconds (var)
+     SHELL_VAR *var;
+{
+  intmax_t now;
+  char *p;
+
+  now = NOW;
+  p = itos (now);
+
+  FREE (value_cell (var));
+  var_setvalue (var, p);
+  return (var);
+}
+
+static SHELL_VAR *
+get_epochrealtime (var)
+     SHELL_VAR *var;
+{
+  char buf[32];
+  char *p;
+  struct timeval tv;
+
+  gettimeofday (&tv, NULL);
+  snprintf (buf, sizeof (buf), "%u%c%06u", (unsigned)tv.tv_sec,
+                                          locale_decpoint (),
+                                          (unsigned)tv.tv_usec);
+
+  p = savestring (buf);
+  FREE (value_cell (var));
+  var_setvalue (var, p);
+  return (var);
+}
+
+static SHELL_VAR *
+get_bashpid (var)
+     SHELL_VAR *var;
+{
+  int pid;
+  char *p;
+
+  pid = getpid ();
+  p = itos (pid);
+
+  FREE (value_cell (var));
+  VSETATTR (var, att_integer); /* XXX - was also att_readonly */
+  var_setvalue (var, p);
+  return (var);
+}
+
+static SHELL_VAR *
+get_bash_argv0 (var)
+     SHELL_VAR *var;
+{
+  char *p;
+
+  p = savestring (dollar_vars[0]);
+  FREE (value_cell (var));
+  var_setvalue (var, p);
+  return var;
+}
+
+static char *static_shell_name = 0;
+
+static SHELL_VAR *
+assign_bash_argv0 (var, value, unused, key)
+     SHELL_VAR *var;
+     char *value;
+     arrayind_t unused;
+     char *key;
+{
+  size_t vlen;
+
+  if (value == 0)
+    return var;
+
+  FREE (dollar_vars[0]);
+  dollar_vars[0] = savestring (value);
+
+  /* Need these gyrations because shell_name isn't dynamically allocated */
+  vlen = STRLEN (value);
+  static_shell_name = xrealloc (static_shell_name, vlen + 1);
+  strcpy (static_shell_name, value);
+  
+  shell_name = static_shell_name;
+  return var;
+}
+  
 static SHELL_VAR *
 get_bash_command (var)
      SHELL_VAR *var;
 {
   char *p;
 
-  p = savestring (the_printed_command_except_trap);
+  if (the_printed_command_except_trap)
+    p = savestring (the_printed_command_except_trap);
+  else
+    {
+      p = (char *)xmalloc (1);
+      p[0] = '\0';
+    }
   FREE (value_cell (var));
   var_setvalue (var, p);
   return (var);
@@ -1254,20 +1613,12 @@ static SHELL_VAR *
 get_comp_wordbreaks (var)
      SHELL_VAR *var;
 {
-  char *p;
-
   /* If we don't have anything yet, assign a default value. */
   if (rl_completer_word_break_characters == 0 && bash_readline_initialized == 0)
     enable_hostname_completion (perform_hostname_completion);
 
-#if 0
   FREE (value_cell (var));
-  p = savestring (rl_completer_word_break_characters);
-  
-  var_setvalue (var, p);
-#else
-  var_setvalue (var, rl_completer_word_break_characters);
-#endif
+  var_setvalue (var, savestring (rl_completer_word_break_characters));
 
   return (var);
 }
@@ -1275,10 +1626,11 @@ get_comp_wordbreaks (var)
 /* When this function returns, rl_completer_word_break_characters points to
    malloced memory. */
 static SHELL_VAR *
-assign_comp_wordbreaks (self, value, unused)
+assign_comp_wordbreaks (self, value, unused, key)
      SHELL_VAR *self;
      char *value;
      arrayind_t unused;
+     char *key;
 {
   if (rl_completer_word_break_characters &&
       rl_completer_word_break_characters != rl_basic_word_break_characters)
@@ -1291,10 +1643,11 @@ assign_comp_wordbreaks (self, value, unused)
 
 #if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS)
 static SHELL_VAR *
-assign_dirstack (self, value, ind)
+assign_dirstack (self, value, ind, key)
      SHELL_VAR *self;
      char *value;
      arrayind_t ind;
+     char *key;
 {
   set_dirstack_element (ind, 1, value);
   return self;
@@ -1307,7 +1660,7 @@ get_dirstack (self)
   ARRAY *a;
   WORD_LIST *l;
 
-  l = get_directory_stack ();
+  l = get_directory_stack (0);
   a = array_from_word_list (l);
   array_dispose (array_cell (self));
   dispose_words (l);
@@ -1337,20 +1690,177 @@ get_groupset (self)
     }
   return (self);
 }
-#endif /* ARRAY_VARS */
 
-/* If ARRAY_VARS is not defined, this just returns the name of any
-   currently-executing function.  If we have arrays, it's a call stack. */
+#  if defined (DEBUGGER)
 static SHELL_VAR *
-get_funcname (self)
+get_bashargcv (self)
      SHELL_VAR *self;
 {
-#if ! defined (ARRAY_VARS)
-  char *t;
-  if (variable_context && this_shell_function)
+  static int self_semaphore = 0;
+
+  /* Backwards compatibility: if we refer to BASH_ARGV or BASH_ARGC at the
+     top level without enabling debug mode, and we don't have an instance
+     of the variable set, initialize the arg arrays.
+     This will already have been done if debugging_mode != 0. */
+  if (self_semaphore == 0 && variable_context == 0 && debugging_mode == 0)     /* don't do it for shell functions */
     {
-      FREE (value_cell (self));
-      t = savestring (this_shell_function->name);
+      self_semaphore = 1;
+      init_bash_argv ();
+      self_semaphore = 0;
+    }
+  return self;
+}
+#  endif
+
+static SHELL_VAR *
+build_hashcmd (self)
+     SHELL_VAR *self;
+{
+  HASH_TABLE *h;
+  int i;
+  char *k, *v;
+  BUCKET_CONTENTS *item;
+
+  h = assoc_cell (self);
+  if (h)
+    assoc_dispose (h);
+
+  if (hashed_filenames == 0 || HASH_ENTRIES (hashed_filenames) == 0)
+    {
+      var_setvalue (self, (char *)NULL);
+      return self;
+    }
+
+  h = assoc_create (hashed_filenames->nbuckets);
+  for (i = 0; i < hashed_filenames->nbuckets; i++)
+    {
+      for (item = hash_items (i, hashed_filenames); item; item = item->next)
+       {
+         k = savestring (item->key);
+         v = pathdata(item)->path;
+         assoc_insert (h, k, v);
+       }
+    }
+
+  var_setvalue (self, (char *)h);
+  return self;
+}
+
+static SHELL_VAR *
+get_hashcmd (self)
+     SHELL_VAR *self;
+{
+  build_hashcmd (self);
+  return (self);
+}
+
+static SHELL_VAR *
+assign_hashcmd (self, value, ind, key)
+     SHELL_VAR *self;
+     char *value;
+     arrayind_t ind;
+     char *key;
+{
+#if defined (RESTRICTED_SHELL)
+  char *full_path;
+
+  if (restricted)
+    {
+      if (strchr (value, '/'))
+       {
+         sh_restricted (value);
+         return (SHELL_VAR *)NULL;
+       }
+      /* If we are changing the hash table in a restricted shell, make sure the
+        target pathname can be found using a $PATH search. */
+      full_path = find_user_command (value);
+      if (full_path == 0 || *full_path == 0 || executable_file (full_path) == 0)
+       {
+         sh_notfound (value);
+         free (full_path);
+         return ((SHELL_VAR *)NULL);
+       }
+      free (full_path);
+    }
+#endif
+  phash_insert (key, value, 0, 0);
+  return (build_hashcmd (self));
+}
+
+#if defined (ALIAS)
+static SHELL_VAR *
+build_aliasvar (self)
+     SHELL_VAR *self;
+{
+  HASH_TABLE *h;
+  int i;
+  char *k, *v;
+  BUCKET_CONTENTS *item;
+
+  h = assoc_cell (self);
+  if (h)
+    assoc_dispose (h);
+
+  if (aliases == 0 || HASH_ENTRIES (aliases) == 0)
+    {
+      var_setvalue (self, (char *)NULL);
+      return self;
+    }
+
+  h = assoc_create (aliases->nbuckets);
+  for (i = 0; i < aliases->nbuckets; i++)
+    {
+      for (item = hash_items (i, aliases); item; item = item->next)
+       {
+         k = savestring (item->key);
+         v = ((alias_t *)(item->data))->value;
+         assoc_insert (h, k, v);
+       }
+    }
+
+  var_setvalue (self, (char *)h);
+  return self;
+}
+
+static SHELL_VAR *
+get_aliasvar (self)
+     SHELL_VAR *self;
+{
+  build_aliasvar (self);
+  return (self);
+}
+
+static SHELL_VAR *
+assign_aliasvar (self, value, ind, key)
+     SHELL_VAR *self;
+     char *value;
+     arrayind_t ind;
+     char *key;
+{
+  if (legal_alias_name (key, 0) == 0)
+    {
+       report_error (_("`%s': invalid alias name"), key);
+       return (self);
+    }
+  add_alias (key, value);
+  return (build_aliasvar (self));
+}
+#endif /* ALIAS */
+
+#endif /* ARRAY_VARS */
+
+/* If ARRAY_VARS is not defined, this just returns the name of any
+   currently-executing function.  If we have arrays, it's a call stack. */
+static SHELL_VAR *
+get_funcname (self)
+     SHELL_VAR *self;
+{
+#if ! defined (ARRAY_VARS)
+  char *t;
+  if (variable_context && this_shell_function)
+    {
+      FREE (value_cell (self));
+      t = savestring (this_shell_function->name);
       var_setvalue (self, t);
     }
 #endif
@@ -1397,14 +1907,27 @@ initialize_dynamic_variables ()
 
   v = init_seconds_var ();
 
+  INIT_DYNAMIC_VAR ("BASH_ARGV0", (char *)NULL, get_bash_argv0, assign_bash_argv0);
+
   INIT_DYNAMIC_VAR ("BASH_COMMAND", (char *)NULL, get_bash_command, (sh_var_assign_func_t *)NULL);
   INIT_DYNAMIC_VAR ("BASH_SUBSHELL", (char *)NULL, get_subshell, assign_subshell);
 
   INIT_DYNAMIC_VAR ("RANDOM", (char *)NULL, get_random, assign_random);
+  VSETATTR (v, att_integer);
   INIT_DYNAMIC_VAR ("LINENO", (char *)NULL, get_lineno, assign_lineno);
+  VSETATTR (v, att_integer|att_regenerate);
+
+  INIT_DYNAMIC_VAR ("BASHPID", (char *)NULL, get_bashpid, null_assign);
+  VSETATTR (v, att_integer);
+
+  INIT_DYNAMIC_VAR ("EPOCHSECONDS", (char *)NULL, get_epochseconds, null_assign);
+  VSETATTR (v, att_regenerate);
+  INIT_DYNAMIC_VAR ("EPOCHREALTIME", (char *)NULL, get_epochrealtime, null_assign);
+  VSETATTR (v, att_regenerate);
 
 #if defined (HISTORY)
   INIT_DYNAMIC_VAR ("HISTCMD", (char *)NULL, get_histcmd, (sh_var_assign_func_t *)NULL);
+  VSETATTR (v, att_integer);
 #endif
 
 #if defined (READLINE)
@@ -1419,11 +1942,16 @@ initialize_dynamic_variables ()
   v = init_dynamic_array_var ("GROUPS", get_groupset, null_array_assign, att_noassign);
 
 #  if defined (DEBUGGER)
-  v = init_dynamic_array_var ("BASH_ARGC", get_self, null_array_assign, att_noassign);
-  v = init_dynamic_array_var ("BASH_ARGV", get_self, null_array_assign, att_noassign);
+  v = init_dynamic_array_var ("BASH_ARGC", get_bashargcv, null_array_assign, att_noassign|att_nounset);
+  v = init_dynamic_array_var ("BASH_ARGV", get_bashargcv, null_array_assign, att_noassign|att_nounset);
 #  endif /* DEBUGGER */
-  v = init_dynamic_array_var ("BASH_SOURCE", get_self, null_array_assign, att_noassign);
-  v = init_dynamic_array_var ("BASH_LINENO", get_self, null_array_assign, att_noassign);
+  v = init_dynamic_array_var ("BASH_SOURCE", get_self, null_array_assign, att_noassign|att_nounset);
+  v = init_dynamic_array_var ("BASH_LINENO", get_self, null_array_assign, att_noassign|att_nounset);
+
+  v = init_dynamic_assoc_var ("BASH_CMDS", get_hashcmd, assign_hashcmd, att_nofree);
+#  if defined (ALIAS)
+  v = init_dynamic_assoc_var ("BASH_ALIASES", get_aliasvar, assign_aliasvar, att_nofree);
+#  endif
 #endif
 
   v = init_funcname_var ();
@@ -1447,6 +1975,10 @@ hash_lookup (name, hashed_vars)
   BUCKET_CONTENTS *bucket;
 
   bucket = hash_search (name, hashed_vars, 0);
+  /* If we find the name in HASHED_VARS, set LAST_TABLE_SEARCHED to that
+     table. */
+  if (bucket)
+    last_table_searched = hashed_vars;
   return (bucket ? (SHELL_VAR *)bucket->data : (SHELL_VAR *)NULL);
 }
 
@@ -1470,19 +2002,22 @@ var_lookup (name, vcontext)
    then also search the temporarily built list of exported variables.
    The lookup order is:
        temporary_env
-        shell_variables list
+       shell_variables list
 */
 
 SHELL_VAR *
-find_variable_internal (name, force_tempenv)
+find_variable_internal (name, flags)
      const char *name;
-     int force_tempenv;
+     int flags;
 {
   SHELL_VAR *var;
-  int search_tempenv;
+  int search_tempenv, force_tempenv;
+  VAR_CONTEXT *vc;
 
   var = (SHELL_VAR *)NULL;
 
+  force_tempenv = (flags & FV_FORCETEMPENV);
+
   /* If explicitly requested, first look in the temporary environment for
      the variable.  This allows constructs such as "foo=x eval 'echo $foo'"
      to get the `exported' value of $foo.  This happens if we are executing
@@ -1494,7 +2029,363 @@ find_variable_internal (name, force_tempenv)
     var = hash_lookup (name, temporary_env);
 
   if (var == 0)
-    var = var_lookup (name, shell_variables);
+    {
+      if ((flags & FV_SKIPINVISIBLE) == 0)
+       var = var_lookup (name, shell_variables);
+      else
+       {
+         /* essentially var_lookup expanded inline so we can check for
+            att_invisible */
+         for (vc = shell_variables; vc; vc = vc->down)
+           {
+             var = hash_lookup (name, vc->table);
+             if (var && invisible_p (var))
+               var = 0;
+             if (var)
+               break;
+           }
+       }
+    }
+
+  if (var == 0)
+    return ((SHELL_VAR *)NULL);
+
+  return (var->dynamic_value ? (*(var->dynamic_value)) (var) : var);
+}
+
+/* Look up and resolve the chain of nameref variables starting at V all the
+   way to NULL or non-nameref. */
+SHELL_VAR *
+find_variable_nameref (v)
+     SHELL_VAR *v;
+{
+  int level, flags;
+  char *newname;
+  SHELL_VAR *orig, *oldv;
+
+  level = 0;
+  orig = v;
+  while (v && nameref_p (v))
+    {
+      level++;
+      if (level > NAMEREF_MAX)
+       return ((SHELL_VAR *)0);        /* error message here? */
+      newname = nameref_cell (v);
+      if (newname == 0 || *newname == '\0')
+       return ((SHELL_VAR *)0);
+      oldv = v;
+      flags = 0;
+      if (expanding_redir == 0 && (assigning_in_environment || executing_builtin))
+       flags |= FV_FORCETEMPENV;
+      /* We don't handle array subscripts here. */
+      v = find_variable_internal (newname, flags);
+      if (v == orig || v == oldv)
+       {
+         internal_warning (_("%s: circular name reference"), orig->name);
+#if 1
+         /* XXX - provisional change - circular refs go to
+            global scope for resolution, without namerefs. */
+         if (variable_context && v->context)
+           return (find_global_variable_noref (v->name));
+         else
+#endif
+         return ((SHELL_VAR *)0);
+       }
+    }
+  return v;
+}
+
+/* Resolve the chain of nameref variables for NAME.  XXX - could change later */
+SHELL_VAR *
+find_variable_last_nameref (name, vflags)
+     const char *name;
+     int vflags;
+{
+  SHELL_VAR *v, *nv;
+  char *newname;
+  int level, flags;
+
+  nv = v = find_variable_noref (name);
+  level = 0;
+  while (v && nameref_p (v))
+    {
+      level++;
+      if (level > NAMEREF_MAX)
+        return ((SHELL_VAR *)0);       /* error message here? */
+      newname = nameref_cell (v);
+      if (newname == 0 || *newname == '\0')
+       return ((vflags && invisible_p (v)) ? v : (SHELL_VAR *)0);
+      nv = v;
+      flags = 0;
+      if (expanding_redir == 0 && (assigning_in_environment || executing_builtin))
+       flags |= FV_FORCETEMPENV;
+      /* We don't accommodate array subscripts here. */
+      v = find_variable_internal (newname, flags);
+    }
+  return nv;
+}
+
+/* Resolve the chain of nameref variables for NAME.  XXX - could change later */
+SHELL_VAR *
+find_global_variable_last_nameref (name, vflags)
+     const char *name;
+     int vflags;
+{
+  SHELL_VAR *v, *nv;
+  char *newname;
+  int level;
+
+  nv = v = find_global_variable_noref (name);
+  level = 0;
+  while (v && nameref_p (v))
+    {
+      level++;
+      if (level > NAMEREF_MAX)
+        return ((SHELL_VAR *)0);       /* error message here? */
+      newname = nameref_cell (v);
+      if (newname == 0 || *newname == '\0')
+       return ((vflags && invisible_p (v)) ? v : (SHELL_VAR *)0);
+      nv = v;
+      /* We don't accommodate array subscripts here. */
+      v = find_global_variable_noref (newname);
+    }
+  return nv;
+}
+
+static SHELL_VAR *
+find_nameref_at_context (v, vc)
+     SHELL_VAR *v;
+     VAR_CONTEXT *vc;
+{
+  SHELL_VAR *nv, *nv2;
+  char *newname;
+  int level;
+
+  nv = v;
+  level = 1;
+  while (nv && nameref_p (nv))
+    {
+      level++;
+      if (level > NAMEREF_MAX)
+        return (&nameref_maxloop_value);
+      newname = nameref_cell (nv);
+      if (newname == 0 || *newname == '\0')
+        return ((SHELL_VAR *)NULL);      
+      nv2 = hash_lookup (newname, vc->table);
+      if (nv2 == 0)
+        break;
+      nv = nv2;
+    }
+  return nv;
+}
+
+/* Do nameref resolution from the VC, which is the local context for some
+   function or builtin, `up' the chain to the global variables context.  If
+   NVCP is not NULL, return the variable context where we finally ended the
+   nameref resolution (so the bind_variable_internal can use the correct
+   variable context and hash table). */
+static SHELL_VAR *
+find_variable_nameref_context (v, vc, nvcp)
+     SHELL_VAR *v;
+     VAR_CONTEXT *vc;
+     VAR_CONTEXT **nvcp;
+{
+  SHELL_VAR *nv, *nv2;
+  VAR_CONTEXT *nvc;
+
+  /* Look starting at the current context all the way `up' */
+  for (nv = v, nvc = vc; nvc; nvc = nvc->down)
+    {
+      nv2 = find_nameref_at_context (nv, nvc);
+      if (nv2 == &nameref_maxloop_value)
+       return (nv2);                   /* XXX */
+      if (nv2 == 0)
+        continue;
+      nv = nv2;
+      if (*nvcp)
+        *nvcp = nvc;
+      if (nameref_p (nv) == 0)
+        break;
+    }
+  return (nameref_p (nv) ? (SHELL_VAR *)NULL : nv);
+}
+
+/* Do nameref resolution from the VC, which is the local context for some
+   function or builtin, `up' the chain to the global variables context.  If
+   NVCP is not NULL, return the variable context where we finally ended the
+   nameref resolution (so the bind_variable_internal can use the correct
+   variable context and hash table). */
+static SHELL_VAR *
+find_variable_last_nameref_context (v, vc, nvcp)
+     SHELL_VAR *v;
+     VAR_CONTEXT *vc;
+     VAR_CONTEXT **nvcp;
+{
+  SHELL_VAR *nv, *nv2;
+  VAR_CONTEXT *nvc;
+
+  /* Look starting at the current context all the way `up' */
+  for (nv = v, nvc = vc; nvc; nvc = nvc->down)
+    {
+      nv2 = find_nameref_at_context (nv, nvc);
+      if (nv2 == &nameref_maxloop_value)
+       return (nv2);                   /* XXX */
+      if (nv2 == 0)
+       continue;
+      nv = nv2;
+      if (*nvcp)
+        *nvcp = nvc;
+    }
+  return (nameref_p (nv) ? nv : (SHELL_VAR *)NULL);
+}
+
+SHELL_VAR *
+find_variable_nameref_for_create (name, flags)
+     const char *name;
+     int flags;
+{
+  SHELL_VAR *var;
+
+  /* See if we have a nameref pointing to a variable that hasn't been
+     created yet. */
+  var = find_variable_last_nameref (name, 1);
+  if ((flags&1) && var && nameref_p (var) && invisible_p (var))
+    {
+      internal_warning (_("%s: removing nameref attribute"), name);
+      VUNSETATTR (var, att_nameref);
+    }
+  if (var && nameref_p (var))
+    {
+      if (legal_identifier (nameref_cell (var)) == 0)
+       {
+         sh_invalidid (nameref_cell (var) ? nameref_cell (var) : "");
+         return ((SHELL_VAR *)INVALID_NAMEREF_VALUE);
+       }
+    }
+  return (var);
+}
+
+SHELL_VAR *
+find_variable_nameref_for_assignment (name, flags)
+     const char *name;
+     int flags;
+{
+  SHELL_VAR *var;
+
+  /* See if we have a nameref pointing to a variable that hasn't been
+     created yet. */
+  var = find_variable_last_nameref (name, 1);
+  if (var && nameref_p (var) && invisible_p (var))     /* XXX - flags */
+    {
+      internal_warning (_("%s: removing nameref attribute"), name);
+      VUNSETATTR (var, att_nameref);
+    }
+  if (var && nameref_p (var))
+    {
+      if (valid_nameref_value (nameref_cell (var), 1) == 0)
+       {
+         sh_invalidid (nameref_cell (var) ? nameref_cell (var) : "");
+         return ((SHELL_VAR *)INVALID_NAMEREF_VALUE);
+       }
+    }
+  return (var);
+}
+
+/* If find_variable (name) returns NULL, check that it's not a nameref
+   referencing a variable that doesn't exist. If it is, return the new
+   name. If not, return the original name. Kind of like the previous
+   function, but dealing strictly with names. This takes assignment flags
+   so it can deal with the various assignment modes used by `declare'. */
+char *
+nameref_transform_name (name, flags)
+     char *name;
+     int flags;
+{
+  SHELL_VAR *v;
+  char *newname;
+
+  v = 0;
+  if (flags & ASS_MKLOCAL)
+    {
+      v = find_variable_last_nameref (name, 1);
+      /* If we're making local variables, only follow namerefs that point to
+        non-existant variables at the same variable context. */
+      if (v && v->context != variable_context)
+       v = 0;
+    }
+  else if (flags & ASS_MKGLOBAL)
+    v = (flags & ASS_CHKLOCAL) ? find_variable_last_nameref (name, 1)
+                              : find_global_variable_last_nameref (name, 1);
+  if (v && nameref_p (v) && valid_nameref_value (nameref_cell (v), 1))
+    return nameref_cell (v);
+  return name;
+}
+
+/* Find a variable, forcing a search of the temporary environment first */
+SHELL_VAR *
+find_variable_tempenv (name)
+     const char *name;
+{
+  SHELL_VAR *var;
+
+  var = find_variable_internal (name, FV_FORCETEMPENV);
+  if (var && nameref_p (var))
+    var = find_variable_nameref (var);
+  return (var);
+}
+
+/* Find a variable, not forcing a search of the temporary environment first */
+SHELL_VAR *
+find_variable_notempenv (name)
+     const char *name;
+{
+  SHELL_VAR *var;
+
+  var = find_variable_internal (name, 0);
+  if (var && nameref_p (var))
+    var = find_variable_nameref (var);
+  return (var);
+}
+
+SHELL_VAR *
+find_global_variable (name)
+     const char *name;
+{
+  SHELL_VAR *var;
+
+  var = var_lookup (name, global_variables);
+  if (var && nameref_p (var))
+    var = find_variable_nameref (var);
+
+  if (var == 0)
+    return ((SHELL_VAR *)NULL);
+
+  return (var->dynamic_value ? (*(var->dynamic_value)) (var) : var);
+}
+
+SHELL_VAR *
+find_global_variable_noref (name)
+     const char *name;
+{
+  SHELL_VAR *var;
+
+  var = var_lookup (name, global_variables);
+
+  if (var == 0)
+    return ((SHELL_VAR *)NULL);
+
+  return (var->dynamic_value ? (*(var->dynamic_value)) (var) : var);
+}
+
+SHELL_VAR *
+find_shell_variable (name)
+     const char *name;
+{
+  SHELL_VAR *var;
+
+  var = var_lookup (name, shell_variables);
+  if (var && nameref_p (var))
+    var = find_variable_nameref (var);
 
   if (var == 0)
     return ((SHELL_VAR *)NULL);
@@ -1507,7 +2398,70 @@ SHELL_VAR *
 find_variable (name)
      const char *name;
 {
-  return (find_variable_internal (name, (expanding_redir == 0 && this_shell_builtin != 0)));
+  SHELL_VAR *v;
+  int flags;
+
+  last_table_searched = 0;
+  flags = 0;
+  if (expanding_redir == 0 && (assigning_in_environment || executing_builtin))
+    flags |= FV_FORCETEMPENV;
+  v = find_variable_internal (name, flags);
+  if (v && nameref_p (v))
+    v = find_variable_nameref (v);
+  return v;
+}
+
+/* Find the first instance of NAME in the variable context chain; return first
+   one found without att_invisible set; return 0 if no non-invisible instances
+   found. */
+SHELL_VAR *
+find_variable_no_invisible (name)
+     const char *name;
+{
+  SHELL_VAR *v;
+  int flags;
+
+  last_table_searched = 0;
+  flags = FV_SKIPINVISIBLE;
+  if (expanding_redir == 0 && (assigning_in_environment || executing_builtin))
+    flags |= FV_FORCETEMPENV;
+  v = find_variable_internal (name, flags);
+  if (v && nameref_p (v))
+    v = find_variable_nameref (v);
+  return v;
+}
+
+/* Find the first instance of NAME in the variable context chain; return first
+   one found even if att_invisible set. */
+SHELL_VAR *
+find_variable_for_assignment (name)
+     const char *name;
+{
+  SHELL_VAR *v;
+  int flags;
+
+  last_table_searched = 0;
+  flags = 0;
+  if (expanding_redir == 0 && (assigning_in_environment || executing_builtin))
+    flags |= FV_FORCETEMPENV;
+  v = find_variable_internal (name, flags);
+  if (v && nameref_p (v))
+    v = find_variable_nameref (v);
+  return v;
+}
+
+SHELL_VAR *
+find_variable_noref (name)
+     const char *name;
+{
+  SHELL_VAR *v;
+  int flags;
+
+  flags = 0;
+  if (expanding_redir == 0 && (assigning_in_environment || executing_builtin))
+    flags |= FV_FORCETEMPENV;
+  v = find_variable_internal (name, flags);
+  return v;
 }
 
 /* Look up the function entry whose name matches STRING.
@@ -1525,7 +2479,11 @@ FUNCTION_DEF *
 find_function_def (name)
      const char *name;
 {
+#if defined (DEBUGGER)
   return ((FUNCTION_DEF *)hash_lookup (name, shell_function_defs));
+#else
+  return ((FUNCTION_DEF *)0);
+#endif
 }
 
 /* Return the value of VAR.  VAR is assumed to have been the result of a
@@ -1539,6 +2497,8 @@ get_variable_value (var)
 #if defined (ARRAY_VARS)
   else if (array_p (var))
     return (array_reference (array_cell (var), 0));
+  else if (assoc_p (var))
+    return (assoc_reference (assoc_cell (var), "0"));
 #endif
   else
     return (value_cell (var));
@@ -1546,9 +2506,9 @@ get_variable_value (var)
 
 /* Return the string value of a variable.  Return NULL if the variable
    doesn't exist.  Don't cons a new string.  This is a potential memory
-   leak if the variable is found in the temporary environment.  Since
-   functions and variables have separate name spaces, returns NULL if
-   var_name is a shell function only. */
+   leak if the variable is found in the temporary environment, but doesn't
+   leak in practice.  Since    functions and variables have separate name
+   spaces, returns NULL if var_name is a shell function only. */
 char *
 get_string_value (var_name)
      const char *var_name;
@@ -1573,6 +2533,42 @@ sh_get_env_value (v)
 /*                                                                 */
 /* **************************************************************** */
 
+static int
+var_sametype (v1, v2)
+     SHELL_VAR *v1;
+     SHELL_VAR *v2;
+{
+  if (v1 == 0 || v2 == 0)
+    return 0;
+#if defined (ARRAY_VARS)
+  else if (assoc_p (v1) && assoc_p (v2))
+    return 1;
+  else if (array_p (v1) && array_p (v2))
+    return 1;
+  else if (array_p (v1) || array_p (v2))
+    return 0;
+  else if (assoc_p (v1) || assoc_p (v2))
+    return 0;
+#endif
+  else
+    return 1;
+}
+
+int
+validate_inherited_value (var, type)
+     SHELL_VAR *var;
+     int type;
+{
+#if defined (ARRAY_VARS)
+  if (type == att_array && assoc_p (var))
+    return 0;
+  else if (type == att_assoc && array_p (var))
+    return 0;
+  else
+#endif
+  return 1;    /* should we run convert_var_to_array here or let the caller? */
+}
+
 /* Set NAME to VALUE if NAME has no value. */
 SHELL_VAR *
 set_if_not (name, value)
@@ -1580,33 +2576,75 @@ set_if_not (name, value)
 {
   SHELL_VAR *v;
 
+  if (shell_variables == 0)
+    create_variable_tables ();
+
   v = find_variable (name);
   if (v == 0)
-    v = bind_variable_internal (name, value, global_variables->table, HASH_NOSRCH);
+    v = bind_variable_internal (name, value, global_variables->table, HASH_NOSRCH, 0);
   return (v);
 }
 
 /* Create a local variable referenced by NAME. */
 SHELL_VAR *
-make_local_variable (name)
+make_local_variable (name, flags)
      const char *name;
+     int flags;
 {
-  SHELL_VAR *new_var, *old_var;
+  SHELL_VAR *new_var, *old_var, *old_ref;
   VAR_CONTEXT *vc;
   int was_tmpvar;
-  char *tmp_value;
+  char *old_value;
 
+  /* We don't want to follow the nameref chain when making local variables; we
+     just want to create them. */
+  old_ref = find_variable_noref (name);
+  if (old_ref && nameref_p (old_ref) == 0)
+    old_ref = 0;
   /* local foo; local foo;  is a no-op. */
   old_var = find_variable (name);
-  if (old_var && local_p (old_var) && old_var->context == variable_context)
+  if (old_ref == 0 && old_var && local_p (old_var) && old_var->context == variable_context)
+    return (old_var);
+
+  /* local -n foo; local -n foo;  is a no-op. */
+  if (old_ref && local_p (old_ref) && old_ref->context == variable_context)
+    return (old_ref);
+
+  /* From here on, we want to use the refvar, not the variable it references */
+  if (old_ref)
+    old_var = old_ref;
+
+  was_tmpvar = old_var && tempvar_p (old_var);
+  /* If we're making a local variable in a shell function, the temporary env
+     has already been merged into the function's variable context stack.  We
+     can assume that a temporary var in the same context appears in the same
+     VAR_CONTEXT and can safely be returned without creating a new variable
+     (which results in duplicate names in the same VAR_CONTEXT->table */
+  /* We can't just test tmpvar_p because variables in the temporary env given
+     to a shell function appear in the function's local variable VAR_CONTEXT
+     but retain their tempvar attribute.  We want temporary variables that are
+     found in temporary_env, hence the test for last_table_searched, which is
+     set in hash_lookup and only (so far) checked here. */
+  if (was_tmpvar && old_var->context == variable_context && last_table_searched != temporary_env)
     {
-      VUNSETATTR (old_var, att_invisible);
+      VUNSETATTR (old_var, att_invisible);     /* XXX */
+#if 0  /* TAG:bash-5.1 */
+      /* We still want to flag this variable as local, though, and set things
+         up so that it gets treated as a local variable. */
+      new_var = old_var;
+      /* Since we found the variable in a temporary environment, this will
+        succeed. */
+      for (vc = shell_variables; vc; vc = vc->down)
+       if (vc_isfuncenv (vc) && vc->scope == variable_context)
+         break;
+      goto set_local_var_flags;
+#endif
       return (old_var);
     }
 
-  was_tmpvar = old_var && tempvar_p (old_var);
-  if (was_tmpvar)
-    tmp_value = value_cell (old_var);
+  /* If we want to change to "inherit the old variable's value" semantics,
+     here is where to save the old value. */
+  old_value = was_tmpvar ? value_cell (old_var) : (char *)NULL;
 
   for (vc = shell_variables; vc; vc = vc->down)
     if (vc_isfuncenv (vc) && vc->scope == variable_context)
@@ -1632,24 +2670,63 @@ make_local_variable (name)
     {
       if (readonly_p (old_var))
        sh_readonly (name);
-      return ((SHELL_VAR *)NULL);
+      else if (noassign_p (old_var))
+       builtin_error (_("%s: variable may not be assigned value"), name);
+#if 0
+      /* Let noassign variables through with a warning */
+      if (readonly_p (old_var))
+#endif
+       return ((SHELL_VAR *)NULL);
     }
 
   if (old_var == 0)
-    new_var = bind_variable_internal (name, "", vc->table, HASH_NOSRCH);
+    new_var = make_new_variable (name, vc->table);
   else
     {
       new_var = make_new_variable (name, vc->table);
 
       /* If we found this variable in one of the temporary environments,
         inherit its value.  Watch to see if this causes problems with
-        things like `x=4 local x'. */
+        things like `x=4 local x'. XXX - see above for temporary env
+        variables with the same context level as variable_context */
+      /* XXX - we should only do this if the variable is not an array. */
+      /* If we want to change the local variable semantics to "inherit
+        the old variable's value" here is where to set it.  And we would
+        need to use copy_variable (currently unused) to do it for all
+        possible variable values. */
       if (was_tmpvar)
-        var_setvalue (new_var, savestring (tmp_value));
+       var_setvalue (new_var, savestring (old_value));
+      else if (localvar_inherit || (flags & MKLOC_INHERIT))
+       {
+         /* This may not make sense for nameref variables that are shadowing
+            variables with the same name, but we don't know that yet. */
+#if defined (ARRAY_VARS)
+         if (assoc_p (old_var))
+           var_setassoc (new_var, assoc_copy (assoc_cell (old_var)));
+         else if (array_p (old_var))
+           var_setarray (new_var, array_copy (array_cell (old_var)));
+         else if (value_cell (old_var))
+#else
+         if (value_cell (old_var))
+#endif
+           var_setvalue (new_var, savestring (value_cell (old_var)));
+         else
+           var_setvalue (new_var, (char *)NULL);
+       }
 
-      new_var->attributes = exported_p (old_var) ? att_exported : 0;
+      if (localvar_inherit || (flags & MKLOC_INHERIT))
+       {
+         /* It doesn't make sense to inherit the nameref attribute */
+         new_var->attributes = old_var->attributes & ~att_nameref;
+         new_var->dynamic_value = old_var->dynamic_value;
+         new_var->assign_func = old_var->assign_func;
+       }
+      else
+       /* We inherit the export attribute, but no others. */
+       new_var->attributes = exported_p (old_var) ? att_exported : 0;
     }
 
+set_local_var_flags:
   vc->flags |= VC_HASLOCAL;
 
   new_var->context = variable_context;
@@ -1658,30 +2735,13 @@ make_local_variable (name)
   if (ifsname (name))
     setifs (new_var);
 
+  /* value_cell will be 0 if localvar_inherit == 0 or there was no old variable
+     with the same name or the old variable was invisible */
+  if (was_tmpvar == 0 && no_invisible_vars == 0 && value_cell (new_var) == 0)
+    VSETATTR (new_var, att_invisible); /* XXX */
   return (new_var);
 }
 
-#if defined (ARRAY_VARS)
-SHELL_VAR *
-make_local_array_variable (name)
-     char *name;
-{
-  SHELL_VAR *var;
-  ARRAY *array;
-
-  var = make_local_variable (name);
-  if (var == 0 || array_p (var))
-    return var;
-
-  array = array_create ();
-
-  FREE (value_cell(var));
-  var_setarray (var, array);
-  VSETATTR (var, att_array);
-  return var;
-}
-#endif /* ARRAY_VARS */
-
 /* Create a new shell variable with name NAME. */
 static SHELL_VAR *
 new_shell_variable (name)
@@ -1701,7 +2761,7 @@ new_shell_variable (name)
   entry->attributes = 0;
 
   /* Always assume variables are to be made at toplevel!
-     make_local_variable has the responsibilty of changing the
+     make_local_variable has the responsibility of changing the
      variable context. */
   entry->context = 0;
 
@@ -1722,11 +2782,7 @@ make_new_variable (name, table)
 
   /* Make sure we have a shell_variables hash table to add to. */
   if (shell_variables == 0)
-    {
-      shell_variables = global_variables = new_var_context ((char *)NULL, 0);
-      shell_variables->scope = 0;
-      shell_variables->table = hash_create (0);
-    }
+    create_variable_tables ();
 
   elt = hash_insert (savestring (name), table, HASH_NOSRCH);
   elt->data = (PTR_T)entry;
@@ -1744,36 +2800,199 @@ make_new_array_variable (name)
 
   entry = make_new_variable (name, global_variables->table);
   array = array_create ();
+
   var_setarray (entry, array);
   VSETATTR (entry, att_array);
   return entry;
 }
+
+SHELL_VAR *
+make_local_array_variable (name, assoc_ok)
+     char *name;
+     int assoc_ok;
+{
+  SHELL_VAR *var;
+  ARRAY *array;
+
+  var = make_local_variable (name, 0); /* XXX for now */
+  /* If ASSOC_OK is non-zero, assume that we are ok with letting an assoc
+     variable return to the caller without converting it. The caller will
+     either flag an error or do the conversion itself. */
+  if (var == 0 || array_p (var) || (assoc_ok && assoc_p (var)))
+    return var;
+
+  /* Validate any value we inherited from a variable instance at a previous
+     scope and disard anything that's invalid. */
+  if (localvar_inherit && assoc_p (var))
+    {
+      internal_warning ("%s: cannot inherit value from incompatible type", name);
+      VUNSETATTR (var, att_assoc);
+      dispose_variable_value (var);
+      array = array_create ();
+      var_setarray (var, array);
+    }
+  else if (localvar_inherit)
+    var = convert_var_to_array (var);          /* XXX */
+  else
+    {
+      dispose_variable_value (var);
+      array = array_create ();
+      var_setarray (var, array);
+    }
+
+  VSETATTR (var, att_array);
+  return var;
+}
+
+SHELL_VAR *
+make_new_assoc_variable (name)
+     char *name;
+{
+  SHELL_VAR *entry;
+  HASH_TABLE *hash;
+
+  entry = make_new_variable (name, global_variables->table);
+  hash = assoc_create (0);
+
+  var_setassoc (entry, hash);
+  VSETATTR (entry, att_assoc);
+  return entry;
+}
+
+SHELL_VAR *
+make_local_assoc_variable (name, array_ok)
+     char *name;
+     int array_ok;
+{
+  SHELL_VAR *var;
+  HASH_TABLE *hash;
+
+  var = make_local_variable (name, 0); /* XXX for now */
+  /* If ARRAY_OK is non-zero, assume that we are ok with letting an array
+     variable return to the caller without converting it. The caller will
+     either flag an error or do the conversion itself. */
+  if (var == 0 || assoc_p (var) || (array_ok && array_p (var)))
+    return var;
+
+  /* Validate any value we inherited from a variable instance at a previous
+     scope and disard anything that's invalid. */
+  if (localvar_inherit && array_p (var))
+    {
+      internal_warning ("%s: cannot inherit value from incompatible type", name);
+      VUNSETATTR (var, att_array);
+      dispose_variable_value (var);
+      hash = assoc_create (0);
+      var_setassoc (var, hash);
+    }
+  else if (localvar_inherit)
+    var = convert_var_to_assoc (var);          /* XXX */
+  else
+    {
+      dispose_variable_value (var);
+      hash = assoc_create (0);
+      var_setassoc (var, hash);
+    }
+
+  VSETATTR (var, att_assoc);
+  return var;
+}
 #endif
 
 char *
-make_variable_value (var, value)
+make_variable_value (var, value, flags)
      SHELL_VAR *var;
      char *value;
+     int flags;
 {
-  char *retval;
-  intmax_t lval;
-  int expok;
+  char *retval, *oval;
+  intmax_t lval, rval;
+  int expok, olen, op;
 
   /* If this variable has had its type set to integer (via `declare -i'),
      then do expression evaluation on it and store the result.  The
      functions in expr.c (evalexp()) and bind_int_variable() are responsible
      for turning off the integer flag if they don't want further
-     evaluation done. */
-  if (integer_p (var))
+     evaluation done.  Callers that find it inconvenient to do this can set
+     the ASS_NOEVAL flag.  For the special case of arithmetic expression
+     evaluation, the caller can set ASS_NOLONGJMP to avoid jumping out to
+     top_level. */
+  if ((flags & ASS_NOEVAL) == 0 && integer_p (var))
     {
-      lval = evalexp (value, &expok);
+      if (flags & ASS_APPEND)
+       {
+         oval = value_cell (var);
+         lval = evalexp (oval, 0, &expok);     /* ksh93 seems to do this */
+         if (expok == 0)
+           {
+             if (flags & ASS_NOLONGJMP)
+               goto make_value;
+             else
+               {
+                 top_level_cleanup ();
+                 jump_to_top_level (DISCARD);
+               }
+           }
+       }
+      rval = evalexp (value, 0, &expok);
       if (expok == 0)
-       jump_to_top_level (DISCARD);
-      retval = itos (lval);
+       {
+         if (flags & ASS_NOLONGJMP)
+           goto make_value;
+         else
+           {
+             top_level_cleanup ();
+             jump_to_top_level (DISCARD);
+           }
+       }
+      /* This can be fooled if the variable's value changes while evaluating
+        `rval'.  We can change it if we move the evaluation of lval to here. */
+      if (flags & ASS_APPEND)
+       rval += lval;
+      retval = itos (rval);
+    }
+#if defined (CASEMOD_ATTRS)
+  else if ((flags & ASS_NOEVAL) == 0 && (capcase_p (var) || uppercase_p (var) || lowercase_p (var)))
+    {
+      if (flags & ASS_APPEND)
+       {
+         oval = get_variable_value (var);
+         if (oval == 0)        /* paranoia */
+           oval = "";
+         olen = STRLEN (oval);
+         retval = (char *)xmalloc (olen + (value ? STRLEN (value) : 0) + 1);
+         strcpy (retval, oval);
+         if (value)
+           strcpy (retval+olen, value);
+       }
+      else if (*value)
+       retval = savestring (value);
+      else
+       {
+         retval = (char *)xmalloc (1);
+         retval[0] = '\0';
+       }
+      op = capcase_p (var) ? CASE_CAPITALIZE
+                        : (uppercase_p (var) ? CASE_UPPER : CASE_LOWER);
+      oval = sh_modcase (retval, (char *)0, op);
+      free (retval);
+      retval = oval;
     }
+#endif /* CASEMOD_ATTRS */
   else if (value)
     {
-      if (*value)
+make_value:
+      if (flags & ASS_APPEND)
+       {
+         oval = get_variable_value (var);
+         if (oval == 0)        /* paranoia */
+           oval = "";
+         olen = STRLEN (oval);
+         retval = (char *)xmalloc (olen + (value ? STRLEN (value) : 0) + 1);
+         strcpy (retval, oval);
+         if (value)
+           strcpy (retval+olen, value);
+       }
+      else if (*value)
        retval = savestring (value);
       else
        {
@@ -1787,43 +3006,177 @@ make_variable_value (var, value)
   return retval;
 }
 
+/* If we can optimize appending to string variables, say so */
+static int
+can_optimize_assignment (entry, value, aflags)
+     SHELL_VAR *entry;
+     char *value;
+     int aflags;
+{
+  if ((aflags & ASS_APPEND) == 0)
+    return 0;
+#if defined (ARRAY_VARS)
+  if (array_p (entry) || assoc_p (entry))
+    return 0;
+#endif
+  if (integer_p (entry) || uppercase_p (entry) || lowercase_p (entry) || capcase_p (entry))
+    return 0;
+  if (readonly_p (entry) || noassign_p (entry))
+    return 0;
+  return 1;
+}
+
+/* right now we optimize appends to string variables */
+static SHELL_VAR *
+optimized_assignment (entry, value, aflags)
+     SHELL_VAR *entry;
+     char *value;
+     int aflags;
+{
+  size_t len, vlen;
+  char *v, *new;
+
+  v = value_cell (entry);
+  len = STRLEN (v);
+  vlen = STRLEN (value);
+
+  new = (char *)xrealloc (v, len + vlen + 8);  /* for now */
+  if (vlen == 1)
+    {
+      new[len] = *value;
+      new[len+1] = '\0';
+    }
+  else
+    strcpy (new + len, value);
+  var_setvalue (entry, new);
+  return entry;
+}
+
 /* Bind a variable NAME to VALUE in the HASH_TABLE TABLE, which may be the
-   temporary environment (but usually is not). */
+   temporary environment (but usually is not).  HFLAGS controls how NAME
+   is looked up in TABLE; AFLAGS controls how VALUE is assigned */
 static SHELL_VAR *
-bind_variable_internal (name, value, table, hflags)
+bind_variable_internal (name, value, table, hflags, aflags)
      const char *name;
      char *value;
      HASH_TABLE *table;
-     int hflags;
+     int hflags, aflags;
 {
-  char *newval;
-  SHELL_VAR *entry;
+  char *newval, *tname;
+  SHELL_VAR *entry, *tentry;
 
   entry = (hflags & HASH_NOSRCH) ? (SHELL_VAR *)NULL : hash_lookup (name, table);
+  /* Follow the nameref chain here if this is the global variables table */
+  if (entry && nameref_p (entry) && (invisible_p (entry) == 0) && table == global_variables->table)
+    {
+      entry = find_global_variable (entry->name);
+      /* Let's see if we have a nameref referencing a variable that hasn't yet
+        been created. */
+      if (entry == 0)
+       entry = find_variable_last_nameref (name, 0);   /* XXX */
+      if (entry == 0)                                  /* just in case */
+        return (entry);
+    }
 
-  if (entry == 0)
+  /* The first clause handles `declare -n ref; ref=x;' or `declare -n ref;
+     declare -n ref' */
+  if (entry && invisible_p (entry) && nameref_p (entry))
+    {
+      if ((aflags & ASS_FORCE) == 0 && value && valid_nameref_value (value, 0) == 0)
+       {
+         sh_invalidid (value);
+         return ((SHELL_VAR *)NULL);
+       }
+      goto assign_value;
+    }
+  else if (entry && nameref_p (entry))
+    {
+      newval = nameref_cell (entry);   /* XXX - newval can't be NULL here */
+      if (valid_nameref_value (newval, 0) == 0)
+       {
+         sh_invalidid (newval);
+         return ((SHELL_VAR *)NULL);
+       }
+#if defined (ARRAY_VARS)
+      /* declare -n foo=x[2] ; foo=bar */
+      if (valid_array_reference (newval, 0))
+       {
+         tname = array_variable_name (newval, 0, (char **)0, (int *)0);
+         if (tname && (tentry = find_variable_noref (tname)) && nameref_p (tentry))
+           {
+             /* nameref variables can't be arrays */
+             internal_warning (_("%s: removing nameref attribute"), name_cell (tentry));
+             FREE (value_cell (tentry));               /* XXX - bash-4.3 compat */
+             var_setvalue (tentry, (char *)NULL);
+             VUNSETATTR (tentry, att_nameref);
+           }
+         free (tname);
+          /* XXX - should it be aflags? */
+         entry = assign_array_element (newval, make_variable_value (entry, value, aflags), aflags|ASS_NAMEREF);
+         if (entry == 0)
+           return entry;
+       }
+      else
+#endif
+       {
+         entry = make_new_variable (newval, table);
+         var_setvalue (entry, make_variable_value (entry, value, aflags));
+       }
+    }
+  else if (entry == 0)
     {
       entry = make_new_variable (name, table);
-      var_setvalue (entry, make_variable_value (entry, value));
+      var_setvalue (entry, make_variable_value (entry, value, aflags)); /* XXX */
     }
   else if (entry->assign_func) /* array vars have assign functions now */
     {
       INVALIDATE_EXPORTSTR (entry);
-      return ((*(entry->assign_func)) (entry, value, -1));
+      newval = (aflags & ASS_APPEND) ? make_variable_value (entry, value, aflags) : value;
+      if (assoc_p (entry))
+       entry = (*(entry->assign_func)) (entry, newval, -1, savestring ("0"));
+      else if (array_p (entry))
+       entry = (*(entry->assign_func)) (entry, newval, 0, 0);
+      else
+       entry = (*(entry->assign_func)) (entry, newval, -1, 0);
+      if (newval != value)
+       free (newval);
+      return (entry);
     }
   else
     {
-      if (readonly_p (entry) || noassign_p (entry))
+assign_value:
+      if ((readonly_p (entry) && (aflags & ASS_FORCE) == 0) || noassign_p (entry))
        {
          if (readonly_p (entry))
-           err_readonly (name);
+           err_readonly (name_cell (entry));
          return (entry);
        }
 
       /* Variables which are bound are visible. */
       VUNSETATTR (entry, att_invisible);
 
-      newval = make_variable_value (entry, value);
+      /* 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))
+       {
+         INVALIDATE_EXPORTSTR (entry);
+         optimized_assignment (entry, value, aflags);
+
+         if (mark_modified_vars)
+           VSETATTR (entry, att_exported);
+
+         if (exported_p (entry))
+           array_needs_making = 1;
+
+         return (entry);
+       }
+
+#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 */
 
       /* Invalidate any cached export string */
       INVALIDATE_EXPORTSTR (entry);
@@ -1833,7 +3186,12 @@ bind_variable_internal (name, value, table, hflags)
       /* If an existing array variable x is being assigned to with x=b or
         `read x' or something of that nature, silently convert it to
         x[0]=b or `read x[0]'. */
-      if (array_p (entry))
+      if (assoc_p (entry))
+       {
+         assoc_insert (assoc_cell (entry), savestring ("0"), newval);
+         free (newval);
+       }
+      else if (array_p (entry))
        {
          array_insert (array_cell (entry), 0, newval);
          free (newval);
@@ -1860,39 +3218,111 @@ bind_variable_internal (name, value, table, hflags)
    first, then we bind into shell_variables. */
 
 SHELL_VAR *
-bind_variable (name, value)
+bind_variable (name, value, flags)
      const char *name;
      char *value;
+     int flags;
 {
-  SHELL_VAR *v;
-  VAR_CONTEXT *vc;
+  SHELL_VAR *v, *nv;
+  VAR_CONTEXT *vc, *nvc;
 
   if (shell_variables == 0)
-    {
-      shell_variables = global_variables = new_var_context ((char *)NULL, 0);
-      shell_variables->scope = 0;
-      shell_variables->table = hash_create (0);
-    }
+    create_variable_tables ();
 
   /* If we have a temporary environment, look there first for the variable,
      and, if found, modify the value there before modifying it in the
      shell_variables table.  This allows sourced scripts to modify values
      given to them in a temporary environment while modifying the variable
      value that the caller sees. */
-  if (temporary_env)
+  if (temporary_env && value)          /* XXX - can value be null here? */
     bind_tempenv_variable (name, value);
 
   /* XXX -- handle local variables here. */
   for (vc = shell_variables; vc; vc = vc->down)
     {
       if (vc_isfuncenv (vc) || vc_isbltnenv (vc))
-        {
-          v = hash_lookup (name, vc->table);
-          if (v)
-           return (bind_variable_internal (name, value, vc->table, 0));
-        }
+       {
+         v = hash_lookup (name, vc->table);
+         nvc = vc;
+         if (v && nameref_p (v))
+           {
+             /* This starts at the context where we found the nameref. If we
+                want to start the name resolution over again at the original
+                context, this is where we need to change it */
+             nv = find_variable_nameref_context (v, vc, &nvc);
+             if (nv == 0)
+               {
+                 nv = find_variable_last_nameref_context (v, vc, &nvc);
+                 if (nv && nameref_p (nv))
+                   {
+                     /* If this nameref variable doesn't have a value yet,
+                        set the value.  Otherwise, assign using the value as
+                        normal. */
+                     if (nameref_cell (nv) == 0)
+                       return (bind_variable_internal (nv->name, value, nvc->table, 0, flags));
+#if defined (ARRAY_VARS)
+                     else if (valid_array_reference (nameref_cell (nv), 0))
+                       return (assign_array_element (nameref_cell (nv), value, flags));
+                     else
+#endif
+                     return (bind_variable_internal (nameref_cell (nv), value, nvc->table, 0, flags));
+                   }
+                 else if (nv == &nameref_maxloop_value)
+                   {
+                     internal_warning (_("%s: circular name reference"), v->name);
+#if 1
+                     /* TAG:bash-5.1 */
+                     return (bind_global_variable (v->name, value, flags));
+#else
+                     v = 0;    /* backwards compat */
+#endif
+                   }
+                 else
+                   v = nv;
+               }
+             else if (nv == &nameref_maxloop_value)
+               {
+                 internal_warning (_("%s: circular name reference"), v->name);
+#if 1
+                 /* TAG:bash-5.1 */
+                 return (bind_global_variable (v->name, value, flags));
+#else
+                 v = 0;        /* backwards compat */
+#endif
+               }
+             else
+               v = nv;
+           }
+         if (v)
+           return (bind_variable_internal (v->name, value, nvc->table, 0, flags));
+       }
     }
-  return (bind_variable_internal (name, value, global_variables->table, 0));
+  /* bind_variable_internal will handle nameref resolution in this case */
+  return (bind_variable_internal (name, value, global_variables->table, 0, flags));
+}
+
+SHELL_VAR *
+bind_global_variable (name, value, flags)
+     const char *name;
+     char *value;
+     int flags;
+{
+  if (shell_variables == 0)
+    create_variable_tables ();
+
+  /* bind_variable_internal will handle nameref resolution in this case */
+  return (bind_variable_internal (name, value, global_variables->table, 0, flags));
+}
+
+static SHELL_VAR *
+bind_invalid_envvar (name, value, flags)
+     const char *name;
+     char *value;
+     int flags;
+{
+  if (invalid_env == 0)
+    invalid_env = hash_create (64);    /* XXX */
+  return (bind_variable_internal (name, value, invalid_env, HASH_NOSRCH, flags));
 }
 
 /* Make VAR, a simple shell variable, have value VALUE.  Once assigned a
@@ -1901,17 +3331,52 @@ bind_variable (name, value)
    all modified variables should be exported, mark the variable for export
    and note that the export environment needs to be recreated. */
 SHELL_VAR *
-bind_variable_value (var, value)
+bind_variable_value (var, value, aflags)
      SHELL_VAR *var;
      char *value;
+     int aflags;
 {
   char *t;
+  int invis;
 
+  invis = invisible_p (var);
   VUNSETATTR (var, att_invisible);
 
-  t = make_variable_value (var, value);
-  FREE (value_cell (var));
-  var_setvalue (var, t);
+  if (var->assign_func)
+    {
+      /* If we're appending, we need the old value, so use
+        make_variable_value */
+      t = (aflags & ASS_APPEND) ? make_variable_value (var, value, aflags) : value;
+      (*(var->assign_func)) (var, t, -1, 0);
+      if (t != value && t)
+       free (t);      
+    }
+  else
+    {
+      t = make_variable_value (var, value, aflags);
+      if ((aflags & (ASS_NAMEREF|ASS_FORCE)) == ASS_NAMEREF && check_selfref (name_cell (var), t, 0))
+       {
+         if (variable_context)
+           internal_warning (_("%s: circular name reference"), name_cell (var));
+         else
+           {
+             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);
+    }
 
   INVALIDATE_EXPORTSTR (var);
 
@@ -1935,23 +3400,24 @@ bind_variable_value (var, value)
    variable we set here, then turn it back on after binding as necessary. */
 
 SHELL_VAR *
-bind_int_variable (lhs, rhs)
+bind_int_variable (lhs, rhs, flags)
      char *lhs, *rhs;
+     int flags;
 {
   register SHELL_VAR *v;
-  char *t;
-  int isint, isarr;
+  int isint, isarr, implicitarray;
 
-  isint = isarr = 0;
+  isint = isarr = implicitarray = 0;
 #if defined (ARRAY_VARS)
-#  if 0
-  if (t = xstrchr (lhs, '['))  /*]*/
-#  else
-  if (valid_array_reference (lhs))
-#  endif
+  if (valid_array_reference (lhs, (flags & ASS_NOEXPAND) != 0))
     {
       isarr = 1;
-      v = array_variable_part (lhs, (char **)0, (int *)0);
+      v = array_variable_part (lhs, (flags & ASS_NOEXPAND) != 0, (char **)0, (int *)0);
+    }
+  else if (legal_identifier (lhs) == 0)
+    {
+      sh_invalidid (lhs);
+      return ((SHELL_VAR *)NULL);      
     }
   else
 #endif
@@ -1961,18 +3427,31 @@ bind_int_variable (lhs, rhs)
     {
       isint = integer_p (v);
       VUNSETATTR (v, att_integer);
+#if defined (ARRAY_VARS)
+      if (array_p (v) && isarr == 0)
+       implicitarray = 1;
+#endif
     }
 
 #if defined (ARRAY_VARS)
   if (isarr)
-    v = assign_array_element (lhs, rhs);
+    v = assign_array_element (lhs, rhs, flags);
+  else if (implicitarray)
+    v = bind_array_variable (lhs, 0, rhs, 0);  /* XXX - check on flags */
   else
 #endif
-    v = bind_variable (lhs, rhs);
+    v = bind_variable (lhs, rhs, 0);   /* why not use bind_variable_value? */
 
-  if (isint)
-    VSETATTR (v, att_integer);
+  if (v)
+    {
+      if (isint)
+       VSETATTR (v, att_integer);
+      VUNSETATTR (v, att_invisible);
+    }
 
+  if (v && nameref_p (v))
+    internal_warning (_("%s: assigning integer to name reference"), lhs);
+     
   return (v);
 }
 
@@ -1984,7 +3463,7 @@ bind_var_to_int (var, val)
   char ibuf[INT_STRLEN_BOUND (intmax_t) + 1], *p;
 
   p = fmtulong (val, 10, ibuf, sizeof (ibuf), 0);
-  return (bind_int_variable (var, p));
+  return (bind_int_variable (var, p, 0));
 }
 
 /* Do a function binding to a variable.  You pass the name and
@@ -2033,23 +3512,29 @@ bind_function (name, value)
   return (entry);
 }
 
+#if defined (DEBUGGER)
 /* Bind a function definition, which includes source file and line number
-   information in addition to the command, into the FUNCTION_DEF hash table.*/
+   information in addition to the command, into the FUNCTION_DEF hash table.
+   If (FLAGS & 1), overwrite any existing definition. If FLAGS == 0, leave
+   any existing definition alone. */
 void
-bind_function_def (name, value)
+bind_function_def (name, value, flags)
      const char *name;
      FUNCTION_DEF *value;
+     int flags;
 {
   FUNCTION_DEF *entry;
   BUCKET_CONTENTS *elt;
   COMMAND *cmd;
 
   entry = find_function_def (name);
-  if (entry)
+  if (entry && (flags & 1))
     {
       dispose_function_def_contents (entry);
       entry = copy_function_def_contents (value, entry);
     }
+  else if (entry)
+    return;
   else
     {
       cmd = value->command;
@@ -2061,28 +3546,67 @@ bind_function_def (name, value)
       elt->data = (PTR_T *)entry;
     }
 }
+#endif /* DEBUGGER */
 
 /* Add STRING, which is of the form foo=bar, to the temporary environment
    HASH_TABLE (temporary_env).  The functions in execute_cmd.c are
    responsible for moving the main temporary env to one of the other
    temporary environments.  The expansion code in subst.c calls this. */
 int
-assign_in_env (string)
-     const char *string;
+assign_in_env (word, flags)
+     WORD_DESC *word;
+     int flags;
 {
-  int offset;
-  char *name, *temp, *value;
+  int offset, aflags;
+  char *name, *temp, *value, *newname;
   SHELL_VAR *var;
+  const char *string;
+
+  string = word->word;
 
+  aflags = 0;
   offset = assignment (string, 0);
-  name = savestring (string);
+  newname = name = savestring (string);
   value = (char *)NULL;
 
   if (name[offset] == '=')
     {
       name[offset] = 0;
 
+      /* don't ignore the `+' when assigning temporary environment */
+      if (name[offset - 1] == '+')
+       {
+         name[offset - 1] = '\0';
+         aflags |= ASS_APPEND;
+       }
+
+      if (legal_identifier (name) == 0)
+       {
+         sh_invalidid (name);
+         return (0);
+       }
+  
       var = find_variable (name);
+      if (var == 0)
+       {
+         var = find_variable_last_nameref (name, 1);
+         /* If we're assigning a value to a nameref variable in the temp
+            environment, and the value of the nameref is valid for assignment,
+            but the variable does not already exist, assign to the nameref
+            target and add the target to the temporary environment.  This is
+            what ksh93 does */
+         /* We use 2 in the call to valid_nameref_value because we don't want
+            to allow array references here at all (newname will be used to
+            create a variable directly below) */
+         if (var && nameref_p (var) && valid_nameref_value (nameref_cell (var), 2))
+           {
+             newname = nameref_cell (var);
+             var = 0;          /* don't use it for append */
+           }
+       }
+      else
+        newname = name_cell (var);     /* no-op if not nameref */
+         
       if (var && (readonly_p (var) || noassign_p (var)))
        {
          if (readonly_p (var))
@@ -2090,26 +3614,35 @@ assign_in_env (string)
          free (name);
          return (0);
        }
-
       temp = name + offset + 1;
-      temp = (xstrchr (temp, '~') != 0) ? bash_tilde_expand (temp, 1) : savestring (temp);
 
-      value = expand_string_unsplit_to_string (temp, 0);
-      free (temp);
+      value = expand_assignment_string_to_string (temp, 0);
+
+      if (var && (aflags & ASS_APPEND))
+       {
+         if (value == 0)
+           {
+             value = (char *)xmalloc (1);      /* like do_assignment_internal */
+             value[0] = '\0';
+           }
+         temp = make_variable_value (var, value, aflags);
+         FREE (value);
+         value = temp;
+       }
     }
 
   if (temporary_env == 0)
     temporary_env = hash_create (TEMPENV_HASH_BUCKETS);
 
-  var = hash_lookup (name, temporary_env);
+  var = hash_lookup (newname, temporary_env);
   if (var == 0)
-    var = make_new_variable (name, temporary_env);
+    var = make_new_variable (newname, temporary_env);
   else
     FREE (value_cell (var));
 
   if (value == 0)
     {
-      value = (char *)xmalloc (1);     /* like do_assignment_internal */
+      value = (char *)xmalloc (1);     /* see above */
       value[0] = '\0';
     }
 
@@ -2118,12 +3651,12 @@ assign_in_env (string)
   var->context = variable_context;     /* XXX */
 
   INVALIDATE_EXPORTSTR (var);
-  var->exportstr = mk_env_string (name, value);
+  var->exportstr = mk_env_string (newname, value, 0);
 
   array_needs_making = 1;
 
-  if (ifsname (name))
-    setifs (var);
+  if (flags)
+    stupidly_hack_special_variables (newname);
 
   if (echo_command_at_execute)
     /* The Korn shell prints the `+ ' in front of assignment statements,
@@ -2159,9 +3692,13 @@ copy_variable (var)
        var_setfunc (copy, copy_command (function_cell (var)));
 #if defined (ARRAY_VARS)
       else if (array_p (var))
-       var_setarray (copy, dup_array (array_cell (var)));
+       var_setarray (copy, array_copy (array_cell (var)));
+      else if (assoc_p (var))
+       var_setassoc (copy, assoc_copy (assoc_cell (var)));
 #endif
-      else if (value_cell (var))
+      else if (nameref_cell (var))     /* XXX - nameref */
+       var_setref (copy, savestring (nameref_cell (var)));
+      else if (value_cell (var))       /* XXX - nameref */
        var_setvalue (copy, savestring (value_cell (var)));
       else
        var_setvalue (copy, (char *)NULL);
@@ -2184,21 +3721,33 @@ copy_variable (var)
 /* **************************************************************** */
 
 /* Dispose of the information attached to VAR. */
-void
-dispose_variable (var)
+static void
+dispose_variable_value (var)
      SHELL_VAR *var;
 {
-  if (var == 0)
-    return;
-
   if (function_p (var))
     dispose_command (function_cell (var));
 #if defined (ARRAY_VARS)
   else if (array_p (var))
     array_dispose (array_cell (var));
+  else if (assoc_p (var))
+    assoc_dispose (assoc_cell (var));
 #endif
+  else if (nameref_p (var))
+    FREE (nameref_cell (var));
   else
     FREE (value_cell (var));
+}
+
+void
+dispose_variable (var)
+     SHELL_VAR *var;
+{
+  if (var == 0)
+    return;
+
+  if (nofree_p (var) == 0)
+    dispose_variable_value (var);
 
   FREE_EXPORTSTR (var);
 
@@ -2210,12 +3759,61 @@ dispose_variable (var)
   free (var);
 }
 
-/* Unset the shell variable referenced by NAME. */
+/* Unset the shell variable referenced by NAME.  Unsetting a nameref variable
+   unsets the variable it resolves to but leaves the nameref alone. */
 int
 unbind_variable (name)
      const char *name;
 {
-  return makunbound (name, shell_variables);
+  SHELL_VAR *v, *nv;
+  int r;
+
+  v = var_lookup (name, shell_variables);
+  nv = (v && nameref_p (v)) ? find_variable_nameref (v) : (SHELL_VAR *)NULL;
+
+  r = nv ? makunbound (nv->name, shell_variables) : makunbound (name, shell_variables);
+  return r;
+}
+
+/* Unbind NAME, where NAME is assumed to be a nameref variable */
+int
+unbind_nameref (name)
+     const char *name;
+{
+  SHELL_VAR *v;
+
+  v = var_lookup (name, shell_variables);
+  if (v && nameref_p (v))
+    return makunbound (name, shell_variables);
+  return 0;
+}
+
+/* Unbind the first instance of NAME, whether it's a nameref or not */
+int
+unbind_variable_noref (name)
+     const char *name;
+{
+  SHELL_VAR *v;
+
+  v = var_lookup (name, shell_variables);
+  if (v)
+    return makunbound (name, shell_variables);
+  return 0;
+}
+
+int
+check_unbind_variable (name)
+     const char *name;
+{
+  SHELL_VAR *v;
+
+  v = find_variable (name);
+  if (v && readonly_p (v))
+    {
+      internal_error (_("%s: cannot unset: readonly %s"), name, "variable");
+      return -1;
+    }
+  return (unbind_variable (name));
 }
 
 /* Unset the shell function named NAME. */
@@ -2249,6 +3847,7 @@ unbind_func (name)
   return 0;  
 }
 
+#if defined (DEBUGGER)
 int
 unbind_function_def (name)
      const char *name;
@@ -2270,6 +3869,31 @@ unbind_function_def (name)
 
   return 0;  
 }
+#endif /* DEBUGGER */
+
+int
+delete_var (name, vc)
+     const char *name;
+     VAR_CONTEXT *vc;
+{
+  BUCKET_CONTENTS *elt;
+  SHELL_VAR *old_var;
+  VAR_CONTEXT *v;
+
+  for (elt = (BUCKET_CONTENTS *)NULL, v = vc; v; v = v->down)
+    if (elt = hash_remove (name, v->table, 0))
+      break;
+
+  if (elt == 0)
+    return (-1);
+
+  old_var = (SHELL_VAR *)elt->data;
+  free (elt->key);
+  free (elt);
+
+  dispose_variable (old_var);
+  return (0);
+}
 
 /* Make the variable associated with NAME go away.  HASH_LIST is the
    hash table from which this variable should be deleted (either
@@ -2303,15 +3927,27 @@ makunbound (name, vc)
      must be done so that if the variable is subsequently assigned a new
      value inside the function, the `local' attribute is still present.
      We also need to add it back into the correct hash table. */
-  if (old_var && local_p (old_var) && variable_context == old_var->context)
+  if (old_var && local_p (old_var) &&
+       (old_var->context == variable_context || (localvar_unset && old_var->context < variable_context)))
     {
+      if (nofree_p (old_var))
+       var_setvalue (old_var, (char *)NULL);
+#if defined (ARRAY_VARS)
+      else if (array_p (old_var))
+       array_dispose (array_cell (old_var));
+      else if (assoc_p (old_var))
+       assoc_dispose (assoc_cell (old_var));
+#endif
+      else if (nameref_p (old_var))
+       FREE (nameref_cell (old_var));
+      else
+       FREE (value_cell (old_var));
       /* Reset the attributes.  Preserve the export attribute if the variable
-         came from a temporary environment.  Make sure it stays local, and
-         make it invisible. */ 
+        came from a temporary environment.  Make sure it stays local, and
+        make it invisible. */ 
       old_var->attributes = (exported_p (old_var) && tempvar_p (old_var)) ? att_exported : 0;
       VSETATTR (old_var, att_local);
       VSETATTR (old_var, att_invisible);
-      FREE (value_cell (old_var));
       var_setvalue (old_var, (char *)NULL);
       INVALIDATE_EXPORTSTR (old_var);
 
@@ -2389,8 +4025,8 @@ delete_all_variables (hashed_vars)
       entry = find_variable (name); \
       if (!entry) \
        { \
-         entry = bind_variable (name, ""); \
-         if (!no_invisible_vars) entry->attributes |= att_invisible; \
+         entry = bind_variable (name, "", 0); \
+         if (!no_invisible_vars && entry) entry->attributes |= att_invisible; \
        } \
     } \
   while (0)
@@ -2674,6 +4310,16 @@ visible_and_exported (var)
   return (invisible_p (var) == 0 && exported_p (var));
 }
 
+/* Candidate variables for the export environment are either valid variables
+   with the export attribute or invalid variables inherited from the initial
+   environment and simply passed through. */
+static int
+export_environment_candidate (var)
+     SHELL_VAR *var;
+{
+  return (exported_p (var) && (invisible_p (var) == 0 || imported_p (var)));
+}
+
 /* Return non-zero if VAR is a local variable in the current context and
    is exported. */
 static int
@@ -2739,7 +4385,7 @@ static int
 visible_array_vars (var)
      SHELL_VAR *var;
 {
-  return (invisible_p (var) == 0 && array_p (var));
+  return (invisible_p (var) == 0 && (array_p (var) || assoc_p (var)));
 }
 
 SHELL_VAR **
@@ -2810,8 +4456,49 @@ find_tempenv_variable (name)
   return (temporary_env ? hash_lookup (name, temporary_env) : (SHELL_VAR *)NULL);
 }
 
+char **tempvar_list;
+int tvlist_ind;
+
+/* Take a variable from an assignment statement preceding a posix special
+   builtin (including `return') and create a global variable from it. This
+   is called from merge_temporary_env, which is only called when in posix
+   mode. */
+static void
+push_posix_temp_var (data)
+     PTR_T data;
+{
+  SHELL_VAR *var, *v;
+  HASH_TABLE *binding_table;
+
+  var = (SHELL_VAR *)data;
+
+  binding_table = global_variables->table;
+  if (binding_table == 0)
+    binding_table = global_variables->table = hash_create (VARIABLES_HASH_BUCKETS);
+
+  v = bind_variable_internal (var->name, value_cell (var), binding_table, 0, ASS_FORCE|ASS_NOLONGJMP);
+
+  /* global variables are no longer temporary and don't need propagating. */
+  var->attributes &= ~(att_tempvar|att_propagate);
+  if (v)
+    v->attributes |= var->attributes;
+
+  if (find_special_var (var->name) >= 0)
+    tempvar_list[tvlist_ind++] = savestring (var->name);
+
+  dispose_variable (var);
+}
+
 /* Push the variable described by (SHELL_VAR *)DATA down to the next
-   variable context from the temporary environment. */
+   variable context from the temporary environment. This can be called
+   from one context:
+      1. propagate_temp_var: which is called to propagate variables in
+        assignments like `var=value declare -x var' to the surrounding
+        scope.
+
+  In this case, the variable should have the att_propagate flag set and
+  we can create variables in the current scope.
+*/
 static void
 push_temp_var (data)
      PTR_T data;
@@ -2826,15 +4513,18 @@ push_temp_var (data)
     {
       if (shell_variables == global_variables)
        /* shouldn't happen */
-       binding_table = shell_variables->table = global_variables->table = hash_create (0);
+       binding_table = shell_variables->table = global_variables->table = hash_create (VARIABLES_HASH_BUCKETS);
       else
        binding_table = shell_variables->table = hash_create (TEMPENV_HASH_BUCKETS);
     }
 
-  v = bind_variable_internal (var->name, value_cell (var), binding_table, 0);
+  v = bind_variable_internal (var->name, value_cell (var), binding_table, 0, ASS_FORCE|ASS_NOLONGJMP);
 
   /* XXX - should we set the context here?  It shouldn't matter because of how
-     assign_in_env works, but might want to check. */
+     assign_in_env works, but we do it anyway. */
+  if (v)
+    v->context = shell_variables->scope;
+
   if (binding_table == global_variables->table)                /* XXX */
     var->attributes &= ~(att_tempvar|att_propagate);
   else
@@ -2843,11 +4533,19 @@ push_temp_var (data)
       if  (binding_table == shell_variables->table)
        shell_variables->flags |= VC_HASTMPVAR;
     }
-  v->attributes |= var->attributes;
+  if (v)
+    v->attributes |= var->attributes;
+
+  if (find_special_var (var->name) >= 0)
+    tempvar_list[tvlist_ind++] = savestring (var->name);
 
   dispose_variable (var);
 }
 
+/* Take a variable described by DATA and push it to the surrounding scope if
+   the PROPAGATE attribute is set. That gets set by push_temp_var if we are
+   taking a variable like `var=value declare -x var' and propagating it to
+   the enclosing scope. */
 static void
 propagate_temp_var (data)
      PTR_T data;
@@ -2858,40 +4556,76 @@ propagate_temp_var (data)
   if (tempvar_p (var) && (var->attributes & att_propagate))
     push_temp_var (data);
   else
-    dispose_variable (var);
+    {
+      if (find_special_var (var->name) >= 0)
+       tempvar_list[tvlist_ind++] = savestring (var->name);
+      dispose_variable (var);
+    }
 }
 
 /* Free the storage used in the hash table for temporary
    environment variables.  PUSHF is a function to be called
    to free each hash table entry.  It takes care of pushing variables
-   to previous scopes if appropriate. */
+   to previous scopes if appropriate.  PUSHF stores names of variables
+   that require special handling (e.g., IFS) on tempvar_list, so this
+   function can call stupidly_hack_special_variables on all the
+   variables in the list when the temporary hash table is destroyed. */
 static void
 dispose_temporary_env (pushf)
      sh_free_func_t *pushf;
 {
+  int i;
+
+  tempvar_list = strvec_create (HASH_ENTRIES (temporary_env) + 1);
+  tempvar_list[tvlist_ind = 0] = 0;
+    
   hash_flush (temporary_env, pushf);
   hash_dispose (temporary_env);
-  temporary_env  = (HASH_TABLE *)NULL;
+  temporary_env = (HASH_TABLE *)NULL;
+
+  tempvar_list[tvlist_ind] = 0;
 
   array_needs_making = 1;
 
-  sv_ifs ("IFS");              /* XXX here for now */
+  for (i = 0; i < tvlist_ind; i++)
+    stupidly_hack_special_variables (tempvar_list[i]);
+
+  strvec_dispose (tempvar_list);
+  tempvar_list = 0;
+  tvlist_ind = 0;
 }
 
 void
 dispose_used_env_vars ()
 {
   if (temporary_env)
-    dispose_temporary_env (propagate_temp_var);
+    {
+      dispose_temporary_env (propagate_temp_var);
+      maybe_make_export_env ();
+    }
 }
 
 /* Take all of the shell variables in the temporary environment HASH_TABLE
-   and make shell variables from them at the current variable context. */
+   and make shell variables from them at the current variable context.
+   Right now, this is only called in Posix mode to implement the historical
+   accident of creating global variables from assignment statements preceding
+   special builtins, but we check in case this acquires another caller later. */
 void
 merge_temporary_env ()
 {
   if (temporary_env)
-    dispose_temporary_env (push_temp_var);
+    dispose_temporary_env (posixly_correct ? push_posix_temp_var : push_temp_var);
+}
+
+void
+flush_temporary_env ()
+{
+  if (temporary_env)
+    {
+      hash_flush (temporary_env, free_variable_hash_data);
+      hash_dispose (temporary_env);
+      temporary_env = (HASH_TABLE *)NULL;
+    }
 }
 
 /* **************************************************************** */
@@ -2901,21 +4635,52 @@ merge_temporary_env ()
 /* **************************************************************** */
 
 static inline char *
-mk_env_string (name, value)
+mk_env_string (name, value, isfunc)
      const char *name, *value;
+     int isfunc;
 {
-  int name_len, value_len;
-  char *p;
+  size_t name_len, value_len;
+  char *p, *q, *t;
 
   name_len = strlen (name);
   value_len = STRLEN (value);
-  p = (char *)xmalloc (2 + name_len + value_len);
-  strcpy (p, name);
-  p[name_len] = '=';
+
+  /* If we are exporting a shell function, construct the encoded function
+     name. */
+  if (isfunc && value)
+    {
+      p = (char *)xmalloc (BASHFUNC_PREFLEN + name_len + BASHFUNC_SUFFLEN + value_len + 2);
+      q = p;
+      memcpy (q, BASHFUNC_PREFIX, BASHFUNC_PREFLEN);
+      q += BASHFUNC_PREFLEN;
+      memcpy (q, name, name_len);
+      q += name_len;
+      memcpy (q, BASHFUNC_SUFFIX, BASHFUNC_SUFFLEN);
+      q += BASHFUNC_SUFFLEN;
+    }
+  else
+    {
+      p = (char *)xmalloc (2 + name_len + value_len);
+      memcpy (p, name, name_len);
+      q = p + name_len;
+    }
+
+  q[0] = '=';
   if (value && *value)
-    strcpy (p + name_len + 1, value);
+    {
+      if (isfunc)
+       {
+         t = dequote_escapes (value);
+         value_len = STRLEN (t);
+         memcpy (q + 1, t, value_len + 1);
+         free (t);
+       }
+      else
+       memcpy (q + 1, value, value_len + 1);
+    }
   else
-    p[name_len + 1] = '\0';
+    q[1] = '\0';
+
   return (p);
 }
 
@@ -2928,6 +4693,11 @@ valid_exportstr (v)
   char *s;
 
   s = v->exportstr;
+  if (s == 0)
+    {
+      internal_error (_("%s has null exportstr"), v->name);
+      return (0);
+    }
   if (legal_variable_starter ((unsigned char)*s) == 0)
     {
       internal_error (_("invalid character %d in exportstr for %s"), *s, v->name);
@@ -2970,16 +4740,30 @@ make_env_array_from_var_list (vars)
       /* We don't use the exportstr stuff on Cygwin at all. */
       INVALIDATE_EXPORTSTR (var);
 #endif
+
+      /* If the value is generated dynamically, generate it here. */
+      if (regen_p (var) && var->dynamic_value)
+       {
+         var = (*(var->dynamic_value)) (var);
+         INVALIDATE_EXPORTSTR (var);
+       }
+
       if (var->exportstr)
        value = var->exportstr;
       else if (function_p (var))
        value = named_function_string ((char *)NULL, function_cell (var), 0);
 #if defined (ARRAY_VARS)
       else if (array_p (var))
-#  if 0
-       value = array_to_assignment_string (array_cell (var));
+#  if ARRAY_EXPORT
+       value = array_to_assign (array_cell (var), 0);
 #  else
        continue;       /* XXX array vars cannot yet be exported */
+#  endif /* ARRAY_EXPORT */
+      else if (assoc_p (var))
+#  if 0
+       value = assoc_to_assign (assoc_cell (var), 0);
+#  else
+       continue;       /* XXX associative array vars cannot yet be exported */
 #  endif
 #endif
       else
@@ -2990,7 +4774,7 @@ make_env_array_from_var_list (vars)
          /* Gee, I'd like to get away with not using savestring() if we're
             using the cached exportstr... */
          list[list_index] = USE_EXPORTSTR ? savestring (value)
-                                          : mk_env_string (var->name, value);
+                                          : mk_env_string (var->name, value, function_p (var));
 
          if (USE_EXPORTSTR == 0)
            SAVE_EXPORTSTR (var, list[list_index]);
@@ -3000,7 +4784,7 @@ make_env_array_from_var_list (vars)
 
 #if 0  /* not yet */
 #if defined (ARRAY_VARS)
-         if (array_p (var))
+         if (array_p (var) || assoc_p (var))
            free (value);
 #endif
 #endif
@@ -3021,7 +4805,11 @@ make_var_export_array (vcxt)
   char **list;
   SHELL_VAR **vars;
 
+#if 0
   vars = map_over (visible_and_exported, vcxt);
+#else
+  vars = map_over (export_environment_candidate, vcxt);
+#endif
 
   if (vars == 0)
     return (char **)NULL;
@@ -3056,6 +4844,7 @@ do \
       { \
        export_env_size += 16; \
        export_env = strvec_resize (export_env, export_env_size); \
+       environ = export_env; \
       } \
     export_env[export_env_index++] = (do_alloc) ? savestring (envstr) : envstr; \
     export_env[export_env_index] = (char *)NULL; \
@@ -3143,50 +4932,79 @@ n_shell_variables ()
   return n;
 }
 
+int
+chkexport (name)
+     char *name;
+{
+  SHELL_VAR *v;
+
+  v = find_variable (name);
+  if (v && exported_p (v))
+    {
+      array_needs_making = 1;
+      maybe_make_export_env ();
+      return 1;
+    }
+  return 0;
+}
+
 void
 maybe_make_export_env ()
 {
   register char **temp_array;
   int new_size;
-  VAR_CONTEXT *tcxt;
+  VAR_CONTEXT *tcxt, *icxt;
 
   if (array_needs_making)
     {
       if (export_env)
-        strvec_flush (export_env);
+       strvec_flush (export_env);
 
       /* Make a guess based on how many shell variables and functions we
         have.  Since there will always be array variables, and array
         variables are not (yet) exported, this will always be big enough
         for the exported variables and functions. */
       new_size = n_shell_variables () + HASH_ENTRIES (shell_functions) + 1 +
-                HASH_ENTRIES (temporary_env);
+                HASH_ENTRIES (temporary_env) + HASH_ENTRIES (invalid_env);
       if (new_size > export_env_size)
        {
          export_env_size = new_size;
          export_env = strvec_resize (export_env, export_env_size);
+         environ = export_env;
        }
       export_env[export_env_index = 0] = (char *)NULL;
 
-      /* Make a dummy variable context from the  temporary_env, stick it on
-         the front of shell_variables, call make_var_export_array on the
-         whole thing to flatten it, and convert the list of SHELL_VAR *s
-         to the form needed by the environment. */
+      /* Make a dummy variable context from the temporary_env, stick it on
+        the front of shell_variables, call make_var_export_array on the
+        whole thing to flatten it, and convert the list of SHELL_VAR *s
+        to the form needed by the environment. */
       if (temporary_env)
-        {
-          tcxt = new_var_context ((char *)NULL, 0);
-          tcxt->table = temporary_env;
-          tcxt->down = shell_variables;
-        }
+       {
+         tcxt = new_var_context ((char *)NULL, 0);
+         tcxt->table = temporary_env;
+         tcxt->down = shell_variables;
+       }
+      else
+       tcxt = shell_variables;
+
+      if (invalid_env)
+       {
+         icxt = new_var_context ((char *)NULL, 0);
+         icxt->table = invalid_env;
+         icxt->down = tcxt;
+       }
       else
-        tcxt = shell_variables;
+       icxt = tcxt;
       
-      temp_array = make_var_export_array (tcxt);
+      temp_array = make_var_export_array (icxt);
       if (temp_array)
        add_temp_array_to_env (temp_array, 0, 0);
 
+      if (icxt != tcxt)
+       free (icxt);
+
       if (tcxt != shell_variables)
-        free (tcxt);
+       free (tcxt);
 
 #if defined (RESTRICTED_SHELL)
       /* Restricted shells may not export shell functions. */
@@ -3233,33 +5051,6 @@ put_command_name_into_env (command_name)
   update_export_env_inplace ("_=", 2, command_name);
 }
 
-#if 0  /* UNUSED -- it caused too many problems */
-void
-put_gnu_argv_flags_into_env (pid, flags_string)
-     intmax_t pid;
-     char *flags_string;
-{
-  char *dummy, *pbuf;
-  int l, fl;
-
-  pbuf = itos (pid);
-  l = strlen (pbuf);
-
-  fl = strlen (flags_string);
-
-  dummy = (char *)xmalloc (l + fl + 30);
-  dummy[0] = '_';
-  strcpy (dummy + 1, pbuf);
-  strcpy (dummy + 1 + l, "_GNU_nonoption_argv_flags_");
-  dummy[l + 27] = '=';
-  strcpy (dummy + l + 28, flags_string);
-
-  free (pbuf);
-
-  export_env = add_or_supercede_exported_var (dummy, 0);
-}
-#endif
-
 /* **************************************************************** */
 /*                                                                 */
 /*                   Managing variable contexts                    */
@@ -3322,11 +5113,26 @@ push_var_context (name, flags, tempvars)
      HASH_TABLE *tempvars;
 {
   VAR_CONTEXT *vc;
+  int posix_func_behavior;
+
+  /* As of IEEE Std 1003.1-2017, assignment statements preceding shell
+     functions no longer behave like assignment statements preceding
+     special builtins, and do not persist in the current shell environment.
+     This is austin group interp #654, though nobody implements it yet. */
+#if 0  /* XXX - TAG: bash-5.1 */
+  posix_func_behavior = 0;
+#else
+  posix_func_behavior = posixly_correct;
+#endif
 
   vc = new_var_context (name, flags);
-  vc->table = tempvars;
-  if (tempvars)
+  /* Posix interp 1009, temporary assignments preceding function calls modify
+     the current environment *before* the command is executed. */
+  if (posix_func_behavior && (flags & VC_FUNCENV) && tempvars == temporary_env)
+    merge_temporary_env ();
+  else if (tempvars)
     {
+      vc->table = tempvars;
       /* Have to do this because the temp environment was created before
         variable_context was incremented. */
       flatten (tempvars, set_context, (VARLIST *)NULL, 0);
@@ -3338,28 +5144,92 @@ push_var_context (name, flags, tempvars)
   return (shell_variables = vc);
 }
 
-static void
-push_func_var (data)
-     PTR_T data;
-{
-  SHELL_VAR *var, *v;
+/* This can be called from one of two code paths:
+       1. pop_scope, which implements the posix rules for propagating variable
+          assignments preceding special builtins to the surrounding scope
+          (push_builtin_var);
+       2. pop_var_context, which is called from pop_context and implements the
+          posix rules for propagating variable assignments preceding function
+          calls to the surrounding scope (push_func_var).
 
-  var = (SHELL_VAR *)data;
+  It takes variables out of a temporary environment hash table. We take the
+  variable in data.
+*/
+
+static inline void
+push_posix_tempvar_internal (var, isbltin)
+     SHELL_VAR *var;
+     int isbltin;
+{
+  SHELL_VAR *v;
+  int posix_var_behavior;
+
+  /* As of IEEE Std 1003.1-2017, assignment statements preceding shell
+     functions no longer behave like assignment statements preceding
+     special builtins, and do not persist in the current shell environment.
+     This is austin group interp #654, though nobody implements it yet. */
+#if 0  /* XXX - TAG: bash-5.1 */
+  posix_var_behavior = posixly_correct && isbltin;
+#else
+  posix_var_behavior = posixly_correct;
+#endif
 
-  if (tempvar_p (var) && (posixly_correct || (var->attributes & att_propagate)))
+  if (local_p (var) && STREQ (var->name, "-"))
+    set_current_options (value_cell (var));
+  else if (tempvar_p (var) && (posix_var_behavior || (var->attributes & att_propagate)))
     {
+      /* Make sure we have a hash table to store the variable in while it is
+        being propagated down to the global variables table.  Create one if
+        we have to */
+      if ((vc_isfuncenv (shell_variables) || vc_istempenv (shell_variables)) && shell_variables->table == 0)
+       shell_variables->table = hash_create (VARIABLES_HASH_BUCKETS);
+      v = bind_variable_internal (var->name, value_cell (var), shell_variables->table, 0, 0);
       /* XXX - should we set v->context here? */
-      v = bind_variable_internal (var->name, value_cell (var), shell_variables->table, 0);
+      if (v)
+       v->context = shell_variables->scope;
+#if defined (ARRAY_VARS)
+      if (v && (array_p (var) || assoc_p (var)))
+       {
+         FREE (value_cell (v));
+         if (array_p (var))
+           var_setarray (v, array_copy (array_cell (var)));
+         else
+           var_setassoc (v, assoc_copy (assoc_cell (var)));
+       }
+#endif   
       if (shell_variables == global_variables)
        var->attributes &= ~(att_tempvar|att_propagate);
       else
-        shell_variables->flags |= VC_HASTMPVAR;
-      v->attributes |= var->attributes;
+       shell_variables->flags |= VC_HASTMPVAR;
+      if (v)
+       v->attributes |= var->attributes;
     }
+  else
+    stupidly_hack_special_variables (var->name);       /* XXX */
 
   dispose_variable (var);
 }
 
+static void
+push_func_var (data)
+     PTR_T data;
+{
+  SHELL_VAR *var;
+
+  var = (SHELL_VAR *)data;
+  push_posix_tempvar_internal (var, 0);
+}
+
+static void
+push_builtin_var (data)
+     PTR_T data;
+{
+  SHELL_VAR *var;
+
+  var = (SHELL_VAR *)data;
+  push_posix_tempvar_internal (var, 1);
+}
+
 /* Pop the top context off of VCXT and dispose of it, returning the rest of
    the stack. */
 void
@@ -3398,7 +5268,7 @@ delete_all_contexts (vcxt)
     {
       t = v->down;
       dispose_var_context (v);
-    }            
+    }    
 
   delete_all_variables (global_variables->table);
   shell_variables = global_variables;
@@ -3428,23 +5298,37 @@ push_exported_var (data)
 
   /* If a temp var had its export attribute set, or it's marked to be
      propagated, bind it in the previous scope before disposing it. */
-  if (exported_p (var) || (var->attributes & att_propagate))
+  /* XXX - This isn't exactly right, because all tempenv variables have the
+    export attribute set. */
+  if (tempvar_p (var) && exported_p (var) && (var->attributes & att_propagate))
     {
       var->attributes &= ~att_tempvar;         /* XXX */
-      v = bind_variable_internal (var->name, value_cell (var), shell_variables->table, 0);
+      v = bind_variable_internal (var->name, value_cell (var), shell_variables->table, 0, 0);
       if (shell_variables == global_variables)
        var->attributes &= ~att_propagate;
-      v->attributes |= var->attributes;
+      if (v)
+       {
+         v->attributes |= var->attributes;
+         v->context = shell_variables->scope;
+       }
     }
+  else
+    stupidly_hack_special_variables (var->name);       /* XXX */
 
   dispose_variable (var);
 }
 
+/* This is called to propagate variables in the temporary environment of a
+   special builtin (if IS_SPECIAL != 0) or exported variables that are the
+   result of a builtin like `source' or `command' that can operate on the
+   variables in its temporary environment. In the first case, we call
+   push_builtin_var, which does the right thing. */
 void
 pop_scope (is_special)
      int is_special;
 {
   VAR_CONTEXT *vcxt, *ret;
+  int is_bltinenv;
 
   vcxt = shell_variables;
   if (vc_istempscope (vcxt) == 0)
@@ -3452,6 +5336,7 @@ pop_scope (is_special)
       internal_error (_("pop_scope: head of shell_variables not a temporary environment scope"));
       return;
     }
+  is_bltinenv = vc_isbltnenv (vcxt);   /* XXX - for later */
 
   ret = vcxt->down;
   if (ret)
@@ -3465,7 +5350,7 @@ pop_scope (is_special)
   if (vcxt->table)
     {
       if (is_special)
-       hash_flush (vcxt->table, push_func_var);
+       hash_flush (vcxt->table, push_builtin_var);
       else
        hash_flush (vcxt->table, push_exported_var);
       hash_dispose (vcxt->table);
@@ -3481,12 +5366,65 @@ pop_scope (is_special)
 /*                                                                 */
 /* **************************************************************** */
 
-static WORD_LIST **dollar_arg_stack = (WORD_LIST **)NULL;
+struct saved_dollar_vars {
+  char **first_ten;
+  WORD_LIST *rest;
+};
+
+static struct saved_dollar_vars *dollar_arg_stack = (struct saved_dollar_vars *)NULL;
 static int dollar_arg_stack_slots;
 static int dollar_arg_stack_index;
 
-/* XXX - we might want to consider pushing and popping the `getopts' state
-   when we modify the positional parameters. */
+/* Functions to manipulate dollar_vars array. Need to keep these in sync with
+   whatever remember_args() does. */
+static char **
+save_dollar_vars ()
+{
+  char **ret;
+  int i;
+
+  ret = strvec_create (10);
+  for (i = 1; i < 10; i++)
+    {
+      ret[i] = dollar_vars[i];
+      dollar_vars[i] = (char *)NULL;
+    }
+  return ret;
+}
+
+static void
+restore_dollar_vars (args)
+     char **args;
+{
+  int i;
+
+  for (i = 1; i < 10; i++)
+    dollar_vars[i] = args[i];
+}
+
+static void
+free_dollar_vars ()
+{
+  int i;
+
+  for (i = 1; i < 10; i++)
+    {
+      FREE (dollar_vars[i]);
+      dollar_vars[i] = (char *)NULL;
+    }
+}
+
+static void
+free_saved_dollar_vars (args)
+     char **args;
+{
+  int i;
+
+  for (i = 1; i < 10; i++)
+    FREE (args[i]);
+}
+
+/* XXX - should always be followed by remember_args () */
 void
 push_context (name, is_subshell, tempvars)
      char *name;       /* function name */
@@ -3517,35 +5455,75 @@ push_dollar_vars ()
 {
   if (dollar_arg_stack_index + 2 > dollar_arg_stack_slots)
     {
-      dollar_arg_stack = (WORD_LIST **)
+      dollar_arg_stack = (struct saved_dollar_vars *)
        xrealloc (dollar_arg_stack, (dollar_arg_stack_slots += 10)
-                 * sizeof (WORD_LIST **));
+                 * sizeof (struct saved_dollar_vars));
     }
-  dollar_arg_stack[dollar_arg_stack_index++] = list_rest_of_args ();
-  dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL;
+  
+  dollar_arg_stack[dollar_arg_stack_index].first_ten = save_dollar_vars ();
+  dollar_arg_stack[dollar_arg_stack_index++].rest = rest_of_args;
+  rest_of_args = (WORD_LIST *)NULL;
+  
+  dollar_arg_stack[dollar_arg_stack_index].first_ten = (char **)NULL;
+  dollar_arg_stack[dollar_arg_stack_index].rest = (WORD_LIST *)NULL;  
 }
 
 /* Restore the positional parameters from our stack. */
 void
 pop_dollar_vars ()
 {
-  if (!dollar_arg_stack || dollar_arg_stack_index == 0)
+  if (dollar_arg_stack == 0 || dollar_arg_stack_index == 0)
     return;
 
-  remember_args (dollar_arg_stack[--dollar_arg_stack_index], 1);
-  dispose_words (dollar_arg_stack[dollar_arg_stack_index]);
-  dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL;
+  /* Do what remember_args (xxx, 1) would have done. */
+  free_dollar_vars ();
+  dispose_words (rest_of_args);
+  
+  rest_of_args = dollar_arg_stack[--dollar_arg_stack_index].rest;
+  restore_dollar_vars (dollar_arg_stack[dollar_arg_stack_index].first_ten);
+  free (dollar_arg_stack[dollar_arg_stack_index].first_ten);
+
+  dollar_arg_stack[dollar_arg_stack_index].first_ten = (char **)NULL;
+  dollar_arg_stack[dollar_arg_stack_index].rest = (WORD_LIST *)NULL;
+  
   set_dollar_vars_unchanged ();
+  invalidate_cached_quoted_dollar_at ();
 }
 
 void
 dispose_saved_dollar_vars ()
 {
-  if (!dollar_arg_stack || dollar_arg_stack_index == 0)
+  if (dollar_arg_stack == 0 || dollar_arg_stack_index == 0)
     return;
 
-  dispose_words (dollar_arg_stack[dollar_arg_stack_index]);
-  dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL;
+  dispose_words (dollar_arg_stack[--dollar_arg_stack_index].rest);    
+  free_saved_dollar_vars (dollar_arg_stack[dollar_arg_stack_index].first_ten); 
+  free (dollar_arg_stack[dollar_arg_stack_index].first_ten);
+
+  dollar_arg_stack[dollar_arg_stack_index].first_ten = (char **)NULL;  
+  dollar_arg_stack[dollar_arg_stack_index].rest = (WORD_LIST *)NULL;
+}
+
+/* Initialize BASH_ARGV and BASH_ARGC after turning on extdebug after the
+   shell is initialized */
+void
+init_bash_argv ()
+{
+  if (bash_argv_initialized == 0)
+    {
+      save_bash_argv ();
+      bash_argv_initialized = 1;
+    }
+}
+
+void
+save_bash_argv ()
+{
+  WORD_LIST *list;
+
+  list = list_rest_of_args ();
+  push_args (list);
+  dispose_words (list);
 }
 
 /* Manipulate the special BASH_ARGV and BASH_ARGC variables. */
@@ -3605,12 +5583,6 @@ pop_args ()
  *************************************************/
 
 /* Extern declarations for variables this code has to manage. */
-extern int eof_encountered, eof_encountered_limit, ignoreeof;
-
-#if defined (READLINE)
-extern int no_line_editing;
-extern int hostname_list_initialized;
-#endif
 
 /* An alist of name.function for each special variable.  Most of the
    functions don't do much, and in fact, this would be faster with a
@@ -3626,6 +5598,24 @@ struct name_and_function {
 };
 
 static struct name_and_function special_vars[] = {
+  { "BASH_COMPAT", sv_shcompat },
+  { "BASH_XTRACEFD", sv_xtracefd },
+
+#if defined (JOB_CONTROL)
+  { "CHILD_MAX", sv_childmax },
+#endif
+
+#if defined (READLINE)
+#  if defined (STRICT_POSIX)
+  { "COLUMNS", sv_winsize },
+#  endif
+  { "COMP_WORDBREAKS", sv_comp_wordbreaks },
+#endif
+
+  { "EXECIGNORE", sv_execignore },
+
+  { "FUNCNEST", sv_funcnest },
+
   { "GLOBIGNORE", sv_globignore },
 
 #if defined (HISTORY)
@@ -3636,6 +5626,10 @@ static struct name_and_function special_vars[] = {
   { "HISTTIMEFORMAT", sv_histtimefmt },
 #endif
 
+#if defined (__CYGWIN__)
+  { "HOME", sv_home },
+#endif
+
 #if defined (READLINE)
   { "HOSTFILE", sv_hostfile },
 #endif
@@ -3649,6 +5643,11 @@ static struct name_and_function special_vars[] = {
   { "LC_CTYPE", sv_locale },
   { "LC_MESSAGES", sv_locale },
   { "LC_NUMERIC", sv_locale },
+  { "LC_TIME", sv_locale },
+
+#if defined (READLINE) && defined (STRICT_POSIX)
+  { "LINES", sv_winsize },
+#endif
 
   { "MAIL", sv_mail },
   { "MAILCHECK", sv_mail },
@@ -3669,7 +5668,7 @@ static struct name_and_function special_vars[] = {
   { "TEXTDOMAIN", sv_locale },
   { "TEXTDOMAINDIR", sv_locale },
 
-#if defined (HAVE_TZSET) && defined (PROMPT_STRING_DECODE)
+#if defined (HAVE_TZSET)
   { "TZ", sv_tz },
 #endif
 
@@ -3711,7 +5710,7 @@ find_special_var (name)
       else if (r > 0)
        /* Can't match any of rest of elements in sorted list.  Take this out
           if it causes problems in certain environments. */
-        break;
+       break;
     }
   return -1;
 }
@@ -3737,6 +5736,18 @@ stupidly_hack_special_variables (name)
     (*(special_vars[i].function)) (name);
 }
 
+/* Special variables that need hooks to be run when they are unset as part
+   of shell reinitialization should have their sv_ functions run here. */
+void
+reinit_special_variables ()
+{
+#if defined (READLINE)
+  sv_comp_wordbreaks ("COMP_WORDBREAKS");
+#endif
+  sv_globignore ("GLOBIGNORE");
+  sv_opterr ("OPTERR");
+}
+
 void
 sv_ifs (name)
      char *name;
@@ -3776,15 +5787,51 @@ sv_mail (name)
     }
 }
 
+void
+sv_funcnest (name)
+     char *name;
+{
+  SHELL_VAR *v;
+  intmax_t num;
+
+  v = find_variable (name);
+  if (v == 0)
+    funcnest_max = 0;
+  else if (legal_number (value_cell (v), &num) == 0)
+    funcnest_max = 0;
+  else
+    funcnest_max = num;
+}
+
+/* What to do when EXECIGNORE changes. */
+void
+sv_execignore (name)
+     char *name;
+{
+  setup_exec_ignore (name);
+}
+
 /* What to do when GLOBIGNORE changes. */
 void
 sv_globignore (name)
      char *name;
 {
-  setup_glob_ignore (name);
+  if (privileged_mode == 0)
+    setup_glob_ignore (name);
 }
 
 #if defined (READLINE)
+void
+sv_comp_wordbreaks (name)
+     char *name;
+{
+  SHELL_VAR *sv;
+
+  sv = find_variable (name);
+  if (sv == 0)
+    reset_completer_word_break_chars ();
+}
+
 /* What to do just after one of the TERMxxx variables has changed.
    If we are an interactive shell, then try to reset the terminal
    information in readline. */
@@ -3808,8 +5855,52 @@ sv_hostfile (name)
   else
     hostname_list_initialized = 0;
 }
+
+#if defined (STRICT_POSIX)
+/* In strict posix mode, we allow assignments to LINES and COLUMNS (and values
+   found in the initial environment) to override the terminal size reported by
+   the kernel. */
+void
+sv_winsize (name)
+     char *name;
+{
+  SHELL_VAR *v;
+  intmax_t xd;
+  int d;
+
+  if (posixly_correct == 0 || interactive_shell == 0 || no_line_editing)
+    return;
+
+  v = find_variable (name);
+  if (v == 0 || var_isset (v) == 0)
+    rl_reset_screen_size ();
+  else
+    {
+      if (legal_number (value_cell (v), &xd) == 0)
+       return;
+      winsize_assignment = 1;
+      d = xd;                  /* truncate */
+      if (name[0] == 'L')      /* LINES */
+       rl_set_screen_size (d, -1);
+      else                     /* COLUMNS */
+       rl_set_screen_size (-1, d);
+      winsize_assignment = 0;
+    }
+}
+#endif /* STRICT_POSIX */
 #endif /* READLINE */
 
+/* Update the value of HOME in the export environment so tilde expansion will
+   work on cygwin. */
+#if defined (__CYGWIN__)
+sv_home (name)
+     char *name;
+{
+  array_needs_making = 1;
+  maybe_make_export_env ();
+}
+#endif
+
 #if defined (HISTORY)
 /* What to do after the HISTSIZE or HISTFILESIZE variables change.
    If there is a value for this HISTSIZE (and it is numeric), then stifle
@@ -3823,6 +5914,7 @@ sv_histsize (name)
 {
   char *temp;
   intmax_t num;
+  int hmax;
 
   temp = get_string_value (name);
 
@@ -3830,18 +5922,24 @@ sv_histsize (name)
     {
       if (legal_number (temp, &num))
        {
-         if (name[4] == 'S')
+         hmax = num;
+         if (hmax < 0 && name[4] == 'S')
+           unstifle_history ();        /* unstifle history if HISTSIZE < 0 */
+         else if (name[4] == 'S')
            {
-             stifle_history (num);
-             num = where_history ();
-             if (history_lines_this_session > num)
-               history_lines_this_session = num;
+             stifle_history (hmax);
+             hmax = where_history ();
+             if (history_lines_this_session > hmax)
+               history_lines_this_session = hmax;
            }
-         else
+         else if (hmax >= 0)   /* truncate HISTFILE if HISTFILESIZE >= 0 */
            {
-             history_truncate_file (get_string_value ("HISTFILE"), (int)num);
-             if (num <= history_lines_in_file)
-               history_lines_in_file = num;
+             history_truncate_file (get_string_value ("HISTFILE"), hmax);
+             /* If we just shrank the history file to fewer lines than we've
+                already read, make sure we adjust our idea of how many lines
+                we have read from the file. */
+             if (hmax < history_lines_in_file)
+               history_lines_in_file = hmax;
            }
        }
     }
@@ -3922,17 +6020,22 @@ sv_histtimefmt (name)
 {
   SHELL_VAR *v;
 
-  v = find_variable (name);
+  if (v = find_variable (name))
+    {
+      if (history_comment_char == 0)
+       history_comment_char = '#';
+    }
   history_write_timestamps = (v != 0);
 }
 #endif /* HISTORY */
 
-#if defined (HAVE_TZSET) && defined (PROMPT_STRING_DECODE)
+#if defined (HAVE_TZSET)
 void
 sv_tz (name)
      char *name;
 {
-  tzset ();
+  if (chkexport (name))
+    tzset ();
 }
 #endif
 
@@ -3949,7 +6052,7 @@ sv_ignoreeof (name)
   eof_encountered = 0;
 
   tmp_var = find_variable (name);
-  ignoreeof = tmp_var != 0;
+  ignoreeof = tmp_var && var_isset (tmp_var);
   temp = tmp_var ? value_cell (tmp_var) : (char *)NULL;
   if (temp)
     eof_encountered_limit = (*temp && all_digits (temp)) ? atoi (temp) : 10;
@@ -3960,10 +6063,16 @@ void
 sv_optind (name)
      char *name;
 {
+  SHELL_VAR *var;
   char *tt;
   int s;
 
-  tt = get_string_value ("OPTIND");
+  var = find_variable ("OPTIND");
+  tt = var ? get_variable_value (var) : (char *)NULL;
+
+  /* Assume that if var->context < variable_context and variable_context > 0
+     then we are restoring the variables's previous state while returning
+     from a function. */
   if (tt && *tt)
     {
       s = atoi (tt);
@@ -3992,7 +6101,10 @@ void
 sv_strict_posix (name)
      char *name;
 {
-  SET_INT_VAR (name, posixly_correct);
+  SHELL_VAR *var;
+
+  var = find_variable (name);
+  posixly_correct = var && var_isset (var);
   posix_initialize (posixly_correct);
 #if defined (READLINE)
   if (interactive_shell)
@@ -4006,12 +6118,18 @@ sv_locale (name)
      char *name;
 {
   char *v;
+  int r;
 
   v = get_string_value (name);
   if (name[0] == 'L' && name[1] == 'A')        /* LANG */
-    set_lang (name, v);
+    r = set_lang (name, v);
   else
-    set_locale_var (name, v);          /* LC_*, TEXTDOMAIN* */
+    r = set_locale_var (name, v);              /* LC_*, TEXTDOMAIN* */
+
+#if 1
+  if (r == 0 && posixly_correct)
+    last_command_exit_value = 1;
+#endif
 }
 
 #if defined (ARRAY_VARS)
@@ -4048,7 +6166,7 @@ set_pipestatus_array (ps, nproc)
     {
       ae = element_forw (a->head);
       free (element_value (ae));
-      ae->value = itos (ps[0]);
+      set_element_value (ae, itos (ps[0]));
     }
   else if (array_num_elements (a) <= nproc)
     {
@@ -4058,7 +6176,7 @@ set_pipestatus_array (ps, nproc)
        {
          ae = element_forw (ae);
          free (element_value (ae));
-         ae->value = itos (ps[i]);
+         set_element_value (ae, itos (ps[i]));
        }
       /* add any more */
       for ( ; i < nproc; i++)
@@ -4078,6 +6196,39 @@ set_pipestatus_array (ps, nproc)
        }
     }
 }
+
+ARRAY *
+save_pipestatus_array ()
+{
+  SHELL_VAR *v;
+  ARRAY *a;
+
+  v = find_variable ("PIPESTATUS");
+  if (v == 0 || array_p (v) == 0 || array_cell (v) == 0)
+    return ((ARRAY *)NULL);
+    
+  a = array_copy (array_cell (v));
+
+  return a;
+}
+
+void
+restore_pipestatus_array (a)
+     ARRAY *a;
+{
+  SHELL_VAR *v;
+  ARRAY *a2;
+
+  v = find_variable ("PIPESTATUS");
+  /* XXX - should we still assign even if existing value is NULL? */
+  if (v == 0 || array_p (v) == 0 || array_cell (v) == 0)
+    return;
+
+  a2 = array_cell (v);
+  var_setarray (v, a); 
+
+  array_dispose (a2);
+}
 #endif
 
 void
@@ -4091,3 +6242,106 @@ set_pipestatus_from_exit (s)
   set_pipestatus_array (v, 1);
 #endif
 }
+
+void
+sv_xtracefd (name)
+     char *name;
+{
+  SHELL_VAR *v;
+  char *t, *e;
+  int fd;
+  FILE *fp;
+
+  v = find_variable (name);
+  if (v == 0)
+    {
+      xtrace_reset ();
+      return;
+    }
+
+  t = value_cell (v);
+  if (t == 0 || *t == 0)
+    xtrace_reset ();
+  else
+    {
+      fd = (int)strtol (t, &e, 10);
+      if (e != t && *e == '\0' && sh_validfd (fd))
+       {
+         fp = fdopen (fd, "w");
+         if (fp == 0)
+           internal_error (_("%s: %s: cannot open as FILE"), name, value_cell (v));
+         else
+           xtrace_set (fd, fp);
+       }
+      else
+       internal_error (_("%s: %s: invalid value for trace file descriptor"), name, value_cell (v));
+    }
+}
+
+#define MIN_COMPAT_LEVEL 31
+
+void
+sv_shcompat (name)
+     char *name;
+{
+  SHELL_VAR *v;
+  char *val;
+  int tens, ones, compatval;
+
+  v = find_variable (name);
+  if (v == 0)
+    {
+      shell_compatibility_level = DEFAULT_COMPAT_LEVEL;
+      set_compatibility_opts ();
+      return;
+    }
+  val = value_cell (v);
+  if (val == 0 || *val == '\0')
+    {
+      shell_compatibility_level = DEFAULT_COMPAT_LEVEL;
+      set_compatibility_opts ();
+      return;
+    }
+  /* Handle decimal-like compatibility version specifications: 4.2 */
+  if (ISDIGIT (val[0]) && val[1] == '.' && ISDIGIT (val[2]) && val[3] == 0)
+    {
+      tens = val[0] - '0';
+      ones = val[2] - '0';
+      compatval = tens*10 + ones;
+    }
+  /* Handle integer-like compatibility version specifications: 42 */
+  else if (ISDIGIT (val[0]) && ISDIGIT (val[1]) && val[2] == 0)
+    {
+      tens = val[0] - '0';
+      ones = val[1] - '0';
+      compatval = tens*10 + ones;
+    }
+  else
+    {
+compat_error:
+      internal_error (_("%s: %s: compatibility value out of range"), name, val);
+      shell_compatibility_level = DEFAULT_COMPAT_LEVEL;
+      set_compatibility_opts ();
+      return;
+    }
+
+  if (compatval < MIN_COMPAT_LEVEL || compatval > DEFAULT_COMPAT_LEVEL)
+    goto compat_error;
+
+  shell_compatibility_level = compatval;
+  set_compatibility_opts ();
+}
+
+#if defined (JOB_CONTROL)
+void
+sv_childmax (name)
+     char *name;
+{
+  char *tt;
+  int s;
+
+  tt = get_string_value (name);
+  s = (tt && *tt) ? atoi (tt) : 0;
+  set_maxchild (s);
+}
+#endif