]> git.ipfire.org Git - thirdparty/bash.git/blobdiff - variables.c
prep for bash-4.3 release
[thirdparty/bash.git] / variables.c
index 615aacd14f12eb482857879fc347e461e39dcef7..7bb850fabdf2a5bb553256cbc6760078f9fc80fb 100644 (file)
@@ -1,40 +1,50 @@
 /* variables.c -- Functions for hacking shell variables. */
 
-/* Copyright (C) 1987,1989 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2010 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
-   Bash is free software; you can redistribute it and/or modify it
-   under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2, or (at your option)
-   any later version.
+   Bash is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
 
-   Bash is distributed in the hope that it will be useful, but WITHOUT
-   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
-   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
-   License for more details.
+   Bash is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
-   along with Bash; see the file COPYING.  If not, write to the Free
-   Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
+   along with Bash.  If not, see <http://www.gnu.org/licenses/>.
+*/
 
 #include "config.h"
 
 #include "bashtypes.h"
 #include "posixstat.h"
+#include "posixtime.h"
 
-#if defined (qnx)
-#  include <sys/vc.h>
-#endif
+#if defined (__QNX__)
+#  if defined (__QNXNTO__)
+#    include <sys/netmgr.h>
+#  else
+#    include <sys/vc.h>
+#  endif /* !__QNXNTO__ */
+#endif /* __QNX__ */
 
 #if defined (HAVE_UNISTD_H)
 #  include <unistd.h>
 #endif
 
 #include <stdio.h>
-#include <ctype.h>
-#include <pwd.h>
+#include "chartypes.h"
+#if defined (HAVE_PWD_H)
+#  include <pwd.h>
+#endif
 #include "bashansi.h"
+#include "bashintl.h"
+
+#define NEED_XTRACE_SET_DECL
 
 #include "shell.h"
 #include "flags.h"
@@ -42,6 +52,9 @@
 #include "findcmd.h"
 #include "mailcheck.h"
 #include "input.h"
+#include "hashcmd.h"
+#include "pathexp.h"
+#include "alias.h"
 
 #include "builtins/getopt.h"
 #include "builtins/common.h"
 #  include "pcomplete.h"
 #endif
 
+#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')
+
+extern char **environ;
+
 /* Variables used here and defined in other files. */
 extern int posixly_correct;
-extern int variable_context, line_number;
-extern int interactive, interactive_shell, login_shell;
-extern int subshell_environment, indirection_level;
+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 char *dist_version, *release_status;
 extern char *shell_name;
 extern char *primary_prompt, *secondary_prompt;
 extern char *current_host_name;
-extern Function *this_shell_builtin;
+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;
 
-/* The list of shell variables that the user has created, or that came from
-   the environment. */
-HASH_TABLE *shell_variables = (HASH_TABLE *)NULL;
+#if defined (READLINE)
+extern int no_line_editing;
+extern int perform_hostname_completion;
+#endif
+
+/* The list of shell variables that the user has created at the global
+   scope, or that came from the environment. */
+VAR_CONTEXT *global_variables = (VAR_CONTEXT *)NULL;
+
+/* The current list of shell variables, including function scopes */
+VAR_CONTEXT *shell_variables = (VAR_CONTEXT *)NULL;
 
 /* The list of shell functions that the user has created, or that came from
    the environment. */
 HASH_TABLE *shell_functions = (HASH_TABLE *)NULL;
 
+#if defined (DEBUGGER)
+/* The table of shell function definitions that the user defined or that
+   came from the environment. */
+HASH_TABLE *shell_function_defs = (HASH_TABLE *)NULL;
+#endif
+
 /* The current variable context.  This is really a count of how deep into
    executing functions we are. */
 int variable_context = 0;
 
-/* The array of shell assignments which are made only in the environment
+/* The set of shell assignments which are made only in the environment
    for a single command. */
-char **temporary_env = (char **)NULL;
-
-/* The array of shell assignments which are in the environment for the
-   execution of a shell function. */
-char **function_env = (char **)NULL;
+HASH_TABLE *temporary_env = (HASH_TABLE *)NULL;
 
-/* The array of shell assignments which are made only in the environment
-   for the execution of a shell builtin command which may cause more than
-   one command to be executed (e.g., "source"). */
-char **builtin_env = (char **)NULL;
+/* Set to non-zero if an assignment error occurs while putting variables
+   into the temporary environment. */
+int tempenv_assign_error;
 
 /* Some funky variables which are known about specially.  Here is where
    "$*", "$1", and all the cruft is kept. */
@@ -108,7 +141,14 @@ char *dollar_vars[10];
 WORD_LIST *rest_of_args = (WORD_LIST *)NULL;
 
 /* The value of $$. */
-int dollar_dollar_pid;
+pid_t dollar_dollar_pid;
+
+/* Non-zero means that we have to remake EXPORT_ENV. */
+int array_needs_making = 1;
+
+/* The number of times BASH has been executed.  This is set
+   by initialize_variables (). */
+int shell_level = 0;
 
 /* An array which is passed to commands as their environment.  It is
    manufactured from the union of the initial environment and the
@@ -117,30 +157,149 @@ char **export_env = (char **)NULL;
 static int export_env_index;
 static int export_env_size;
 
-/* Non-zero means that we have to remake EXPORT_ENV. */
-int array_needs_making = 1;
+#if defined (READLINE)
+static int winsize_assignment;         /* currently assigning to LINES or COLUMNS */
+#endif
 
