]> git.ipfire.org Git - thirdparty/bash.git/blobdiff - variables.c
Bash-5.2 patch 26: fix typo when specifying readline's custom color prefix
[thirdparty/bash.git] / variables.c
index 92a5a103e4fe8f67d4358c5275e132b8a41f2591..1a0c2c45c053fcce715ba2fa9095c41488994e70 100644 (file)
@@ -1,6 +1,6 @@
 /* variables.c -- Functions for hacking shell variables. */
 
-/* Copyright (C) 1987-2013 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2022 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
 #endif
 #include "bashansi.h"
 #include "bashintl.h"
+#include "filecntl.h"
 
 #define NEED_XTRACE_SET_DECL
 
 #include "shell.h"
+#include "parser.h"
 #include "flags.h"
 #include "execute_cmd.h"
 #include "findcmd.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) */
+
+#if ARRAY_EXPORT
+#define BASHARRAY_PREFIX       "BASH_ARRAY_"
+#define BASHARRAY_PREFLEN      11
+#define BASHARRAY_SUFFIX       "%%"
+#define BASHARRAY_SUFFLEN      2
+
+#define BASHASSOC_PREFIX       "BASH_ASSOC_"
+#define BASHASSOC_PREFLEN      11
+#define BASHASSOC_SUFFIX       "%%"    /* needs to be the same as BASHARRAY_SUFFIX */
+#define BASHASSOC_SUFFLEN      2
+#endif
+
+/* flags for find_variable_internal */
+
+#define FV_FORCETEMPENV                0x01
+#define FV_SKIPINVISIBLE       0x02
+#define FV_NODYNAMIC           0x04
 
 extern char **environ;
 
 /* Variables used here and defined in other files. */
-extern int posixly_correct;
-extern int line_number, line_number_base;
-extern int subshell_environment, indirection_level, subshell_level;
-extern int build_version, patch_level;
-extern int expanding_redir;
-extern int last_command_exit_value;
-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;
-extern int assigning_in_environment;
-extern int executing_builtin;
-extern int funcnest_max;
-
-#if defined (READLINE)
-extern int no_line_editing;
-extern int perform_hostname_completion;
-#endif
+extern struct timeval shellstart;
 
 /* The list of shell variables that the user has created at the global
    scope, or that came from the environment. */
@@ -122,6 +125,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. */
@@ -132,6 +137,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;
@@ -144,6 +158,7 @@ int tempenv_assign_error;
    "$*", "$1", and all the cruft is kept. */
 char *dollar_vars[10];
 WORD_LIST *rest_of_args = (WORD_LIST *)NULL;
+int posparam_count = 0;
 
 /* The value of $$. */
 pid_t dollar_dollar_pid;
@@ -166,132 +181,161 @@ static int export_env_size;
 static int winsize_assignment;         /* currently assigning to LINES or COLUMNS */
 #endif
 
+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));
-static char *get_bash_name __P((void));
-static void initialize_shell_level __P((void));
-static void uidset __P((void));
+static void create_variable_tables PARAMS((void));
+
+static void set_machine_vars PARAMS((void));
+static void set_home_var PARAMS((void));
+static void set_shell_var PARAMS((void));
+static char *get_bash_name PARAMS((void));
+static void initialize_shell_level PARAMS((void));
+static void uidset PARAMS((void));
 #if defined (ARRAY_VARS)
-static void make_vers_array __P((void));
+static void make_vers_array PARAMS((void));
 #endif
 
-static SHELL_VAR *null_assign __P((SHELL_VAR *, char *, arrayind_t, char *));
+static SHELL_VAR *null_assign PARAMS((SHELL_VAR *, char *, arrayind_t, char *));
 #if defined (ARRAY_VARS)
-static SHELL_VAR *null_array_assign __P((SHELL_VAR *, char *, arrayind_t, char *));
+static SHELL_VAR *null_array_assign PARAMS((SHELL_VAR *, char *, arrayind_t, char *));
 #endif
-static SHELL_VAR *get_self __P((SHELL_VAR *));
+static SHELL_VAR *get_self PARAMS((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));
+static SHELL_VAR *init_dynamic_array_var PARAMS((char *, sh_var_value_func_t *, sh_var_assign_func_t *, int));
+static SHELL_VAR *init_dynamic_assoc_var PARAMS((char *, sh_var_value_func_t *, sh_var_assign_func_t *, int));
 #endif
 
-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 inline SHELL_VAR *set_int_value (SHELL_VAR *, intmax_t, int);
+static inline SHELL_VAR *set_string_value (SHELL_VAR *, const char *, int);
+
+static SHELL_VAR *assign_seconds PARAMS((SHELL_VAR *, char *, arrayind_t, char *));
+static SHELL_VAR *get_seconds PARAMS((SHELL_VAR *));
+static SHELL_VAR *init_seconds_var PARAMS((void));
+
+static SHELL_VAR *assign_random PARAMS((SHELL_VAR *, char *, arrayind_t, char *));
+static SHELL_VAR *get_random PARAMS((SHELL_VAR *));
 
-static int brand __P((void));
-static void sbrand __P((unsigned long));               /* set bash random number generator. */
-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 *get_urandom PARAMS((SHELL_VAR *));
 
-static SHELL_VAR *assign_lineno __P((SHELL_VAR *, char *, arrayind_t, char *));
-static SHELL_VAR *get_lineno __P((SHELL_VAR *));
+static SHELL_VAR *assign_lineno PARAMS((SHELL_VAR *, char *, arrayind_t, char *));
+static SHELL_VAR *get_lineno PARAMS((SHELL_VAR *));
 
-static SHELL_VAR *assign_subshell __P((SHELL_VAR *, char *, arrayind_t, char *));
-static SHELL_VAR *get_subshell __P((SHELL_VAR *));
+static SHELL_VAR *assign_subshell PARAMS((SHELL_VAR *, char *, arrayind_t, char *));
+static SHELL_VAR *get_subshell PARAMS((SHELL_VAR *));
 
-static SHELL_VAR *get_bashpid __P((SHELL_VAR *));
+static SHELL_VAR *get_epochseconds PARAMS((SHELL_VAR *));
+static SHELL_VAR *get_epochrealtime PARAMS((SHELL_VAR *));
+
+static SHELL_VAR *get_bashpid PARAMS((SHELL_VAR *));
+
+static SHELL_VAR *get_bash_argv0 PARAMS((SHELL_VAR *));
+static SHELL_VAR *assign_bash_argv0 PARAMS((SHELL_VAR *, char *, arrayind_t, char *));
+static void set_argv0 PARAMS((void));
 
 #if defined (HISTORY)
-static SHELL_VAR *get_histcmd __P((SHELL_VAR *));
+static SHELL_VAR *get_histcmd PARAMS((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 *));
+static SHELL_VAR *get_comp_wordbreaks PARAMS((SHELL_VAR *));
+static SHELL_VAR *assign_comp_wordbreaks PARAMS((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, char *));
-static SHELL_VAR *get_dirstack __P((SHELL_VAR *));
+static SHELL_VAR *assign_dirstack PARAMS((SHELL_VAR *, char *, arrayind_t, char *));
+static SHELL_VAR *get_dirstack PARAMS((SHELL_VAR *));
 #endif
 
 #if defined (ARRAY_VARS)
-static SHELL_VAR *get_groupset __P((SHELL_VAR *));
-
-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 *));
+static SHELL_VAR *get_groupset PARAMS((SHELL_VAR *));
+#  if defined (DEBUGGER)
+static SHELL_VAR *get_bashargcv PARAMS((SHELL_VAR *));
+#  endif
+static SHELL_VAR *build_hashcmd PARAMS((SHELL_VAR *));
+static SHELL_VAR *get_hashcmd PARAMS((SHELL_VAR *));
+static SHELL_VAR *assign_hashcmd PARAMS((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 *));
+static SHELL_VAR *build_aliasvar PARAMS((SHELL_VAR *));
+static SHELL_VAR *get_aliasvar PARAMS((SHELL_VAR *));
+static SHELL_VAR *assign_aliasvar PARAMS((SHELL_VAR *,  char *, arrayind_t, char *));
 #  endif
 #endif
 
-static SHELL_VAR *get_funcname __P((SHELL_VAR *));
-static SHELL_VAR *init_funcname_var __P((void));
+static SHELL_VAR *get_funcname PARAMS((SHELL_VAR *));
+static SHELL_VAR *init_funcname_var PARAMS((void));
+
+static void initialize_dynamic_variables PARAMS((void));
 
-static void initialize_dynamic_variables __P((void));
+static SHELL_VAR *bind_invalid_envvar PARAMS((const char *, char *, int));
 
-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, int));
+static int var_sametype PARAMS((SHELL_VAR *, SHELL_VAR *));
 
-static void dispose_variable_value __P((SHELL_VAR *));
-static void free_variable_hash_data __P((PTR_T));
+static SHELL_VAR *hash_lookup PARAMS((const char *, HASH_TABLE *));
+static SHELL_VAR *new_shell_variable PARAMS((const char *));
+static SHELL_VAR *make_new_variable PARAMS((const char *, HASH_TABLE *));
+static SHELL_VAR *bind_variable_internal PARAMS((const char *, char *, HASH_TABLE *, int, int));
 
-static VARLIST *vlist_alloc __P((int));
-static VARLIST *vlist_realloc __P((VARLIST *, int));
-static void vlist_add __P((VARLIST *, SHELL_VAR *, int));
+static void dispose_variable_value PARAMS((SHELL_VAR *));
+static void free_variable_hash_data PARAMS((PTR_T));
 