-/* The number of times BASH has been executed.  This is set
-   by initialize_variables (). */
-int shell_level = 0;
+/* 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));
+#if defined (ARRAY_VARS)
+static void make_vers_array __P((void));
+#endif
 
-static char *have_local_variables;
-static int local_variable_stack_size;
+static SHELL_VAR *null_assign __P((SHELL_VAR *, char *, arrayind_t, char *));
+#if defined (ARRAY_VARS)
+static SHELL_VAR *null_array_assign __P((SHELL_VAR *, char *, arrayind_t, char *));
+#endif
+static SHELL_VAR *get_self __P((SHELL_VAR *));
 
-/* Some forward declarations. */
-static void set_home_var ();
-static void set_shell_var ();
-static char *get_bash_name ();
-static void initialize_shell_level ();
-static void uidset ();
-static void initialize_dynamic_variables ();
-static void make_vers_array ();
-static void sbrand ();         /* set bash random number generator. */
-static int qsort_var_comp ();
-
-/* Make VAR be auto-exported.  VAR is a pointer to a SHELL_VAR. */
-#define set_auto_export(var) \
-  do { var->attributes |= att_exported; array_needs_making = 1; } while (0)
+#if defined (ARRAY_VARS)
+static SHELL_VAR *init_dynamic_array_var __P((char *, sh_var_value_func_t *, sh_var_assign_func_t *, int));
+static SHELL_VAR *init_dynamic_assoc_var __P((char *, sh_var_value_func_t *, sh_var_assign_func_t *, int));
+#endif
+
+static SHELL_VAR *assign_seconds __P((SHELL_VAR *, char *, arrayind_t, char *));
+static SHELL_VAR *get_seconds __P((SHELL_VAR *));
+static SHELL_VAR *init_seconds_var __P((void));
+
+static int brand __P((void));
+static void sbrand __P((unsigned long));               /* set bash random number generator. */
+static void seedrand __P((void));                      /* seed generator randomly */
+static SHELL_VAR *assign_random __P((SHELL_VAR *, char *, arrayind_t, char *));
+static SHELL_VAR *get_random __P((SHELL_VAR *));
+
+static SHELL_VAR *assign_lineno __P((SHELL_VAR *, char *, arrayind_t, char *));
+static SHELL_VAR *get_lineno __P((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 *get_bashpid __P((SHELL_VAR *));
+
+#if defined (HISTORY)
+static SHELL_VAR *get_histcmd __P((SHELL_VAR *));
+#endif
+
+#if defined (READLINE)
+static SHELL_VAR *get_comp_wordbreaks __P((SHELL_VAR *));
+static SHELL_VAR *assign_comp_wordbreaks __P((SHELL_VAR *, char *, arrayind_t, char *));
+#endif
+
+#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS)
+static SHELL_VAR *assign_dirstack __P((SHELL_VAR *, char *, arrayind_t, char *));
+static SHELL_VAR *get_dirstack __P((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 *));
+#  if defined (ALIAS)
+static SHELL_VAR *build_aliasvar __P((SHELL_VAR *));
+static SHELL_VAR *get_aliasvar __P((SHELL_VAR *));
+static SHELL_VAR *assign_aliasvar __P((SHELL_VAR *,  char *, arrayind_t, char *));
+#  endif
+#endif
+
+static SHELL_VAR *get_funcname __P((SHELL_VAR *));
+static SHELL_VAR *init_funcname_var __P((void));
+
+static void initialize_dynamic_variables __P((void));
+
+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 void dispose_variable_value __P((SHELL_VAR *));
+static void free_variable_hash_data __P((PTR_T));
+
+static VARLIST *vlist_alloc __P((int));
+static VARLIST *vlist_realloc __P((VARLIST *, int));
+static void vlist_add __P((VARLIST *, SHELL_VAR *, int));
+
+static void flatten __P((HASH_TABLE *, sh_var_map_func_t *, VARLIST *, int));
+
+static int qsort_var_comp __P((SHELL_VAR **, SHELL_VAR **));
+
+static SHELL_VAR **vapply __P((sh_var_map_func_t *));
+static SHELL_VAR **fapply __P((sh_var_map_func_t *));
+
+static int visible_var __P((SHELL_VAR *));
+static int visible_and_exported __P((SHELL_VAR *));
+static int export_environment_candidate __P((SHELL_VAR *));
+static int local_and_exported __P((SHELL_VAR *));
+static int variable_in_context __P((SHELL_VAR *));
+#if defined (ARRAY_VARS)
+static int visible_array_vars __P((SHELL_VAR *));
+#endif
+
+static SHELL_VAR *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 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 int n_shell_variables __P((void));
+static int set_context __P((SHELL_VAR *));
+
+static void push_func_var __P((PTR_T));
+static void push_exported_var __P((PTR_T));
+
+static inline int find_special_var __P((const char *));
+
+static void
+create_variable_tables ()
+{
+  if (shell_variables == 0)
+    {
+      shell_variables = global_variables = new_var_context ((char *)NULL, 0);
+      shell_variables->scope = 0;
+      shell_variables->table = hash_create (0);
+    }
+
+  if (shell_functions == 0)
+    shell_functions = hash_create (0);
+
+#if defined (DEBUGGER)
+  if (shell_function_defs == 0)
+    shell_function_defs = hash_create (0);
+#endif
+}
 
 /* Initialize the shell variables from the current environment.
    If PRIVMODE is nonzero, don't import functions from ENV or
@@ -154,11 +313,7 @@ initialize_shell_variables (env, privmode)
   int c, char_index, string_index, string_length;
   SHELL_VAR *temp_var;
 
-  if (shell_variables == 0)
-    shell_variables = make_hash_table (0);
-
-  if (shell_functions == 0)
-    shell_functions = make_hash_table (0);
+  create_variable_tables ();
 
   for (string_index = 0; string = env[string_index++]; )
     {
@@ -167,23 +322,26 @@ initialize_shell_variables (env, privmode)
       while ((c = *string++) && c != '=')
        ;
       if (string[-1] == '=')
-        char_index = string - name - 1;
+       char_index = string - name - 1;
 
       /* If there are weird things in the environment, like `=xxx' or a
         string without an `=', just skip them. */
       if (char_index == 0)
-        continue;
+       continue;
 
       /* ASSERT(name[char_index] == '=') */
       name[char_index] = '\0';
       /* Now, name = env variable name, string = env variable value, and
-         char_index == strlen (name) */
+        char_index == strlen (name) */
+
+      temp_var = (SHELL_VAR *)NULL;
 
-      /* If exported function, define it now. */
+      /* 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))
        {
          string_length = strlen (string);
-         temp_string = xmalloc (3 + string_length + char_index);
+         temp_string = (char *)xmalloc (3 + string_length + char_index);
 
          strcpy (temp_string, name);
          temp_string[char_index] = ' ';
@@ -202,7 +360,7 @@ initialize_shell_variables (env, privmode)
              array_needs_making = 1;
            }
          else
-           report_error ("error importing function definition for `%s'", name);
+           report_error (_("error importing function definition for `%s'"), name);
 
          /* ( */
          if (name[char_index - 1] == ')' && name[char_index - 2] == '\0')
@@ -211,7 +369,7 @@ initialize_shell_variables (env, privmode)
 #if defined (ARRAY_VARS)
 #  if 0
       /* Array variables may not yet be exported. */
-      else if (*string == '(' && string[1] == '[' && strchr (string, ')'))
+      else if (*string == '(' && string[1] == '[' && string[strlen (string) - 1] == ')')
        {
          string_length = 1;
          temp_string = extract_array_assignment_list (string, &string_length);
@@ -222,68 +380,62 @@ initialize_shell_variables (env, privmode)
        }
 #  endif
 #endif
+#if 0
+      else if (legal_identifier (name))
+#else
       else
+#endif
        {
-         temp_var = bind_variable (name, string);
-         VSETATTR (temp_var, (att_exported | att_imported));
-         array_needs_making = 1;
+         temp_var = bind_variable (name, string, 0);
+         if (temp_var)
+           {
+             if (legal_identifier (name))
+               VSETATTR (temp_var, (att_exported | att_imported));
+             else
+               VSETATTR (temp_var, (att_exported | att_imported | att_invisible));
+             array_needs_making = 1;
+           }
        }
 
       name[char_index] = '=';
       /* temp_var can be NULL if it was an exported function with a syntax
-         error (a different bug, but it still shouldn't dump core). */
+        error (a different bug, but it still shouldn't dump core). */
       if (temp_var && function_p (temp_var) == 0)      /* XXX not yet */
        {
          CACHE_IMPORTSTR (temp_var, name);
        }
     }
 
-  /* If we got PWD from the environment, update our idea of the current
-     working directory.  In any case, make sure that PWD exists before
-     checking it.  It is possible for getcwd () to fail on shell startup,
-     and in that case, PWD would be undefined. */
-  temp_var = find_variable ("PWD");
-  if (temp_var && imported_p (temp_var) &&
-      (temp_string = value_cell (temp_var)) &&
-      same_file (temp_string, ".", (struct stat *)NULL, (struct stat *)NULL))
-    set_working_directory (temp_string);
-  else
-    {
-      temp_string = get_working_directory ("shell-init");
-      if (temp_string)
-       {
-         temp_var = bind_variable ("PWD", temp_string);
-         set_auto_export (temp_var);
-         free (temp_string);
-       }
-    }
-
-  /* According to the Single Unix Specification, v2, $OLDPWD is an
-     `environment variable' and therefore should be auto-exported.
-     Make a dummy invisible variable for OLDPWD, and mark it as exported. */
-  temp_var = bind_variable ("OLDPWD", (char *)NULL);
-  VSETATTR (temp_var, (att_exported | att_invisible));
+  set_pwd ();
 
   /* Set up initial value of $_ */
-  temp_var = bind_variable ("_", dollar_vars[0]);
+  temp_var = set_if_not ("_", dollar_vars[0]);
 
   /* Remember this pid. */
-  dollar_dollar_pid = (int)getpid ();
+  dollar_dollar_pid = getpid ();
 
   /* 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);
-  set_auto_export (temp_var);
+#if 0
+  set_auto_export (temp_var);  /* XXX */
+#endif
 
   temp_var = set_if_not ("TERM", "dumb");
-  set_auto_export (temp_var);
+#if 0
+  set_auto_export (temp_var);  /* XXX */
+#endif
 
-#if defined (qnx)
+#if defined (__QNX__)
   /* set node id -- don't import it from the environment */
   {
     char node_name[22];
+#  if defined (__QNXNTO__)
+    netmgr_ndtostr(ND2S_LOCAL_STR, ND_LOCAL_NODE, node_name, sizeof(node_name));
+#  else
     qnx_nidtostr (getnid (), node_name, sizeof (node_name));
-    temp_var = bind_variable ("NODE", node_name);
+#  endif
+    temp_var = bind_variable ("NODE", node_name, 0);
     set_auto_export (temp_var);
   }
 #endif
@@ -303,24 +455,21 @@ initialize_shell_variables (env, privmode)
   set_if_not ("PS4", "+ ");
 
   /* Don't allow IFS to be imported from the environment. */
-  temp_var = bind_variable ("IFS", " \t\n");
+  temp_var = bind_variable ("IFS", " \t\n", 0);
+  setifs (temp_var);
 
   /* Magic machine types.  Pretty convenient. */
-  temp_var = bind_variable ("HOSTTYPE", HOSTTYPE);
-  set_auto_export (temp_var);
-  temp_var = bind_variable ("OSTYPE", OSTYPE);
-  set_auto_export (temp_var);
-  temp_var = bind_variable ("MACHTYPE", MACHTYPE);
-  set_auto_export (temp_var);
-  temp_var = bind_variable ("HOSTNAME", current_host_name);
-  set_auto_export (temp_var);
+  set_machine_vars ();
 
   /* Default MAILCHECK for interactive shells.  Defer the creation of a
      default MAILPATH until the startup files are read, because MAIL
-     names a mail file if MAILCHECK is not set, and we should provide a
+     names a mail file if MAILPATH is not set, and we should provide a
      default only if neither is set. */
   if (interactive_shell)
-    set_if_not ("MAILCHECK", "60");
+    {
+      temp_var = set_if_not ("MAILCHECK", posixly_correct ? "600" : "60");
+      VSETATTR (temp_var, att_integer);
+    }
 
   /* Do some things with shell level. */
   initialize_shell_level ();
@@ -328,18 +477,19 @@ initialize_shell_variables (env, privmode)
   set_ppid ();
 
   /* Initialize the `getopts' stuff. */
-  bind_variable ("OPTIND", "1");
+  temp_var = bind_variable ("OPTIND", "1", 0);
+  VSETATTR (temp_var, att_integer);
   getopts_reset (0);
-  bind_variable ("OPTERR", "1");
+  bind_variable ("OPTERR", "1", 0);
   sh_opterr = 1;
 
-  if (login_shell == 1)
+  if (login_shell == 1 && posixly_correct == 0)
     set_home_var ();
 
   /* Get the full pathname to THIS shell, and set the BASH variable
      to it. */
   name = get_bash_name ();
-  temp_var = bind_variable ("BASH", name);
+  temp_var = bind_variable ("BASH", name, 0);
   free (name);
 
   /* Make the exported environment variable SHELL be the user's login
@@ -349,11 +499,14 @@ initialize_shell_variables (env, privmode)
   set_shell_var ();
 
   /* Make a variable called BASH_VERSION which contains the version info. */
-  bind_variable ("BASH_VERSION", shell_version_string ());
+  bind_variable ("BASH_VERSION", shell_version_string (), 0);
 #if defined (ARRAY_VARS)
   make_vers_array ();
 #endif
 
+  if (command_execution_string)
+    bind_variable ("BASH_EXECUTION_STRING", command_execution_string, 0);
+
   /* Find out if we're supposed to be in Posix.2 mode via an
      environment variable. */
   temp_var = find_variable ("POSIXLY_CORRECT");
@@ -368,18 +521,20 @@ initialize_shell_variables (env, privmode)
      that we are remembering commands on the history list. */
   if (remember_on_history)
     {
-      name = bash_tilde_expand (posixly_correct ? "~/.sh_history" : "~/.bash_history");
+      name = bash_tilde_expand (posixly_correct ? "~/.sh_history" : "~/.bash_history", 0);
 
       set_if_not ("HISTFILE", name);
       free (name);
 
+#if 0
       set_if_not ("HISTSIZE", "500");
       sv_histsize ("HISTSIZE");
+#endif
     }
 #endif /* HISTORY */
 
   /* Seed the random number generator. */
-  sbrand (dollar_dollar_pid + (long)shell_start_time);
+  seedrand ();
 
   /* Handle some "special" variables that we may have inherited from a
      parent shell. */
@@ -397,30 +552,78 @@ initialize_shell_variables (env, privmode)
     {
       sv_history_control ("HISTCONTROL");
       sv_histignore ("HISTIGNORE");
+      sv_histtimefmt ("HISTTIMEFORMAT");
     }
 #endif /* HISTORY */
 
+#if defined (READLINE) && defined (STRICT_POSIX)
+  /* POSIXLY_CORRECT will only be 1 here if the shell was compiled
+     -DSTRICT_POSIX */
+  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 = find_variable ("BASH_XTRACEFD");
+  if (temp_var && imported_p (temp_var))
+    sv_xtracefd (temp_var->name);
+
   /* Initialize the dynamic variables, and seed their values. */
   initialize_dynamic_variables ();
 }
 
+/* **************************************************************** */
+/*                                                                 */
+/*          Setting values for special shell variables             */
+/*                                                                 */
+/* **************************************************************** */
+
+static void
+set_machine_vars ()
+{
+  SHELL_VAR *temp_var;
+
+  temp_var = set_if_not ("HOSTTYPE", HOSTTYPE);
+  temp_var = set_if_not ("OSTYPE", OSTYPE);
+  temp_var = set_if_not ("MACHTYPE", MACHTYPE);
+
+  temp_var = set_if_not ("HOSTNAME", current_host_name);
+}
+
 /* Set $HOME to the information in the password file if we didn't get
    it from the environment. */
 
 /* This function is not static so the tilde and readline libraries can
    use it. */
 char *
-get_home_dir ()
+sh_get_home_dir ()
 {
   if (current_user.home_dir == 0)
     get_current_user_info ();
@@ -434,8 +637,10 @@ set_home_var ()
 
   temp_var = find_variable ("HOME");
   if (temp_var == 0)
-    temp_var = bind_variable ("HOME", get_home_dir ());
+    temp_var = bind_variable ("HOME", sh_get_home_dir (), 0);
+#if 0
   VSETATTR (temp_var, att_exported);
+#endif
 }
 
 /* Set $SHELL to the user's login shell if it is not already set.  Call
@@ -450,9 +655,11 @@ set_shell_var ()
     {
       if (current_user.shell == 0)
        get_current_user_info ();
-      temp_var = bind_variable ("SHELL", current_user.shell);
+      temp_var = bind_variable ("SHELL", current_user.shell, 0);
     }
+#if 0
   VSETATTR (temp_var, att_exported);
+#endif
 }
 
 static char *
@@ -460,13 +667,13 @@ get_bash_name ()
 {
   char *name;
 
-  if ((login_shell == 1) && (*shell_name != '/'))
+  if ((login_shell == 1) && RELPATH(shell_name))
     {
       if (current_user.shell == 0)
-        get_current_user_info ();
+       get_current_user_info ();
       name = savestring (current_user.shell);
     }
-  else if (*shell_name == '/')
+  else if (ABSPATH(shell_name))
     name = savestring (shell_name);
   else if (shell_name[0] == '.' && shell_name[1] == '/')
     {
@@ -475,10 +682,15 @@ get_bash_name ()
       int len;
 
       cdir = get_string_value ("PWD");
-      len = strlen (cdir);
-      name = xmalloc (len + strlen (shell_name) + 1);
-      strcpy (name, cdir);
-      strcpy (name + len, shell_name + 1);
+      if (cdir)
+       {
+         len = strlen (cdir);
+         name = (char *)xmalloc (len + strlen (shell_name) + 1);
+         strcpy (name, cdir);
+         strcpy (name + len, shell_name + 1);
+       }
+      else
+       name = savestring (shell_name);
     }
   else
     {
@@ -497,7 +709,7 @@ get_bash_name ()
              tname = make_absolute (shell_name, get_string_value ("PWD"));
              if (*shell_name == '.')
                {
-                 name = canonicalize_pathname (tname);
+                 name = sh_canonpath (tname, PATH_CHECKDOTDOT|PATH_CHECKEXISTS);
                  if (name == 0)
                    name = tname;
                  else
@@ -528,18 +740,19 @@ adjust_shell_level (change)
      int change;
 {
   char new_level[5], *old_SHLVL;
-  int old_level;
+  intmax_t old_level;
   SHELL_VAR *temp_var;
 
   old_SHLVL = get_string_value ("SHLVL");
-  old_level = old_SHLVL ? atoi (old_SHLVL) : 0;
+  if (old_SHLVL == 0 || *old_SHLVL == '\0' || legal_number (old_SHLVL, &old_level) == 0)
+    old_level = 0;
 
   shell_level = old_level + change;
   if (shell_level < 0)
     shell_level = 0;
   else if (shell_level > 1000)
     {
-      internal_warning ("shell level (%d) too high, resetting to 1", shell_level);
+      internal_warning (_("shell level (%d) too high, resetting to 1"), shell_level);
       shell_level = 1;
     }
 
@@ -564,7 +777,7 @@ adjust_shell_level (change)
       new_level[3] = '\0';
     }
 
-  temp_var = bind_variable ("SHLVL", new_level);
+  temp_var = bind_variable ("SHLVL", new_level, 0);
   set_auto_export (temp_var);
 }
 
@@ -574,44 +787,90 @@ initialize_shell_level ()
   adjust_shell_level (1);
 }
 
+/* If we got PWD from the environment, update our idea of the current
+   working directory.  In any case, make sure that PWD exists before
+   checking it.  It is possible for getcwd () to fail on shell startup,
+   and in that case, PWD would be undefined.  If this is an interactive
+   login shell, see if $HOME is the current working directory, and if
+   that's not the same string as $PWD, set PWD=$HOME. */
+
+void
+set_pwd ()
+{
+  SHELL_VAR *temp_var, *home_var;
+  char *temp_string, *home_string;
+
+  home_var = find_variable ("HOME");
+  home_string = home_var ? value_cell (home_var) : (char *)NULL;
+
+  temp_var = find_variable ("PWD");
+  if (temp_var && imported_p (temp_var) &&
+      (temp_string = value_cell (temp_var)) &&
+      same_file (temp_string, ".", (struct stat *)NULL, (struct stat *)NULL))
+    set_working_directory (temp_string);
+  else if (home_string && interactive_shell && login_shell &&
+          same_file (home_string, ".", (struct stat *)NULL, (struct stat *)NULL))
+    {
+      set_working_directory (home_string);
+      temp_var = bind_variable ("PWD", home_string, 0);
+      set_auto_export (temp_var);
+    }
+  else
+    {
+      temp_string = get_working_directory ("shell-init");
+      if (temp_string)
+       {
+         temp_var = bind_variable ("PWD", temp_string, 0);
+         set_auto_export (temp_var);
+         free (temp_string);
+       }
+    }
+
+  /* According to the Single Unix Specification, v2, $OLDPWD is an
+     `environment variable' and therefore should be auto-exported.
+     Make a dummy invisible variable for OLDPWD, and mark it as exported. */
+  temp_var = bind_variable ("OLDPWD", (char *)NULL, 0);
+  VSETATTR (temp_var, (att_exported | att_invisible));
+}
+
 /* Make a variable $PPID, which holds the pid of the shell's parent.  */
 void
 set_ppid ()
 {
-  char namebuf[32], *name;
+  char namebuf[INT_STRLEN_BOUND(pid_t) + 1], *name;
   SHELL_VAR *temp_var;
 
-  name = inttostr ((int) getppid (), namebuf, sizeof(namebuf));
+  name = inttostr (getppid (), namebuf, sizeof(namebuf));
   temp_var = find_variable ("PPID");
   if (temp_var)
     VUNSETATTR (temp_var, (att_readonly | att_exported));
-  temp_var = bind_variable ("PPID", name);
+  temp_var = bind_variable ("PPID", name, 0);
   VSETATTR (temp_var, (att_readonly | att_integer));
 }
 
 static void
 uidset ()
 {
-  char buff[32], *b;
+  char buff[INT_STRLEN_BOUND(uid_t) + 1], *b;
   register SHELL_VAR *v;
 
   b = inttostr (current_user.uid, buff, sizeof (buff));
   v = find_variable ("UID");
-  if (v)
-    VUNSETATTR (v, att_readonly);
-
-  v = bind_variable ("UID", b);
-  VSETATTR (v, (att_readonly | att_integer));
+  if (v == 0)
+    {
+      v = bind_variable ("UID", b, 0);
+      VSETATTR (v, (att_readonly | att_integer));
+    }
 
   if (current_user.euid != current_user.uid)
     b = inttostr (current_user.euid, buff, sizeof (buff));
 
   v = find_variable ("EUID");
-  if (v)
-    VUNSETATTR (v, att_readonly);
-
-  v = bind_variable ("EUID", b);
-  VSETATTR (v, (att_readonly | att_integer));
+  if (v == 0)
+    {
+      v = bind_variable ("EUID", b, 0);
+      VSETATTR (v, (att_readonly | att_integer));
+    }
 }
 
 #if defined (ARRAY_VARS)
@@ -620,9 +879,9 @@ make_vers_array ()
 {
   SHELL_VAR *vv;
   ARRAY *av;
-  char *s, d[32];
+  char *s, d[32], b[INT_STRLEN_BOUND(int) + 1];
 
-  makunbound ("BASH_VERSINFO", shell_variables);
+  unbind_variable ("BASH_VERSINFO");
 
   vv = make_new_array_variable ("BASH_VERSINFO");
   av = array_cell (vv);
@@ -630,14 +889,14 @@ make_vers_array ()
   s = strchr (d, '.');
   if (s)
     *s++ = '\0';
-  array_add_element (av, 0, d);
-  array_add_element (av, 1, s);
-  s = inttostr (patch_level, d, sizeof (d));
-  array_add_element (av, 2, s);
-  s = inttostr (build_version, d, sizeof (d));
-  array_add_element (av, 3, s);
-  array_add_element (av, 4, release_status);
-  array_add_element (av, 5, MACHTYPE);
+  array_insert (av, 0, d);
+  array_insert (av, 1, s);
+  s = inttostr (patch_level, b, sizeof (b));
+  array_insert (av, 2, s);
+  s = inttostr (build_version, b, sizeof (b));
+  array_insert (av, 3, s);
+  array_insert (av, 4, release_status);
+  array_insert (av, 5, MACHTYPE);
 
   VSETATTR (vv, att_readonly);
 }
@@ -646,162 +905,84 @@ make_vers_array ()
 /* Set the environment variables $LINES and $COLUMNS in response to
    a window size change. */
 void
-set_lines_and_columns (lines, cols)
+sh_set_lines_and_columns (lines, cols)
      int lines, cols;
 {
-  char val[32], *v;
+  char val[INT_STRLEN_BOUND(int) + 1], *v;
+
+#if defined (READLINE)
+  /* If we are currently assigning to LINES or COLUMNS, don't do anything. */
+  if (winsize_assignment)
+    return;
+#endif
 
   v = inttostr (lines, val, sizeof (val));
-  bind_variable ("LINES", v);
+  bind_variable ("LINES", v, 0);
 
   v = inttostr (cols, val, sizeof (val));
-  bind_variable ("COLUMNS", v);
+  bind_variable ("COLUMNS", v, 0);
 }
 
-/* Set NAME to VALUE if NAME has no value. */
-SHELL_VAR *
-set_if_not (name, value)
-     char *name, *value;
+/* **************************************************************** */
+/*                                                                 */
+/*                Printing variables and values                    */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Print LIST (a list of shell variables) to stdout in such a way that
+   they can be read back in. */
+void
+print_var_list (list)
+     register SHELL_VAR **list;
 {
-  SHELL_VAR *v;
+  register int i;
+  register SHELL_VAR *var;
 
-  v = find_variable (name);
-  if (v == 0)
-    v = bind_variable (name, value);
-  return (v);
+  for (i = 0; list && (var = list[i]); i++)
+    if (invisible_p (var) == 0)
+      print_assignment (var);
 }
 
-/* Map FUNCTION over the variables in VARIABLES.  Return an array of the
-   variables for which FUNCTION returns a non-zero value.  A NULL value
-   for FUNCTION means to use all variables. */
-SHELL_VAR **
-map_over (function, var_hash_table)
-     Function *function;
-     HASH_TABLE* var_hash_table;
+/* Print LIST (a list of shell functions) to stdout in such a way that
+   they can be read back in. */
+void
+print_func_list (list)
+     register SHELL_VAR **list;
 {
   register int i;
-  register BUCKET_CONTENTS *tlist;
-  SHELL_VAR *var, **list;
-  int list_index, list_size;
+  register SHELL_VAR *var;
 
-  list = (SHELL_VAR **)NULL;
-  for (i = list_index = list_size = 0; i < var_hash_table->nbuckets; i++)
+  for (i = 0; list && (var = list[i]); i++)
     {
-      tlist = get_hash_bucket (i, var_hash_table);
-
-      while (tlist)
-       {
-         var = (SHELL_VAR *)tlist->data;
-
-         if (!function || (*function) (var))
-           {
-             if (list_index + 1 >= list_size)
-               list = (SHELL_VAR **)
-                 xrealloc (list, (list_size += 20) * sizeof (SHELL_VAR *));
-
-             list[list_index++] = var;
-             list[list_index] = (SHELL_VAR *)NULL;
-           }
-         tlist = tlist->next;
-       }
+      printf ("%s ", var->name);
+      print_var_function (var);
+      printf ("\n");
     }
-  return (list);
 }
-
+      
+/* Print the value of a single SHELL_VAR.  No newline is
+   output, but the variable is printed in such a way that
+   it can be read back in. */
 void
-sort_variables (array)
-     SHELL_VAR **array;
+print_assignment (var)
+     SHELL_VAR *var;
 {
-  qsort (array, array_len ((char **)array), sizeof (SHELL_VAR *), qsort_var_comp);
-}
+  if (var_isset (var) == 0)
+    return;
 
-static int
-qsort_var_comp (var1, var2)
-     SHELL_VAR **var1, **var2;
-{
-  int result;
-
-  if ((result = (*var1)->name[0] - (*var2)->name[0]) == 0)
-    result = strcmp ((*var1)->name, (*var2)->name);
-
-  return (result);
-}
-
-/* Create a NULL terminated array of all the shell variables in TABLE. */
-static SHELL_VAR **
-all_vars (table)
-     HASH_TABLE *table;
-{
-  SHELL_VAR **list;
-
-  list = map_over ((Function *)NULL, table);
-  if (list /* && posixly_correct */)
-    sort_variables (list);
-  return (list);
-}
-
-/* Create a NULL terminated array of all the shell variables. */
-SHELL_VAR **
-all_shell_variables ()
-{
-  return (all_vars (shell_variables));
-}
-
-/* Create a NULL terminated array of all the shell functions. */
-SHELL_VAR **
-all_shell_functions ()
-{
-  return (all_vars (shell_functions));
-}
-
-/* Print VARS to stdout in such a way that they can be read back in. */
-void
-print_var_list (list)
-     register SHELL_VAR **list;
-{
-  register int i;
-  register SHELL_VAR *var;
-
-  for (i = 0; list && (var = list[i]); i++)
-    if (!invisible_p (var))
-      print_assignment (var);
-}
-
-#if defined (NOTDEF)
-/* Print LIST (a linked list of shell variables) to stdout
-   by printing the names, without the values.  Used to support the
-   `set +' command. */
-void
-print_vars_no_values (list)
-     register SHELL_VAR **list;
-{
-  register int i;
-  register SHELL_VAR *var;
-
-  for (i = 0; list && (var = list[i]); i++)
-    if (!invisible_p (var))
-      printf ("%s\n", var->name);
-}
-#endif
-
-/* Print the value of a single SHELL_VAR.  No newline is
-   output, but the variable is printed in such a way that
-   it can be read back in. */
-void
-print_assignment (var)
-     SHELL_VAR *var;
-{
-  if (function_p (var) && var->value)
+  if (function_p (var))
     {
-      printf ("%s=", var->name);
+      printf ("%s", var->name);
       print_var_function (var);
       printf ("\n");
     }
 #if defined (ARRAY_VARS)
-  else if (array_p (var) && var->value)
+  else if (array_p (var))
     print_array_assignment (var, 0);
+  else if (assoc_p (var))
+    print_assoc_assignment (var, 0);
 #endif /* ARRAY_VARS */
-  else if (var->value)
+  else
     {
       printf ("%s=", var->name);
       print_var_value (var, 1);
@@ -820,17 +1001,23 @@ print_var_value (var, quote)
 {
   char *t;
 
-  if (var->value)
+  if (var_isset (var) == 0)
+    return;
+
+  if (quote && posixly_correct == 0 && ansic_shouldquote (value_cell (var)))
     {
-      if (quote && contains_shell_metas (var->value))
-       {
-         t = single_quote (var->value);
-         printf ("%s", t);
-         free (t);
-       }
-      else
-       printf ("%s", var->value);
+      t = ansic_quote (value_cell (var), 0, (int *)0);
+      printf ("%s", t);
+      free (t);
     }
+  else if (quote && sh_contains_shell_metas (value_cell (var)))
+    {
+      t = sh_single_quote (value_cell (var));
+      printf ("%s", t);
+      free (t);
+    }
+  else
+    printf ("%s", value_cell (var));
 }
 
 /* Print the function cell of VAR, a shell variable.  Do not
@@ -839,36 +1026,18 @@ void
 print_var_function (var)
      SHELL_VAR *var;
 {
-  if (function_p (var) && var->value)
-    printf ("%s", named_function_string ((char *)NULL, function_cell(var), 1));
-}
-
-#if defined (ARRAY_VARS)
-void
-print_array_assignment (var, quoted)
-     SHELL_VAR *var;
-     int quoted;
-{
-  char *vstr;
-
-  if (quoted)
-    vstr = quoted_array_assignment_string (array_cell (var));
-  else
-    vstr = array_to_assignment_string (array_cell (var));
+  char *x;
 
-  if (vstr == 0)
-    printf ("%s=%s\n", var->name, quoted ? "'()'" : "()");
-  else
+  if (function_p (var) && var_isset (var))
     {
-      printf ("%s=%s\n", var->name, vstr);
-      free (vstr);
+      x = named_function_string ((char *)NULL, function_cell(var), FUNC_MULTILINE|FUNC_EXTERNAL);
+      printf ("%s", x);
     }
 }
-#endif /* ARRAY_VARS */
 
 /* **************************************************************** */
 /*                                                                 */
-/*              Dynamic Variable Extension                         */
+/*                     Dynamic Variables                           */
 /*                                                                 */
 /* **************************************************************** */
 
@@ -876,21 +1045,23 @@ print_array_assignment (var, quoted)
 
    These are variables whose values are generated anew each time they are
    referenced.  These are implemented using a pair of function pointers
-   in the struct variable: assign_func, which is called from bind_variable,
-   and dynamic_value, which is called from find_variable.
-
-   assign_func is called from bind_variable, if bind_variable discovers
-   that the variable being assigned to has such a function.  The function
-   is called as
-       SHELL_VAR *temp = (*(entry->assign_func)) (entry, value)
+   in the struct variable: assign_func, which is called from bind_variable
+   and, if arrays are compiled into the shell, some of the functions in
+   arrayfunc.c, and dynamic_value, which is called from find_variable.
+
+   assign_func is called from bind_variable_internal, if
+   bind_variable_internal discovers that the variable being assigned to
+   has such a function.  The function is called as
+       SHELL_VAR *temp = (*(entry->assign_func)) (entry, value, ind)
    and the (SHELL_VAR *)temp is returned as the value of bind_variable.  It
-   is usually ENTRY (self).
+   is usually ENTRY (self).  IND is an index for an array variable, and
+   unused otherwise.
 
-   dynamic_value is called from find_variable to return a `new' value for
-   the specified dynamic varible.  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);
+   dynamic_value is called from find_variable_internal to return a `new'
+   value for the specified dynamic varible.  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);
 
    Sometimes `tempvar' will replace the value of `var'.  Other times, the
    shell will simply use the string value.  Pretty object-oriented, huh?
@@ -899,40 +1070,122 @@ print_array_assignment (var, quoted)
    special meaning, even if you subsequently set it.
 
    The special assignment code would probably have been better put in
-   subst.c: do_assignment, in the same style as
+   subst.c: do_assignment_internal, in the same style as
    stupidly_hack_special_variables, but I wanted the changes as
    localized as possible.  */
 
+#define INIT_DYNAMIC_VAR(var, val, gfunc, afunc) \
+  do \
+    { \
+      v = bind_variable (var, (val), 0); \
+      v->dynamic_value = gfunc; \
+      v->assign_func = afunc; \
+    } \
+  while (0)
+
+#define INIT_DYNAMIC_ARRAY_VAR(var, gfunc, afunc) \
+  do \
+    { \
+      v = make_new_array_variable (var); \
+      v->dynamic_value = gfunc; \
+      v->assign_func = afunc; \
+    } \
+  while (0)
+
+#define INIT_DYNAMIC_ASSOC_VAR(var, gfunc, afunc) \
+  do \
+    { \
+      v = make_new_assoc_variable (var); \
+      v->dynamic_value = gfunc; \
+      v->assign_func = afunc; \
+    } \
+  while (0)
+
 static SHELL_VAR *
-null_assign (self, value)
+null_assign (self, value, unused, key)
      SHELL_VAR *self;
      char *value;
+     arrayind_t unused;
+     char *key;
 {
   return (self);
 }
 
 #if defined (ARRAY_VARS)
 static SHELL_VAR *
-null_array_assign (self, ind, value)
+null_array_assign (self, value, ind, key)
      SHELL_VAR *self;
-     int ind;
      char *value;
+     arrayind_t ind;
+     char *key;
+{
+  return (self);
+}
+#endif
+
+/* Degenerate `dynamic_value' function; just returns what's passed without
+   manipulation. */
+static SHELL_VAR *
+get_self (self)
+     SHELL_VAR *self;
 {
   return (self);
 }
+
+#if defined (ARRAY_VARS)
+/* A generic dynamic array variable initializer.  Intialize array variable
+   NAME with dynamic value function GETFUNC and assignment function SETFUNC. */
+static SHELL_VAR *
+init_dynamic_array_var (name, getfunc, setfunc, attrs)
+     char *name;
+     sh_var_value_func_t *getfunc;
+     sh_var_assign_func_t *setfunc;
+     int attrs;
+{
+  SHELL_VAR *v;
+
+  v = find_variable (name);
+  if (v)
+    return (v);
+  INIT_DYNAMIC_ARRAY_VAR (name, getfunc, setfunc);
+  if (attrs)
+    VSETATTR (v, attrs);
+  return v;
+}
+
+static SHELL_VAR *
+init_dynamic_assoc_var (name, getfunc, setfunc, attrs)
+     char *name;
+     sh_var_value_func_t *getfunc;
+     sh_var_assign_func_t *setfunc;
+     int attrs;
+{
+  SHELL_VAR *v;
+
+  v = find_variable (name);
+  if (v)
+    return (v);
+  INIT_DYNAMIC_ASSOC_VAR (name, getfunc, setfunc);
+  if (attrs)
+    VSETATTR (v, attrs);
+  return v;
+}
 #endif
 
 /* The value of $SECONDS.  This is the number of seconds since shell
    invocation, or, the number of seconds since the last assignment + the
    value of the last assignment. */
-static long seconds_value_assigned;
+static intmax_t seconds_value_assigned;
 
 static SHELL_VAR *
-assign_seconds (self, value)
+assign_seconds (self, value, unused, key)
      SHELL_VAR *self;
      char *value;
+     arrayind_t unused;
+     char *key;
 {
-  seconds_value_assigned = strtol (value, (char **)NULL, 10);
+  if (legal_number (value, &seconds_value_assigned) == 0)
+    seconds_value_assigned = 0;
   shell_start_time = NOW;
   return (self);
 }
@@ -945,74 +1198,144 @@ get_seconds (var)
   char *p;
 
   time_since_start = NOW - shell_start_time;
-  p = itos((int) seconds_value_assigned + time_since_start);
+  p = itos(seconds_value_assigned + time_since_start);
 
-  FREE (var->value);
+  FREE (value_cell (var));
 
   VSETATTR (var, att_integer);
-  var->value = p;
+  var_setvalue (var, p);
   return (var);
 }
 
+static SHELL_VAR *
+init_seconds_var ()
+{
+  SHELL_VAR *v;
+
+  v = find_variable ("SECONDS");
+  if (v)
+    {
+      if (legal_number (value_cell(v), &seconds_value_assigned) == 0)
+       seconds_value_assigned = 0;
+    }
+  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 unsigned long last_random_value;
+static int last_random_value;
+static int seeded_subshell = 0;
 
-/* A linear congruential random number generator based on the ANSI
-   C standard.  This one isn't very good (the values are alternately
-   odd and even, for example), but a more complicated one is overkill.  */
+/* 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 ()
 {
-  rseed = rseed * 1103515245 + 12345;
+  /* 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)
-     int seed;
+     unsigned long seed;
 {
   rseed = seed;
   last_random_value = 0;
 }
 
+static void
+seedrand ()
+{
+  struct timeval tv;
+
+  gettimeofday (&tv, NULL);
+  sbrand (tv.tv_sec ^ tv.tv_usec ^ getpid ());
+}
+
 static SHELL_VAR *
-assign_random (self, value)
+assign_random (self, value, unused, key)
      SHELL_VAR *self;
      char *value;
+     arrayind_t unused;
+     char *key;
 {
-  sbrand (atoi (value));
+  sbrand (strtoul (value, (char **)NULL, 10));
+  if (subshell_environment)
+    seeded_subshell = getpid ();
   return (self);
 }
 
-static SHELL_VAR *
-get_random (var)
-     SHELL_VAR *var;
+int
+get_random_number ()
 {
-  int rv;
-  char *p;
+  int rv, pid;
 
   /* Reset for command and process substitution. */
-  if (subshell_environment)
-    sbrand (rseed + (int)(getpid() + NOW));
+  pid = getpid ();
+  if (subshell_environment && seeded_subshell != pid)
+    {
+      seedrand ();
+      seeded_subshell = pid;
+    }
 
   do
     rv = brand ();
-  while (rv == (int)last_random_value);
+  while (rv == last_random_value);
+  return rv;
+}
+
+static SHELL_VAR *
+get_random (var)
+     SHELL_VAR *var;
+{
+  int rv;
+  char *p;
 
+  rv = get_random_number ();
   last_random_value = rv;
-  p = itos ((int)rv);
+  p = itos (rv);
 
-  FREE (var->value);
+  FREE (value_cell (var));
 
   VSETATTR (var, att_integer);
-  var->value = p;
+  var_setvalue (var, p);
   return (var);
 }
 
+static SHELL_VAR *
+assign_lineno (var, value, unused, key)
+     SHELL_VAR *var;
+     char *value;
+     arrayind_t unused;
+     char *key;
+{
+  intmax_t new_value;
+
+  if (value == 0 || *value == '\0' || legal_number (value, &new_value) == 0)
+    new_value = 0;
+  line_number = line_number_base = new_value;
+  return var;
+}
+
 /* Function which returns the current line number. */
 static SHELL_VAR *
 get_lineno (var)
@@ -1023,20 +1346,72 @@ get_lineno (var)
 
   ln = executing_line_number ();
   p = itos (ln);
-  FREE (var->value);
-  var->value = p;
+  FREE (value_cell (var));
+  var_setvalue (var, p);
   return (var);
 }
 
 static SHELL_VAR *
-assign_lineno (var, value)
+assign_subshell (var, value, unused, key)
      SHELL_VAR *var;
      char *value;
+     arrayind_t unused;
+     char *key;
 {
-  line_number = atoi (value);
+  intmax_t new_value;
+
+  if (value == 0 || *value == '\0' || legal_number (value, &new_value) == 0)
+    new_value = 0;
+  subshell_level = new_value;
   return var;
 }
 
+static SHELL_VAR *
+get_subshell (var)
+     SHELL_VAR *var;
+{
+  char *p;
+
+  p = itos (subshell_level);
+  FREE (value_cell (var));
+  var_setvalue (var, p);
+  return (var);
+}
+
+static SHELL_VAR *
+get_bashpid (var)
+     SHELL_VAR *var;
+{
+  int pid;
+  char *p;
+
+  pid = getpid ();
+  p = itos (pid);
+
+  FREE (value_cell (var));
+  VSETATTR (var, att_integer|att_readonly);
+  var_setvalue (var, p);
+  return (var);
+}
+
+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);
+}
+
 #if defined (HISTORY)
 static SHELL_VAR *
 get_histcmd (var)
@@ -1045,37 +1420,72 @@ get_histcmd (var)
   char *p;
 
   p = itos (history_number ());
-  FREE (var->value);
-  var->value = p;
+  FREE (value_cell (var));
+  var_setvalue (var, p);
   return (var);
 }
 #endif
 
-#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS)
+#if defined (READLINE)
+/* When this function returns, VAR->value points to malloced memory. */
 static SHELL_VAR *
-get_dirstack (self)
+get_comp_wordbreaks (var)
+     SHELL_VAR *var;
+{
+  /* If we don't have anything yet, assign a default value. */
+  if (rl_completer_word_break_characters == 0 && bash_readline_initialized == 0)
+    enable_hostname_completion (perform_hostname_completion);
+
+  FREE (value_cell (var));
+  var_setvalue (var, savestring (rl_completer_word_break_characters));
+
+  return (var);
+}
+
+/* When this function returns, rl_completer_word_break_characters points to
+   malloced memory. */
+static SHELL_VAR *
+assign_comp_wordbreaks (self, value, unused, key)
      SHELL_VAR *self;
+     char *value;
+     arrayind_t unused;
+     char *key;
 {
-  ARRAY *a;
-  WORD_LIST *l;
+  if (rl_completer_word_break_characters &&
+      rl_completer_word_break_characters != rl_basic_word_break_characters)
+    free (rl_completer_word_break_characters);
 
-  l = get_directory_stack ();
-  a = word_list_to_array (l);
-  dispose_array (array_cell (self));
-  dispose_words (l);
-  self->value = (char *)a;
+  rl_completer_word_break_characters = savestring (value);
   return self;
 }
+#endif /* READLINE */
 
-static  SHELL_VAR *
-assign_dirstack (self, ind, value)
+#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS)
+static SHELL_VAR *
+assign_dirstack (self, value, ind, key)
      SHELL_VAR *self;
-     int ind;
      char *value;
+     arrayind_t ind;
+     char *key;
 {
   set_dirstack_element (ind, 1, value);
   return self;
 }
+
+static SHELL_VAR *
+get_dirstack (self)
+     SHELL_VAR *self;
+{
+  ARRAY *a;
+  WORD_LIST *l;
+
+  l = get_directory_stack (0);
+  a = array_from_word_list (l);
+  array_dispose (array_cell (self));
+  dispose_words (l);
+  var_setarray (self, a);
+  return self;
+}
 #endif /* PUSHD AND POPD && ARRAY_VARS */
 
 #if defined (ARRAY_VARS)
@@ -1095,29 +1505,144 @@ get_groupset (self)
       group_set = get_group_list (&ng);
       a = array_cell (self);
       for (i = 0; i < ng; i++)
-       array_add_element (a, i, group_set[i]);
+       array_insert (a, i, group_set[i]);
     }
   return (self);
 }
-#endif /* ARRAY_VARS */
 
 static SHELL_VAR *
-get_funcname (self)
+build_hashcmd (self)
      SHELL_VAR *self;
 {
-  if (variable_context && this_shell_function)
+  HASH_TABLE *h;
+  int i;
+  char *k, *v;
+  BUCKET_CONTENTS *item;
+
+  h = assoc_cell (self);
+  if (h)
+    assoc_dispose (h);
+
+  if (hashed_filenames == 0 || HASH_ENTRIES (hashed_filenames) == 0)
     {
-      FREE (self->value);
-      self->value = savestring (this_shell_function->name);
+      var_setvalue (self, (char *)NULL);
+      return self;
     }
-  return (self);
-}
 
-void
-make_funcname_visible (on_or_off)
-     int on_or_off;
-{
-  SHELL_VAR *v;
+  h = assoc_create (hashed_filenames->nbuckets);
+  for (i = 0; i < hashed_filenames->nbuckets; i++)
+    {
+      for (item = hash_items (i, hashed_filenames); item; item = item->next)
+       {
+         k = savestring (item->key);
+         v = pathdata(item)->path;
+         assoc_insert (h, k, v);
+       }
+    }
+
+  var_setvalue (self, (char *)h);
+  return self;
+}
+
+static SHELL_VAR *
+get_hashcmd (self)
+     SHELL_VAR *self;
+{
+  build_hashcmd (self);
+  return (self);
+}
+
+static SHELL_VAR *
+assign_hashcmd (self, value, ind, key)
+     SHELL_VAR *self;
+     char *value;
+     arrayind_t ind;
+     char *key;
+{
+  phash_insert (key, value, 0, 0);
+  return (build_hashcmd (self));
+}
+
+#if defined (ALIAS)
+static SHELL_VAR *
+build_aliasvar (self)
+     SHELL_VAR *self;
+{
+  HASH_TABLE *h;
+  int i;
+  char *k, *v;
+  BUCKET_CONTENTS *item;
+
+  h = assoc_cell (self);
+  if (h)
+    assoc_dispose (h);
+
+  if (aliases == 0 || HASH_ENTRIES (aliases) == 0)
+    {
+      var_setvalue (self, (char *)NULL);
+      return self;
+    }
+
+  h = assoc_create (aliases->nbuckets);
+  for (i = 0; i < aliases->nbuckets; i++)
+    {
+      for (item = hash_items (i, aliases); item; item = item->next)
+       {
+         k = savestring (item->key);
+         v = ((alias_t *)(item->data))->value;
+         assoc_insert (h, k, v);
+       }
+    }
+
+  var_setvalue (self, (char *)h);
+  return self;
+}
+
+static SHELL_VAR *
+get_aliasvar (self)
+     SHELL_VAR *self;
+{
+  build_aliasvar (self);
+  return (self);
+}
+
+static SHELL_VAR *
+assign_aliasvar (self, value, ind, key)
+     SHELL_VAR *self;
+     char *value;
+     arrayind_t ind;
+     char *key;
+{
+  add_alias (key, value);
+  return (build_aliasvar (self));
+}
+#endif /* ALIAS */
+
+#endif /* ARRAY_VARS */
+
+/* If ARRAY_VARS is not defined, this just returns the name of any
+   currently-executing function.  If we have arrays, it's a call stack. */
+static SHELL_VAR *
+get_funcname (self)
+     SHELL_VAR *self;
+{
+#if ! defined (ARRAY_VARS)
+  char *t;
+  if (variable_context && this_shell_function)
+    {
+      FREE (value_cell (self));
+      t = savestring (this_shell_function->name);
+      var_setvalue (self, t);
+    }
+#endif
+  return (self);
+}
+
+void
+make_funcname_visible (on_or_off)
+     int on_or_off;
+{
+  SHELL_VAR *v;
 
   v = find_variable ("FUNCNAME");
   if (v == 0 || v->dynamic_value == 0)
@@ -1129,83 +1654,155 @@ make_funcname_visible (on_or_off)
     VSETATTR (v, att_invisible);
 }
 
-#define INIT_DYNAMIC_VAR(var, val, gfunc, afunc) \
-  do \
-    { \
-      v = bind_variable (var, val); \
-      v->dynamic_value = gfunc; \
-      v->assign_func = afunc; \
-    } while (0)
+static SHELL_VAR *
+init_funcname_var ()
+{
+  SHELL_VAR *v;
 
-#define INIT_DYNAMIC_ARRAY_VAR(var, gfunc, afunc) \
-  do \
-    { \
-      v = make_new_array_variable (var); \
-      v->dynamic_value = gfunc; \
-      v->assign_func = afunc; \
-    } while (0)
+  v = find_variable ("FUNCNAME");
+  if (v)
+    return v;
+#if defined (ARRAY_VARS)
+  INIT_DYNAMIC_ARRAY_VAR ("FUNCNAME", get_funcname, null_array_assign);
+#else
+  INIT_DYNAMIC_VAR ("FUNCNAME", (char *)NULL, get_funcname, null_assign);
+#endif
+  VSETATTR (v, att_invisible|att_noassign);
+  return v;
+}
 
 static void
 initialize_dynamic_variables ()
 {
   SHELL_VAR *v;
 
-  INIT_DYNAMIC_VAR ("SECONDS", (char *)NULL, get_seconds, assign_seconds);
+  v = init_seconds_var ();
+
+  INIT_DYNAMIC_VAR ("BASH_COMMAND", (char *)NULL, get_bash_command, (sh_var_assign_func_t *)NULL);
+  INIT_DYNAMIC_VAR ("BASH_SUBSHELL", (char *)NULL, get_subshell, assign_subshell);
+
   INIT_DYNAMIC_VAR ("RANDOM", (char *)NULL, get_random, assign_random);
+  VSETATTR (v, att_integer);
   INIT_DYNAMIC_VAR ("LINENO", (char *)NULL, get_lineno, assign_lineno);
+  VSETATTR (v, att_integer);
+
+  INIT_DYNAMIC_VAR ("BASHPID", (char *)NULL, get_bashpid, null_assign);
+  VSETATTR (v, att_integer|att_readonly);
 
 #if defined (HISTORY)
-  INIT_DYNAMIC_VAR ("HISTCMD", (char *)NULL, get_histcmd, (DYNAMIC_FUNC *)NULL);
+  INIT_DYNAMIC_VAR ("HISTCMD", (char *)NULL, get_histcmd, (sh_var_assign_func_t *)NULL);
+  VSETATTR (v, att_integer);
+#endif
+
+#if defined (READLINE)
+  INIT_DYNAMIC_VAR ("COMP_WORDBREAKS", (char *)NULL, get_comp_wordbreaks, assign_comp_wordbreaks);
 #endif
 
 #if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS)
-  INIT_DYNAMIC_ARRAY_VAR ("DIRSTACK", get_dirstack, assign_dirstack);
+  v = init_dynamic_array_var ("DIRSTACK", get_dirstack, assign_dirstack, 0);
 #endif /* PUSHD_AND_POPD && ARRAY_VARS */
 
 #if defined (ARRAY_VARS)
-  INIT_DYNAMIC_ARRAY_VAR ("GROUPS", get_groupset, null_array_assign);
+  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);
+#  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);
+
+  v = init_dynamic_assoc_var ("BASH_CMDS", get_hashcmd, assign_hashcmd, att_nofree);
+#  if defined (ALIAS)
+  v = init_dynamic_assoc_var ("BASH_ALIASES", get_aliasvar, assign_aliasvar, att_nofree);
+#  endif
 #endif
 
-  INIT_DYNAMIC_VAR ("FUNCNAME", (char *)NULL, get_funcname, null_assign);
-  VSETATTR (v, att_invisible);
+  v = init_funcname_var ();
 }
 
+/* **************************************************************** */
+/*                                                                 */
+/*             Retrieving variables and values                     */
+/*                                                                 */
+/* **************************************************************** */
+
 /* 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). */
-SHELL_VAR *
-var_lookup (name, hashed_vars)
-     char *name;
+
+static SHELL_VAR *
+hash_lookup (name, hashed_vars)
+     const char *name;
      HASH_TABLE *hashed_vars;
 {
   BUCKET_CONTENTS *bucket;
 
-  bucket = find_hash_item (name, hashed_vars);
+  bucket = hash_search (name, hashed_vars, 0);
   return (bucket ? (SHELL_VAR *)bucket->data : (SHELL_VAR *)NULL);
 }
 
+SHELL_VAR *
+var_lookup (name, vcontext)
+     const char *name;
+     VAR_CONTEXT *vcontext;
+{
+  VAR_CONTEXT *vc;
+  SHELL_VAR *v;
+
+  v = (SHELL_VAR *)NULL;
+  for (vc = vcontext; vc; vc = vc->down)
+    if (v = hash_lookup (name, vc->table))
+      break;
+
+  return v;
+}
+
 /* Look up the variable entry named NAME.  If SEARCH_TEMPENV is non-zero,
-   then also search the temporarily built list of exported variables. */
+   then also search the temporarily built list of exported variables.
+   The lookup order is:
+       temporary_env
+       shell_variables list
+*/
+
 SHELL_VAR *
-find_variable_internal (name, search_tempenv)
-     char *name;
-     int search_tempenv;
+find_variable_internal (name, force_tempenv)
+     const char *name;
+     int force_tempenv;
 {
-  SHELL_VAR *var = (SHELL_VAR *)NULL;
+  SHELL_VAR *var;
+  int search_tempenv;
+
+  var = (SHELL_VAR *)NULL;
 
   /* 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
      a function or builtin, or if we are looking up a variable in a
      "subshell environment". */
-  if ((search_tempenv || subshell_environment) &&
-      (temporary_env || builtin_env || function_env))
-    var = find_tempenv_variable (name);
+  search_tempenv = force_tempenv || (expanding_redir == 0 && subshell_environment);
 
-  if (!var)
+  if (search_tempenv && temporary_env)         
+    var = hash_lookup (name, temporary_env);
+
+  if (var == 0)
     var = var_lookup (name, shell_variables);
 
-  if (!var)
+  if (var == 0)
+    return ((SHELL_VAR *)NULL);
+
+  return (var->dynamic_value ? (*(var->dynamic_value)) (var) : var);
+}
+
+SHELL_VAR *
+find_global_variable (name)
+     const char *name;
+{
+  SHELL_VAR *var;
+
+  var = var_lookup (name, global_variables);
+
+  if (var == 0)
     return ((SHELL_VAR *)NULL);
 
   return (var->dynamic_value ? (*(var->dynamic_value)) (var) : var);
@@ -1214,172 +1811,215 @@ find_variable_internal (name, search_tempenv)
 /* Look up the variable entry named NAME.  Returns the entry or NULL. */
 SHELL_VAR *
 find_variable (name)
-     char *name;
+     const char *name;
 {
-  return (find_variable_internal
-         (name, (variable_context || this_shell_builtin || builtin_env)));
+  return (find_variable_internal (name, (expanding_redir == 0 && (assigning_in_environment || executing_builtin))));
 }
 
 /* Look up the function entry whose name matches STRING.
    Returns the entry or NULL. */
 SHELL_VAR *
 find_function (name)
-     char *name;
+     const char *name;
 {
-  return (var_lookup (name, shell_functions));
+  return (hash_lookup (name, shell_functions));
 }
 
-/* Return the string value of a variable.  Return NULL if the variable
-   doesn't exist, or only has a function as a value.  Don't cons a new
-   string.  This is a potential memory leak if the variable is found
-   in the temporary environment. */
-char *
-get_string_value (var_name)
-     char *var_name;
+/* Find the function definition for the shell function named NAME.  Returns
+   the entry or NULL. */
+FUNCTION_DEF *
+find_function_def (name)
+     const char *name;
 {
-  SHELL_VAR *var;
-
-  var = find_variable (var_name);
+#if defined (DEBUGGER)
+  return ((FUNCTION_DEF *)hash_lookup (name, shell_function_defs));
+#else
+  return ((FUNCTION_DEF *)0);
+#endif
+}
 
-  if (!var)
-    return (char *)NULL;
+/* Return the value of VAR.  VAR is assumed to have been the result of a
+   lookup without any subscript, if arrays are compiled into the shell. */
+char *
+get_variable_value (var)
+     SHELL_VAR *var;
+{
+  if (var == 0)
+    return ((char *)NULL);
 #if defined (ARRAY_VARS)
   else if (array_p (var))
     return (array_reference (array_cell (var), 0));
+  else if (assoc_p (var))
+    return (assoc_reference (assoc_cell (var), "0"));
 #endif
   else
-    return (var->value);
+    return (value_cell (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. */
+char *
+get_string_value (var_name)
+     const char *var_name;
+{
+  SHELL_VAR *var;
+
+  var = find_variable (var_name);
+  return ((var) ? get_variable_value (var) : (char *)NULL);
 }
 
 /* This is present for use by the tilde and readline libraries. */
 char *
-get_env_value (v)
-     char *v;
+sh_get_env_value (v)
+     const char *v;
 {
   return get_string_value (v);
 }
 
+/* **************************************************************** */
+/*                                                                 */
+/*               Creating and setting variables                    */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Set NAME to VALUE if NAME has no value. */
+SHELL_VAR *
+set_if_not (name, value)
+     char *name, *value;
+{
+  SHELL_VAR *v;
+
+  if (shell_variables == 0)
+    create_variable_tables ();
+
+  v = find_variable (name);
+  if (v == 0)
+    v = bind_variable_internal (name, value, global_variables->table, HASH_NOSRCH, 0);
+  return (v);
+}
+
 /* Create a local variable referenced by NAME. */
 SHELL_VAR *
 make_local_variable (name)
-     char *name;
+     const char *name;
 {
   SHELL_VAR *new_var, *old_var;
-  BUCKET_CONTENTS *elt;
+  VAR_CONTEXT *vc;
+  int was_tmpvar;
+  char *tmp_value;
 
   /* local foo; local foo;  is a no-op. */
   old_var = find_variable (name);
-  if (old_var && old_var->context == variable_context)
-    return (old_var);
+  if (old_var && local_p (old_var) && old_var->context == variable_context)
+    {
+      VUNSETATTR (old_var, att_invisible);
+      return (old_var);
+    }
 
-  /* Since this is called only from the local/declare/typeset code, we can
-     call builtin_error here without worry (of course, it will also work
-     for anything that sets this_command_name). */
-  if (old_var && readonly_p (old_var))
+  was_tmpvar = old_var && tempvar_p (old_var);
+  if (was_tmpvar)
+    tmp_value = value_cell (old_var);
+
+  for (vc = shell_variables; vc; vc = vc->down)
+    if (vc_isfuncenv (vc) && vc->scope == variable_context)
+      break;
+
+  if (vc == 0)
     {
-      builtin_error ("%s: readonly variable");
+      internal_error (_("make_local_variable: no function context at current scope"));
       return ((SHELL_VAR *)NULL);
     }
+  else if (vc->table == 0)
+    vc->table = hash_create (TEMPENV_HASH_BUCKETS);
 
-  elt = remove_hash_item (name, shell_variables);
-  if (elt)
+  /* Since this is called only from the local/declare/typeset code, we can
+     call builtin_error here without worry (of course, it will also work
+     for anything that sets this_command_name).  Variables with the `noassign'
+     attribute may not be made local.  The test against old_var's context
+     level is to disallow local copies of readonly global variables (since I
+     believe that this could be a security hole).  Readonly copies of calling
+     function local variables are OK. */
+  if (old_var && (noassign_p (old_var) ||
+                (readonly_p (old_var) && old_var->context == 0)))
     {
-      old_var = (SHELL_VAR *)elt->data;
-      free (elt->key);
-      free (elt);
+      if (readonly_p (old_var))
+       sh_readonly (name);
+      return ((SHELL_VAR *)NULL);
     }
-  else
-    old_var = (SHELL_VAR *)NULL;
 
-  /* If a variable does not already exist with this name, then
-     just make a new one. */
   if (old_var == 0)
-    new_var = bind_variable (name, "");
+    new_var = make_new_variable (name, vc->table);
   else
     {
-      new_var = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR));
-
-      new_var->name = savestring (name);
-      new_var->value = xmalloc (1);
-      new_var->value[0] = '\0';
+      new_var = make_new_variable (name, vc->table);
 
-      CLEAR_EXPORTSTR (new_var);
-
-      new_var->dynamic_value = (DYNAMIC_FUNC *)NULL;
-      new_var->assign_func = (DYNAMIC_FUNC *)NULL;
+      /* If we found this variable in one of the temporary environments,
+        inherit its value.  Watch to see if this causes problems with
+        things like `x=4 local x'. */
+      if (was_tmpvar)
+       var_setvalue (new_var, savestring (tmp_value));
 
       new_var->attributes = exported_p (old_var) ? att_exported : 0;
-
-      new_var->prev_context = old_var;
-      elt = add_hash_item (savestring (name), shell_variables);
-      elt->data = (char *)new_var;
     }
 
+  vc->flags |= VC_HASLOCAL;
+
   new_var->context = variable_context;
   VSETATTR (new_var, att_local);
 
-  /* XXX */
-  if (variable_context >= local_variable_stack_size)
-    {
-      int old_size = local_variable_stack_size;
-      RESIZE_MALLOCED_BUFFER (have_local_variables, variable_context, 1,
-                             local_variable_stack_size, 8);
-      bzero ((char *)have_local_variables + old_size,
-            local_variable_stack_size - old_size);
-    }
-  have_local_variables[variable_context] = 1;          /* XXX */
+  if (ifsname (name))
+    setifs (new_var);
 
   return (new_var);
 }
 
-#if defined (ARRAY_VARS)
-SHELL_VAR *
-make_local_array_variable (name)
-     char *name;
-{
-  SHELL_VAR *var;
-  ARRAY *array;
-
-  var = make_local_variable (name);
-  if (var == 0)
-    return var;
-  array = new_array ();
-
-  FREE (value_cell(var));
-  var->value = (char *)array;
-  VSETATTR (var, att_array);
-  return var;
-}
-#endif /* ARRAY_VARS */
-
-/* Create a new shell variable with name NAME and add it to the hash table
-   of shell variables. */
-static
-SHELL_VAR *
-make_new_variable (name)
-     char *name;
+/* Create a new shell variable with name NAME. */
+static SHELL_VAR *
+new_shell_variable (name)
+     const char *name;
 {
   SHELL_VAR *entry;
-  BUCKET_CONTENTS *elt;
 
   entry = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR));
 
-  entry->attributes = 0;
   entry->name = savestring (name);
-  entry->value = (char *)NULL;
+  var_setvalue (entry, (char *)NULL);
   CLEAR_EXPORTSTR (entry);
 
-  entry->dynamic_value = (DYNAMIC_FUNC *)NULL;
-  entry->assign_func = (DYNAMIC_FUNC *)NULL;
+  entry->dynamic_value = (sh_var_value_func_t *)NULL;
+  entry->assign_func = (sh_var_assign_func_t *)NULL;
+
+  entry->attributes = 0;
 
   /* Always assume variables are to be made at toplevel!
      make_local_variable has the responsibilty of changing the
      variable context. */
   entry->context = 0;
-  entry->prev_context = (SHELL_VAR *)NULL;
 
-  elt = add_hash_item (savestring (name), shell_variables);
-  elt->data = (char *)entry;
+  return (entry);
+}
+
+/* Create a new shell variable with name NAME and add it to the hash table
+   TABLE. */
+static SHELL_VAR *
+make_new_variable (name, table)
+     const char *name;
+     HASH_TABLE *table;
+{
+  SHELL_VAR *entry;
+  BUCKET_CONTENTS *elt;
+
+  entry = new_shell_variable (name);
+
+  /* Make sure we have a shell_variables hash table to add to. */
+  if (shell_variables == 0)
+    create_variable_tables ();
+
+  elt = hash_insert (savestring (name), table, HASH_NOSRCH);
+  elt->data = (PTR_T)entry;
 
   return entry;
 }