-static void flatten __P((HASH_TABLE *, sh_var_map_func_t *, VARLIST *, int));
+static VARLIST *vlist_alloc PARAMS((int));
+static VARLIST *vlist_realloc PARAMS((VARLIST *, int));
+static void vlist_add PARAMS((VARLIST *, SHELL_VAR *, int));
 
-static int qsort_var_comp __P((SHELL_VAR **, SHELL_VAR **));
+static void flatten PARAMS((HASH_TABLE *, sh_var_map_func_t *, VARLIST *, int));
 
-static SHELL_VAR **vapply __P((sh_var_map_func_t *));
-static SHELL_VAR **fapply __P((sh_var_map_func_t *));
+static int qsort_var_comp PARAMS((SHELL_VAR **, SHELL_VAR **));
 
-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 *));
+static SHELL_VAR **vapply PARAMS((sh_var_map_func_t *));
+static SHELL_VAR **fapply PARAMS((sh_var_map_func_t *));
+
+static int visible_var PARAMS((SHELL_VAR *));
+static int visible_and_exported PARAMS((SHELL_VAR *));
+static int export_environment_candidate PARAMS((SHELL_VAR *));
+static int local_and_exported PARAMS((SHELL_VAR *));
+static int visible_variable_in_context PARAMS((SHELL_VAR *));
+static int variable_in_context PARAMS((SHELL_VAR *));
 #if defined (ARRAY_VARS)
-static int visible_array_vars __P((SHELL_VAR *));
+static int visible_array_vars PARAMS((SHELL_VAR *));
 #endif
 
-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 *find_variable_internal PARAMS((const char *, int));
+
+static SHELL_VAR *find_nameref_at_context PARAMS((SHELL_VAR *, VAR_CONTEXT *));
+static SHELL_VAR *find_variable_nameref_context PARAMS((SHELL_VAR *, VAR_CONTEXT *, VAR_CONTEXT **));
+static SHELL_VAR *find_variable_last_nameref_context PARAMS((SHELL_VAR *, VAR_CONTEXT *, VAR_CONTEXT **));
 
-static SHELL_VAR *bind_tempenv_variable __P((const char *, char *));
-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 SHELL_VAR *bind_tempenv_variable PARAMS((const char *, char *));
+static void push_posix_temp_var PARAMS((PTR_T));
+static void push_temp_var PARAMS((PTR_T));
+static void propagate_temp_var PARAMS((PTR_T));
+static void dispose_temporary_env PARAMS((sh_free_func_t *));     
 
-static inline char *mk_env_string __P((const char *, const char *));
-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));
-static void add_temp_array_to_env __P((char **, int, int));
+static inline char *mk_env_string PARAMS((const char *, const char *, int));
+static char **make_env_array_from_var_list PARAMS((SHELL_VAR **));
+static char **make_var_export_array PARAMS((VAR_CONTEXT *));
+static char **make_func_export_array PARAMS((void));
+static void add_temp_array_to_env PARAMS((char **, int, int));
 
-static int n_shell_variables __P((void));
-static int set_context __P((SHELL_VAR *));
+static int n_shell_variables PARAMS((void));
+static int set_context PARAMS((SHELL_VAR *));
 
-static void push_func_var __P((PTR_T));
-static void push_exported_var __P((PTR_T));
+static void push_func_var PARAMS((PTR_T));
+static void push_builtin_var PARAMS((PTR_T));
+static void push_exported_var PARAMS((PTR_T));
 
-static inline int find_special_var __P((const char *));
+static void delete_local_contexts PARAMS((VAR_CONTEXT *));
+
+/* This needs to be looked at again. */
+static inline void push_posix_tempvar_internal PARAMS((SHELL_VAR *, int));
+
+static inline int find_special_var PARAMS((const char *));
 
 static void
 create_variable_tables ()
@@ -300,15 +344,15 @@ create_variable_tables ()
     {
       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
 }
 
@@ -326,7 +370,7 @@ initialize_shell_variables (env, privmode)
 
   create_variable_tables ();
 
-  for (string_index = 0; string = env[string_index++]; )
+  for (string_index = 0; env && (string = env[string_index++]); )
     {
       char_index = 0;
       name = string;
@@ -347,78 +391,143 @@ initialize_shell_variables (env, privmode)
 
       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))
        {
+         size_t namelen;
+         char *tname;          /* desired imported function name */
+
+         namelen = char_index - BASHFUNC_PREFLEN - BASHFUNC_SUFFLEN;
+
+         tname = name + BASHFUNC_PREFLEN;      /* start of func name */
+         tname[namelen] = '\0';                /* now tname == func name */
+
          string_length = strlen (string);
-         temp_string = (char *)xmalloc (3 + string_length + char_index);
+         temp_string = (char *)xmalloc (namelen + string_length + 2);
 
-         strcpy (temp_string, name);
-         temp_string[char_index] = ' ';
-         strcpy (temp_string + char_index + 1, string);
+         memcpy (temp_string, tname, namelen);
+         temp_string[namelen] = ' ';
+         memcpy (temp_string + namelen + 1, string, string_length + 1);
 
          /* Don't import function names that are invalid identifiers from the
-            environment, though we still allow them to be defined as shell
-            variables. */
-         if (legal_identifier (name))
-           parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST|SEVAL_FUNCDEF|SEVAL_ONECMD);
+            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
            {
-             if (temp_var = bind_variable (name, string, 0))
+             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'"), name);
+             last_command_exit_value = EXECUTION_FAILURE;
+             report_error (_("error importing function definition for `%s'"), tname);
            }
+
+         /* Restore original suffix */
+         tname[namelen] = BASHFUNC_SUFFIX[0];
        }
+      else
+#endif /* FUNCTION_IMPORT */
 #if defined (ARRAY_VARS)
 #  if ARRAY_EXPORT
       /* Array variables may not yet be exported. */
-      else if (*string == '(' && string[1] == '[' && string[strlen (string) - 1] == ')')
+      if (STREQN (BASHARRAY_PREFIX, name, BASHARRAY_PREFLEN) &&
+         STREQN (BASHARRAY_SUFFIX, name + char_index - BASHARRAY_SUFFLEN, BASHARRAY_SUFFLEN) &&
+         *string == '(' && string[1] == '[' && string[strlen (string) - 1] == ')')
        {
+         size_t namelen;
+         char *tname;          /* desired imported array variable name */
+
+         namelen = char_index - BASHARRAY_PREFLEN - BASHARRAY_SUFFLEN;
+
+         tname = name + BASHARRAY_PREFLEN;     /* start of variable name */
+         tname[namelen] = '\0';                /* now tname == varname */
+         
          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 (tname, temp_string, 0);
+         FREE (temp_string);
+         if (temp_var)
+           {
+             VSETATTR (temp_var, (att_exported | att_imported));
+             array_needs_making = 1;
+           }
+       }
+      else if (STREQN (BASHASSOC_PREFIX, name, BASHASSOC_PREFLEN) &&
+         STREQN (BASHASSOC_SUFFIX, name + char_index - BASHASSOC_SUFFLEN, BASHASSOC_SUFFLEN) &&
+         *string == '(' && string[1] == '[' && string[strlen (string) - 1] == ')')
+       {
+         size_t namelen;
+         char *tname;          /* desired imported assoc variable name */
+
+         namelen = char_index - BASHASSOC_PREFLEN - BASHASSOC_SUFFLEN;
+
+         tname = name + BASHASSOC_PREFLEN;     /* start of variable name */
+         tname[namelen] = '\0';                /* now tname == varname */
+
+         /* need to make sure it exists as an associative array first */
+         temp_var = find_or_make_array_variable (tname, 2);
+         if (temp_var)
+           {
+             string_length = 1;
+             temp_string = extract_array_assignment_list (string, &string_length);
+             temp_var = assign_array_var_from_string (temp_var, temp_string, 0);
+           }
          FREE (temp_string);
-         VSETATTR (temp_var, (att_exported | att_imported));
-         array_needs_making = 1;
+         if (temp_var)
+           {
+             VSETATTR (temp_var, (att_exported | att_imported));
+             array_needs_making = 1;
+           }
        }
-#  endif /* ARRAY_EXPORT */
-#endif
-#if 0
-      else if (legal_identifier (name))
-#else
       else
+#  endif /* ARRAY_EXPORT */
 #endif
        {
          ro = 0;
-         if (posixly_correct && STREQ (name, "SHELLOPTS"))
+         /* 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);
            }
-         temp_var = bind_variable (name, string, 0);
-         if (temp_var)
+         if (legal_identifier (name))
            {
-             if (legal_identifier (name))
-               VSETATTR (temp_var, (att_exported | att_imported));
-             else
+             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 (ro)
-               VSETATTR (temp_var, att_readonly);
-             array_needs_making = 1;
            }
+         if (temp_var)
+           array_needs_making = 1;
        }
 
       name[char_index] = '=';
@@ -441,14 +550,7 @@ 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__)
   /* set node id -- don't import it from the environment */
@@ -460,7 +562,8 @@ initialize_shell_variables (env, privmode)
     qnx_nidtostr (getnid (), node_name, sizeof (node_name));
 #  endif
     temp_var = bind_variable ("NODE", node_name, 0);
-    set_auto_export (temp_var);
+    if (temp_var)
+      set_auto_export (temp_var);
   }
 #endif
 