@@ -1392,88 +2032,206 @@ make_new_array_variable (name)
   SHELL_VAR *entry;
   ARRAY *array;
 
-  entry = make_new_variable (name);
-  array = new_array ();
-  entry->value = (char *)array;
+  entry = make_new_variable (name, global_variables->table);
+  array = array_create ();
+
+  var_setarray (entry, array);
   VSETATTR (entry, att_array);
   return entry;
 }
-#endif
 
-char *
-make_variable_value (var, value)
-     SHELL_VAR *var;
-     char *value;
+SHELL_VAR *
+make_local_array_variable (name)
+     char *name;
 {
-  char *retval;
-  long lval;
-  int expok;
+  SHELL_VAR *var;
+  ARRAY *array;
 
-  /* If this variable has had its type set to integer (via `declare -i'),
-     then do expression evaluation on it and store the result.  The
-     functions in expr.c (evalexp and bind_int_variable) are responsible
-     for turning off the integer flag if they don't want further
-     evaluation done. */
-  if (integer_p (var))
-    {
-      lval = evalexp (value, &expok);
-      if (expok == 0)
-       jump_to_top_level (DISCARD);
-      retval = itos (lval);
-    }
-  else if (value)
-    {
-      if (*value)
-       retval = savestring (value);
-      else
-       {
-         retval = xmalloc (1);
-         retval[0] = '\0';
-       }
-    }
+  var = make_local_variable (name);
+  if (var == 0 || array_p (var))
+    return var;
+
+  array = array_create ();
+
+  dispose_variable_value (var);
+  var_setarray (var, array);
+  VSETATTR (var, att_array);
+  return var;
+}
+
+SHELL_VAR *
+make_new_assoc_variable (name)
+     char *name;
+{
+  SHELL_VAR *entry;
+  HASH_TABLE *hash;
+
+  entry = make_new_variable (name, global_variables->table);
+  hash = assoc_create (0);
+
+  var_setassoc (entry, hash);
+  VSETATTR (entry, att_assoc);
+  return entry;
+}
+
+SHELL_VAR *
+make_local_assoc_variable (name)
+     char *name;
+{
+  SHELL_VAR *var;
+  HASH_TABLE *hash;
+
+  var = make_local_variable (name);
+  if (var == 0 || assoc_p (var))
+    return var;
+
+  dispose_variable_value (var);
+  hash = assoc_create (0);
+
+  var_setassoc (var, hash);
+  VSETATTR (var, att_assoc);
+  return var;
+}
+#endif
+
+char *
+make_variable_value (var, value, flags)
+     SHELL_VAR *var;
+     char *value;
+     int flags;
+{
+  char *retval, *oval;
+  intmax_t lval, rval;
+  int expok, olen, op;
+
+  /* If this variable has had its type set to integer (via `declare -i'),
+     then do expression evaluation on it and store the result.  The
+     functions in expr.c (evalexp()) and bind_int_variable() are responsible
+     for turning off the integer flag if they don't want further
+     evaluation done. */
+  if (integer_p (var))
+    {
+      if (flags & ASS_APPEND)
+       {
+         oval = value_cell (var);
+         lval = evalexp (oval, &expok);        /* ksh93 seems to do this */
+         if (expok == 0)
+           {
+             top_level_cleanup ();
+             jump_to_top_level (DISCARD);
+           }
+       }
+      rval = evalexp (value, &expok);
+      if (expok == 0)
+       {
+         top_level_cleanup ();
+         jump_to_top_level (DISCARD);
+       }
+      if (flags & ASS_APPEND)
+       rval += lval;
+      retval = itos (rval);
+    }
+#if defined (CASEMOD_ATTRS)
+  else if (capcase_p (var) || uppercase_p (var) || lowercase_p (var))
+    {
+      if (flags & ASS_APPEND)
+       {
+         oval = get_variable_value (var);
+         if (oval == 0)        /* paranoia */
+           oval = "";
+         olen = STRLEN (oval);
+         retval = (char *)xmalloc (olen + (value ? STRLEN (value) : 0) + 1);
+         strcpy (retval, oval);
+         if (value)
+           strcpy (retval+olen, value);
+       }
+      else if (*value)
+       retval = savestring (value);
+      else
+       {
+         retval = (char *)xmalloc (1);
+         retval[0] = '\0';
+       }
+      op = capcase_p (var) ? CASE_CAPITALIZE
+                        : (uppercase_p (var) ? CASE_UPPER : CASE_LOWER);
+      oval = sh_modcase (retval, (char *)0, op);
+      free (retval);
+      retval = oval;
+    }
+#endif /* CASEMOD_ATTRS */
+  else if (value)
+    {
+      if (flags & ASS_APPEND)
+       {
+         oval = get_variable_value (var);
+         if (oval == 0)        /* paranoia */
+           oval = "";
+         olen = STRLEN (oval);
+         retval = (char *)xmalloc (olen + (value ? STRLEN (value) : 0) + 1);
+         strcpy (retval, oval);
+         if (value)
+           strcpy (retval+olen, value);
+       }
+      else if (*value)
+       retval = savestring (value);
+      else
+       {
+         retval = (char *)xmalloc (1);
+         retval[0] = '\0';
+       }
+    }
   else
     retval = (char *)NULL;
 
   return retval;
 }
 
-/* Bind a variable NAME to VALUE.  This conses up the name
-   and value strings. */
-SHELL_VAR *
-bind_variable (name, value)
-     char *name, *value;
+/* Bind a variable NAME to VALUE in the HASH_TABLE TABLE, which may be the
+   temporary environment (but usually is not). */
+static SHELL_VAR *
+bind_variable_internal (name, value, table, hflags, aflags)
+     const char *name;
+     char *value;
+     HASH_TABLE *table;
+     int hflags, aflags;
 {
   char *newval;
   SHELL_VAR *entry;
 
-  entry = var_lookup (name, shell_variables);
+  entry = (hflags & HASH_NOSRCH) ? (SHELL_VAR *)NULL : hash_lookup (name, table);
 
   if (entry == 0)
     {
-      entry = make_new_variable (name);
-      entry->value = make_variable_value (entry, value);
+      entry = make_new_variable (name, table);
+      var_setvalue (entry, make_variable_value (entry, value, 0)); /* XXX */
     }
-#if defined (ARRAY_VARS)
-  else if (entry->assign_func && array_p (entry) == 0)
-#else
-  else if (entry->assign_func)
-#endif
+  else if (entry->assign_func) /* array vars have assign functions now */
     {
       INVALIDATE_EXPORTSTR (entry);
-      return ((*(entry->assign_func)) (entry, value));
+      newval = (aflags & ASS_APPEND) ? make_variable_value (entry, value, aflags) : value;
+      if (assoc_p (entry))
+       entry = (*(entry->assign_func)) (entry, newval, -1, savestring ("0"));
+      else if (array_p (entry))
+       entry = (*(entry->assign_func)) (entry, newval, 0, 0);
+      else
+       entry = (*(entry->assign_func)) (entry, newval, -1, 0);
+      if (newval != value)
+       free (newval);
+      return (entry);
     }
   else
     {
-      if (readonly_p (entry))
+      if (readonly_p (entry) || noassign_p (entry))
        {
-         report_error ("%s: readonly variable", name);
+         if (readonly_p (entry))
+           err_readonly (name);
          return (entry);
        }
 
       /* Variables which are bound are visible. */
       VUNSETATTR (entry, att_invisible);
 
-      newval = make_variable_value (entry, value);
+      newval = make_variable_value (entry, value, aflags);     /* XXX */
 
       /* Invalidate any cached export string */
       INVALIDATE_EXPORTSTR (entry);
@@ -1485,18 +2243,20 @@ bind_variable (name, value)
         x[0]=b or `read x[0]'. */
       if (array_p (entry))
        {
-         array_add_element (array_cell (entry), 0, newval);
+         array_insert (array_cell (entry), 0, newval);
          free (newval);
        }
-      else
+      else if (assoc_p (entry))
        {
-         FREE (entry->value);
-         entry->value = newval;
+         assoc_insert (assoc_cell (entry), savestring ("0"), newval);
+         free (newval);
        }
-#else
-      FREE (entry->value);
-      entry->value = newval;
+      else
 #endif
+       {
+         FREE (value_cell (entry));
+         var_setvalue (entry, newval);
+       }
     }
 
   if (mark_modified_vars)
@@ -1507,6 +2267,43 @@ bind_variable (name, value)
 
   return (entry);
 }
+       
+/* Bind a variable NAME to VALUE.  This conses up the name
+   and value strings.  If we have a temporary environment, we bind there
+   first, then we bind into shell_variables. */
+
+SHELL_VAR *
+bind_variable (name, value, flags)
+     const char *name;
+     char *value;
+     int flags;
+{
+  SHELL_VAR *v;
+  VAR_CONTEXT *vc;
+
+  if (shell_variables == 0)
+    create_variable_tables ();
+
+  /* If we have a temporary environment, look there first for the variable,
+     and, if found, modify the value there before modifying it in the
+     shell_variables table.  This allows sourced scripts to modify values
+     given to them in a temporary environment while modifying the variable
+     value that the caller sees. */
+  if (temporary_env)
+    bind_tempenv_variable (name, value);
+
+  /* XXX -- handle local variables here. */
+  for (vc = shell_variables; vc; vc = vc->down)
+    {
+      if (vc_isfuncenv (vc) || vc_isbltnenv (vc))
+       {
+         v = hash_lookup (name, vc->table);
+         if (v)
+           return (bind_variable_internal (name, value, vc->table, 0, flags));
+       }
+    }
+  return (bind_variable_internal (name, value, global_variables->table, 0, 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
@@ -1514,17 +2311,30 @@ bind_variable (name, value)
    all modified variables should be exported, mark the variable for export
    and note that the export environment needs to be recreated. */
 SHELL_VAR *
-bind_variable_value (var, value)
+bind_variable_value (var, value, aflags)
      SHELL_VAR *var;
      char *value;
+     int aflags;
 {
   char *t;
 
   VUNSETATTR (var, att_invisible);
 
-  t = make_variable_value (var, value);
-  FREE (var->value);
-  var->value = t;
+  if (var->assign_func)
+    {
+      /* If we're appending, we need the old value, so use
+        make_variable_value */
+      t = (aflags & ASS_APPEND) ? make_variable_value (var, value, aflags) : value;
+      (*(var->assign_func)) (var, t, -1, 0);
+      if (t != value && t)
+       free (t);      
+    }
+  else
+    {
+      t = make_variable_value (var, value, aflags);
+      FREE (value_cell (var));
+      var_setvalue (var, t);
+    }
 
   INVALIDATE_EXPORTSTR (var);
 
@@ -1552,295 +2362,285 @@ bind_int_variable (lhs, rhs)
      char *lhs, *rhs;
 {
   register SHELL_VAR *v;
-  int isint;
+  int isint, isarr;
+
+  isint = isarr = 0;
+#if defined (ARRAY_VARS)
+  if (valid_array_reference (lhs))
+    {
+      isarr = 1;
+      v = array_variable_part (lhs, (char **)0, (int *)0);
+    }
+  else
+#endif
+    v = find_variable (lhs);
 
-  isint = 0;
-  v = find_variable (lhs);
   if (v)
     {
       isint = integer_p (v);
       VUNSETATTR (v, att_integer);
     }
 
-  v = bind_variable (lhs, rhs);
-  if (isint)
+#if defined (ARRAY_VARS)
+  if (isarr)
+    v = assign_array_element (lhs, rhs, 0);
+  else
+#endif
+    v = bind_variable (lhs, rhs, 0);
+
+  if (v && isint)
     VSETATTR (v, att_integer);
 
   return (v);
 }
 
-#if defined (ARRAY_VARS)
-/* Convert a shell variable to an array variable.  The original value is
-   saved as array[0]. */
 SHELL_VAR *
-convert_var_to_array (var)
-     SHELL_VAR *var;
+bind_var_to_int (var, val)
+     char *var;
+     intmax_t val;
 {
-  char *oldval;
-  ARRAY *array;
-
-  oldval = value_cell (var);
-  array = new_array ();
-  array_add_element (array, 0, oldval);
+  char ibuf[INT_STRLEN_BOUND (intmax_t) + 1], *p;
 
-  FREE (value_cell (var));
-  var->value = (char *)array;
-
-  INVALIDATE_EXPORTSTR (var);
-
-  VSETATTR (var, att_array);
-  VUNSETATTR (var, att_invisible);
-
-  return var;
+  p = fmtulong (val, 10, ibuf, sizeof (ibuf), 0);
+  return (bind_int_variable (var, p));
 }
 
-/* Perform an array assignment name[ind]=value.  If NAME already exists and
-   is not an array, and IND is 0, perform name=value instead.  If NAME exists
-   and is not an array, and IND is not 0, convert it into an array with the
-   existing value as name[0].
-
-   If NAME does not exist, just create an array variable, no matter what
-   IND's value may be. */
+/* Do a function binding to a variable.  You pass the name and
+   the command to bind to.  This conses the name and command. */
 SHELL_VAR *
-bind_array_variable (name, ind, value)
-     char *name;
-     int ind;
-     char *value;
+bind_function (name, value)
+     const char *name;
+     COMMAND *value;
 {
   SHELL_VAR *entry;
-  char *newval;
 
-  entry = var_lookup (name, shell_variables);
-
-  if (entry == (SHELL_VAR *) 0)
-    entry = make_new_array_variable (name);
-  else if (readonly_p (entry))
+  entry = find_function (name);
+  if (entry == 0)
     {
-      report_error ("%s: readonly variable", name);
-      return (entry);
+      BUCKET_CONTENTS *elt;
+
+      elt = hash_insert (savestring (name), shell_functions, HASH_NOSRCH);
+      entry = new_shell_variable (name);
+      elt->data = (PTR_T)entry;
     }
-  else if (array_p (entry) == 0)
-    entry = convert_var_to_array (entry);
+  else
+    INVALIDATE_EXPORTSTR (entry);
 
-  /* ENTRY is an array variable, and ARRAY points to the value. */
-  newval = make_variable_value (entry, value);
-  if (entry->assign_func)
-    (*entry->assign_func) (entry, ind, newval);
+  if (var_isset (entry))
+    dispose_command (function_cell (entry));
+
+  if (value)
+    var_setfunc (entry, copy_command (value));
   else
-    array_add_element (array_cell (entry), ind, newval);
-  FREE (newval);
+    var_setfunc (entry, 0);
+
+  VSETATTR (entry, att_function);
+
+  if (mark_modified_vars)
+    VSETATTR (entry, att_exported);
+
+  VUNSETATTR (entry, att_invisible);           /* Just to be sure */
+
+  if (exported_p (entry))
+    array_needs_making = 1;
+
+#if defined (PROGRAMMABLE_COMPLETION)
+  set_itemlist_dirty (&it_functions);
+#endif
 
   return (entry);
 }
 
-/* Perform a compound assignment statement for array NAME, where VALUE is
-   the text between the parens:  NAME=( VALUE ) */
-SHELL_VAR *
-assign_array_from_string (name, value)
-     char *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.*/
+void
+bind_function_def (name, value)
+     const char *name;
+     FUNCTION_DEF *value;
 {
-  SHELL_VAR *var;
+  FUNCTION_DEF *entry;
+  BUCKET_CONTENTS *elt;
+  COMMAND *cmd;
 
-  var = find_variable (name);
-  if (var == 0)
-    var = make_new_array_variable (name);
-  else if (readonly_p (var))
+  entry = find_function_def (name);
+  if (entry)
     {
-      report_error ("%s: readonly variable", name);
-      return ((SHELL_VAR *)NULL);
+      dispose_function_def_contents (entry);
+      entry = copy_function_def_contents (value, entry);
     }
-  else if (array_p (var) == 0)
-    var = convert_var_to_array (var);
+  else
+    {
+      cmd = value->command;
+      value->command = 0;
+      entry = copy_function_def (value);
+      value->command = cmd;
 
-  return (assign_array_var_from_string (var, value));
+      elt = hash_insert (savestring (name), shell_function_defs, HASH_NOSRCH);
+      elt->data = (PTR_T *)entry;
+    }
 }
+#endif /* DEBUGGER */
 
-SHELL_VAR *
-assign_array_var_from_word_list (var, list)
-     SHELL_VAR *var;
-     WORD_LIST *list;
+/* Add STRING, which is of the form foo=bar, to the temporary environment
+   HASH_TABLE (temporary_env).  The functions in execute_cmd.c are
+   responsible for moving the main temporary env to one of the other
+   temporary environments.  The expansion code in subst.c calls this. */
+int
+assign_in_env (word, flags)
+     WORD_DESC *word;
+     int flags;
 {
-  register int i;
-  register WORD_LIST *l;
-  ARRAY *a;
+  int offset;
+  char *name, *temp, *value;
+  SHELL_VAR *var;
+  const char *string;
 
-  for (a = array_cell (var), l = list, i = 0; l; l = l->next, i++)
-    if (var->assign_func)
-      (*var->assign_func) (var, i, l->word->word);
-    else
-      array_add_element (a, i, l->word->word);
-  return var;
-}
+  string = word->word;
 
-/* For each word in a compound array assignment, if the word looks like
-   [ind]=value, quote the `[' and `]' before the `=' to protect them from
-   unwanted filename expansion. */
-static void
-quote_array_assignment_chars (list)
-     WORD_LIST *list;
-{
-  char *s, *t, *nword;
-  int saw_eq;
-  WORD_LIST *l;
+  offset = assignment (string, 0);
+  name = savestring (string);
+  value = (char *)NULL;
 
-  for (l = list; l; l = l->next)
+  if (name[offset] == '=')
     {
-      if (l->word == 0 || l->word->word == 0 || l->word->word[0] == '\0')
-       continue;       /* should not happen, but just in case... */
-      /* Don't bother if it doesn't look like [ind]=value */
-      if (l->word->word[0] != '[' || strchr (l->word->word, '=') == 0) /* ] */
-       continue;
-      s = nword = xmalloc (strlen (l->word->word) * 2 + 1);
-      saw_eq = 0;
-      for (t = l->word->word; *t; )
+      name[offset] = 0;
+
+      /* ignore the `+' when assigning temporary environment */
+      if (name[offset - 1] == '+')
+       name[offset - 1] = '\0';
+
+      var = find_variable (name);
+      if (var && (readonly_p (var) || noassign_p (var)))
        {
-         if (*t == '=')
-           saw_eq = 1;
-         if (saw_eq == 0 && (*t == '[' || *t == ']'))
-           *s++ = '\\';
-         *s++ = *t++;
+         if (readonly_p (var))
+           err_readonly (name);
+         free (name);
+         return (0);
        }
-      *s = '\0';
-      free (l->word->word);
-      l->word->word = nword;
+
+      temp = name + offset + 1;
+      value = expand_assignment_string_to_string (temp, 0);
     }
-}
 
-/* Perform a compound array assignment:  VAR->name=( VALUE ).  The
-   VALUE has already had the parentheses stripped. */
-SHELL_VAR *
-assign_array_var_from_string (var, value)
-     SHELL_VAR *var;
-     char *value;
-{
-  ARRAY *a;
-  WORD_LIST *list, *nlist;
-  char *w, *val, *nval;
-  int ni, len, ind, last_ind;
+  if (temporary_env == 0)
+    temporary_env = hash_create (TEMPENV_HASH_BUCKETS);
 
-  if (value == 0)
-    return var;
+  var = hash_lookup (name, temporary_env);
+  if (var == 0)
+    var = make_new_variable (name, temporary_env);
+  else
+    FREE (value_cell (var));
 
-  /* If this is called from declare_builtin, value[0] == '(' and
-     strchr(value, ')') != 0.  In this case, we need to extract
-     the value from between the parens before going on. */
-  if (*value == '(')   /*)*/
+  if (value == 0)
     {
-      ni = 1;
-      val = extract_array_assignment_list (value, &ni);
-      if (val == 0)
-        return var;
+      value = (char *)xmalloc (1);     /* like do_assignment_internal */
+      value[0] = '\0';
     }
-  else
-    val = value;
 
-  /* Expand the value string into a list of words, performing all the
-     shell expansions including pathname generation and word splitting. */
-  /* First we split the string on whitespace, using the shell parser
-     (ksh93 seems to do this). */
-  list = parse_string_to_word_list (val, "array assign");
+  var_setvalue (var, value);
+  var->attributes |= (att_exported|att_tempvar);
+  var->context = variable_context;     /* XXX */
+
+  INVALIDATE_EXPORTSTR (var);
+  var->exportstr = mk_env_string (name, value);
 
-  /* If we're using [subscript]=value, we need to quote each [ and ] to
-     prevent unwanted filename expansion. */
-  if (list)
-    quote_array_assignment_chars (list);
+  array_needs_making = 1;
 
-  /* Now that we've split it, perform the shell expansions on each
-     word in the list. */
-  nlist = list ? expand_words_no_vars (list) : (WORD_LIST *)NULL;
+#if 0
+  if (ifsname (name))
+    setifs (var);
+else
+#endif
+  if (flags)
+    stupidly_hack_special_variables (name);
 
-  dispose_words (list);
+  if (echo_command_at_execute)
+    /* The Korn shell prints the `+ ' in front of assignment statements,
+       so we do too. */
+    xtrace_print_assignment (name, value, 0, 1);
 
-  if (val != value)
-    free (val);
+  free (name);
+  return 1;
+}
 
-  a = array_cell (var);
+/* **************************************************************** */
+/*                                                                 */
+/*                     Copying variables                           */
+/*                                                                 */
+/* **************************************************************** */
 
-  /* Now that we are ready to assign values to the array, kill the existing
-     value. */
-  if (a)
-    empty_array (a);
+#ifdef INCLUDE_UNUSED
+/* Copy VAR to a new data structure and return that structure. */
+SHELL_VAR *
+copy_variable (var)
+     SHELL_VAR *var;
+{
+  SHELL_VAR *copy = (SHELL_VAR *)NULL;
 
-  for (last_ind = 0, list = nlist; list; list = list->next)
+  if (var)
     {
-      w = list->word->word;
+      copy = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR));
 
-      /* We have a word of the form [ind]=value */
-      if (w[0] == '[')
-       {
-         len = skipsubscript (w, 0);
+      copy->attributes = var->attributes;
+      copy->name = savestring (var->name);
 
-         if (w[len] != ']' || w[len+1] != '=')
-           {
-             nval = make_variable_value (var, w);
-             if (var->assign_func)
-               (*var->assign_func) (var, last_ind, nval);
-             else
-               array_add_element (a, last_ind, nval);
-             FREE (nval);
-             last_ind++;
-             continue;
-           }
+      if (function_p (var))
+       var_setfunc (copy, copy_command (function_cell (var)));
+#if defined (ARRAY_VARS)
+      else if (array_p (var))
+       var_setarray (copy, array_copy (array_cell (var)));
+      else if (assoc_p (var))
+       var_setassoc (copy, assoc_copy (assoc_cell (var)));
+#endif
+      else if (value_cell (var))
+       var_setvalue (copy, savestring (value_cell (var)));
+      else
+       var_setvalue (copy, (char *)NULL);
 
-         if (len == 1)
-           {
-             report_error ("%s: bad array subscript", w);
-             continue;
-           }
+      copy->dynamic_value = var->dynamic_value;
+      copy->assign_func = var->assign_func;
 
-         if (ALL_ELEMENT_SUB (w[1]) && len == 2)
-           {
-             report_error ("%s: cannot assign to non-numeric index", w);
-             continue;
-           }
+      copy->exportstr = COPY_EXPORTSTR (var);
 
-         ind = array_expand_index (w + 1, len);
-         if (ind < 0)
-           {
-             report_error ("%s: bad array subscript", w);
-             continue;
-           }
-         last_ind = ind;
-         val = w + len + 2;
-       }
-      else             /* No [ind]=value, just a stray `=' */
-       {
-         ind = last_ind;
-         val = w;
-       }
-
-      if (integer_p (var))
-        this_command_name = (char *)NULL;      /* no command name for errors */
-      nval = make_variable_value (var, val);
-      if (var->assign_func)
-       (*var->assign_func) (var, ind, nval);
-      else
-       array_add_element (a, ind, nval);
-      FREE (nval);
-      last_ind++;
+      copy->context = var->context;
     }
-
-  dispose_words (nlist);
-  return (var);
+  return (copy);
 }
-#endif /* ARRAY_VARS */
+#endif
+
+/* **************************************************************** */
+/*                                                                 */
+/*               Deleting and unsetting variables                  */
+/*                                                                 */
+/* **************************************************************** */
 
 /* Dispose of the information attached to VAR. */
-void
-dispose_variable (var)
+static void
+dispose_variable_value (var)
      SHELL_VAR *var;
 {
-  if (!var)
-    return;
-
   if (function_p (var))
     dispose_command (function_cell (var));
 #if defined (ARRAY_VARS)
   else if (array_p (var))
-    dispose_array (array_cell (var));
+    array_dispose (array_cell (var));
+  else if (assoc_p (var))
+    assoc_dispose (assoc_cell (var));
 #endif
   else
     FREE (value_cell (var));
+}
+
+void
+dispose_variable (var)
+     SHELL_VAR *var;
+{
+  if (var == 0)
+    return;
+
+  if (nofree_p (var) == 0)
+    dispose_variable_value (var);
 
   FREE_EXPORTSTR (var);
 
@@ -1852,127 +2652,131 @@ dispose_variable (var)
   free (var);
 }
 
-#if defined (ARRAY_VARS)
-/* This function is called with SUB pointing to just after the beginning
-   `[' of an array subscript. */
+/* Unset the shell variable referenced by NAME. */
 int
-unbind_array_element (var, sub)
-     SHELL_VAR *var;
-     char *sub;
+unbind_variable (name)
+     const char *name;
 {
-  int len, ind;
-  ARRAY_ELEMENT *ae;
+  return makunbound (name, shell_variables);
+}
 
-  len = skipsubscript (sub, 0);
-  if (sub[len] != ']' || len == 0)
-    {
-      builtin_error ("%s[%s: bad array subscript", var->name, sub);
-      return -1;
-    }
-  sub[len] = '\0';
+/* Unset the shell function named NAME. */
+int
+unbind_func (name)
+     const char *name;
+{
+  BUCKET_CONTENTS *elt;
+  SHELL_VAR *func;
 
-  if (ALL_ELEMENT_SUB (sub[0]) && sub[1] == 0)
-    {
-      makunbound (var->name, shell_variables);
-      return (0);
-    }
-  ind = array_expand_index (sub, len+1);
-  if (ind < 0)
+  elt = hash_remove (name, shell_functions, 0);
+
+  if (elt == 0)
+    return -1;
+
+#if defined (PROGRAMMABLE_COMPLETION)
+  set_itemlist_dirty (&it_functions);
+#endif
+
+  func = (SHELL_VAR *)elt->data;
+  if (func)
     {
-      builtin_error ("[%s]: bad array subscript", sub);
-      return -1;
+      if (exported_p (func))
+       array_needs_making++;
+      dispose_variable (func);
     }
-  ae = array_delete_element (array_cell (var), ind);
-  if (ae)
-    destroy_array_element (ae);
-  return 0;
+
+  free (elt->key);
+  free (elt);
+
+  return 0;  
 }
-#endif
 
-/* Unset the variable referenced by NAME. */
+#if defined (DEBUGGER)
 int
-unbind_variable (name)
-     char *name;
+unbind_function_def (name)
+     const char *name;
 {
-  SHELL_VAR *var;
+  BUCKET_CONTENTS *elt;
+  FUNCTION_DEF *funcdef;
 
-  var = find_variable (name);
-  if (!var)
-    return (-1);
+  elt = hash_remove (name, shell_function_defs, 0);
 
-  /* This function should never be called with an array variable name. */
-#if defined (ARRAY_VARS)
-  if (array_p (var) == 0 && var->value)
-#else
-  if (var->value)
-#endif
-    {
-      free (var->value);
-      var->value = (char *)NULL;
-    }
+  if (elt == 0)
+    return -1;
 
-  makunbound (name, shell_variables);
+  funcdef = (FUNCTION_DEF *)elt->data;
+  if (funcdef)
+    dispose_function_def (funcdef);
 
-  return (0);
+  free (elt->key);
+  free (elt);
+
+  return 0;  
 }
+#endif /* DEBUGGER */
 
 /* Make the variable associated with NAME go away.  HASH_LIST is the
    hash table from which this variable should be deleted (either
    shell_variables or shell_functions).
    Returns non-zero if the variable couldn't be found. */
 int
-makunbound (name, hash_list)
-     char *name;
-     HASH_TABLE *hash_list;
+makunbound (name, vc)
+     const char *name;
+     VAR_CONTEXT *vc;
 {
   BUCKET_CONTENTS *elt, *new_elt;
-  SHELL_VAR *old_var, *new_var;
+  SHELL_VAR *old_var;
+  VAR_CONTEXT *v;
   char *t;
 
-  elt = remove_hash_item (name, hash_list);
+  for (elt = (BUCKET_CONTENTS *)NULL, v = vc; v; v = v->down)
+    if (elt = hash_remove (name, v->table, 0))
+      break;
 
   if (elt == 0)
     return (-1);
 
   old_var = (SHELL_VAR *)elt->data;
-  new_var = old_var->prev_context;
 
   if (old_var && exported_p (old_var))
     array_needs_making++;
 
-#if defined (PROGRAMMABLE_COMPLETION)
-  if (hash_list == shell_functions)
-    set_itemlist_dirty (&it_functions);
-#endif
-
   /* If we're unsetting a local variable and we're still executing inside
-     the function, just mark the variable as invisible.
-     kill_all_local_variables will clean it up later.  This 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. */
+     the function, just mark the variable as invisible.  The function
+     eventually called by pop_var_context() will clean it up later.  This
+     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 (nofree_p (old_var))
+       var_setvalue (old_var, (char *)NULL);
+#if defined (ARRAY_VARS)
+      else if (array_p (old_var))
+       array_dispose (array_cell (old_var));
+      else if (assoc_p (old_var))
+       assoc_dispose (assoc_cell (old_var));
+#endif
+      else
+       FREE (value_cell (old_var));
+      /* Reset the attributes.  Preserve the export attribute if the variable
+        came from a temporary environment.  Make sure it stays local, and
+        make it invisible. */ 
+      old_var->attributes = (exported_p (old_var) && tempvar_p (old_var)) ? att_exported : 0;
+      VSETATTR (old_var, att_local);
       VSETATTR (old_var, att_invisible);
+      var_setvalue (old_var, (char *)NULL);
       INVALIDATE_EXPORTSTR (old_var);
-      new_elt = add_hash_item (savestring (old_var->name), hash_list);
-      new_elt->data = (char *)old_var;
+
+      new_elt = hash_insert (savestring (old_var->name), v->table, 0);
+      new_elt->data = (PTR_T)old_var;
       stupidly_hack_special_variables (old_var->name);
+
       free (elt->key);
       free (elt);
       return (0);
     }
 
-  if (new_var)
-    {
-      /* Has to be a variable, functions don't have previous contexts. */
-      new_elt = add_hash_item (savestring (new_var->name), hash_list);
-      new_elt->data = (char *)new_var;
-
-      if (exported_p (new_var))
-       set_auto_export (new_var);
-    }
-
   /* Have to save a copy of name here, because it might refer to
      old_var->name.  If so, stupidly_hack_special_variables will
      reference freed memory. */
@@ -1984,87 +2788,38 @@ makunbound (name, hash_list)
   dispose_variable (old_var);
   stupidly_hack_special_variables (t);
   free (t);
-  return (0);
-}
-
-#ifdef INCLUDE_UNUSED
-/* Remove the variable with NAME if it is a local variable in the
-   current context. */
-int
-kill_local_variable (name)
-     char *name;
-{
-  SHELL_VAR *temp;
 
-  temp = find_variable (name);
-  if (temp && temp->context == variable_context)
-    {
-      makunbound (name, shell_variables);
-      return (0);
-    }
-  return (-1);
+  return (0);
 }
-#endif
 
 /* Get rid of all of the variables in the current context. */
-int
-variable_in_context (var)
-     SHELL_VAR *var;
-{
-  return (var && var->context == variable_context);
-}
-
 void
 kill_all_local_variables ()
 {
-  register int i, pass;
-  register SHELL_VAR *var, **list;
-  HASH_TABLE *varlist;
-
-  /* If HAVE_LOCAL_VARIABLES == 0, it means that we don't have any local
-     variables at all.  If VARIABLE_CONTEXT >= LOCAL_VARIABLE_STACK_SIZE,
-     it means that we have some local variables, but not in this variable
-     context (level of function nesting).  Also, if
-     HAVE_LOCAL_VARIABLES[VARIABLE_CONTEXT] == 0, we have no local variables
-     at this context. */
-  if (have_local_variables == 0 ||
-      variable_context >= local_variable_stack_size ||
-      have_local_variables[variable_context] == 0)
-    return;
-
-  for (pass = 0; pass < 2; pass++)
-    {
-      varlist = pass ? shell_functions : shell_variables;
+  VAR_CONTEXT *vc;
 
-      list = map_over (variable_in_context, varlist);
+  for (vc = shell_variables; vc; vc = vc->down)
+    if (vc_isfuncenv (vc) && vc->scope == variable_context)
+      break;
+  if (vc == 0)
+    return;            /* XXX */
 
-      if (list)
-       {
-         for (i = 0; var = list[i]; i++)
-           {
-             VUNSETATTR (var, att_local);
-             makunbound (var->name, varlist);
-           }
-         free (list);
-       }
+  if (vc->table && vc_haslocals (vc))
+    {
+      delete_all_variables (vc->table);
+      hash_dispose (vc->table);
     }
-
-  have_local_variables[variable_context] = 0;          /* XXX */
+  vc->table = (HASH_TABLE *)NULL;
 }
 
 static void
 free_variable_hash_data (data)
-     char *data;
+     PTR_T data;
 {
-  SHELL_VAR *var, *prev;
+  SHELL_VAR *var;
 
   var = (SHELL_VAR *)data;
-  while (var)
-    {
-      prev = var->prev_context;
-      dispose_variable (var);
-      var = prev;
-    }
+  dispose_variable (var);
 }
 
 /* Delete the entire contents of the hash table. */
@@ -2072,111 +2827,14 @@ void
 delete_all_variables (hashed_vars)
      HASH_TABLE *hashed_vars;
 {
-  flush_hash_table (hashed_vars, free_variable_hash_data);
-}
-
-static SHELL_VAR *
-new_shell_variable (name)
-     char *name;
-{
-  SHELL_VAR *var;
-
-  var = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR));
-
-  bzero ((char *)var, sizeof (SHELL_VAR));
-  var->name = savestring (name);
-  return (var);
-}
-
-/* Do a function binding to a variable.  You pass the name and
-   the command to bind to.  This conses the name and command. */
-SHELL_VAR *
-bind_function (name, value)
-     char *name;
-     COMMAND *value;
-{
-  SHELL_VAR *entry;
-
-  entry = find_function (name);
-  if (!entry)
-    {
-      BUCKET_CONTENTS *elt;
-
-      elt = add_hash_item (savestring (name), shell_functions);
-
-      entry = new_shell_variable (name);
-      entry->dynamic_value = entry->assign_func = (DYNAMIC_FUNC *)NULL;
-      CLEAR_EXPORTSTR (entry);
-
-      /* Functions are always made at the top level.  This allows a
-        function to define another function (like autoload). */
-      entry->context = 0;
-
-      elt->data = (char *)entry;
-    }
-
-  INVALIDATE_EXPORTSTR (entry);
-
-  if (entry->value)
-    dispose_command ((COMMAND *)entry->value);
-
-  entry->value = value ? (char *)copy_command (value) : (char *)NULL;
-  VSETATTR (entry, att_function);
-
-  if (mark_modified_vars)
-    VSETATTR (entry, att_exported);
-
-  VUNSETATTR (entry, att_invisible);           /* Just to be sure */
-
-  if (exported_p (entry))
-    array_needs_making = 1;
-
-#if defined (PROGRAMMABLE_COMPLETION)
-  set_itemlist_dirty (&it_functions);
-#endif
-
-  return (entry);
+  hash_flush (hashed_vars, free_variable_hash_data);
 }
 
-#ifdef INCLUDE_UNUSED
-/* Copy VAR to a new data structure and return that structure. */
-SHELL_VAR *
-copy_variable (var)
-     SHELL_VAR *var;
-{
-  SHELL_VAR *copy = (SHELL_VAR *)NULL;
-
-  if (var)
-    {
-      copy = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR));
-
-      copy->attributes = var->attributes;
-      copy->name = savestring (var->name);
-
-      if (function_p (var))
-       copy->value = (char *)copy_command (function_cell (var));
-#if defined (ARRAY_VARS)
-      else if (array_p (var))
-       copy->value = (char *)dup_array (array_cell (var));
-#endif
-      else if (value_cell (var))
-       copy->value = savestring (value_cell (var));
-      else
-       copy->value = (char *)NULL;
-
-      copy->dynamic_value = var->dynamic_value;
-      copy->assign_func = var->assign_func;
-
-      copy->exportstr = COPY_EXPORTSTR (var);
-
-      copy->context = var->context;
-
-      /* Don't bother copying previous contexts along with this variable. */
-      copy->prev_context = (SHELL_VAR *)NULL;
-    }
-  return (copy);
-}
-#endif
+/* **************************************************************** */
+/*                                                                 */
+/*                  Setting variable attributes                    */
+/*                                                                 */
+/* **************************************************************** */
 
 #define FIND_OR_MAKE_VARIABLE(name, entry) \
   do \
@@ -2184,8 +2842,8 @@ copy_variable (var)
       entry = find_variable (name); \
       if (!entry) \
        { \
-         entry = bind_variable (name, ""); \
-         if (!no_invisible_vars) entry->attributes |= att_invisible; \
+         entry = bind_variable (name, "", 0); \
+         if (!no_invisible_vars && entry) entry->attributes |= att_invisible; \
        } \
     } \
   while (0)
@@ -2207,7 +2865,7 @@ set_var_read_only (name)
    If NAME does not exist, we just punt, like auto_export code below. */
 void
 set_func_read_only (name)