@@ -476,7 +579,11 @@ 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", 0);
@@ -500,6 +607,8 @@ initialize_shell_variables (env, privmode)
 
   set_ppid ();
 
+  set_argv0 ();
+
   /* Initialize the `getopts' stuff. */
   temp_var = bind_variable ("OPTIND", "1", 0);
   VSETATTR (temp_var, att_integer);
@@ -552,8 +661,9 @@ initialize_shell_variables (env, privmode)
     }
 #endif /* HISTORY */
 
-  /* Seed the random number generator. */
+  /* Seed the random number generators. */
   seedrand ();
+  seedrand32 ();
 
   /* Handle some "special" variables that we may have inherited from a
      parent shell. */
@@ -576,44 +686,27 @@ initialize_shell_variables (env, privmode)
 #endif /* HISTORY */
 
 #if defined (READLINE) && defined (STRICT_POSIX)
-  /* POSIXLY_CORRECT will only be 1 here if the shell was compiled
-     -DSTRICT_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
-      *
-      * I'm tired of the arguing and bug reports.  Bash now leaves SSH_CLIENT
-      * and SSH2_CLIENT alone.  I'm going to rely on the shell_level check in
-      * isnetconn() to avoid running the startup files more often than wanted.
-      * That will, of course, only work if the user's login shell is bash, so
-      * I've made that behavior conditional on SSH_SOURCE_BASHRC being defined
-      * in config-top.h.
-      */
-#if 0
-  temp_var = find_variable ("SSH_CLIENT");
-  if (temp_var && imported_p (temp_var))
-    {
-      VUNSETATTR (temp_var, att_exported);
-      array_needs_making = 1;
-    }
-  temp_var = find_variable ("SSH2_CLIENT");
-  if (temp_var && imported_p (temp_var))
-    {
-      VUNSETATTR (temp_var, att_exported);
-      array_needs_making = 1;
-    }
-#endif
-
   /* Get the user's real and effective user ids. */
   uidset ();
 
+  temp_var = set_if_not ("BASH_LOADABLES_PATH", DEFAULT_LOADABLE_BUILTINS_PATH);
+
   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 ();
 }
@@ -769,7 +862,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;
@@ -817,16 +910,30 @@ 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))
     {
@@ -846,10 +953,19 @@ set_pwd ()
     }
 
   /* 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, 0);
-  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.  */
@@ -900,7 +1016,7 @@ 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);
@@ -1077,7 +1193,7 @@ print_var_function (var)
    unused otherwise.
 
    dynamic_value is called from find_variable_internal to return a `new'
-   value for the specified dynamic varible.  If this function is NULL,
+   value for the specified dynamic variable.  If this function is NULL,
    the variable is treated as a `normal' shell variable.  If it is not,
    however, then this function is called like this:
        tempvar = (*(var->dynamic_value)) (var);
@@ -1191,6 +1307,39 @@ init_dynamic_assoc_var (name, getfunc, setfunc, attrs)
 }
 #endif
 
+/* Set the string value of VAR to the string representation of VALUE.
+   Right now this takes an INTMAX_T because that's what itos needs. If
+   FLAGS&1, we force the integer attribute on. */
+static inline SHELL_VAR *
+set_int_value (SHELL_VAR *var, intmax_t value, int flags)
+{
+  char *p;
+
+  p = itos (value);
+  FREE (value_cell (var));
+  var_setvalue (var, p);
+  if (flags & 1)
+    VSETATTR (var, att_integer);
+  return (var);
+}
+
+static inline SHELL_VAR *
+set_string_value (SHELL_VAR *var, const char *value, int flags)
+{
+  char *p;
+
+  if (value && *value)
+    p = savestring (value);
+  else
+    {
+      p = (char *)xmalloc (1);
+      p[0] = '\0';
+    }
+  FREE (value_cell (var));
+  var_setvalue (var, p);
+  return (var);
+}
+
 /* The value of $SECONDS.  This is the number of seconds since shell
    invocation, or, the number of seconds since the last assignment + the
    value of the last assignment. */
@@ -1203,10 +1352,17 @@ assign_seconds (self, value, unused, key)
      arrayind_t unused;
      char *key;
 {
-  if (legal_number (value, &seconds_value_assigned) == 0)
-    seconds_value_assigned = 0;
-  shell_start_time = NOW;
-  return (self);
+  intmax_t nval;
+  int expok;
+
+  if (integer_p (self))
+    nval = evalexp (value, 0, &expok);
+  else
+    expok = legal_number (value, &nval);
+  seconds_value_assigned = expok ? nval : 0;
+  gettimeofday (&shellstart, NULL);
+  shell_start_time = shellstart.tv_sec;
+  return (set_int_value (self, nval, integer_p (self) != 0));
 }
 
 static SHELL_VAR *
@@ -1214,16 +1370,11 @@ get_seconds (var)
      SHELL_VAR *var;
 {
   time_t time_since_start;
-  char *p;
-
-  time_since_start = NOW - shell_start_time;
-  p = itos(seconds_value_assigned + time_since_start);
-
-  FREE (value_cell (var));
+  struct timeval tv;
 
-  VSETATTR (var, att_integer);
-  var_setvalue (var, p);
-  return (var);
+  gettimeofday(&tv, NULL);
+  time_since_start = tv.tv_sec - shell_start_time;
+  return (set_int_value (var, seconds_value_assigned + time_since_start, 1));
 }
 
 static SHELL_VAR *
@@ -1240,55 +1391,11 @@ init_seconds_var ()
   INIT_DYNAMIC_VAR ("SECONDS", (v ? value_cell (v) : (char *)NULL), get_seconds, assign_seconds);
   return v;      
 }
-     
-/* The random number seed.  You can change this by setting RANDOM. */
-static unsigned long rseed = 1;
-static int last_random_value;
-static int seeded_subshell = 0;
-
-/* 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. */
-
-/* Returns a pseudo-random number between 0 and 32767. */
-static int
-brand ()
-{
-  /* 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 */
-  long h, l;
-
-  /* Can't seed with 0. */
-  if (rseed == 0)
-    rseed = 123459876;
-  h = rseed / 127773;
-  l = rseed % 127773;
-  rseed = 16807 * l - 2836 * h;
-#if 0
-  if (rseed < 0)
-    rseed += 0x7fffffff;
-#endif
-  return ((unsigned int)(rseed & 32767));      /* was % 32768 */
-}
-
-/* Set the random number generator seed to SEED. */
-static void
-sbrand (seed)
-     unsigned long seed;
-{
-  rseed = seed;
-  last_random_value = 0;
-}
 
-static void
-seedrand ()
-{
-  struct timeval tv;
+/* Functions for $RANDOM and $SRANDOM */
 
-  gettimeofday (&tv, NULL);
-  sbrand (tv.tv_sec ^ tv.tv_usec ^ getpid ());
-}
+int last_random_value;
+static int seeded_subshell = 0;
 
 static SHELL_VAR *
 assign_random (self, value, unused, key)
@@ -1297,10 +1404,19 @@ assign_random (self, value, unused, key)
      arrayind_t unused;
      char *key;
 {
-  sbrand (strtoul (value, (char **)NULL, 10));
+  intmax_t seedval;
+  int expok;
+
+  if (integer_p (self))
+    seedval = evalexp (value, 0, &expok);
+  else
+    expok = legal_number (value, &seedval);
+  if (expok == 0)
+    return (self);
+  sbrand (seedval);
   if (subshell_environment)
     seeded_subshell = getpid ();
-  return (self);
+  return (set_int_value (self, seedval, integer_p (self) != 0));
 }
 
 int
@@ -1319,7 +1435,8 @@ get_random_number ()
   do
     rv = brand ();
   while (rv == last_random_value);
-  return rv;
+
+  return (last_random_value = rv);
 }
 
 static SHELL_VAR *
@@ -1327,17 +1444,19 @@ get_random (var)
      SHELL_VAR *var;
 {
   int rv;
-  char *p;
 
   rv = get_random_number ();
-  last_random_value = rv;
-  p = itos (rv);
+  return (set_int_value (var, rv, 1));
+}
 
-  FREE (value_cell (var));
+static SHELL_VAR *
+get_urandom (var)
+     SHELL_VAR *var;
+{
+  u_bits32_t rv;
 
-  VSETATTR (var, att_integer);
-  var_setvalue (var, p);
-  return (var);
+  rv = get_urandom32 ();
+  return (set_int_value (var, rv, 1));
 }
 
 static SHELL_VAR *
@@ -1352,7 +1471,7 @@ assign_lineno (var, value, unused, key)
   if (value == 0 || *value == '\0' || legal_number (value, &new_value) == 0)
     new_value = 0;
   line_number = line_number_base = new_value;
-  return var;
+  return (set_int_value (var, line_number, integer_p (var) != 0));
 }
 
 /* Function which returns the current line number. */
@@ -1360,14 +1479,10 @@ static SHELL_VAR *
 get_lineno (var)
      SHELL_VAR *var;
 {
-  char *p;
   int ln;
 
   ln = executing_line_number ();
-  p = itos (ln);
-  FREE (value_cell (var));
-  var_setvalue (var, p);
-  return (var);
+  return (set_int_value (var, ln, 0));
 }
 
 static SHELL_VAR *
@@ -1389,12 +1504,32 @@ static SHELL_VAR *
 get_subshell (var)
      SHELL_VAR *var;
 {
-  char *p;
+  return (set_int_value (var, subshell_level, 0));
+}
 