-     char *name;
+     const char *name;
 {
   SHELL_VAR *entry;
 
@@ -2231,7 +2889,7 @@ set_var_auto_export (name)
 /* Make the function associated with NAME be auto-exported. */
 void
 set_func_auto_export (name)
-     char *name;
+     const char *name;
 {
   SHELL_VAR *entry;
 
@@ -2241,101 +2899,226 @@ set_func_auto_export (name)
 }
 #endif
 
-#if defined (ARRAY_VARS)
-/* This function assumes s[i] == '['; returns with s[ret] == ']' if
-   an array subscript is correctly parsed. */
-int
-skipsubscript (s, i)
-     char *s;
-     int i;
-{
-  int count, c;
-
-  for (count = 1; count && (c = s[++i]); )
-    {
-      if (c == '[')
-       count++;
-      else if (c == ']')
-       count--;
-    }
-  return i;
-}
-#endif /* ARRAY_VARS */
+/* **************************************************************** */
+/*                                                                 */
+/*                  Creating lists of variables                    */
+/*                                                                 */
+/* **************************************************************** */
 
-/* Returns non-zero if STRING is an assignment statement.  The returned value
-   is the index of the `=' sign. */
-int
-assignment (string)
-     char *string;
+static VARLIST *
+vlist_alloc (nentries)
+     int nentries;
 {
-  register int c, newi, indx;
+  VARLIST  *vlist;
 
-  c = string[indx = 0];
+  vlist = (VARLIST *)xmalloc (sizeof (VARLIST));
+  vlist->list = (SHELL_VAR **)xmalloc ((nentries + 1) * sizeof (SHELL_VAR *));
+  vlist->list_size = nentries;
+  vlist->list_len = 0;
+  vlist->list[0] = (SHELL_VAR *)NULL;
 
-  if (legal_variable_starter (c) == 0)
-    return (0);
+  return vlist;
+}
 
-  while (c = string[indx])
+static VARLIST *
+vlist_realloc (vlist, n)
+     VARLIST *vlist;
+     int n;
+{
+  if (vlist == 0)
+    return (vlist = vlist_alloc (n));
+  if (n > vlist->list_size)
     {
-      /* The following is safe.  Note that '=' at the start of a word
-        is not an assignment statement. */
-      if (c == '=')
-       return (indx);
-
-#if defined (ARRAY_VARS)
-      if (c == '[')
-       {
-         newi = skipsubscript (string, indx);
-         if (string[newi++] != ']')
-           return (0);
-         return ((string[newi] == '=') ? newi : 0);
-       }
-#endif /* ARRAY_VARS */
-
-      /* Variable names in assignment statements may contain only letters,
-        digits, and `_'. */
-      if (legal_variable_char (c) == 0)
-       return (0);
-
-      indx++;
+      vlist->list_size = n;
+      vlist->list = (SHELL_VAR **)xrealloc (vlist->list, (vlist->list_size + 1) * sizeof (SHELL_VAR *));
     }
-  return (0);
+  return vlist;
 }
 
-static int
-visible_var (var)
+static void
+vlist_add (vlist, var, flags)
+     VARLIST *vlist;
      SHELL_VAR *var;
+     int flags;
 {
-  return (invisible_p (var) == 0);
+  register int i;
+
+  for (i = 0; i < vlist->list_len; i++)
+    if (STREQ (var->name, vlist->list[i]->name))
+      break;
+  if (i < vlist->list_len)
+    return;
+
+  if (i >= vlist->list_size)
+    vlist = vlist_realloc (vlist, vlist->list_size + 16);
+
+  vlist->list[vlist->list_len++] = var;
+  vlist->list[vlist->list_len] = (SHELL_VAR *)NULL;
 }
 
-static SHELL_VAR **
-_visible_names (table)
-     HASH_TABLE *table;
+/* Map FUNCTION over the variables in VAR_HASH_TABLE.  Return an array of the
+   variables for which FUNCTION returns a non-zero value.  A NULL value
+   for FUNCTION means to use all variables. */
+SHELL_VAR **
+map_over (function, vc)
+     sh_var_map_func_t *function;
+     VAR_CONTEXT *vc;
 {
-  SHELL_VAR **list;
+  VAR_CONTEXT *v;
+  VARLIST *vlist;
+  SHELL_VAR **ret;
+  int nentries;
 
-  list = map_over (visible_var, table);
+  for (nentries = 0, v = vc; v; v = v->down)
+    nentries += HASH_ENTRIES (v->table);
 
-  if (list /* && posixly_correct */)
-    sort_variables (list);
+  if (nentries == 0)
+    return (SHELL_VAR **)NULL;
 
-  return (list);
+  vlist = vlist_alloc (nentries);
+
+  for (v = vc; v; v = v->down)
+    flatten (v->table, function, vlist, 0);
+
+  ret = vlist->list;
+  free (vlist);
+  return ret;
 }
 
 SHELL_VAR **
-all_visible_functions ()
+map_over_funcs (function)
+     sh_var_map_func_t *function;
 {
-  return (_visible_names (shell_functions));
+  VARLIST *vlist;
+  SHELL_VAR **ret;
+
+  if (shell_functions == 0 || HASH_ENTRIES (shell_functions) == 0)
+    return ((SHELL_VAR **)NULL);
+
+  vlist = vlist_alloc (HASH_ENTRIES (shell_functions));
+
+  flatten (shell_functions, function, vlist, 0);
+
+  ret = vlist->list;
+  free (vlist);
+  return ret;
 }
 
-SHELL_VAR **
-all_visible_variables ()
+/* Flatten VAR_HASH_TABLE, applying FUNC to each member and adding those
+   elements for which FUNC succeeds to VLIST->list.  FLAGS is reserved
+   for future use.  Only unique names are added to VLIST.  If FUNC is
+   NULL, each variable in VAR_HASH_TABLE is added to VLIST.  If VLIST is
+   NULL, FUNC is applied to each SHELL_VAR in VAR_HASH_TABLE.  If VLIST
+   and FUNC are both NULL, nothing happens. */
+static void
+flatten (var_hash_table, func, vlist, flags)
+     HASH_TABLE *var_hash_table;
+     sh_var_map_func_t *func;
+     VARLIST *vlist;
+     int flags;
 {
-  return (_visible_names (shell_variables));
-}
+  register int i;
+  register BUCKET_CONTENTS *tlist;
+  int r;
+  SHELL_VAR *var;
 
-/* Return non-zero if the variable VAR is visible and exported.  Array
+  if (var_hash_table == 0 || (HASH_ENTRIES (var_hash_table) == 0) || (vlist == 0 && func == 0))
+    return;
+
+  for (i = 0; i < var_hash_table->nbuckets; i++)
+    {
+      for (tlist = hash_items (i, var_hash_table); tlist; tlist = tlist->next)
+       {
+         var = (SHELL_VAR *)tlist->data;
+
+         r = func ? (*func) (var) : 1;
+         if (r && vlist)
+           vlist_add (vlist, var, flags);
+       }
+    }
+}
+
+void
+sort_variables (array)
+     SHELL_VAR **array;
+{
+  qsort (array, strvec_len ((char **)array), sizeof (SHELL_VAR *), (QSFUNC *)qsort_var_comp);
+}
+
+static int
+qsort_var_comp (var1, var2)
+     SHELL_VAR **var1, **var2;
+{
+  int result;
+
+  if ((result = (*var1)->name[0] - (*var2)->name[0]) == 0)
+    result = strcmp ((*var1)->name, (*var2)->name);
+
+  return (result);
+}
+
+/* Apply FUNC to each variable in SHELL_VARIABLES, adding each one for
+   which FUNC succeeds to an array of SHELL_VAR *s.  Returns the array. */
+static SHELL_VAR **
+vapply (func)
+     sh_var_map_func_t *func;
+{
+  SHELL_VAR **list;
+
+  list = map_over (func, shell_variables);
+  if (list /* && posixly_correct */)
+    sort_variables (list);
+  return (list);
+}
+
+/* Apply FUNC to each variable in SHELL_FUNCTIONS, adding each one for
+   which FUNC succeeds to an array of SHELL_VAR *s.  Returns the array. */
+static SHELL_VAR **
+fapply (func)
+     sh_var_map_func_t *func;
+{
+  SHELL_VAR **list;
+
+  list = map_over_funcs (func);
+  if (list /* && posixly_correct */)
+    sort_variables (list);
+  return (list);
+}
+
+/* Create a NULL terminated array of all the shell variables. */
+SHELL_VAR **
+all_shell_variables ()
+{
+  return (vapply ((sh_var_map_func_t *)NULL));
+}
+
+/* Create a NULL terminated array of all the shell functions. */
+SHELL_VAR **
+all_shell_functions ()
+{
+  return (fapply ((sh_var_map_func_t *)NULL));
+}
+
+static int
+visible_var (var)
+     SHELL_VAR *var;
+{
+  return (invisible_p (var) == 0);
+}
+
+SHELL_VAR **
+all_visible_functions ()
+{
+  return (fapply (visible_var));
+}
+
+SHELL_VAR **
+all_visible_variables ()
+{
+  return (vapply (visible_var));
+}
+
+/* Return non-zero if the variable VAR is visible and exported.  Array
    variables cannot be exported. */
 static int
 visible_and_exported (var)
@@ -2344,15 +3127,73 @@ visible_and_exported (var)
   return (invisible_p (var) == 0 && exported_p (var));
 }
 
+/* Candidate variables for the export environment are either valid variables
+   with the export attribute or invalid variables inherited from the initial
+   environment and simply passed through. */
+static int
+export_environment_candidate (var)
+     SHELL_VAR *var;
+{
+  return (exported_p (var) && (invisible_p (var) == 0 || imported_p (var)));
+}
+
+/* Return non-zero if VAR is a local variable in the current context and
+   is exported. */
+static int
+local_and_exported (var)
+     SHELL_VAR *var;
+{
+  return (invisible_p (var) == 0 && local_p (var) && var->context == variable_context && exported_p (var));
+}
+
 SHELL_VAR **
 all_exported_variables ()
 {
-  SHELL_VAR **list;
+  return (vapply (visible_and_exported));
+}
 
-  list = map_over (visible_and_exported, shell_variables);
-  if (list)
-    sort_variables (list);
-  return (list);
+SHELL_VAR **
+local_exported_variables ()
+{
+  return (vapply (local_and_exported));
+}
+
+static int
+variable_in_context (var)
+     SHELL_VAR *var;
+{
+  return (invisible_p (var) == 0 && local_p (var) && var->context == variable_context);
+}
+
+SHELL_VAR **
+all_local_variables ()
+{
+  VARLIST *vlist;
+  SHELL_VAR **ret;
+  VAR_CONTEXT *vc;
+
+  vc = shell_variables;
+  for (vc = shell_variables; vc; vc = vc->down)
+    if (vc_isfuncenv (vc) && vc->scope == variable_context)
+      break;
+
+  if (vc == 0)
+    {
+      internal_error (_("all_local_variables: no function context at current scope"));
+      return (SHELL_VAR **)NULL;
+    }
+  if (vc->table == 0 || HASH_ENTRIES (vc->table) == 0 || vc_haslocals (vc) == 0)
+    return (SHELL_VAR **)NULL;
+    
+  vlist = vlist_alloc (HASH_ENTRIES (vc->table));
+
+  flatten (vc->table, variable_in_context, vlist, 0);
+
+  ret = vlist->list;
+  free (vlist);
+  if (ret)
+    sort_variables (ret);
+  return ret;
 }
 
 #if defined (ARRAY_VARS)
@@ -2367,18 +3208,13 @@ visible_array_vars (var)
 SHELL_VAR **
 all_array_variables ()
 {
-  SHELL_VAR **list;
-
-  list = map_over (visible_array_vars, shell_variables);
-  if (list)
-    sort_variables (list);
-  return (list);
+  return (vapply (visible_array_vars));
 }
 #endif /* ARRAY_VARS */
 
 char **
 all_variables_matching_prefix (prefix)
-     char *prefix;
+     const char *prefix;
 {
   SHELL_VAR **varlist;
   char **rlist;
@@ -2390,11 +3226,11 @@ all_variables_matching_prefix (prefix)
     ;
   if (varlist == 0 || vind == 0)
     return ((char **)NULL);
-  rlist = alloc_array (vind + 1);
+  rlist = strvec_create (vind + 1);
   for (vind = rind = 0; varlist[vind]; vind++)
     {
       if (plen == 0 || STREQN (prefix, varlist[vind]->name, plen))
-        rlist[rind++] = savestring (varlist[vind]->name);
+       rlist[rind++] = savestring (varlist[vind]->name);
     }
   rlist[rind] = (char *)0;
   free (varlist);
@@ -2402,16 +3238,172 @@ all_variables_matching_prefix (prefix)
   return rlist;
 }
 
+/* **************************************************************** */
+/*                                                                 */
+/*              Managing temporary variable scopes                 */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Make variable NAME have VALUE in the temporary environment. */
+static SHELL_VAR *
+bind_tempenv_variable (name, value)
+     const char *name;
+     char *value;
+{
+  SHELL_VAR *var;
+
+  var = temporary_env ? hash_lookup (name, temporary_env) : (SHELL_VAR *)NULL;
+
+  if (var)
+    {
+      FREE (value_cell (var));
+      var_setvalue (var, savestring (value));
+      INVALIDATE_EXPORTSTR (var);
+    }
+
+  return (var);
+}
+
+/* Find a variable in the temporary environment that is named NAME.
+   Return the SHELL_VAR *, or NULL if not found. */
+SHELL_VAR *
+find_tempenv_variable (name)
+     const char *name;
+{
+  return (temporary_env ? hash_lookup (name, temporary_env) : (SHELL_VAR *)NULL);
+}
+
+char **tempvar_list;
+int tvlist_ind;
+
+/* Push the variable described by (SHELL_VAR *)DATA down to the next
+   variable context from the temporary environment. */
+static void
+push_temp_var (data)
+     PTR_T data;
+{
+  SHELL_VAR *var, *v;
+  HASH_TABLE *binding_table;
+
+  var = (SHELL_VAR *)data;
+
+  binding_table = shell_variables->table;
+  if (binding_table == 0)
+    {
+      if (shell_variables == global_variables)
+       /* shouldn't happen */
+       binding_table = shell_variables->table = global_variables->table = hash_create (0);
+      else
+       binding_table = shell_variables->table = hash_create (TEMPENV_HASH_BUCKETS);
+    }
+
+  v = bind_variable_internal (var->name, value_cell (var), binding_table, 0, 0);
+
+  /* XXX - should we set the context here?  It shouldn't matter because of how
+     assign_in_env works, but might want to check. */
+  if (binding_table == global_variables->table)                /* XXX */
+    var->attributes &= ~(att_tempvar|att_propagate);
+  else
+    {
+      var->attributes |= att_propagate;
+      if  (binding_table == shell_variables->table)
+       shell_variables->flags |= VC_HASTMPVAR;
+    }
+  v->attributes |= var->attributes;
+
+  if (find_special_var (var->name) >= 0)
+    tempvar_list[tvlist_ind++] = savestring (var->name);
+
+  dispose_variable (var);
+}
+
+static void
+propagate_temp_var (data)
+     PTR_T data;
+{
+  SHELL_VAR *var;
+
+  var = (SHELL_VAR *)data;
+  if (tempvar_p (var) && (var->attributes & att_propagate))
+    push_temp_var (data);
+  else
+    {
+      if (find_special_var (var->name) >= 0)
+       tempvar_list[tvlist_ind++] = savestring (var->name);
+      dispose_variable (var);
+    }
+}
+
+/* Free the storage used in the hash table for temporary
+   environment variables.  PUSHF is a function to be called
+   to free each hash table entry.  It takes care of pushing variables
+   to previous scopes if appropriate.  PUSHF stores names of variables
+   that require special handling (e.g., IFS) on tempvar_list, so this
+   function can call stupidly_hack_special_variables on all the
+   variables in the list when the temporary hash table is destroyed. */
+static void
+dispose_temporary_env (pushf)
+     sh_free_func_t *pushf;
+{
+  int i;
+
+  tempvar_list = strvec_create (HASH_ENTRIES (temporary_env) + 1);
+  tempvar_list[tvlist_ind = 0] = 0;
+    
+  hash_flush (temporary_env, pushf);
+  hash_dispose (temporary_env);
+  temporary_env = (HASH_TABLE *)NULL;
+
+  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]);
+
+  strvec_dispose (tempvar_list);
+  tempvar_list = 0;
+  tvlist_ind = 0;
+}
+
+void
+dispose_used_env_vars ()
+{
+  if (temporary_env)
+    {
+      dispose_temporary_env (propagate_temp_var);
+      maybe_make_export_env ();
+    }
+}
+
+/* Take all of the shell variables in the temporary environment HASH_TABLE
+   and make shell variables from them at the current variable context. */
+void
+merge_temporary_env ()
+{
+  if (temporary_env)
+    dispose_temporary_env (push_temp_var);
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*          Creating and manipulating the environment              */
+/*                                                                 */
+/* **************************************************************** */
+
 static inline char *
 mk_env_string (name, value)
-     char *name, *value;
+     const char *name, *value;
 {
   int name_len, value_len;
   char *p;
 
   name_len = strlen (name);
   value_len = STRLEN (value);
-  p = xmalloc (2 + name_len + value_len);
+  p = (char *)xmalloc (2 + name_len + value_len);
   strcpy (p, name);
   p[name_len] = '=';
   if (value && *value)
@@ -2421,6 +3413,7 @@ mk_env_string (name, value)
   return (p);
 }
 
+#ifdef DEBUG
 /* Debugging */
 static int
 valid_exportstr (v)
@@ -2429,71 +3422,55 @@ valid_exportstr (v)
   char *s;
 
   s = v->exportstr;
-  if (legal_variable_starter (*s) == 0)
+  if (s == 0)
     {
-      internal_error ("invalid character %d in exportstr for %s", *s, v->name);
+      internal_error (_("%s has null exportstr"), v->name);
+      return (0);
+    }
+  if (legal_variable_starter ((unsigned char)*s) == 0)
+    {
+      internal_error (_("invalid character %d in exportstr for %s"), *s, v->name);
       return (0);
     }
   for (s = v->exportstr + 1; s && *s; s++)
     {
       if (*s == '=')
-        break;
-      if (legal_variable_char (*s) == 0)
+       break;
+      if (legal_variable_char ((unsigned char)*s) == 0)
        {
-         internal_error ("invalid character %d in exportstr for %s", *s, v->name);
+         internal_error (_("invalid character %d in exportstr for %s"), *s, v->name);
          return (0);
        }
     }
   if (*s != '=')
     {
-      internal_error ("no `=' in exportstr for %s", v->name);
+      internal_error (_("no `=' in exportstr for %s"), v->name);
       return (0);
     }
   return (1);
 }
+#endif
 
-/* Make an array of assignment statements from the hash table
-   HASHED_VARS which contains SHELL_VARs.  Only visible, exported
-   variables are eligible. */
-char **
-make_var_array (hashed_vars)
-     HASH_TABLE *hashed_vars;
+static char **
+make_env_array_from_var_list (vars)
+     SHELL_VAR **vars;
 {
   register int i, list_index;
   register SHELL_VAR *var;
   char **list, *value;
-  SHELL_VAR **vars;
-
-  vars = map_over (visible_and_exported, hashed_vars);
-
-  if (vars == 0)
-    return (char **)NULL;
 
-  list = alloc_array ((1 + array_len ((char **)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 (var->exportstr)
-        {
-#if defined(__CYGWIN__) || defined (__CYGWIN32__)
-         INVALIDATE_EXPORTSTR (var);
-         value = value_cell (var);
-#else
-         /* XXX -- this test can go away in the next release, to be replaced
-            by a simple `value = var->exportstr;', when the exportstr code
-            is better-tested.  Until then, don't do it for cygwin at all,
-            since that system has some weird environment variables. */
-          if (valid_exportstr (var))
-           value = var->exportstr;
-         else
-           {
-             INVALIDATE_EXPORTSTR (var);
-             value = value_cell (var);
-           }
+#if defined (__CYGWIN__)
+      /* We don't use the exportstr stuff on Cygwin at all. */
+      INVALIDATE_EXPORTSTR (var);
 #endif
-        }
+      if (var->exportstr)
+       value = var->exportstr;
       else if (function_p (var))
        value = named_function_string ((char *)NULL, function_cell (var), 0);
 #if defined (ARRAY_VARS)
@@ -2502,6 +3479,12 @@ make_var_array (hashed_vars)
        value = array_to_assignment_string (array_cell (var));
 #  else
        continue;       /* XXX array vars cannot yet be exported */
+#  endif
+      else if (assoc_p (var))
+#  if 0
+       value = assoc_to_assignment_string (assoc_cell (var));
+#  else
+       continue;       /* XXX associative array vars cannot yet be exported */
 #  endif
 #endif
       else
@@ -2514,440 +3497,647 @@ make_var_array (hashed_vars)
          list[list_index] = USE_EXPORTSTR ? savestring (value)
                                           : mk_env_string (var->name, value);
 
-         if (USE_EXPORTSTR == 0 && function_p (var))
-           {
-             SAVE_EXPORTSTR (var, list[list_index]);
-           }
+         if (USE_EXPORTSTR == 0)
+           SAVE_EXPORTSTR (var, list[list_index]);
+
          list_index++;
 #undef USE_EXPORTSTR
 
 #if 0  /* not yet */
 #if defined (ARRAY_VARS)
-         if (array_p (var))
+         if (array_p (var) || assoc_p (var))
            free (value);
 #endif
 #endif
        }
     }
 
-  free (vars);
   list[list_index] = (char *)NULL;
   return (list);
 }
 
-/* Add STRING to the array of foo=bar strings that we already
-   have to add to the environment.  */
-int
-assign_in_env (string)
-     char *string;
+/* Make an array of assignment statements from the hash table
+   HASHED_VARS which contains SHELL_VARs.  Only visible, exported
+   variables are eligible. */
+static char **
+make_var_export_array (vcxt)
+     VAR_CONTEXT *vcxt;
 {
-  int size, offset;
-  char *name, *temp, *value;
-  int nlen, vlen;
-  WORD_LIST *list;
-  SHELL_VAR *var;
+  char **list;
+  SHELL_VAR **vars;
 
-  offset = assignment (string);
-  name = savestring (string);
-  value = (char *)NULL;
+#if 0
+  vars = map_over (visible_and_exported, vcxt);
+#else
+  vars = map_over (export_environment_candidate, vcxt);
+#endif
 
-  if (name[offset] == '=')
-    {
-      name[offset] = 0;
+  if (vars == 0)
+    return (char **)NULL;
 
-      var = find_variable (name);
-      if (var && readonly_p (var))
-       {
-         report_error ("%s: readonly variable", name);
-         free (name);
-         return (0);
-       }
-      temp = name + offset + 1;
-      temp = (strchr (temp, '~') != 0) ? bash_tilde_expand (temp) : savestring (temp);
+  list = make_env_array_from_var_list (vars);
 
-      list = expand_string_unsplit (temp, 0);
-      value = string_list (list);
+  free (vars);
+  return (list);
+}
 
-      if (list)
-       dispose_words (list);
+static char **
+make_func_export_array ()
+{
+  char **list;
+  SHELL_VAR **vars;
 
-      free (temp);
-    }
+  vars = map_over_funcs (visible_and_exported);
+  if (vars == 0)
+    return (char **)NULL;
 
-  temp = mk_env_string (name, value);
-  FREE (value);
-  free (name);
+  list = make_env_array_from_var_list (vars);
 
-  if (temporary_env == 0)
-    {
-      temporary_env = (char **)xmalloc (sizeof (char *));
-      temporary_env [0] = (char *)NULL;
-    }
+  free (vars);
+  return (list);
+}
 
-  size = array_len (temporary_env);
-  temporary_env = (char **)
-    xrealloc (temporary_env, (size + 2) * (sizeof (char *)));
+/* Add ENVSTR to the end of the exported environment, EXPORT_ENV. */
+#define add_to_export_env(envstr,do_alloc) \
+do \
+  { \
+    if (export_env_index >= (export_env_size - 1)) \
+      { \
+       export_env_size += 16; \
+       export_env = strvec_resize (export_env, export_env_size); \
+       environ = export_env; \
+      } \
+    export_env[export_env_index++] = (do_alloc) ? savestring (envstr) : envstr; \
+    export_env[export_env_index] = (char *)NULL; \
+  } while (0)
 
-  temporary_env[size] = temp;
-  temporary_env[size + 1] = (char *)NULL;
-  array_needs_making = 1;
+/* Add ASSIGN to EXPORT_ENV, or supercede 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)
+     char *assign;
+     int do_alloc;
+{
+  register int i;
+  int equal_offset;
 
-  if (echo_command_at_execute)
+  equal_offset = assignment (assign, 0);
+  if (equal_offset == 0)
+    return (export_env);
+
+  /* If this is a function, then only supersede the function definition.
+     We do this by including the `=() {' in the comparison, like
+     initialize_shell_variables does. */
+  if (assign[equal_offset + 1] == '(' &&
+     strncmp (assign + equal_offset + 2, ") {", 3) == 0)               /* } */
+    equal_offset += 4;
+
+  for (i = 0; i < export_env_index; i++)
     {
-      /* The Korn shell prints the `+ ' in front of assignment statements,
-        so we do too. */
-      fprintf (stderr, "%s%s\n", indirection_level_string (), temp);
-      fflush (stderr);
+      if (STREQN (assign, export_env[i], equal_offset + 1))
+       {
+         free (export_env[i]);
+         export_env[i] = do_alloc ? savestring (assign) : assign;
+         return (export_env);
+       }
     }
-
-  return 1;
+  add_to_export_env (assign, do_alloc);
+  return (export_env);
 }
 
-/* Search for NAME in ARRAY, an array of strings in the same format as the
-   environment array (i.e, name=value).  If NAME is present, make a new
-   variable and return it.  Otherwise, return NULL. */
-static SHELL_VAR *
-find_name_in_env_array (name, array)
-     char *name;
-     char **array;
+static void
+add_temp_array_to_env (temp_array, do_alloc, do_supercede)
+     char **temp_array;
+     int do_alloc, do_supercede;
 {
-  register int i, l;
+  register int i;
 
-  if (array == 0)
-    return ((SHELL_VAR *)NULL);
+  if (temp_array == 0)
+    return;
 
-  for (i = 0, l = strlen (name); array[i]; i++)
+  for (i = 0; temp_array[i]; i++)
     {
-      if (STREQN (array[i], name, l) && array[i][l] == '=')
-       {
-         SHELL_VAR *temp;
-         char *w;
+      if (do_supercede)
+       export_env = add_or_supercede_exported_var (temp_array[i], do_alloc);
+      else
+       add_to_export_env (temp_array[i], do_alloc);
+    }
 
-         /* This is a potential memory leak.  The code should really save
-            the created variables in some auxiliary data structure, which
-            can be disposed of at the appropriate time. */
-         temp = new_shell_variable (name);
-         w = array[i] + l + 1;
+  free (temp_array);
+}
 
-         temp->value = *w ? savestring (w) : (char *)NULL;
+/* Make the environment array for the command about to be executed, if the
+   array needs making.  Otherwise, do nothing.  If a shell action could
+   change the array that commands receive for their environment, then the
+   code should `array_needs_making++'.
 
-         temp->attributes = att_exported|att_tempvar;
-         temp->context = 0;
-         temp->prev_context = (SHELL_VAR *)NULL;
+   The order to add to the array is:
+       temporary_env
+       list of var contexts whose head is shell_variables
+       shell_functions
 
-         temp->dynamic_value = temp->assign_func = (DYNAMIC_FUNC *)NULL;
-         CLEAR_EXPORTSTR (temp);
+  This is the shell variable lookup order.  We add only new variable
+  names at each step, which allows local variables and variables in
+  the temporary environments to shadow variables in the global (or
+  any previous) scope.
+*/
 
-         return (temp);
-       }
-    }
-  return ((SHELL_VAR *)NULL);
+static int
+n_shell_variables ()
+{
+  VAR_CONTEXT *vc;
+  int n;
+
+  for (n = 0, vc = shell_variables; vc; vc = vc->down)
+    n += HASH_ENTRIES (vc->table);
+  return n;
 }
 
-/* Find a variable in the temporary environment that is named NAME.
-   The temporary environment can be either the environment provided
-   to a simple command, or the environment provided to a shell function.
-   We only search the function environment if we are currently executing
-   a shell function body (variable_context > 0).  Return a consed variable,
-   or NULL if not found. */
-SHELL_VAR *
-find_tempenv_variable (name)
+int
+chkexport (name)
      char *name;
 {
-  SHELL_VAR *var;
+  SHELL_VAR *v;
 
-  var = (SHELL_VAR *)NULL;
+  v = find_variable (name);
+  if (v && exported_p (v))
+    {
+      array_needs_making = 1;
+      maybe_make_export_env ();
+      return 1;
+    }
+  return 0;
+}
 
-  if (temporary_env)
-    var = find_name_in_env_array (name, temporary_env);
+void
+maybe_make_export_env ()
+{
+  register char **temp_array;
+  int new_size;
+  VAR_CONTEXT *tcxt;
 
-  /* We don't check this_shell_builtin because the command that needs the
-     value from builtin_env may be a disk command run inside a script run
-     with `.' and a temporary env. */
-  if (!var && builtin_env)
-    var = find_name_in_env_array (name, builtin_env);
+  if (array_needs_making)
+    {
+      if (export_env)
+       strvec_flush (export_env);
 
-  if (!var && variable_context && function_env)
-    var = find_name_in_env_array (name, function_env);
+      /* Make a guess based on how many shell variables and functions we
+        have.  Since there will always be array variables, and array
+        variables are not (yet) exported, this will always be big enough
+        for the exported variables and functions. */
+      new_size = n_shell_variables () + HASH_ENTRIES (shell_functions) + 1 +
+                HASH_ENTRIES (temporary_env);
+      if (new_size > export_env_size)
+       {
+         export_env_size = new_size;
+         export_env = strvec_resize (export_env, export_env_size);
+         environ = export_env;
+       }
+      export_env[export_env_index = 0] = (char *)NULL;
 
-  return (var);
-}
+      /* Make a dummy variable context from the temporary_env, stick it on
+        the front of shell_variables, call make_var_export_array on the
+        whole thing to flatten it, and convert the list of SHELL_VAR *s
+        to the form needed by the environment. */
+      if (temporary_env)
+       {
+         tcxt = new_var_context ((char *)NULL, 0);
+         tcxt->table = temporary_env;
+         tcxt->down = shell_variables;
+       }
+      else
+       tcxt = shell_variables;
+      
+      temp_array = make_var_export_array (tcxt);
+      if (temp_array)
+       add_temp_array_to_env (temp_array, 0, 0);
 
-/* Free the storage allocated to the string array pointed to by ARRAYP, and
-   make that variable have a null pointer as a value. */
-static void
-dispose_temporary_vars (arrayp)
-     char ***arrayp;
-{
-  if (!*arrayp)
-    return;
+      if (tcxt != shell_variables)
+       free (tcxt);
 
-  free_array (*arrayp);
-  *arrayp = (char **)NULL;
-  array_needs_making = 1;
+#if defined (RESTRICTED_SHELL)
+      /* Restricted shells may not export shell functions. */
+      temp_array = restricted ? (char **)0 : make_func_export_array ();
+#else
+      temp_array = make_func_export_array ();
+#endif
+      if (temp_array)
+       add_temp_array_to_env (temp_array, 0, 0);
+
+      array_needs_making = 0;
+    }
 }
 
-/* Free the storage used in the variable array for temporary
-   environment variables. */
+/* This is an efficiency hack.  PWD and OLDPWD are auto-exported, so
+   we will need to remake the exported environment every time we
+   change directories.  `_' is always put into the environment for
+   every external command, so without special treatment it will always
+   cause the environment to be remade.
+
+   If there is no other reason to make the exported environment, we can
+   just update the variables in place and mark the exported environment
+   as no longer needing a remake. */
 void
-dispose_used_env_vars ()
+update_export_env_inplace (env_prefix, preflen, value)
+     char *env_prefix;
+     int preflen;
+     char *value;
 {
-  dispose_temporary_vars (&temporary_env);
+  char *evar;
+
+  evar = (char *)xmalloc (STRLEN (value) + preflen + 1);
+  strcpy (evar, env_prefix);
+  if (value)
+    strcpy (evar + preflen, value);
+  export_env = add_or_supercede_exported_var (evar, 0);
 }
 
-/* Free the storage used for temporary environment variables given to
-   commands when executing inside of a function body. */
+/* We always put _ in the environment as the name of this command. */
 void
-dispose_function_env ()
+put_command_name_into_env (command_name)
+     char *command_name;
 {
-  dispose_temporary_vars (&function_env);
+  update_export_env_inplace ("_=", 2, command_name);
 }
 
-/* Free the storage used for temporary environment variables given to
-   commands when executing a builtin command such as "source". */
+#if 0  /* UNUSED -- it caused too many problems */
 void
-dispose_builtin_env ()
+put_gnu_argv_flags_into_env (pid, flags_string)
+     intmax_t pid;
+     char *flags_string;
 {
-  dispose_temporary_vars (&builtin_env);
+  char *dummy, *pbuf;
+  int l, fl;
+
+  pbuf = itos (pid);
+  l = strlen (pbuf);
+
+  fl = strlen (flags_string);
+
+  dummy = (char *)xmalloc (l + fl + 30);
+  dummy[0] = '_';
+  strcpy (dummy + 1, pbuf);
+  strcpy (dummy + 1 + l, "_GNU_nonoption_argv_flags_");
+  dummy[l + 27] = '=';
+  strcpy (dummy + l + 28, flags_string);
+
+  free (pbuf);
+
+  export_env = add_or_supercede_exported_var (dummy, 0);
 }
+#endif
 
-/* Take all of the shell variables in ENV_ARRAY and make shell variables
-   from them at the current variable context. */
-static void
-merge_env_array (env_array)
-     char **env_array;
+/* **************************************************************** */
+/*                                                                 */
+/*                   Managing variable contexts                    */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Allocate and return a new variable context with NAME and FLAGS.
+   NAME can be NULL. */
+
+VAR_CONTEXT *
+new_var_context (name, flags)
+     char *name;
+     int flags;
 {
-  register int i, l;
-  SHELL_VAR *temp;
-  char *val, *name;
+  VAR_CONTEXT *vc;
 
-  if (env_array == 0)
-    return;
+  vc = (VAR_CONTEXT *)xmalloc (sizeof (VAR_CONTEXT));
+  vc->name = name ? savestring (name) : (char *)NULL;
+  vc->scope = variable_context;
+  vc->flags = flags;
 
-  for (i = 0; env_array[i]; i++)
-    {
-      l = assignment (env_array[i]);
-      name = env_array[i];
-      val = env_array[i] + l + 1;
-      name[l] = '\0';
-      temp = bind_variable (name, val);
-      name[l] = '=';
-    }
+  vc->up = vc->down = (VAR_CONTEXT *)NULL;
+  vc->table = (HASH_TABLE *)NULL;
+
+  return vc;
 }
 
+/* Free a variable context and its data, including the hash table.  Dispose
+   all of the variables. */
 void
-merge_temporary_env ()
+dispose_var_context (vc)
+     VAR_CONTEXT *vc;
 {
-  merge_env_array (temporary_env);
+  FREE (vc->name);
+
+  if (vc->table)
+    {
+      delete_all_variables (vc->table);
+      hash_dispose (vc->table);
+    }
+
+  free (vc);
 }
 
-void
-merge_builtin_env ()
+/* Set VAR's scope level to the current variable context. */
+static int
+set_context (var)
+     SHELL_VAR *var;
 {
-  merge_env_array (builtin_env);
+  return (var->context = variable_context);
 }
 
-int
-any_temporary_variables ()
+/* Make a new variable context with NAME and FLAGS and a HASH_TABLE of
+   temporary variables, and push it onto shell_variables.  This is
+   for shell functions. */
+VAR_CONTEXT *
+push_var_context (name, flags, tempvars)
+     char *name;
+     int flags;
+     HASH_TABLE *tempvars;
 {
-  return (temporary_env || function_env);
-}
+  VAR_CONTEXT *vc;
 
-/* Add ENVSTR to the end of the exported environment, EXPORT_ENV. */
-#define add_to_export_env(envstr,do_alloc) \
-do \
-  { \
-    if (export_env_index >= (export_env_size - 1)) \
-      { \
-       export_env_size += 16; \
-       export_env = (char **)xrealloc (export_env, export_env_size * sizeof (char *)); \
-      } \
-    export_env[export_env_index++] = (do_alloc) ? savestring (envstr) : envstr; \
-    export_env[export_env_index] = (char *)NULL; \
-  } while (0)
+  vc = new_var_context (name, flags);
+  vc->table = tempvars;
+  if (tempvars)
+    {
+      /* Have to do this because the temp environment was created before
+        variable_context was incremented. */
+      flatten (tempvars, set_context, (VARLIST *)NULL, 0);
+      vc->flags |= VC_HASTMPVAR;
+    }
+  vc->down = shell_variables;
+  shell_variables->up = vc;
 
-#define ISFUNCTION(s, o) ((s[o + 1] == '(')  && (s[o + 2] == ')'))
+  return (shell_variables = vc);
+}
 
-/* Add ASSIGN to EXPORT_ENV, or supercede 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)
-     char *assign;
-     int do_alloc;
+static void
+push_func_var (data)
+     PTR_T data;
 {
-  register int i;
-  int equal_offset;
+  SHELL_VAR *var, *v;
 
-  equal_offset = assignment (assign);
-  if (equal_offset == 0)
-    return (export_env);
+  var = (SHELL_VAR *)data;
 
-  /* If this is a function, then only supercede the function definition.
-     We do this by including the `=(' in the comparison.  */
-  if (assign[equal_offset + 1] == '(')
-    equal_offset++;
+  if (tempvar_p (var) && (posixly_correct || (var->attributes & att_propagate)))
+    {
+      /* Make sure we have a hash table to store the variable in while it is
+        being propagated down to the global variables table.  Create one if
+        we have to */
+      if ((vc_isfuncenv (shell_variables) || vc_istempenv (shell_variables)) && shell_variables->table == 0)
+       shell_variables->table = hash_create (0);
+      /* XXX - should we set v->context here? */
+      v = bind_variable_internal (var->name, value_cell (var), shell_variables->table, 0, 0);
+      if (shell_variables == global_variables)
+       var->attributes &= ~(att_tempvar|att_propagate);
+      else
+       shell_variables->flags |= VC_HASTMPVAR;
+      v->attributes |= var->attributes;
+    }
+  else
+    stupidly_hack_special_variables (var->name);       /* XXX */
 
-  for (i = 0; i < export_env_index; i++)
+  dispose_variable (var);
+}
+
+/* Pop the top context off of VCXT and dispose of it, returning the rest of
+   the stack. */
+void
+pop_var_context ()
+{
+  VAR_CONTEXT *ret, *vcxt;
+
+  vcxt = shell_variables;
+  if (vc_isfuncenv (vcxt) == 0)
     {
-      if (STREQN (assign, export_env[i], equal_offset + 1))
-       {
-         free (export_env[i]);
-         export_env[i] = do_alloc ? savestring (assign) : assign;
-         return (export_env);
-       }
+      internal_error (_("pop_var_context: head of shell_variables not a function context"));
+      return;
     }
-  add_to_export_env (assign, do_alloc);
-  return (export_env);
+
+  if (ret = vcxt->down)
+    {
+      ret->up = (VAR_CONTEXT *)NULL;
+      shell_variables = ret;
+      if (vcxt->table)
+       hash_flush (vcxt->table, push_func_var);
+      dispose_var_context (vcxt);
+    }
+  else
+    internal_error (_("pop_var_context: no global_variables context"));
 }
 
-/* Make the environment array for the command about to be executed, if the
-   array needs making.  Otherwise, do nothing.  If a shell action could
-   change the array that commands receive for their environment, then the
-   code should `array_needs_making++'. */
+/* Delete the HASH_TABLEs for all variable contexts beginning at VCXT, and
+   all of the VAR_CONTEXTs except GLOBAL_VARIABLES. */
 void
-maybe_make_export_env ()
+delete_all_contexts (vcxt)
+     VAR_CONTEXT *vcxt;
 {
-  register int i;
-  register char **temp_array;
-  int new_size;
+  VAR_CONTEXT *v, *t;
 
-  if (array_needs_making)
+  for (v = vcxt; v != global_variables; v = t)
     {
-      if (export_env)
-       free_array_members (export_env);
+      t = v->down;
+      dispose_var_context (v);
+    }    
 
-      /* Make a guess based on how many shell variables and functions we
-        have.  Since there will always be array variables, and array
-        variables are not (yet) exported, this will always be big enough
-        for the exported variables and functions, without any temporary
-        or function environments. */
-      new_size = HASH_ENTRIES (shell_variables) + HASH_ENTRIES (shell_functions) + 1;
-      if (new_size > export_env_size)
-       {
-         export_env_size = new_size;
-          export_env = (char **)xrealloc (export_env, export_env_size * sizeof (char *));
-       }
-      export_env[export_env_index = 0] = (char *)NULL;
+  delete_all_variables (global_variables->table);
+  shell_variables = global_variables;
+}
 
-      temp_array = make_var_array (shell_variables);
-      if (temp_array)
-       {
-         for (i = 0; temp_array[i]; i++)
-           add_to_export_env (temp_array[i], 0);
-         free (temp_array);
-       }
+/* **************************************************************** */
+/*                                                                 */
+/*        Pushing and Popping temporary variable scopes            */
+/*                                                                 */
+/* **************************************************************** */
 
-      temp_array = make_var_array (shell_functions);
-      if (temp_array)
-       {
-         for (i = 0; temp_array[i]; i++)
-           add_to_export_env (temp_array[i], 0);
-         free (temp_array);
-       }
+VAR_CONTEXT *
+push_scope (flags, tmpvars)
+     int flags;
+     HASH_TABLE *tmpvars;
+{
+  return (push_var_context ((char *)NULL, flags, tmpvars));
+}
 
-      if (function_env)
-       for (i = 0; function_env[i]; i++)
-         export_env = add_or_supercede_exported_var (function_env[i], 1);
+static void
+push_exported_var (data)
+     PTR_T data;
+{
+  SHELL_VAR *var, *v;
 
-      if (temporary_env)
-       for (i = 0; temporary_env[i]; i++)
-         export_env = add_or_supercede_exported_var (temporary_env[i], 1);
+  var = (SHELL_VAR *)data;
 
+  /* If a temp var had its export attribute set, or it's marked to be
+     propagated, bind it in the previous scope before disposing it. */
+  /* XXX - This isn't exactly right, because all tempenv variables have the
+    export attribute set. */
 #if 0
-      /* If we changed the array, then sort it alphabetically. */
-      if (posixly_correct == 0 && (temporary_env || function_env))
-       sort_char_array (export_env);
+  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;
+    }
+  else
+    stupidly_hack_special_variables (var->name);       /* XXX */
 
-      array_needs_making = 0;
+  dispose_variable (var);
+}
+
+void
+pop_scope (is_special)
+     int is_special;
+{
+  VAR_CONTEXT *vcxt, *ret;
+
+  vcxt = shell_variables;
+  if (vc_istempscope (vcxt) == 0)
+    {
+      internal_error (_("pop_scope: head of shell_variables not a temporary environment scope"));
+      return;
     }
+
+  ret = vcxt->down;
+  if (ret)
+    ret->up = (VAR_CONTEXT *)NULL;
+
+  shell_variables = ret;
+
+  /* Now we can take care of merging variables in VCXT into set of scopes
+     whose head is RET (shell_variables). */
+  FREE (vcxt->name);
+  if (vcxt->table)
+    {
+      if (is_special)
+       hash_flush (vcxt->table, push_func_var);
+      else
+       hash_flush (vcxt->table, push_exported_var);
+      hash_dispose (vcxt->table);
+    }
+  free (vcxt);
+
+  sv_ifs ("IFS");      /* XXX here for now */
 }
 
-/* This is an efficiency hack.  PWD and OLDPWD are auto-exported, so
-   we will need to remake the exported environment every time we
-   change directories.  `_' is always put into the environment for
-   every external command, so without special treatment it will always
-   cause the environment to be remade.
+/* **************************************************************** */
+/*                                                                 */
+/*              Pushing and Popping function contexts              */
+/*                                                                 */
+/* **************************************************************** */
+
+static WORD_LIST **dollar_arg_stack = (WORD_LIST **)NULL;
+static int dollar_arg_stack_slots;
+static int dollar_arg_stack_index;
 