-  p = itos (subshell_level);
-  FREE (value_cell (var));
-  var_setvalue (var, p);
-  return (var);
+static SHELL_VAR *
+get_epochseconds (var)
+     SHELL_VAR *var;
+{
+  intmax_t now;
+
+  now = NOW;
+  return (set_int_value (var, now, 0));
+}
+
+static SHELL_VAR *
+get_epochrealtime (var)
+     SHELL_VAR *var;
+{
+  char buf[32];
+  struct timeval tv;
+
+  gettimeofday (&tv, NULL);
+  snprintf (buf, sizeof (buf), "%u%c%06u", (unsigned)tv.tv_sec,
+                                          locale_decpoint (),
+                                          (unsigned)tv.tv_usec);
+
+  return (set_string_value (var, buf, 0));
 }
 
 static SHELL_VAR *
@@ -1402,33 +1537,62 @@ get_bashpid (var)
      SHELL_VAR *var;
 {
   int pid;
-  char *p;
 
   pid = getpid ();
-  p = itos (pid);
+  return (set_int_value (var, pid, 1));
+}
 
-  FREE (value_cell (var));
-  VSETATTR (var, att_integer|att_readonly);
-  var_setvalue (var, p);
-  return (var);
+static SHELL_VAR *
+get_bash_argv0 (var)
+     SHELL_VAR *var;
+{
+  return (set_string_value (var, dollar_vars[0], 0));
+}
+
+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 void
+set_argv0 ()
+{
+  SHELL_VAR *v;
+
+  v = find_variable ("BASH_ARGV0");
+  if (v && imported_p (v))
+    assign_bash_argv0 (v, value_cell (v), 0, 0);
+}
+  
 static SHELL_VAR *
 get_bash_command (var)
      SHELL_VAR *var;
 {
   char *p;
 
-  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);
+  p = the_printed_command_except_trap ? the_printed_command_except_trap : "";
+  return (set_string_value (var, p, 0));
 }
 
 #if defined (HISTORY)
@@ -1436,12 +1600,17 @@ static SHELL_VAR *
 get_histcmd (var)
      SHELL_VAR *var;
 {
-  char *p;
+  int n;
 
-  p = itos (history_number ());
-  FREE (value_cell (var));
-  var_setvalue (var, p);
-  return (var);
+  /* Do the same adjustment here we do in parse.y:prompt_history_number,
+     assuming that we are in one of two states: decoding this as part of
+     the prompt string, in which case we do not want to assume that the
+     command has been saved to the history and the history number incremented,
+     or the expansion is part of the current command being executed and has
+     already been saved to history and the history number incremented.
+     Right now we use EXECUTING as the determinant. */
+  n = history_number () - executing;
+  return (set_int_value (var, n, 0));
 }
 #endif
 
@@ -1455,10 +1624,7 @@ get_comp_wordbreaks (var)
   if (rl_completer_word_break_characters == 0 && bash_readline_initialized == 0)
     enable_hostname_completion (perform_hostname_completion);
 
-  FREE (value_cell (var));
-  var_setvalue (var, savestring (rl_completer_word_break_characters));
-
-  return (var);
+  return (set_string_value (var, rl_completer_word_break_characters, 0));
 }
 
 /* When this function returns, rl_completer_word_break_characters points to
@@ -1472,7 +1638,7 @@ assign_comp_wordbreaks (self, value, unused, key)
 {
   if (rl_completer_word_break_characters &&
       rl_completer_word_break_characters != rl_basic_word_break_characters)
-    free (rl_completer_word_break_characters);
+    free ((void *)rl_completer_word_break_characters);
 
   rl_completer_word_break_characters = savestring (value);
   return self;
@@ -1529,6 +1695,27 @@ get_groupset (self)
   return (self);
 }
 
+#  if defined (DEBUGGER)
+static SHELL_VAR *
+get_bashargcv (self)
+     SHELL_VAR *self;
+{
+  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 */
+    {
+      self_semaphore = 1;
+      init_bash_argv ();
+      self_semaphore = 0;
+    }
+  return self;
+}
+#  endif
+
 static SHELL_VAR *
 build_hashcmd (self)
      SHELL_VAR *self;
@@ -1578,6 +1765,28 @@ assign_hashcmd (self, value, ind, key)
      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));
 }
@@ -1632,6 +1841,11 @@ assign_aliasvar (self, value, ind, key)
      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));
 }
@@ -1646,13 +1860,8 @@ 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);
-    }
+    return (set_string_value (self, this_shell_function->name, 0));
 #endif
   return (self);
 }
@@ -1697,16 +1906,25 @@ 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 ("SRANDOM", (char *)NULL, get_urandom, (sh_var_assign_func_t *)NULL);
+  VSETATTR (v, att_integer);  
   INIT_DYNAMIC_VAR ("LINENO", (char *)NULL, get_lineno, assign_lineno);
-  VSETATTR (v, att_integer);
+  VSETATTR (v, att_regenerate);
 
   INIT_DYNAMIC_VAR ("BASHPID", (char *)NULL, get_bashpid, null_assign);
-  VSETATTR (v, att_integer|att_readonly);
+  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);
@@ -1725,8 +1943,8 @@ 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|att_nounset);
-  v = init_dynamic_array_var ("BASH_ARGV", get_self, null_array_assign, att_noassign|att_nounset);
+  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|att_nounset);
   v = init_dynamic_array_var ("BASH_LINENO", get_self, null_array_assign, att_noassign|att_nounset);
@@ -1746,6 +1964,22 @@ initialize_dynamic_variables ()
 /*                                                                 */
 /* **************************************************************** */
 
+#if 0  /* not yet */
+int
+var_isset (var)
+     SHELL_VAR *var;
+{
+  return (var->value != 0);
+}
+
+int
+var_isunset (var)
+     SHELL_VAR *var;
+{
+  return (var->value == 0);
+}
+#endif
+
 /* How to get a pointer to the shell variable or function named NAME.
    HASHED_VARS is a pointer to the hash table containing the list
    of interest (either variables or functions). */
@@ -1789,16 +2023,18 @@ var_lookup (name, vcontext)
 */
 
 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
@@ -1809,22 +2045,24 @@ find_variable_internal (name, force_tempenv)
   if (search_tempenv && temporary_env)         
     var = hash_lookup (name, temporary_env);
 
-  vc = shell_variables;
-#if 0
-if (search_tempenv == 0 && /* (subshell_environment & SUBSHELL_COMSUB) && */
-    expanding_redir &&
-    (this_shell_builtin == eval_builtin || this_shell_builtin == command_builtin))
-  {
-  itrace("find_variable_internal: search_tempenv == 0: skipping VC_BLTNENV");
-  while (vc && (vc->flags & VC_BLTNENV))
-    vc = vc->down;
-  if (vc == 0)
-    vc = shell_variables;
-  }
-#endif
-
   if (var == 0)
-    var = var_lookup (name, vc);
+    {
+      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);
@@ -1838,7 +2076,7 @@ SHELL_VAR *
 find_variable_nameref (v)
      SHELL_VAR *v;
 {
-  int level;
+  int level, flags;
   char *newname;
   SHELL_VAR *orig, *oldv;
 
@@ -1853,10 +2091,21 @@ find_variable_nameref (v)
       if (newname == 0 || *newname == '\0')
        return ((SHELL_VAR *)0);
       oldv = v;
-      v = find_variable_internal (newname, (expanding_redir == 0 && (assigning_in_environment || executing_builtin)));
+      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);
        }
     }
@@ -1865,12 +2114,13 @@ find_variable_nameref (v)
 
 /* Resolve the chain of nameref variables for NAME.  XXX - could change later */
 SHELL_VAR *
-find_variable_last_nameref (name)
+find_variable_last_nameref (name, vflags)
      const char *name;
+     int vflags;
 {
   SHELL_VAR *v, *nv;
   char *newname;
-  int level;
+  int level, flags;
 
   nv = v = find_variable_noref (name);
   level = 0;
@@ -1881,17 +2131,22 @@ find_variable_last_nameref (name)
         return ((SHELL_VAR *)0);       /* error message here? */
       newname = nameref_cell (v);
       if (newname == 0 || *newname == '\0')
-       return ((SHELL_VAR *)0);
+       return ((vflags && invisible_p (v)) ? v : (SHELL_VAR *)0);
       nv = v;
-      v = find_variable_internal (newname, (expanding_redir == 0 && (assigning_in_environment || executing_builtin)));
+      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)
+find_global_variable_last_nameref (name, vflags)
      const char *name;
+     int vflags;
 {
   SHELL_VAR *v, *nv;
   char *newname;
@@ -1906,8 +2161,9 @@ find_global_variable_last_nameref (name)
         return ((SHELL_VAR *)0);       /* error message here? */
       newname = nameref_cell (v);
       if (newname == 0 || *newname == '\0')
-       return ((SHELL_VAR *)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;
@@ -1919,7 +2175,6 @@ find_nameref_at_context (v, vc)
      VAR_CONTEXT *vc;
 {
   SHELL_VAR *nv, *nv2;
-  VAR_CONTEXT *nvc;
   char *newname;
   int level;
 
@@ -1929,7 +2184,7 @@ find_nameref_at_context (v, vc)
     {
       level++;
       if (level > NAMEREF_MAX)
-        return ((SHELL_VAR *)NULL);
+        return (&nameref_maxloop_value);
       newname = nameref_cell (nv);
       if (newname == 0 || *newname == '\0')
         return ((SHELL_VAR *)NULL);      
@@ -1959,6 +2214,8 @@ find_variable_nameref_context (v, vc, nvcp)
   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;
@@ -1988,6 +2245,8 @@ find_variable_last_nameref_context (v, vc, nvcp)
   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;
@@ -1997,6 +2256,88 @@ find_variable_last_nameref_context (v, vc, nvcp)
   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-existent 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)
@@ -2004,7 +2345,7 @@ find_variable_tempenv (name)
 {
   SHELL_VAR *var;
 
-  var = find_variable_internal (name, 1);
+  var = find_variable_internal (name, FV_FORCETEMPENV);
   if (var && nameref_p (var))
     var = find_variable_nameref (var);
   return (var);
@@ -2031,7 +2372,7 @@ find_global_variable (name)
 
   var = var_lookup (name, global_variables);
   if (var && nameref_p (var))
-    var = find_variable_nameref (var);
+    var = find_variable_nameref (var); /* XXX - find_global_variable_noref? */
 
   if (var == 0)
     return ((SHELL_VAR *)NULL);
@@ -2075,9 +2416,52 @@ find_variable (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;
+}
+
+/* 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;
-  v = find_variable_internal (name, (expanding_redir == 0 && (assigning_in_environment || executing_builtin)));
+  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;
@@ -2088,8 +2472,12 @@ find_variable_noref (name)
      const char *name;
 {
   SHELL_VAR *v;
+  int flags;
 
-  v = find_variable_internal (name, (expanding_redir == 0 && (assigning_in_environment || executing_builtin)));
+  flags = 0;
+  if (expanding_redir == 0 && (assigning_in_environment || executing_builtin))
+    flags |= FV_FORCETEMPENV;
+  v = find_variable_internal (name, flags);
   return v;
 }
 
@@ -2135,9 +2523,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;
@@ -2162,6 +2550,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)
@@ -2180,19 +2604,33 @@ set_if_not (name, value)
 
 /* 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
@@ -2206,11 +2644,23 @@ make_local_variable (name)
      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 */
+      /* 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;
+
       return (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)
@@ -2256,12 +2706,43 @@ make_local_variable (name)
         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;
@@ -2270,7 +2751,9 @@ make_local_variable (name)
   if (ifsname (name))
     setifs (new_var);
 
-  if (was_tmpvar == 0)
+  /* 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 && value_cell (new_var) == 0)
     VSETATTR (new_var, att_invisible); /* XXX */
   return (new_var);
 }
@@ -2340,21 +2823,42 @@ make_new_array_variable (name)
 }
 
 SHELL_VAR *
-make_local_array_variable (name, assoc_ok)
+make_local_array_variable (name, flags)
      char *name;
-     int assoc_ok;
+     int flags;
 {
   SHELL_VAR *var;
   ARRAY *array;
+  int assoc_ok;
 
-  var = make_local_variable (name);
+  assoc_ok = flags & MKLOC_ASSOCOK;
+
+  var = make_local_variable (name, flags & MKLOC_INHERIT);     /* 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;
 
-  array = array_create ();
+  /* Validate any value we inherited from a variable instance at a previous
+     scope and discard anything that's invalid. */
+  if (localvar_inherit && assoc_p (var))
+    {
+      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);
+    }
 
-  dispose_variable_value (var);
-  var_setarray (var, array);
   VSETATTR (var, att_array);
   return var;
 }
@@ -2367,7 +2871,7 @@ make_new_assoc_variable (name)
   HASH_TABLE *hash;
 
   entry = make_new_variable (name, global_variables->table);
-  hash = assoc_create (0);
+  hash = assoc_create (ASSOC_HASH_BUCKETS);
 
   var_setassoc (entry, hash);
   VSETATTR (entry, att_assoc);
@@ -2375,20 +2879,42 @@ make_new_assoc_variable (name)
 }
 
 SHELL_VAR *
-make_local_assoc_variable (name)
+make_local_assoc_variable (name, flags)
      char *name;
+     int flags;
 {
   SHELL_VAR *var;
   HASH_TABLE *hash;
+  int array_ok;
+
+  array_ok = flags & MKLOC_ARRAYOK;
 
-  var = make_local_variable (name);
-  if (var == 0 || assoc_p (var))
+  var = make_local_variable (name, flags & MKLOC_INHERIT);     /* 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;
 
-  dispose_variable_value (var);
-  hash = assoc_create (0);
+  /* Validate any value we inherited from a variable instance at a previous
+     scope and discard anything that's invalid. */
+  if (localvar_inherit && array_p (var))
+    {
+      internal_warning (_("%s: cannot inherit value from incompatible type"), name);
+      VUNSETATTR (var, att_array);
+      dispose_variable_value (var);
+      hash = assoc_create (ASSOC_HASH_BUCKETS);
+      var_setassoc (var, hash);
+    }
+  else if (localvar_inherit)
+    var = convert_var_to_assoc (var);          /* XXX */
+  else
+    {
+      dispose_variable_value (var);
+      hash = assoc_create (ASSOC_HASH_BUCKETS);
+      var_setassoc (var, hash);
+    }
 
-  var_setassoc (var, hash);
   VSETATTR (var, att_assoc);
   return var;
 }
@@ -2408,24 +2934,37 @@ make_variable_value (var, value, flags)
      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))
     {
       if (flags & ASS_APPEND)
        {
          oval = value_cell (var);
-         lval = evalexp (oval, &expok);        /* ksh93 seems to do this */
+         lval = evalexp (oval, 0, &expok);     /* ksh93 seems to do this */
          if (expok == 0)
            {
-             top_level_cleanup ();
-             jump_to_top_level (DISCARD);
+             if (flags & ASS_NOLONGJMP)
+               goto make_value;
+             else
+               {
+                 top_level_cleanup ();
+                 jump_to_top_level (DISCARD);
+               }
            }
        }
-      rval = evalexp (value, &expok);
+      rval = evalexp (value, 0, &expok);
       if (expok == 0)
        {
-         top_level_cleanup ();
-         jump_to_top_level (DISCARD);
+         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. */
@@ -2434,7 +2973,7 @@ make_variable_value (var, value, flags)
       retval = itos (rval);
     }
 #if defined (CASEMOD_ATTRS)
-  else if (capcase_p (var) || uppercase_p (var) || lowercase_p (var))
+  else if ((flags & ASS_NOEVAL) == 0 && (capcase_p (var) || uppercase_p (var) || lowercase_p (var)))
     {
       if (flags & ASS_APPEND)
        {
@@ -2463,6 +3002,7 @@ make_variable_value (var, value, flags)
 #endif /* CASEMOD_ATTRS */
   else if (value)
     {
+make_value:
       if (flags & ASS_APPEND)
        {
          oval = get_variable_value (var);
@@ -2488,8 +3028,55 @@ make_variable_value (var, value, flags)
   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, aflags)
      const char *name;
@@ -2497,8 +3084,8 @@ bind_variable_internal (name, value, table, hflags, aflags)
      HASH_TABLE *table;
      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 */
@@ -2508,36 +3095,76 @@ bind_variable_internal (name, value, table, hflags, aflags)
       /* 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);      /* XXX */
+       entry = find_variable_last_nameref (name, 0);   /* XXX */
       if (entry == 0)                                  /* just in case */
         return (entry);
     }
 
-  /* The first clause handles `declare -n ref; ref=x;' */
+  /* The first clause handles `declare -n ref; ref=x;' or `declare -n ref;
+     declare -n ref' */
   if (entry && invisible_p (entry) && nameref_p (entry))
-    goto assign_value;
+    {
+      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);
+      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] */
-      if (valid_array_reference (newval))
-        /* XXX - should it be aflags? */
-       entry = assign_array_element (newval, make_variable_value (entry, value, 0), aflags);
+      /* 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);
+
+         /* entry == nameref variable; tentry == array variable;
+            newval == x[2]; value = bar
+            We don't need to call make_variable_value here, since
+            assign_array_element will eventually do it itself based on
+            newval and aflags. */
+
+         entry = assign_array_element (newval, value, aflags|ASS_NAMEREF, (array_eltstate_t *)0);
+         if (entry == 0)
+           return entry;
+       }
       else
 #endif
-      {
-      entry = make_new_variable (newval, table);
-      var_setvalue (entry, make_variable_value (entry, value, 0));
-      }
+       {
+         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, 0)); /* XXX */
+      var_setvalue (entry, make_variable_value (entry, value, aflags)); /* XXX */
     }
   else if (entry->assign_func) /* array vars have assign functions now */
     {
+      if ((readonly_p (entry) && (aflags & ASS_FORCE) == 0) || noassign_p (entry))
+       {
+         if (readonly_p (entry))
+           err_readonly (name_cell (entry));
+         return (entry);
+       }
+
       INVALIDATE_EXPORTSTR (entry);
       newval = (aflags & ASS_APPEND) ? make_variable_value (entry, value, aflags) : value;
       if (assoc_p (entry))
@@ -2553,22 +3180,37 @@ bind_variable_internal (name, value, table, hflags, aflags)
   else
     {
 assign_value:
-      if (readonly_p (entry) || noassign_p (entry))
+      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);
 
+      /* 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 */
@@ -2618,7 +3260,6 @@ bind_variable (name, value, flags)
 {
   SHELL_VAR *v, *nv;
   VAR_CONTEXT *vc, *nvc;
-  int level;
 
   if (shell_variables == 0)
     create_variable_tables ();
@@ -2628,7 +3269,7 @@ bind_variable (name, value, flags)
      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. */
@@ -2640,6 +3281,9 @@ bind_variable (name, value, flags)
          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)
                {
@@ -2651,11 +3295,26 @@ bind_variable (name, value, flags)
                         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, (array_eltstate_t *)0));