-   If there is no other reason to make the exported environment, we can
-   just update the variables in place and mark the exported environment
-   as no longer needing a remake. */
+/* XXX - we might want to consider pushing and popping the `getopts' state
+   when we modify the positional parameters. */
 void
-update_export_env_inplace (env_prefix, preflen, value)
-     char *env_prefix;
-     int preflen;
-     char *value;
+push_context (name, is_subshell, tempvars)
+     char *name;       /* function name */
+     int is_subshell;
+     HASH_TABLE *tempvars;
 {
-  char *evar;
-
-  evar = xmalloc (STRLEN (value) + preflen + 1);
-  strcpy (evar, env_prefix);
-  if (value)
-    strcpy (evar + preflen, value);
-  export_env = add_or_supercede_exported_var (evar, 0);
+  if (is_subshell == 0)
+    push_dollar_vars ();
+  variable_context++;
+  push_var_context (name, VC_FUNCENV, tempvars);
 }
 
-/* We always put _ in the environment as the name of this command. */
+/* Only called when subshell == 0, so we don't need to check, and can
+   unconditionally pop the dollar vars off the stack. */
 void
-put_command_name_into_env (command_name)
-     char *command_name;
+pop_context ()
 {
-  update_export_env_inplace ("_=", 2, command_name);
+  pop_dollar_vars ();
+  variable_context--;
+  pop_var_context ();
+
+  sv_ifs ("IFS");              /* XXX here for now */
 }
 
-#if 0  /* UNUSED -- it caused too many problems */
+/* Save the existing positional parameters on a stack. */
 void
-put_gnu_argv_flags_into_env (pid, flags_string)
-     int pid;
-     char *flags_string;
+push_dollar_vars ()
 {
-  char *dummy, *pbuf;
-  int l, fl;
-
-  pbuf = itos (pid);
-  l = strlen (pbuf);
+  if (dollar_arg_stack_index + 2 > dollar_arg_stack_slots)
+    {
+      dollar_arg_stack = (WORD_LIST **)
+       xrealloc (dollar_arg_stack, (dollar_arg_stack_slots += 10)
+                 * sizeof (WORD_LIST **));
+    }
+  dollar_arg_stack[dollar_arg_stack_index++] = list_rest_of_args ();
+  dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL;
+}
 
-  fl = strlen (flags_string);
+/* Restore the positional parameters from our stack. */
+void
+pop_dollar_vars ()
+{
+  if (!dollar_arg_stack || dollar_arg_stack_index == 0)
+    return;
 
-  dummy = xmalloc (l + fl + 30);
-  dummy[0] = '_';
-  strcpy (dummy + 1, pbuf);
-  strcpy (dummy + 1 + l, "_GNU_nonoption_argv_flags_");
-  dummy[l + 27] = '=';
-  strcpy (dummy + l + 28, flags_string);
+  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;
+  set_dollar_vars_unchanged ();
+}
 
-  free (pbuf);
+void
+dispose_saved_dollar_vars ()
+{
+  if (!dollar_arg_stack || dollar_arg_stack_index == 0)
+    return;
 
-  export_env = add_or_supercede_exported_var (dummy, 0);
+  dispose_words (dollar_arg_stack[dollar_arg_stack_index]);
+  dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL;
 }
-#endif
 
-/* Return a string denoting what our indirection level is. */
-static char indirection_string[100];
+/* Manipulate the special BASH_ARGV and BASH_ARGC variables. */
 
-char *
-indirection_level_string ()
+void
+push_args (list)
+     WORD_LIST *list;
 {
-  register int i, j;
-  char *ps4;
-
-  indirection_string[0] = '\0';
-  ps4 = get_string_value ("PS4");
-
-  if (ps4 == 0 || *ps4 == '\0')
-    return (indirection_string);
+#if defined (ARRAY_VARS) && defined (DEBUGGER)
+  SHELL_VAR *bash_argv_v, *bash_argc_v;
+  ARRAY *bash_argv_a, *bash_argc_a;
+  WORD_LIST *l;
+  arrayind_t i;
+  char *t;
 
-  ps4 = decode_prompt_string (ps4);
+  GET_ARRAY_FROM_VAR ("BASH_ARGV", bash_argv_v, bash_argv_a);
+  GET_ARRAY_FROM_VAR ("BASH_ARGC", bash_argc_v, bash_argc_a);
 
-  for (i = 0; *ps4 && i < indirection_level && i < 99; i++)
-    indirection_string[i] = *ps4;
+  for (l = list, i = 0; l; l = l->next, i++)
+    array_push (bash_argv_a, l->word->word);
 
-  for (j = 1; *ps4 && ps4[j] && i < 99; i++, j++)
-    indirection_string[i] = ps4[j];
+  t = itos (i);
+  array_push (bash_argc_a, t);
+  free (t);
+#endif /* ARRAY_VARS && DEBUGGER */
+}
 
-  indirection_string[i] = '\0';
-  free (ps4);
-  return (indirection_string);
+/* Remove arguments from BASH_ARGV array.  Pop top element off BASH_ARGC
+   array and use that value as the count of elements to remove from
+   BASH_ARGV. */
+void
+pop_args ()
+{
+#if defined (ARRAY_VARS) && defined (DEBUGGER)
+  SHELL_VAR *bash_argv_v, *bash_argc_v;
+  ARRAY *bash_argv_a, *bash_argc_a;
+  ARRAY_ELEMENT *ce;
+  intmax_t i;
+
+  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);
+  if (ce == 0 || legal_number (element_value (ce), &i) == 0)
+    i = 0;
+
+  for ( ; i > 0; i--)
+    array_pop (bash_argv_a);
+  array_dispose_element (ce);
+#endif /* ARRAY_VARS && DEBUGGER */
 }
 
 /*************************************************
@@ -2960,7 +4150,6 @@ indirection_level_string ()
 extern int eof_encountered, eof_encountered_limit, ignoreeof;
 
 #if defined (READLINE)
-extern int no_line_editing;
 extern int hostname_list_initialized;
 #endif
 
@@ -2971,75 +4160,164 @@ extern int hostname_list_initialized;
 
 #define SET_INT_VAR(name, intvar)  intvar = find_variable (name) != 0
 
+/* This table will be sorted with qsort() the first time it's accessed. */
 struct name_and_function {
   char *name;
-  VFunction *function;
-} special_vars[] = {
-  { "PATH", sv_path },
-  { "MAIL", sv_mail },
-  { "MAILPATH", sv_mail },
-  { "MAILCHECK", sv_mail },
+  sh_sv_func_t *function;
+};
 
-  { "POSIXLY_CORRECT", sv_strict_posix },
-  { "GLOBIGNORE", sv_globignore },
+static struct name_and_function special_vars[] = {
+  { "BASH_XTRACEFD", sv_xtracefd },
 
-  /* Variables which only do something special when READLINE is defined. */
 #if defined (READLINE)
-  { "TERM", sv_terminal },
-  { "TERMCAP", sv_terminal },
-  { "TERMINFO", sv_terminal },
-  { "HOSTFILE", sv_hostfile },
-#endif /* READLINE */
+#  if defined (STRICT_POSIX)
+  { "COLUMNS", sv_winsize },
+#  endif
+  { "COMP_WORDBREAKS", sv_comp_wordbreaks },
+#endif
+
+  { "FUNCNEST", sv_funcnest },
+
+  { "GLOBIGNORE", sv_globignore },
 
-  /* Variables which only do something special when HISTORY is defined. */
 #if defined (HISTORY)
+  { "HISTCONTROL", sv_history_control },
+  { "HISTFILESIZE", sv_histsize },
   { "HISTIGNORE", sv_histignore },
   { "HISTSIZE", sv_histsize },
-  { "HISTFILESIZE", sv_histsize },
-  { "HISTCONTROL", sv_history_control },
-#  if defined (BANG_HISTORY)
-  { "histchars", sv_histchars },
-#  endif /* BANG_HISTORY */
-#endif /* HISTORY */
+  { "HISTTIMEFORMAT", sv_histtimefmt },
+#endif
 
-  { "IGNOREEOF", sv_ignoreeof },
-  { "ignoreeof", sv_ignoreeof },
+#if defined (__CYGWIN__)
+  { "HOME", sv_home },
+#endif
 
-  { "OPTIND", sv_optind },
-  { "OPTERR", sv_opterr },
+#if defined (READLINE)
+  { "HOSTFILE", sv_hostfile },
+#endif
 
-  { "TEXTDOMAIN", sv_locale },
-  { "TEXTDOMAINDIR", sv_locale },
+  { "IFS", sv_ifs },
+  { "IGNOREEOF", sv_ignoreeof },
+
+  { "LANG", sv_locale },
   { "LC_ALL", sv_locale },
   { "LC_COLLATE", sv_locale },
   { "LC_CTYPE", sv_locale },
   { "LC_MESSAGES", sv_locale },
   { "LC_NUMERIC", sv_locale },
-  { "LANG", sv_locale },
+  { "LC_TIME", sv_locale },
+
+#if defined (READLINE) && defined (STRICT_POSIX)
+  { "LINES", sv_winsize },
+#endif
+
+  { "MAIL", sv_mail },
+  { "MAILCHECK", sv_mail },
+  { "MAILPATH", sv_mail },
+
+  { "OPTERR", sv_opterr },
+  { "OPTIND", sv_optind },
+
+  { "PATH", sv_path },
+  { "POSIXLY_CORRECT", sv_strict_posix },
 
-#if defined (HAVE_TZSET) && defined (PROMPT_STRING_DECODE)
+#if defined (READLINE)
+  { "TERM", sv_terminal },
+  { "TERMCAP", sv_terminal },
+  { "TERMINFO", sv_terminal },
+#endif /* READLINE */
+
+  { "TEXTDOMAIN", sv_locale },
+  { "TEXTDOMAINDIR", sv_locale },
+
+#if defined (HAVE_TZSET)
   { "TZ", sv_tz },
 #endif
 
-  { (char *)0, (VFunction *)0 }
+#if defined (HISTORY) && defined (BANG_HISTORY)
+  { "histchars", sv_histchars },
+#endif /* HISTORY && BANG_HISTORY */
+
+  { "ignoreeof", sv_ignoreeof },
+
+  { (char *)0, (sh_sv_func_t *)0 }
 };
 
+#define N_SPECIAL_VARS (sizeof (special_vars) / sizeof (special_vars[0]) - 1)
+
+static int
+sv_compare (sv1, sv2)
+     struct name_and_function *sv1, *sv2;
+{
+  int r;
+
+  if ((r = sv1->name[0] - sv2->name[0]) == 0)
+    r = strcmp (sv1->name, sv2->name);
+  return r;
+}
+
+static inline int
+find_special_var (name)
+     const char *name;
+{
+  register int i, r;
+
+  for (i = 0; special_vars[i].name; i++)
+    {
+      r = special_vars[i].name[0] - name[0];
+      if (r == 0)
+       r = strcmp (special_vars[i].name, name);
+      if (r == 0)
+       return i;
+      else if (r > 0)
+       /* Can't match any of rest of elements in sorted list.  Take this out
+          if it causes problems in certain environments. */
+       break;
+    }
+  return -1;
+}
+
 /* The variable in NAME has just had its state changed.  Check to see if it
    is one of the special ones where something special happens. */
 void
 stupidly_hack_special_variables (name)
      char *name;
 {
+  static int sv_sorted = 0;
   int i;
 
-  for (i = 0; special_vars[i].name; i++)
+  if (sv_sorted == 0)  /* shouldn't need, but it's fairly cheap. */
     {
-      if (STREQ (special_vars[i].name, name))
-       {
-         (*(special_vars[i].function)) (name);
-         return;
-       }
+      qsort (special_vars, N_SPECIAL_VARS, sizeof (special_vars[0]),
+               (QSFUNC *)sv_compare);
+      sv_sorted = 1;
     }
+
+  i = find_special_var (name);
+  if (i != -1)
+    (*(special_vars[i].function)) (name);
+}
+
+/* Special variables that need hooks to be run when they are unset as part
+   of shell reinitialization should have their sv_ functions run here. */
+void
+reinit_special_variables ()
+{
+#if defined (READLINE)
+  sv_comp_wordbreaks ("COMP_WORDBREAKS");
+#endif
+  sv_globignore ("GLOBIGNORE");
+  sv_opterr ("OPTERR");
+}
+
+void
+sv_ifs (name)
+     char *name;
+{
+  SHELL_VAR *v;
+
+  v = find_variable ("IFS");
+  setifs (v);
 }
 
 /* What to do just after the PATH variable has changed. */
@@ -3048,7 +4326,7 @@ sv_path (name)
      char *name;
 {
   /* hash -r */
-  flush_hashed_filenames ();
+  phash_flush ();
 }
 
 /* What to do just after one of the MAILxxxx variables has changed.  NAME
@@ -3071,15 +4349,43 @@ sv_mail (name)
     }
 }
 
+void
+sv_funcnest (name)
+     char *name;
+{
+  SHELL_VAR *v;
+  intmax_t num;
+
+  v = find_variable (name);
+  if (v == 0)
+    funcnest_max = 0;
+  else if (legal_number (value_cell (v), &num) == 0)
+    funcnest_max = 0;
+  else
+    funcnest_max = num;
+}
+
 /* What to do when GLOBIGNORE changes. */
 void
 sv_globignore (name)
      char *name;
 {
-  setup_glob_ignore (name);
+  if (privileged_mode == 0)
+    setup_glob_ignore (name);
 }
 
 #if defined (READLINE)
+void
+sv_comp_wordbreaks (name)
+     char *name;
+{
+  SHELL_VAR *sv;
+
+  sv = find_variable (name);
+  if (sv == 0)
+    reset_completer_word_break_chars ();
+}
+
 /* What to do just after one of the TERMxxx variables has changed.
    If we are an interactive shell, then try to reset the terminal
    information in readline. */
@@ -3103,8 +4409,52 @@ sv_hostfile (name)
   else
     hostname_list_initialized = 0;
 }
+
+#if defined (STRICT_POSIX)
+/* In strict posix mode, we allow assignments to LINES and COLUMNS (and values
+   found in the initial environment) to override the terminal size reported by
+   the kernel. */
+void
+sv_winsize (name)
+     char *name;
+{
+  SHELL_VAR *v;
+  intmax_t xd;
+  int d;
+
+  if (posixly_correct == 0 || interactive_shell == 0 || no_line_editing)
+    return;
+
+  v = find_variable (name);
+  if (v == 0 || var_isnull (v))
+    rl_reset_screen_size ();
+  else
+    {
+      if (legal_number (value_cell (v), &xd) == 0)
+       return;
+      winsize_assignment = 1;
+      d = xd;                  /* truncate */
+      if (name[0] == 'L')      /* LINES */
+       rl_set_screen_size (d, -1);
+      else                     /* COLUMNS */
+       rl_set_screen_size (-1, d);
+      winsize_assignment = 0;
+    }
+}
+#endif /* STRICT_POSIX */
 #endif /* READLINE */
 
+/* Update the value of HOME in the export environment so tilde expansion will
+   work on cygwin. */
+#if defined (__CYGWIN__)
+sv_home (name)
+     char *name;
+{
+  array_needs_making = 1;
+  maybe_make_export_env ();
+}
+#endif
+
 #if defined (HISTORY)
 /* What to do after the HISTSIZE or HISTFILESIZE variables change.
    If there is a value for this HISTSIZE (and it is numeric), then stifle
@@ -3117,26 +4467,28 @@ sv_histsize (name)
      char *name;
 {
   char *temp;
-  long num;
+  intmax_t num;
+  int hmax;
 
   temp = get_string_value (name);
 
   if (temp && *temp)
     {
       if (legal_number (temp, &num))
-        {
+       {
+         hmax = num;
          if (name[4] == 'S')
            {
-             stifle_history (num);
-             num = where_history ();
-             if (history_lines_this_session > num)
-               history_lines_this_session = num;
+             stifle_history (hmax);
+             hmax = where_history ();
+             if (history_lines_this_session > hmax)
+               history_lines_this_session = hmax;
            }
          else
            {
-             history_truncate_file (get_string_value ("HISTFILE"), (int)num);
-             if (num <= history_lines_in_file)
-               history_lines_in_file = num;
+             history_truncate_file (get_string_value ("HISTFILE"), hmax);
+             if (hmax <= history_lines_in_file)
+               history_lines_in_file = hmax;
            }
        }
     }
@@ -3158,18 +4510,28 @@ sv_history_control (name)
      char *name;
 {
   char *temp;
+  char *val;
+  int tptr;
 
   history_control = 0;
   temp = get_string_value (name);
 
-  if (temp && *temp && STREQN (temp, "ignore", 6))
+  if (temp == 0 || *temp == 0)
+    return;
+
+  tptr = 0;
+  while (val = extract_colon_unit (temp, &tptr))
     {
-      if (temp[6] == 's')      /* ignorespace */
-       history_control = 1;
-      else if (temp[6] == 'd') /* ignoredups */
-       history_control = 2;
-      else if (temp[6] == 'b') /* ignoreboth */
-       history_control = 3;
+      if (STREQ (val, "ignorespace"))
+       history_control |= HC_IGNSPACE;
+      else if (STREQ (val, "ignoredups"))
+       history_control |= HC_IGNDUPS;
+      else if (STREQ (val, "ignoreboth"))
+       history_control |= HC_IGNBOTH;
+      else if (STREQ (val, "erasedups"))
+       history_control |= HC_ERASEDUPS;
+
+      free (val);
     }
 }
 
@@ -3200,14 +4562,25 @@ sv_histchars (name)
     }
 }
 #endif /* BANG_HISTORY */
+
+void
+sv_histtimefmt (name)
+     char *name;
+{
+  SHELL_VAR *v;
+
+  v = find_variable (name);
+  history_write_timestamps = (v != 0);
+}
 #endif /* HISTORY */
 
-#if defined (HAVE_TZSET) && defined (PROMPT_STRING_DECODE)
+#if defined (HAVE_TZSET)
 void
 sv_tz (name)
      char *name;
 {
-  tzset ();
+  if (chkexport (name))
+    tzset ();
 }
 #endif
 
@@ -3291,13 +4664,15 @@ sv_locale (name)
 
 #if defined (ARRAY_VARS)
 void
-set_pipestatus_array (ps)
+set_pipestatus_array (ps, nproc)
      int *ps;
+     int nproc;
 {
   SHELL_VAR *v;
   ARRAY *a;
+  ARRAY_ELEMENT *ae;
   register int i;
-  char *t, tbuf[16];
+  char *t, tbuf[INT_STRLEN_BOUND(int) + 1];
 
   v = find_variable ("PIPESTATUS");
   if (v == 0)
@@ -3305,13 +4680,85 @@ set_pipestatus_array (ps)
   if (array_p (v) == 0)
     return;            /* Do nothing if not an array variable. */
   a = array_cell (v);
-  if (a)
-    empty_array (a);
-  for (i = 0; ps[i] != -1; i++)
+
+  if (a == 0 || array_num_elements (a) == 0)
+    {
+      for (i = 0; i < nproc; i++)      /* was ps[i] != -1, not i < nproc */
+       {
+         t = inttostr (ps[i], tbuf, sizeof (tbuf));
+         array_insert (a, i, t);
+       }
+      return;
+    }
+
+  /* Fast case */
+  if (array_num_elements (a) == nproc && nproc == 1)
     {
-      t = inttostr (ps[i], tbuf, sizeof (tbuf));
-      array_add_element (a, i, t);
+      ae = element_forw (a->head);
+      free (element_value (ae));
+      ae->value = itos (ps[0]);
     }
+  else if (array_num_elements (a) <= nproc)
+    {
+      /* modify in array_num_elements members in place, then add */
+      ae = a->head;
+      for (i = 0; i < array_num_elements (a); i++)
+       {
+         ae = element_forw (ae);
+         free (element_value (ae));
+         ae->value = itos (ps[i]);
+       }
+      /* add any more */
+      for ( ; i < nproc; i++)
+       {
+         t = inttostr (ps[i], tbuf, sizeof (tbuf));
+         array_insert (a, i, t);
+       }
+    }
+  else
+    {
+      /* deleting elements.  it's faster to rebuild the array. */        
+      array_flush (a);
+      for (i = 0; ps[i] != -1; i++)
+       {
+         t = inttostr (ps[i], tbuf, sizeof (tbuf));
+         array_insert (a, i, t);
+       }
+    }
+}
+
+ARRAY *
+save_pipestatus_array ()
+{
+  SHELL_VAR *v;
+  ARRAY *a, *a2;
+
+  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));
+
+  return a2;
+}
+
+void
+restore_pipestatus_array (a)
+     ARRAY *a;
+{
+  SHELL_VAR *v;
+  ARRAY *a2;
+
+  v = find_variable ("PIPESTATUS");
+  /* XXX - should we still assign even if existing value is NULL? */
+  if (v == 0 || array_p (v) == 0 || array_cell (v) == 0)
+    return;
+
+  a2 = array_cell (v);
+  var_setarray (v, a); 
+
+  array_dispose (a2);
 }
 #endif
 
@@ -3323,6 +4770,41 @@ set_pipestatus_from_exit (s)
   static int v[2] = { 0, -1 };
 
   v[0] = s;
-  set_pipestatus_array (v);
+  set_pipestatus_array (v, 1);
 #endif
 }
+
+void
+sv_xtracefd (name)
+     char *name;
+{
+  SHELL_VAR *v;
+  char *t, *e;
+  int fd;
+  FILE *fp;
+
+  v = find_variable (name);
+  if (v == 0)
+    {
+      xtrace_reset ();
+      return;
+    }
+
+  t = value_cell (v);
+  if (t == 0 || *t == 0)
+    xtrace_reset ();
+  else
+    {
+      fd = (int)strtol (t, &e, 10);
+      if (e != t && *e == '\0' && sh_validfd (fd))
+       {
+         fp = fdopen (fd, "w");
+         if (fp == 0)
+           internal_error (_("%s: %s: cannot open as FILE"), name, value_cell (v));
+         else
+           xtrace_set (fd, fp);
+       }
+      else
+       internal_error (_("%s: %s: invalid value for trace file descriptor"), name, value_cell (v));
+    }
+}