+                     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);
+                     return (bind_global_variable (v->name, value, flags));
+                   }
                  else
                    v = nv;
                }
+             else if (nv == &nameref_maxloop_value)
+               {
+                 internal_warning (_("%s: circular name reference"), v->name);
+                 return (bind_global_variable (v->name, value, flags));
+               }
              else
                v = nv;
            }
@@ -2673,10 +3332,6 @@ bind_global_variable (name, value, flags)
      char *value;
      int flags;
 {
-  SHELL_VAR *v, *nv;
-  VAR_CONTEXT *vc, *nvc;
-  int level;
-
   if (shell_variables == 0)
     create_variable_tables ();
 
@@ -2684,6 +3339,17 @@ bind_global_variable (name, value, flags)
   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
    value, variables are no longer invisible.  This is a duplicate of part
    of the internals of bind_variable.  If the variable is exported, or
@@ -2713,11 +3379,20 @@ bind_variable_value (var, value, aflags)
   else
     {
       t = make_variable_value (var, value, aflags);
-#if defined (ARRAY_VARS)
-      if ((aflags & ASS_NAMEREF) && (t == 0 || *t == 0 || (legal_identifier (t) == 0 && valid_array_reference (t) == 0)))
-#else
-      if ((aflags & ASS_NAMEREF) && (t == 0 || *t == 0 || legal_identifier (t) == 0))
-#endif
+      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)
@@ -2750,18 +3425,34 @@ bind_variable_value (var, value, aflags)
    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;
-  int isint, isarr, implicitarray;
+  int isint, isarr, implicitarray, vflags, avflags;
 
   isint = isarr = implicitarray = 0;
 #if defined (ARRAY_VARS)
-  if (valid_array_reference (lhs))
+  /* Don't rely on VA_NOEXPAND being 1, set it explicitly */
+  vflags = (flags & ASS_NOEXPAND) ? VA_NOEXPAND : 0;
+  if (flags & ASS_ONEWORD)
+    vflags |= VA_ONEWORD;
+  if (valid_array_reference (lhs, vflags))
     {
       isarr = 1;
-      v = array_variable_part (lhs, (char **)0, (int *)0);
+      avflags = 0;
+      /* Common code to translate between assignment and reference flags. */
+      if (flags & ASS_NOEXPAND)
+       avflags |= AV_NOEXPAND;
+      if (flags & ASS_ONEWORD)
+       avflags |= AV_ONEWORD;
+      v = array_variable_part (lhs, avflags, (char **)0, (int *)0);
+    }
+  else if (legal_identifier (lhs) == 0)
+    {
+      sh_invalidid (lhs);
+      return ((SHELL_VAR *)NULL);      
     }
   else
 #endif
@@ -2779,30 +3470,36 @@ bind_int_variable (lhs, rhs)
 
 #if defined (ARRAY_VARS)
   if (isarr)
-    v = assign_array_element (lhs, rhs, 0);
+    v = assign_array_element (lhs, rhs, flags, (array_eltstate_t *)0);
   else if (implicitarray)
-    v = bind_array_variable (lhs, 0, rhs, 0);
+    v = bind_array_variable (lhs, 0, rhs, 0);  /* XXX - check on flags */
   else
 #endif
-    v = bind_variable (lhs, rhs, 0);
-
-  if (v && isint)
-    VSETATTR (v, att_integer);
+    v = bind_variable (lhs, rhs, 0);   /* why not use bind_variable_value? */
 
-  VUNSETATTR (v, att_invisible);
+  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);
 }
 
 SHELL_VAR *
-bind_var_to_int (var, val)
+bind_var_to_int (var, val, flags)
      char *var;
      intmax_t val;
+     int flags;
 {
   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, flags));
 }
 
 /* Do a function binding to a variable.  You pass the name and
@@ -2853,22 +3550,27 @@ bind_function (name, value)
 
 #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;
@@ -2892,7 +3594,7 @@ assign_in_env (word, flags)
      int flags;
 {
   int offset, aflags;
-  char *name, *temp, *value;
+  char *name, *temp, *value, *newname;
   SHELL_VAR *var;
   const char *string;
 
@@ -2900,7 +3602,7 @@ assign_in_env (word, flags)
 
   aflags = 0;
   offset = assignment (string, 0);
-  name = savestring (string);
+  newname = name = savestring (string);
   value = (char *)NULL;
 
   if (name[offset] == '=')
@@ -2914,7 +3616,34 @@ assign_in_env (word, flags)
          aflags |= ASS_APPEND;
        }
 
+      if (legal_identifier (name) == 0)
+       {
+         sh_invalidid (name);
+         free (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))
@@ -2922,12 +3651,17 @@ assign_in_env (word, flags)
          free (name);
          return (0);
        }
-
       temp = name + offset + 1;
+
       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;
@@ -2937,15 +3671,15 @@ assign_in_env (word, flags)
   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';
     }
 
@@ -2954,12 +3688,16 @@ assign_in_env (word, flags)
   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 (flags)
-    stupidly_hack_special_variables (name);
+    {
+      if (STREQ (newname, "POSIXLY_CORRECT") || STREQ (newname, "POSIX_PEDANDTIC"))
+       save_posix_options ();          /* XXX one level of saving right now */
+      stupidly_hack_special_variables (newname);
+    }
 
   if (echo_command_at_execute)
     /* The Korn shell prints the `+ ' in front of assignment statements,
@@ -3091,6 +3829,67 @@ unbind_nameref (name)
   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
+unbind_global_variable (name)
+     const char *name;
+{
+  SHELL_VAR *v, *nv;
+  int r;
+
+  v = var_lookup (name, global_variables);
+  /* This starts at the current scope, just like find_global_variable; should we
+     use find_global_variable_nameref here? */
+  nv = (v && nameref_p (v)) ? find_variable_nameref (v) : (SHELL_VAR *)NULL;
+
+  r = nv ? makunbound (nv->name, shell_variables) : makunbound (name, global_variables);
+  return r;
+}
+
+int
+unbind_global_variable_noref (name)
+     const char *name;
+{
+  SHELL_VAR *v;
+
+  v = var_lookup (name, global_variables);
+  if (v)
+    return makunbound (name, global_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 -2;
+    }
+  else if (v && non_unsettable_p (v))
+    {
+      internal_error (_("%s: cannot unset"), name);
+      return -2;
+    }
+  return (unbind_variable (name));
+}
+
 /* Unset the shell function named NAME. */
 int
 unbind_func (name)
@@ -3202,7 +4001,8 @@ 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);
@@ -3300,7 +4100,7 @@ delete_all_variables (hashed_vars)
       if (!entry) \
        { \
          entry = bind_variable (name, "", 0); \
-         if (!no_invisible_vars && entry) entry->attributes |= att_invisible; \
+         if (entry) entry->attributes |= att_invisible; \
        } \
     } \
   while (0)
@@ -3618,12 +4418,20 @@ local_exported_variables ()
 static int
 variable_in_context (var)
      SHELL_VAR *var;
+{
+  return (local_p (var) && var->context == variable_context);
+}
+
+static int
+visible_variable_in_context (var)
+     SHELL_VAR *var;
 {
   return (invisible_p (var) == 0 && local_p (var) && var->context == variable_context);
 }
 
 SHELL_VAR **
-all_local_variables ()
+all_local_variables (visible_only)
+     int visible_only;
 {
   VARLIST *vlist;
   SHELL_VAR **ret;
@@ -3644,7 +4452,10 @@ all_local_variables ()
     
   vlist = vlist_alloc (HASH_ENTRIES (vc->table));
 
-  flatten (vc->table, variable_in_context, vlist, 0);
+  if (visible_only)
+    flatten (vc->table, visible_variable_in_context, vlist, 0);
+  else
+    flatten (vc->table, variable_in_context, vlist, 0);
 
   ret = vlist->list;
   free (vlist);
@@ -3659,7 +4470,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 **
@@ -3733,8 +4544,65 @@ find_tempenv_variable (name)
 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;
+
+  /* Just like do_assignment_internal(). This makes assignments preceding
+     special builtins act like standalone assignment statements when in
+     posix mode, satisfying the posix requirement that this affect the
+     "current execution environment." */
+  v = bind_variable (var->name, value_cell (var), ASS_FORCE|ASS_NOLONGJMP);
+
+  /* XXX - do we need to worry about array variables here? */
+
+  /* If this modifies an existing local variable, v->context will be non-zero.
+     If it comes back with v->context == 0, we bound at the global context.
+     Set binding_table appropriately. It doesn't matter whether it's correct
+     if the variable is local, only that it's not global_variables->table */
+  binding_table = v->context ? shell_variables->table : global_variables->table;
+
+  /* global variables are no longer temporary and don't need propagating. */
+  if (v->context == 0)
+    var->attributes &= ~(att_tempvar|att_propagate);
+
+  if (v)
+    {
+      v->attributes |= var->attributes;                /* preserve tempvar attribute if appropriate */
+      /* If we don't bind a local variable, propagate the value. If we bind a
+        local variable (the "current execution environment"), keep it as local
+        and don't propagate it to the calling environment. */
+      if (v->context > 0 && local_p (v) == 0)
+       v->attributes |= att_propagate;
+      else
+       v->attributes &= ~att_propagate;
+    }
+
+  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;
@@ -3749,24 +4617,28 @@ 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, 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
     {
-      var->attributes |= att_propagate;
+      var->attributes |= att_propagate;                        /* XXX - propagate more than once? */
       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);
@@ -3774,6 +4646,10 @@ push_temp_var (data)
   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;
@@ -3803,21 +4679,21 @@ dispose_temporary_env (pushf)
      sh_free_func_t *pushf;
 {
   int i;
+  HASH_TABLE *disposer;
 
   tempvar_list = strvec_create (HASH_ENTRIES (temporary_env) + 1);
   tempvar_list[tvlist_ind = 0] = 0;
-    
-  hash_flush (temporary_env, pushf);
-  hash_dispose (temporary_env);
+
+  disposer = temporary_env;
   temporary_env = (HASH_TABLE *)NULL;
 
+  hash_flush (disposer, pushf);
+  hash_dispose (disposer);
+
   tempvar_list[tvlist_ind] = 0;
 
   array_needs_making = 1;
 
-#if 0
-  sv_ifs ("IFS");              /* XXX here for now -- check setifs in assign_in_env */  
-#endif
   for (i = 0; i < tvlist_ind; i++)
     stupidly_hack_special_variables (tempvar_list[i]);
 
@@ -3837,14 +4713,37 @@ dispose_used_env_vars ()
 }
 
 /* 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 (posixly_correct ? push_posix_temp_var : push_temp_var);
+}
+
+/* Temporary function to use if we want to separate function and special
+   builtin behavior. */
+void
+merge_function_temporary_env ()
 {
   if (temporary_env)
     dispose_temporary_env (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;
+    }
+}
+
 /* **************************************************************** */
 /*                                                                 */
 /*          Creating and manipulating the environment              */
@@ -3852,21 +4751,91 @@ merge_temporary_env ()
 /* **************************************************************** */
 
 static inline char *
-mk_env_string (name, value)
+mk_env_string (name, value, attributes)
      const char *name, *value;
+     int attributes;
 {
-  int name_len, value_len;
-  char *p;
+  size_t name_len, value_len;
+  char *p, *q, *t;
+  int isfunc, isarray;
 
   name_len = strlen (name);
   value_len = STRLEN (value);
-  p = (char *)xmalloc (2 + name_len + value_len);
-  strcpy (p, name);
-  p[name_len] = '=';
+
+  isfunc = attributes & att_function;
+#if defined (ARRAY_VARS) && defined (ARRAY_EXPORT)
+  isarray = attributes & (att_array|att_assoc);
+#endif
+
+  /* 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;
+    }
+#if defined (ARRAY_VARS) && defined (ARRAY_EXPORT)
+  else if (isarray && value)
+    {
+      if (attributes & att_assoc)
+       p = (char *)xmalloc (BASHASSOC_PREFLEN + name_len + BASHASSOC_SUFFLEN + value_len + 2);
+      else
+       p = (char *)xmalloc (BASHARRAY_PREFLEN + name_len + BASHARRAY_SUFFLEN + value_len + 2);
+      q = p;
+      if (attributes & att_assoc)
+       {
+         memcpy (q, BASHASSOC_PREFIX, BASHASSOC_PREFLEN);
+         q += BASHASSOC_PREFLEN;
+       }
+      else
+       {
+         memcpy (q, BASHARRAY_PREFIX, BASHARRAY_PREFLEN);
+         q += BASHARRAY_PREFLEN;
+       }
+      memcpy (q, name, name_len);
+      q += name_len;
+      /* These are actually the same currently */
+      if (attributes & att_assoc)
+        {
+         memcpy (q, BASHASSOC_SUFFIX, BASHASSOC_SUFFLEN);
+         q += BASHARRAY_SUFFLEN;
+        }
+      else
+        {
+         memcpy (q, BASHARRAY_SUFFIX, BASHARRAY_SUFFLEN);
+         q += BASHARRAY_SUFFLEN;
+        }
+    }
+#endif  
+  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);
 }
 
@@ -3908,6 +4877,12 @@ valid_exportstr (v)
 }
 #endif
 
+#if defined (ARRAY_VARS)
+#  define USE_EXPORTSTR (value == var->exportstr && array_p (var) == 0 && assoc_p (var) == 0)
+#else
+#  define USE_EXPORTSTR (value == var->exportstr)
+#endif
+
 static char **
 make_env_array_from_var_list (vars)
      SHELL_VAR **vars;
@@ -3918,14 +4893,20 @@ make_env_array_from_var_list (vars)
 
   list = strvec_create ((1 + strvec_len ((char **)vars)));
 
-#define USE_EXPORTSTR (value == var->exportstr)
-
   for (i = 0, list_index = 0; var = vars[i]; i++)
     {
 #if defined (__CYGWIN__)
       /* 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))
@@ -3933,16 +4914,16 @@ make_env_array_from_var_list (vars)
 #if defined (ARRAY_VARS)
       else if (array_p (var))
 #  if ARRAY_EXPORT
-       value = array_to_assignment_string (array_cell (var));
+       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_assignment_string (assoc_cell (var));
+#  if ARRAY_EXPORT
+       value = assoc_to_assign (assoc_cell (var), 0);
 #  else
        continue;       /* XXX associative array vars cannot yet be exported */
-#  endif
+#  endif /* ARRAY_EXPORT */
 #endif
       else
        value = value_cell (var);
@@ -3952,7 +4933,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, var->attributes);
 
          if (USE_EXPORTSTR == 0)
            SAVE_EXPORTSTR (var, list[list_index]);
@@ -3960,11 +4941,9 @@ make_env_array_from_var_list (vars)
          list_index++;
 #undef USE_EXPORTSTR
 
-#if 0  /* not yet */
-#if defined (ARRAY_VARS)
+#if defined (ARRAY_VARS) && defined (ARRAY_EXPORT)
          if (array_p (var) || assoc_p (var))
            free (value);
-#endif
 #endif
        }
     }
@@ -4028,7 +5007,7 @@ do \
     export_env[export_env_index] = (char *)NULL; \
   } while (0)
 
-/* Add ASSIGN to EXPORT_ENV, or supercede a previous assignment in the
+/* Add ASSIGN to EXPORT_ENV, or supersede a previous assignment in the
    array with the same left-hand side.  Return the new EXPORT_ENV. */
 char **
 add_or_supercede_exported_var (assign, do_alloc)
@@ -4131,7 +5110,7 @@ maybe_make_export_env ()
 {
   register char **temp_array;
   int new_size;
-  VAR_CONTEXT *tcxt;
+  VAR_CONTEXT *tcxt, *icxt;
 
   if (array_needs_making)
     {
@@ -4143,7 +5122,7 @@ maybe_make_export_env ()
         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;
@@ -4164,11 +5143,23 @@ maybe_make_export_env ()
        }
       else
        tcxt = shell_variables;
+
+      if (invalid_env)
+       {
+         icxt = new_var_context ((char *)NULL, 0);
+         icxt->table = invalid_env;
+         icxt->down = tcxt;
+       }
+      else
+       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);
 
@@ -4279,13 +5270,25 @@ 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. */
+  posix_func_behavior = 0;
 
   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. */
+      /* XXX - only need to do it if flags&VC_FUNCENV */
       flatten (tempvars, set_context, (VARLIST *)NULL, 0);
       vc->flags |= VC_HASTMPVAR;
     }
@@ -4295,35 +5298,111 @@ push_var_context (name, flags, tempvars)
   return (shell_variables = vc);
 }
 
-static void
-push_func_var (data)
-     PTR_T data;
+/* 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 -- isbltin == 1);
+       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 -- isbltin == 0)
+
+  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 *var, *v;
+  SHELL_VAR *v;
+  int posix_var_behavior;
 
-  var = (SHELL_VAR *)data;
+  /* 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. */
+  posix_var_behavior = posixly_correct && isbltin;
+  v = 0;
 
-  if (tempvar_p (var) && (posixly_correct || (var->attributes & att_propagate)))
+  if (local_p (var) && STREQ (var->name, "-"))
+    {
+      set_current_options (value_cell (var));
+      set_shellopts ();
+    }
+  /* This takes variable assignments preceding special builtins that can execute
+     multiple commands (source, eval, etc.) and performs the equivalent of
+     an assignment statement to modify the closest enclosing variable (the
+     posix "current execution environment"). This makes the behavior the same
+     as push_posix_temp_var; but the circumstances of calling are slightly
+     different. */
+  else if (tempvar_p (var) && posix_var_behavior)
+    {
+      /* similar to push_posix_temp_var */
+      v = bind_variable (var->name, value_cell (var), ASS_FORCE|ASS_NOLONGJMP);
+      if (v)
+       {
+         v->attributes |= var->attributes;
+         if (v->context == 0)
+           v->attributes &= ~(att_tempvar|att_propagate);
+         /* XXX - set att_propagate here if v->context > 0? */
+       }
+    }
+  else if (tempvar_p (var) && propagate_p (var))
     {
       /* 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 (0);
-      /* XXX - should we set v->context here? */
+       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? */
+      if (v)
+       v->context = shell_variables->scope;
       if (shell_variables == global_variables)
        var->attributes &= ~(att_tempvar|att_propagate);
       else
        shell_variables->flags |= VC_HASTMPVAR;
-      v->attributes |= var->attributes;
+      if (v)
+       v->attributes |= var->attributes;
     }
   else
     stupidly_hack_special_variables (var->name);       /* XXX */
 
+#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   
+
   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
@@ -4350,10 +5429,8 @@ pop_var_context ()
     internal_error (_("pop_var_context: no global_variables context"));
 }
 
-/* Delete the HASH_TABLEs for all variable contexts beginning at VCXT, and
-   all of the VAR_CONTEXTs except GLOBAL_VARIABLES. */
-void
-delete_all_contexts (vcxt)
+static void
+delete_local_contexts (vcxt)
      VAR_CONTEXT *vcxt;
 {
   VAR_CONTEXT *v, *t;
@@ -4362,12 +5439,30 @@ delete_all_contexts (vcxt)
     {
       t = v->down;
       dispose_var_context (v);
-    }    
+    }
+}
 
+/* Delete the HASH_TABLEs for all variable contexts beginning at VCXT, and
+   all of the VAR_CONTEXTs except GLOBAL_VARIABLES. */
+void
+delete_all_contexts (vcxt)
+     VAR_CONTEXT *vcxt;
+{
+  delete_local_contexts (vcxt);
   delete_all_variables (global_variables->table);
   shell_variables = global_variables;
 }
 
+/* Reset the context so we are not executing in a shell function. Only call
+   this if you are getting ready to exit the shell. */
+void
+reset_local_contexts ()
+{
+  delete_local_contexts (shell_variables);
+  shell_variables = global_variables;
+  variable_context = 0;
+}
+
 /* **************************************************************** */
 /*                                                                 */
 /*        Pushing and Popping temporary variable scopes            */
@@ -4394,17 +5489,17 @@ push_exported_var (data)
      propagated, bind it in the previous scope before disposing it. */
   /* XXX - This isn't exactly right, because all tempenv variables have the
     export attribute set. */
-#if 0
-  if (exported_p (var) || (var->attributes & att_propagate))
-#else
   if (tempvar_p (var) && exported_p (var) && (var->attributes & att_propagate))
-#endif
     {
       var->attributes &= ~att_tempvar;         /* XXX */
       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 */
@@ -4412,11 +5507,17 @@ push_exported_var (data)
   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)
@@ -4424,6 +5525,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)
@@ -4437,7 +5539,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);
@@ -4453,12 +5555,77 @@ pop_scope (is_special)
 /*                                                                 */
 /* **************************************************************** */
 
-static WORD_LIST **dollar_arg_stack = (WORD_LIST **)NULL;
+struct saved_dollar_vars {
+  char **first_ten;
+  WORD_LIST *rest;
+  int count;
+};
+
+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]);
+}
+
+/* Do what remember_args (xxx, 1) would have done. */
+void
+clear_dollar_vars ()
+{
+  free_dollar_vars ();
+  dispose_words (rest_of_args);
+
+  rest_of_args = (WORD_LIST *)NULL;
+  posparam_count = 0;
+}
+
+/* XXX - should always be followed by remember_args () */
 void
 push_context (name, is_subshell, tempvars)
      char *name;       /* function name */
@@ -4489,35 +5656,79 @@ 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].count = posparam_count;
+  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;
+  posparam_count = 0;
+  
+  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;
+  /* Wipe out current values */
+  clear_dollar_vars ();
+
+  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);
+  posparam_count = dollar_arg_stack[dollar_arg_stack_index].count;
+
+  dollar_arg_stack[dollar_arg_stack_index].first_ten = (char **)NULL;
+  dollar_arg_stack[dollar_arg_stack_index].rest = (WORD_LIST *)NULL;
+  dollar_arg_stack[dollar_arg_stack_index].count = 0;
+
   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;
+  dollar_arg_stack[dollar_arg_stack_index].count = 0;
+}
+
+/* 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. */
@@ -4560,7 +5771,7 @@ pop_args ()
   GET_ARRAY_FROM_VAR ("BASH_ARGV", bash_argv_v, bash_argv_a);
   GET_ARRAY_FROM_VAR ("BASH_ARGC", bash_argc_v, bash_argc_a);
 
-  ce = array_shift (bash_argc_a, 1, 0);
+  ce = array_unshift_element (bash_argc_a);
   if (ce == 0 || legal_number (element_value (ce), &i) == 0)
     i = 0;
 
@@ -4577,11 +5788,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 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
@@ -4611,6 +5817,8 @@ static struct name_and_function special_vars[] = {
   { "COMP_WORDBREAKS", sv_comp_wordbreaks },
 #endif
 
+  { "EXECIGNORE", sv_execignore },
+
   { "FUNCNEST", sv_funcnest },
 
   { "GLOBIGNORE", sv_globignore },
@@ -4800,6 +6008,14 @@ sv_funcnest (name)
     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)
@@ -4861,7 +6077,7 @@ sv_winsize (name)
     return;
 
   v = find_variable (name);
-  if (v == 0 || var_isnull (v))
+  if (v == 0 || var_isset (v) == 0)
     rl_reset_screen_size ();
   else
     {
@@ -4924,7 +6140,10 @@ sv_histsize (name)
          else if (hmax >= 0)   /* truncate HISTFILE if HISTFILESIZE >= 0 */
            {
              history_truncate_file (get_string_value ("HISTFILE"), hmax);
-             if (hmax <= history_lines_in_file)
+             /* 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;
            }
        }
@@ -5020,8 +6239,19 @@ void
 sv_tz (name)
      char *name;
 {
-  if (chkexport (name))
-    tzset ();
+  SHELL_VAR *v;
+
+  v = find_variable (name);
+  if (v && exported_p (v))
+    array_needs_making = 1;
+  else if (v == 0)
+    array_needs_making = 1;
+
+  if (array_needs_making)
+    {
+      maybe_make_export_env ();  
+      tzset ();
+    }
 }
 #endif
 
@@ -5038,7 +6268,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;
@@ -5049,10 +6279,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);
@@ -5081,7 +6317,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)
@@ -5105,7 +6344,7 @@ sv_locale (name)
 
 #if 1
   if (r == 0 && posixly_correct)
-    last_command_exit_value = 1;
+    set_exit_status (EXECUTION_FAILURE);
 #endif
 }
 
@@ -5141,19 +6380,27 @@ set_pipestatus_array (ps, nproc)
   /* Fast case */
   if (array_num_elements (a) == nproc && nproc == 1)
     {
+#ifndef ALT_ARRAY_IMPLEMENTATION
       ae = element_forw (a->head);
-      free (element_value (ae));
-      ae->value = itos (ps[0]);
+#else
+      ae = a->elements[0];
+#endif
+      ARRAY_ELEMENT_REPLACE (ae, itos (ps[0]));
     }
   else if (array_num_elements (a) <= nproc)
     {
       /* modify in array_num_elements members in place, then add */
+#ifndef ALT_ARRAY_IMPLEMENTATION
       ae = a->head;
+#endif
       for (i = 0; i < array_num_elements (a); i++)
        {
+#ifndef ALT_ARRAY_IMPLEMENTATION
          ae = element_forw (ae);
-         free (element_value (ae));
-         ae->value = itos (ps[i]);
+#else
+         ae = a->elements[i];
+#endif
+         ARRAY_ELEMENT_REPLACE (ae, itos (ps[i]));
        }
       /* add any more */
       for ( ; i < nproc; i++)
@@ -5164,13 +6411,32 @@ set_pipestatus_array (ps, nproc)
     }
   else
     {
+#ifndef ALT_ARRAY_IMPLEMENTATION
       /* deleting elements.  it's faster to rebuild the array. */        
       array_flush (a);
-      for (i = 0; ps[i] != -1; i++)
+      for (i = 0; i < nproc; i++)
        {
          t = inttostr (ps[i], tbuf, sizeof (tbuf));
          array_insert (a, i, t);
        }
+#else
+      /* deleting elements. replace the first NPROC, free the rest */
+      for (i = 0; i < nproc; i++)
+       {
+         ae = a->elements[i];
+         ARRAY_ELEMENT_REPLACE (ae, itos (ps[i]));
+       }
+      for ( ; i <= array_max_index (a); i++)
+       {
+         array_dispose_element (a->elements[i]);
+         a->elements[i] = (ARRAY_ELEMENT *)NULL;
+       }
+
+      /* bookkeeping usually taken care of by array_insert */
+      set_max_index (a, nproc - 1);
+      set_first_index (a, 0);
+      set_num_elements (a, nproc);
+#endif /* ALT_ARRAY_IMPLEMENTATION */
     }
 }
 
@@ -5178,16 +6444,15 @@ ARRAY *
 save_pipestatus_array ()
 {
   SHELL_VAR *v;
-  ARRAY *a, *a2;
+  ARRAY *a;
 
   v = find_variable ("PIPESTATUS");
   if (v == 0 || array_p (v) == 0 || array_cell (v) == 0)
     return ((ARRAY *)NULL);
     
-  a = array_cell (v);
-  a2 = array_copy (array_cell (v));
+  a = array_copy (array_cell (v));
 
-  return a2;
+  return a;
 }
 
 void
@@ -5281,14 +6546,14 @@ sv_shcompat (name)
       return;
     }
   /* Handle decimal-like compatibility version specifications: 4.2 */
-  if (isdigit (val[0]) && val[1] == '.' && isdigit (val[2]) && val[3] == 0)
+  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)
+  else if (ISDIGIT (val[0]) && ISDIGIT (val[1]) && val[2] == 0)
     {
       tens = val[0] - '0';
       ones = val[1] - '0';