Imported from ../bash-3.1.tar.gz.
[platform/upstream/bash.git] / variables.c
index b72fc4f..62e2604 100644 (file)
@@ -1,12 +1,12 @@
 /* variables.c -- Functions for hacking shell variables. */
 
-/* Copyright (C) 1987,1989 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2005 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 1, or (at your option)
+   the Free Software Foundation; either version 2, or (at your option)
    any later version.
 
    Bash is distributed in the hope that it will be useful, but WITHOUT
 
    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, 675 Mass Ave, Cambridge, MA 02139, USA. */
+   Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
 
 #include "config.h"
 
 #include "bashtypes.h"
 #include "posixstat.h"
+#include "posixtime.h"
 
 #if defined (qnx)
-#  include <sys/vc.h>
-#endif
+#  if defined (qnx6)
+#    include <sy/netmgr.h>
+#  else
+#    include <sys/vc.h>
+#  endif /* !qnx6 */
+#endif /* qnx */
 
 #if defined (HAVE_UNISTD_H)
 #  include <unistd.h>
 #endif
 
 #include <stdio.h>
-#include <ctype.h>
+#include "chartypes.h"
 #include <pwd.h>
 #include "bashansi.h"
+#include "bashintl.h"
 
 #include "shell.h"
 #include "flags.h"
@@ -42,6 +48,8 @@
 #include "findcmd.h"
 #include "mailcheck.h"
 #include "input.h"
+#include "hashcmd.h"
+#include "pathexp.h"
 
 #include "builtins/getopt.h"
 #include "builtins/common.h"
 #  include <readline/history.h>
 #endif /* HISTORY */
 
+#if defined (PROGRAMMABLE_COMPLETION)
+#  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;
+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;
 
-/* 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. */
@@ -103,7 +133,7 @@ char *dollar_vars[10];
 WORD_LIST *rest_of_args = (WORD_LIST *)NULL;
 
 /* The value of $$. */
-int dollar_dollar_pid;
+pid_t dollar_dollar_pid;
 
 /* An array which is passed to commands as their environment.  It is
    manufactured from the union of the initial environment and the
@@ -112,6 +142,11 @@ char **export_env = (char **)NULL;
 static int export_env_index;
 static int export_env_size;
 
+#if defined (READLINE)
+static int winsize_assignment;         /* currently assigning to LINES or COLUMNS */
+static int winsize_assigned;           /* assigned to LINES or COLUMNS */
+#endif
+
 /* Non-zero means that we have to remake EXPORT_ENV. */
 int array_needs_making = 1;
 
@@ -119,24 +154,105 @@ int array_needs_making = 1;
    by initialize_variables (). */
 int shell_level = 0;
 
-static char *have_local_variables;
-static int local_variable_stack_size;
-
 /* 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)
+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 SHELL_VAR *null_assign __P((SHELL_VAR *, char *, arrayind_t));
+#if defined (ARRAY_VARS)
+static SHELL_VAR *null_array_assign __P((SHELL_VAR *, char *, arrayind_t));
+#endif
+static SHELL_VAR *get_self __P((SHELL_VAR *));
+
+#if defined (ARRAY_VARS)
+static SHELL_VAR *init_dynamic_array_var __P((char *, sh_var_value_func_t *, sh_var_assign_func_t *, int));
+#endif
+
+static SHELL_VAR *assign_seconds __P((SHELL_VAR *, char *, arrayind_t));
+static SHELL_VAR *get_seconds __P((SHELL_VAR *));
+static SHELL_VAR *init_seconds_var __P((void));
+
+static int brand __P((void));
+static void sbrand __P((unsigned long));               /* set bash random number generator. */
+static SHELL_VAR *assign_random __P((SHELL_VAR *, char *, arrayind_t));
+static SHELL_VAR *get_random __P((SHELL_VAR *));
+
+static SHELL_VAR *assign_lineno __P((SHELL_VAR *, char *, arrayind_t));
+static SHELL_VAR *get_lineno __P((SHELL_VAR *));
+
+static SHELL_VAR *assign_subshell __P((SHELL_VAR *, char *, arrayind_t));
+static SHELL_VAR *get_subshell __P((SHELL_VAR *));
 
+#if defined (HISTORY)
+static SHELL_VAR *get_histcmd __P((SHELL_VAR *));
+#endif
+
+#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS)
+static SHELL_VAR *assign_dirstack __P((SHELL_VAR *, char *, arrayind_t));
+static SHELL_VAR *get_dirstack __P((SHELL_VAR *));
+#endif
+
+#if defined (ARRAY_VARS)
+static SHELL_VAR *get_groupset __P((SHELL_VAR *));
+#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 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 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 *));
+              
 /* Initialize the shell variables from the current environment.
    If PRIVMODE is nonzero, don't import functions from ENV or
    parse $SHELLOPTS. */
@@ -150,10 +266,19 @@ initialize_shell_variables (env, privmode)
   SHELL_VAR *temp_var;
 
   if (shell_variables == 0)
-    shell_variables = make_hash_table (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 = make_hash_table (0);
+    shell_functions = hash_create (0);
+
+#if defined (DEBUGGER)
+  if (shell_function_defs == 0)
+    shell_function_defs = hash_create (0);
+#endif
 
   for (string_index = 0; string = env[string_index++]; )
     {
@@ -162,30 +287,28 @@ 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) */
 
-      /* If exported function, define it now. */
-      if (privmode == 0 && STREQN ("() {", string, 4))
+      /* 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);
-#if 1
+         temp_string = (char *)xmalloc (3 + string_length + char_index);
+
          strcpy (temp_string, name);
          temp_string[char_index] = ' ';
          strcpy (temp_string + char_index + 1, string);
-#else
-         sprintf (temp_string, "%s %s", name, string);
-#endif
 
          parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST);
 
@@ -196,79 +319,80 @@ initialize_shell_variables (env, privmode)
 
          if (temp_var = find_function (name))
            {
-             temp_var->attributes |= (att_exported | att_imported);
+             VSETATTR (temp_var, (att_exported|att_imported));
              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')
-           name[char_index - 2] = '(';
+           name[char_index - 2] = '(';         /* ) */
        }
 #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);
          temp_var = assign_array_from_string (name, temp_string);
          FREE (temp_string);
-         temp_var->attributes |= (att_exported | att_imported);
+         VSETATTR (temp_var, (att_exported | att_imported));
          array_needs_making = 1;
        }
 #  endif
 #endif
       else
        {
-         temp_var = bind_variable (name, string);
-         temp_var->attributes |= (att_exported | att_imported);
+         temp_var = bind_variable (name, string, 0);
+         VSETATTR (temp_var, (att_exported | att_imported));
          array_needs_making = 1;
        }
 
       name[char_index] = '=';
-    }
-
-  /* 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 can be NULL if it was an exported function with a syntax
+        error (a different bug, but it still shouldn't dump core). */
+      if (temp_var && function_p (temp_var) == 0)      /* XXX not yet */
        {
-         temp_var = bind_variable ("PWD", temp_string);
-         set_auto_export (temp_var);
-         free (temp_string);
+         CACHE_IMPORTSTR (temp_var, name);
        }
     }
 
+  set_pwd ();
+
   /* Set up initial value of $_ */
-  temp_var = bind_variable ("_", dollar_vars[0]);
+#if 0
+  temp_var = bind_variable ("_", dollar_vars[0], 0);
+#else
+  temp_var = set_if_not ("_", dollar_vars[0]);
+#endif
 
   /* 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)
   /* set node id -- don't import it from the environment */
   {
     char node_name[22];
+#  if defined (qnx6)
+    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
@@ -276,47 +400,44 @@ initialize_shell_variables (env, privmode)
   /* set up the prompts. */
   if (interactive_shell)
     {
+#if defined (PROMPT_STRING_DECODE)
       set_if_not ("PS1", primary_prompt);
+#else
+      if (current_user.uid == -1)
+       get_current_user_info ();
+      set_if_not ("PS1", current_user.euid == 0 ? "# " : primary_prompt);
+#endif
       set_if_not ("PS2", secondary_prompt);
     }
   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 ();
 
-  /* Make a variable $PPID, which holds the pid of the shell's parent.  */
-  name = itos ((int) getppid ());
-  temp_var = find_variable ("PPID");
-  if (temp_var)
-    temp_var->attributes &= ~(att_readonly | att_exported);
-  temp_var = bind_variable ("PPID", name);
-  temp_var->attributes |= (att_readonly | att_integer);
-  free (name);
+  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)
@@ -325,7 +446,7 @@ initialize_shell_variables (env, privmode)
   /* 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
@@ -335,11 +456,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");
@@ -354,7 +478,7 @@ 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);
@@ -365,7 +489,7 @@ initialize_shell_variables (env, privmode)
 #endif /* HISTORY */
 
   /* Seed the random number generator. */
-  sbrand (dollar_dollar_pid + (long)shell_start_time);
+  sbrand (dollar_dollar_pid + shell_start_time);
 
   /* Handle some "special" variables that we may have inherited from a
      parent shell. */
@@ -386,6 +510,38 @@ initialize_shell_variables (env, privmode)
     }
 #endif /* HISTORY */
 
+#if defined (READLINE) && defined (STRICT_POSIX)
+  /* POSIXLY_CORRECT will only be 1 here if the shell was compiled
+     -DSTRICT_POSIX */
+  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 ();
 
@@ -393,8 +549,37 @@ initialize_shell_variables (env, privmode)
   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 *
+sh_get_home_dir ()
+{
+  if (current_user.home_dir == 0)
+    get_current_user_info ();
+  return current_user.home_dir;
+}
+
 static void
 set_home_var ()
 {
@@ -402,12 +587,10 @@ set_home_var ()
 
   temp_var = find_variable ("HOME");
   if (temp_var == 0)
-    {
-      if (current_user.home_dir == 0)
-       get_current_user_info ();
-      temp_var = bind_variable ("HOME", current_user.home_dir);
-    }
-  temp_var->attributes |= att_exported;
+    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
@@ -422,9 +605,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);
     }
-  temp_var->attributes |= att_exported;
+#if 0
+  VSETATTR (temp_var, att_exported);
+#endif
 }
 
 static char *
@@ -432,13 +617,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] == '/')
     {
@@ -447,10 +632,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
     {
@@ -469,7 +659,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
@@ -500,18 +690,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;
     }
 
@@ -536,49 +727,100 @@ 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);
 }
 
 static void
 initialize_shell_level ()
 {
-#if 0
+  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[INT_STRLEN_BOUND(pid_t) + 1], *name;
   SHELL_VAR *temp_var;
 
-  temp_var = set_if_not ("SHLVL", "0");
-  set_auto_export (temp_var);
-#endif
-  adjust_shell_level (1);
+  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, 0);
+  VSETATTR (temp_var, (att_readonly | att_integer));
 }
 
 static void
 uidset ()
 {
-  char *buff;
+  char buff[INT_STRLEN_BOUND(uid_t) + 1], *b;
   register SHELL_VAR *v;
 
-  buff = itos (current_user.uid);
+  b = inttostr (current_user.uid, buff, sizeof (buff));
   v = find_variable ("UID");
-  if (v)
-    v->attributes &= ~att_readonly;
-
-  v = bind_variable ("UID", buff);
-  v->attributes |= (att_readonly | att_integer);
-
-  if (current_user.euid != current_user.uid)
+  if (v == 0)
     {
-      free (buff);
-      buff = itos (current_user.euid);
+      v = bind_variable ("UID", b, 0);
+      VSETATTR (v, (att_readonly | att_integer));
     }
 
-  v = find_variable ("EUID");
-  if (v)
-    v->attributes &= ~att_readonly;
+  if (current_user.euid != current_user.uid)
+    b = inttostr (current_user.euid, buff, sizeof (buff));
 
-  v = bind_variable ("EUID", buff);
-  v->attributes |= (att_readonly | att_integer);
-  free (buff);
+  v = find_variable ("EUID");
+  if (v == 0)
+    {
+      v = bind_variable ("EUID", b, 0);
+      VSETATTR (v, (att_readonly | att_integer));
+    }
 }
 
 #if defined (ARRAY_VARS)
@@ -587,197 +829,111 @@ make_vers_array ()
 {
   SHELL_VAR *vv;
   ARRAY *av;
-  char *s, d[16];
+  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);
   strcpy (d, dist_version);
-  s = strchr (d, '.');
+  s = xstrchr (d, '.');
   if (s)
     *s++ = '\0';
-  array_add_element (av, 0, d);
-  array_add_element (av, 1, s);
-  s = itos (patch_level);
-  array_add_element (av, 2, s);
-  free (s);
-  s = itos (build_version);
-  array_add_element (av, 3, s);
-  free (s);
-  array_add_element (av, 4, release_status);
-  array_add_element (av, 5, MACHTYPE);
-
-  vv->attributes |= att_readonly;
+  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);
 }
 #endif /* ARRAY_VARS */
 
 /* 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;
+  char val[INT_STRLEN_BOUND(int) + 1], *v;
+
+  /* If we are currently assigning to LINES or COLUMNS, don't do anything. */
+  if (winsize_assignment)
+    return;
 
-  val = itos (lines);
-  bind_variable ("LINES", val);
-  free (val);
+  v = inttostr (lines, val, sizeof (val));
+  bind_variable ("LINES", v, 0);
 
-  val = itos (cols);
-  bind_variable ("COLUMNS", val);
-  free (val);
+  v = inttostr (cols, val, sizeof (val));
+  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);
-}
-
-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)
-    {
-      printf ("%s=", var->name);
-      print_var_function (var);
-      printf ("\n");
-    }
-#if defined (ARRAY_VARS)
-  else if (array_p (var) && var->value)
-    print_array_assignment (var, 0);
-#endif /* ARRAY_VARS */
-  else if (var->value)
-    {
-      printf ("%s=", var->name);
-      print_var_value (var, 1);
-      printf ("\n");
-    }
+  if (var_isset (var) == 0)
+    return;
+
+  if (function_p (var))
+    {
+      printf ("%s", var->name);
+      print_var_function (var);
+      printf ("\n");
+    }
+#if defined (ARRAY_VARS)
+  else if (array_p (var))
+    print_array_assignment (var, 0);
+#endif /* ARRAY_VARS */
+  else
+    {
+      printf ("%s=", var->name);
+      print_var_value (var, 1);
+      printf ("\n");
+    }
 }
 
 /* Print the value cell of VAR, a shell variable.  Do not print
@@ -791,17 +947,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
@@ -810,36 +972,13 @@ void
 print_var_function (var)
      SHELL_VAR *var;
 {
-  if (function_p (var) && var->value)
+  if (function_p (var) && var_isset (var))
     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));
-
-  if (vstr == 0)
-    printf ("%s=%s\n", var->name, quoted ? "'()'" : "()");
-  else
-    {
-      printf ("%s=%s\n", var->name, vstr);
-      free (vstr);
-    }
-}
-#endif /* ARRAY_VARS */
-
 /* **************************************************************** */
 /*                                                                 */
-/*              Dynamic Variable Extension                         */
+/*                     Dynamic Variables                           */
 /*                                                                 */
 /* **************************************************************** */
 
@@ -847,21 +986,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?
@@ -870,21 +1011,93 @@ 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)
+
+static SHELL_VAR *
+null_assign (self, value, unused)
+     SHELL_VAR *self;
+     char *value;
+     arrayind_t unused;
+{
+  return (self);
+}
+
+#if defined (ARRAY_VARS)
+static SHELL_VAR *
+null_array_assign (self, value, ind)
+     SHELL_VAR *self;
+     char *value;
+     arrayind_t ind;
+{
+  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;
+}
+#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)
      SHELL_VAR *self;
      char *value;
+     arrayind_t unused;
 {
-  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);
 }
@@ -897,74 +1110,118 @@ 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));
 
-  var->attributes |= att_integer;
-  var->value = p;
+  VSETATTR (var, att_integer);
+  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;
-  return ((unsigned int)(rseed & 32767));      /* was % 32768 */
+  return ((unsigned int)((rseed >> 16) & 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 SHELL_VAR *
-assign_random (self, value)
+assign_random (self, value, unused)
      SHELL_VAR *self;
      char *value;
+     arrayind_t unused;
 {
-  sbrand (atoi (value));
+  sbrand (strtoul (value, (char **)NULL, 10));
+  if (subshell_environment)
+    seeded_subshell = 1;
   return (self);
 }
 
-static SHELL_VAR *
-get_random (var)
-     SHELL_VAR *var;
+int
+get_random_number ()
 {
   int rv;
-  char *p;
 
   /* Reset for command and process substitution. */
-  if (subshell_environment)
-    sbrand (rseed + (int)(getpid() + NOW));
+  if (subshell_environment && seeded_subshell == 0)
+    {
+      sbrand (rseed + getpid() + NOW);
+      seeded_subshell = 1;
+    }
 
   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));
 
-  var->attributes |= att_integer;
-  var->value = p;
+  VSETATTR (var, att_integer);
+  var_setvalue (var, p);
   return (var);
 }
 
+static SHELL_VAR *
+assign_lineno (var, value, unused)
+     SHELL_VAR *var;
+     char *value;
+     arrayind_t unused;
+{
+  intmax_t new_value;
+
+  if (value == 0 || *value == '\0' || legal_number (value, &new_value) == 0)
+    new_value = 0;
+  line_number = new_value;
+  return var;
+}
+
 /* Function which returns the current line number. */
 static SHELL_VAR *
 get_lineno (var)
@@ -975,20 +1232,56 @@ 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)
      SHELL_VAR *var;
      char *value;
+     arrayind_t unused;
 {
-  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_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)
@@ -997,37 +1290,78 @@ 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;
+{
+  char *p;
+
+  /* If we don't have anything yet, assign a default value. */
+  if (rl_completer_word_break_characters == 0 && bash_readline_initialized == 0)
+    enable_hostname_completion (perform_hostname_completion);
+
+#if 0
+  FREE (value_cell (var));
+  p = savestring (rl_completer_word_break_characters);
+  
+  var_setvalue (var, p);
+#else
+  var_setvalue (var, rl_completer_word_break_characters);
+#endif
+
+  return (var);
+}
+
+/* When this function returns, rl_completer_word_break_characters points to
+   malloced memory. */
+static SHELL_VAR *
+assign_comp_wordbreaks (self, value, unused)
      SHELL_VAR *self;
+     char *value;
+     arrayind_t unused;
 {
-  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)
      SHELL_VAR *self;
-     int ind;
      char *value;
+     arrayind_t ind;
 {
   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 ();
+  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)
@@ -1047,85 +1381,173 @@ 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 void
-initialize_dynamic_variables ()
+
+/* 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;
 {
-  SHELL_VAR *v;
+#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);
+}
 
-  v = bind_variable ("SECONDS", (char *)NULL);
-  v->dynamic_value = get_seconds;
-  v->assign_func = assign_seconds;
+void
+make_funcname_visible (on_or_off)
+     int on_or_off;
+{
+  SHELL_VAR *v;
 
-  v = bind_variable ("RANDOM", (char *)NULL);
-  v->dynamic_value = get_random;
-  v->assign_func = assign_random;
+  v = find_variable ("FUNCNAME");
+  if (v == 0 || v->dynamic_value == 0)
+    return;
 
-  v = bind_variable ("LINENO", (char *)NULL);
-  v->dynamic_value = get_lineno;
-  v->assign_func = assign_lineno;
+  if (on_or_off)
+    VUNSETATTR (v, att_invisible);
+  else
+    VSETATTR (v, att_invisible);
+}
+
+static SHELL_VAR *
+init_funcname_var ()
+{
+  SHELL_VAR *v;
+
+  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;
+
+  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);
 
 #if defined (HISTORY)
-  v = bind_variable ("HISTCMD", (char *)NULL);
-  v->dynamic_value = get_histcmd;
-  v->assign_func = (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)
-  v = make_new_array_variable ("DIRSTACK");
-  v->dynamic_value = get_dirstack;
-  v->assign_func = assign_dirstack;
+  v = init_dynamic_array_var ("DIRSTACK", get_dirstack, assign_dirstack, 0);
 #endif /* PUSHD_AND_POPD && ARRAY_VARS */
 
 #if defined (ARRAY_VARS)
-  v = make_new_array_variable ("GROUPS");
-  v->dynamic_value = get_groupset;
-  v->assign_func = (DYNAMIC_FUNC *)NULL;
-  v->attributes |= att_readonly;
+  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);
 #endif
+
+  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 (search_tempenv && temporary_env)         
+    var = hash_lookup (name, temporary_env);
 
-  if (!var)
+  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);
@@ -1134,101 +1556,158 @@ 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 && this_shell_builtin != 0)));
 }
 
 /* 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 (hash_lookup (name, shell_functions));
+}
+
+/* Find the function definition for the shell function named NAME.  Returns
+   the entry or NULL. */
+FUNCTION_DEF *
+find_function_def (name)
+     const char *name;
+{
+  return ((FUNCTION_DEF *)hash_lookup (name, shell_function_defs));
+}
+
+/* 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;
 {
-  return (var_lookup (name, shell_functions));
+  if (var == 0)
+    return ((char *)NULL);
+#if defined (ARRAY_VARS)
+  else if (array_p (var))
+    return (array_reference (array_cell (var), 0));
+#endif
+  else
+    return (value_cell (var));
 }
 
 /* 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. */
+   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)
-     char *var_name;
+     const char *var_name;
 {
   SHELL_VAR *var;
 
   var = find_variable (var_name);
+  return ((var) ? get_variable_value (var) : (char *)NULL);
+}
 
-  if (!var)
-    return (char *)NULL;
-#if defined (ARRAY_VARS)
-  else if (array_p (var))
-    return (array_reference (array_cell (var), 0));
-#endif
-  else
-    return (var->value);
+/* This is present for use by the tilde and readline libraries. */
+char *
+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;
+
+  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);
+    }
+
+  was_tmpvar = old_var && tempvar_p (old_var);
+  if (was_tmpvar)
+    tmp_value = value_cell (old_var);
 
-  elt = remove_hash_item (name, shell_variables);
-  if (elt)
+  for (vc = shell_variables; vc; vc = vc->down)
+    if (vc_isfuncenv (vc) && vc->scope == variable_context)
+      break;
+
+  if (vc == 0)
     {
-      old_var = (SHELL_VAR *)elt->data;
-      free (elt->key);
-      free (elt);
+      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);
+
+  /* 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)))
+    {
+      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 = bind_variable_internal (name, "", vc->table, HASH_NOSRCH, 0);
   else
     {
-      new_var = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR));
+      new_var = make_new_variable (name, vc->table);
 
-      new_var->name = savestring (name);
-      new_var->value = xmalloc (1);
-      new_var->value[0] = '\0';
-
-      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;
-  new_var->attributes |= att_local;
+  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);
 }
@@ -1242,42 +1721,66 @@ make_local_array_variable (name)
   ARRAY *array;
 
   var = make_local_variable (name);
-  array = new_array ();
+  if (var == 0 || array_p (var))
+    return var;
+
+  array = array_create ();
 
   FREE (value_cell(var));
-  var->value = (char *)array;
-  var->attributes |= att_array;
+  var_setarray (var, 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)
+    {
+      shell_variables = global_variables = new_var_context ((char *)NULL, 0);
+      shell_variables->scope = 0;
+      shell_variables->table = hash_create (0);
+    }
+
+  elt = hash_insert (savestring (name), table, HASH_NOSRCH);
+  elt->data = (PTR_T)entry;
 
   return entry;
 }
@@ -1290,42 +1793,63 @@ make_new_array_variable (name)
   SHELL_VAR *entry;
   ARRAY *array;
 
-  entry = make_new_variable (name);
-  array = new_array ();
-  entry->value = (char *)array;
-  entry->attributes |= att_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)
+make_variable_value (var, value, flags)
      SHELL_VAR *var;
      char *value;
+     int flags;
 {
-  char *retval;
-  long lval;
-  int expok;
+  char *retval, *oval;
+  intmax_t lval, rval;
+  int expok, olen;
 
   /* 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
+     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 (flags & ASS_APPEND)
+       {
+         oval = value_cell (var);
+         lval = evalexp (oval, &expok);        /* ksh93 seems to do this */
+         if (expok == 0)
+           jump_to_top_level (DISCARD);
+       }
+      rval = evalexp (value, &expok);
       if (expok == 0)
        jump_to_top_level (DISCARD);
-      retval = itos (lval);
+      if (flags & ASS_APPEND)
+       rval += lval;
+      retval = itos (rval);
     }
   else if (value)
     {
-      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 = xmalloc (1);
+         retval = (char *)xmalloc (1);
          retval[0] = '\0';
        }
     }
@@ -1335,40 +1859,50 @@ make_variable_value (var, value)
   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 */
+    }
+  else if (entry->assign_func) /* array vars have assign functions now */
+    {
+      INVALIDATE_EXPORTSTR (entry);
+      newval = (aflags & ASS_APPEND) ? make_variable_value (entry, value, aflags) : value;
+      entry = (*(entry->assign_func)) (entry, newval, -1);
+      if (newval != value)
+        free (newval);
+      return (entry);
     }
-#if defined (ARRAY_VARS)
-  else if (entry->assign_func && array_p (entry) == 0)
-#else
-  else if (entry->assign_func)
-#endif
-    return ((*(entry->assign_func)) (entry, value));
   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. */
-      entry->attributes &= ~att_invisible;
+      VUNSETATTR (entry, att_invisible);
+
+      newval = make_variable_value (entry, value, aflags);     /* XXX */
 
-      newval = make_variable_value (entry, value);
+      /* Invalidate any cached export string */
+      INVALIDATE_EXPORTSTR (entry);
 
 #if defined (ARRAY_VARS)
       /* XXX -- this bears looking at again -- XXX */
@@ -1377,261 +1911,397 @@ 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
+#endif
        {
-         FREE (entry->value);
-         entry->value = newval;
+         FREE (value_cell (entry));
+         var_setvalue (entry, newval);
        }
-#else
-      FREE (entry->value);
-      entry->value = newval;
-#endif
     }
 
   if (mark_modified_vars)
-    entry->attributes |= att_exported;
+    VSETATTR (entry, att_exported);
 
   if (exported_p (entry))
     array_needs_making = 1;
 
   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. */
 
-#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_variable (name, value, flags)
+     const char *name;
+     char *value;
+     int flags;
 {
-  char *oldval;
-  ARRAY *array;
+  SHELL_VAR *v;
+  VAR_CONTEXT *vc;
 
-  oldval = value_cell (var);
-  array = new_array ();
-  array_add_element (array, 0, oldval);
-  FREE (value_cell (var));
-  var->value = (char *)array;
-  var->attributes |= att_array;
-  var->attributes &= ~att_invisible;
+  if (shell_variables == 0)
+    {
+      shell_variables = global_variables = new_var_context ((char *)NULL, 0);
+      shell_variables->scope = 0;
+      shell_variables->table = hash_create (0);
+    }
 
-  return var;
-}
+  /* 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);
 
-/* 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].
+  /* 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));
+}
 
-   If NAME does not exist, just create an array variable, no matter what
-   IND's value may be. */
+/* Make VAR, a simple shell variable, have value VALUE.  Once assigned a
+   value, variables are no longer invisible.  This is a duplicate of part
+   of the internals of bind_variable.  If the variable is exported, or
+   all modified variables should be exported, mark the variable for export
+   and note that the export environment needs to be recreated. */
 SHELL_VAR *
-bind_array_variable (name, ind, value)
-     char *name;
-     int ind;
+bind_variable_value (var, value, aflags)
+     SHELL_VAR *var;
      char *value;
+     int aflags;
 {
-  SHELL_VAR *entry;
-  char *newval;
+  char *t;
 
-  entry = var_lookup (name, shell_variables);
+  VUNSETATTR (var, att_invisible);
 
-  if (entry == (SHELL_VAR *) 0)
-    entry = make_new_array_variable (name);
-  else if (readonly_p (entry))
+  if (var->assign_func)
     {
-      report_error ("%s: readonly variable", name);
-      return (entry);
+      /* 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);
+      if (t != value && t)
+       free (t);      
     }
-  else if (array_p (entry) == 0)
-    entry = convert_var_to_array (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);
   else
-    array_add_element (array_cell (entry), ind, newval);
-  FREE (newval);
+    {
+      t = make_variable_value (var, value, aflags);
+      FREE (value_cell (var));
+      var_setvalue (var, t);
+    }
 
-  return (entry);
+  INVALIDATE_EXPORTSTR (var);
+
+  if (mark_modified_vars)
+    VSETATTR (var, att_exported);
+
+  if (exported_p (var))
+    array_needs_making = 1;
+
+  return (var);
 }
 
-/* Perform a compound assignment statement for array NAME, where VALUE is
-   the text between the parens:  NAME=( VALUE ) */
+/* Bind/create a shell variable with the name LHS to the RHS.
+   This creates or modifies a variable such that it is an integer.
+
+   This used to be in expr.c, but it is here so that all of the
+   variable binding stuff is localized.  Since we don't want any
+   recursive evaluation from bind_variable() (possible without this code,
+   since bind_variable() calls the evaluator for variables with the integer
+   attribute set), we temporarily turn off the integer attribute for each
+   variable we set here, then turn it back on after binding as necessary. */
+
 SHELL_VAR *
-assign_array_from_string (name, value)
-     char *name, *value;
+bind_int_variable (lhs, rhs)
+     char *lhs, *rhs;
 {
-  SHELL_VAR *var;
+  register SHELL_VAR *v;
+  char *t;
+  int isint, isarr;
 
-  var = find_variable (name);
-  if (var == 0)
-    var = make_new_array_variable (name);
-  else if (readonly_p (var))
+  isint = isarr = 0;
+#if defined (ARRAY_VARS)
+#  if 0
+  if (t = xstrchr (lhs, '['))  /*]*/
+#  else
+  if (valid_array_reference (lhs))
+#  endif
     {
-      report_error ("%s: readonly variable", name);
-      return ((SHELL_VAR *)NULL);
+      isarr = 1;
+      v = array_variable_part (lhs, (char **)0, (int *)0);
+    }
+  else
+#endif
+    v = find_variable (lhs);
+
+  if (v)
+    {
+      isint = integer_p (v);
+      VUNSETATTR (v, att_integer);
     }
-  else if (array_p (var) == 0)
-    var = convert_var_to_array (var);
 
-  return (assign_array_var_from_string (var, value));
+#if defined (ARRAY_VARS)
+  if (isarr)
+    v = assign_array_element (lhs, rhs, 0);
+  else
+#endif
+    v = bind_variable (lhs, rhs, 0);
+
+  if (isint)
+    VSETATTR (v, att_integer);
+
+  return (v);
 }
 
 SHELL_VAR *
-assign_array_var_from_word_list (var, list)
-     SHELL_VAR *var;
-     WORD_LIST *list;
+bind_var_to_int (var, val)
+     char *var;
+     intmax_t val;
 {
-  register int i;
-  register WORD_LIST *l;
-  ARRAY *a;
+  char ibuf[INT_STRLEN_BOUND (intmax_t) + 1], *p;
 
-  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;
+  p = fmtulong (val, 10, ibuf, sizeof (ibuf), 0);
+  return (bind_int_variable (var, p));
 }
 
-/* Perform a compound array assignment:  VAR->name=( VALUE ).  The
-   VALUE has already had the parentheses stripped. */
+/* 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 *
-assign_array_var_from_string (var, value)
-     SHELL_VAR *var;
-     char *value;
+bind_function (name, value)
+     const char *name;
+     COMMAND *value;
 {
-  ARRAY *a;
-  WORD_LIST *list, *nlist;
-  char *w, *val, *nval;
-  int ni, len, ind, last_ind;
-
-  if (value == 0)
-    return var;
+  SHELL_VAR *entry;
 
-  /* 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 == '(')   /*)*/
+  entry = find_function (name);
+  if (entry == 0)
     {
-      ni = 1;
-      val = extract_array_assignment_list (value, &ni);
-      if (val == 0)
-        return var;
+      BUCKET_CONTENTS *elt;
+
+      elt = hash_insert (savestring (name), shell_functions, HASH_NOSRCH);
+      entry = new_shell_variable (name);
+      elt->data = (PTR_T)entry;
     }
   else
-    val = value;
-
-  /* Expand the value string into a list of words, performing all the
-     shell expansions including word splitting. */
-#if 1
-  /* 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");
-  /* Now that we've split it, perform the shell expansions on each
-     word in the list. */
-  nlist = list ? expand_words_shellexp (list) : (WORD_LIST *)NULL;
-  dispose_words (list);
-#else
-  nlist = expand_string (val, 0);
-#endif
-
-  if (val != value)
-    free (val);
+    INVALIDATE_EXPORTSTR (entry);
 
-  a = array_cell (var);
+  if (var_isset (entry))
+    dispose_command (function_cell (entry));
 
-  /* Now that we are ready to assign values to the array, kill the existing
-     value. */
-  if (a)
-    empty_array (a);
+  if (value)
+    var_setfunc (entry, copy_command (value));
+  else
+    var_setfunc (entry, 0);
 
-  for (last_ind = 0, list = nlist; list; list = list->next)
-    {
-      w = list->word->word;
+  VSETATTR (entry, att_function);
 
-      /* We have a word of the form [ind]=value */
-      if (w[0] == '[')
-       {
-         len = skipsubscript (w, 0);
+  if (mark_modified_vars)
+    VSETATTR (entry, att_exported);
 
-         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;
-           }
+  VUNSETATTR (entry, att_invisible);           /* Just to be sure */
 
-         if (len == 1)
-           {
-             report_error ("%s: bad array subscript", w);
-             continue;
-           }
+  if (exported_p (entry))
+    array_needs_making = 1;
 
-         if (ALL_ELEMENT_SUB (w[1]) && len == 2)
-           {
-             report_error ("%s: cannot assign to non-numeric index", w);
-             continue;
-           }
+#if defined (PROGRAMMABLE_COMPLETION)
+  set_itemlist_dirty (&it_functions);
+#endif
 
-         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;
-       }
+  return (entry);
+}
 
-      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++;
+/* 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;
+{
+  FUNCTION_DEF *entry;
+  BUCKET_CONTENTS *elt;
+  COMMAND *cmd;
+
+  entry = find_function_def (name);
+  if (entry)
+    {
+      dispose_function_def_contents (entry);
+      entry = copy_function_def_contents (value, entry);
     }
+  else
+    {
+      cmd = value->command;
+      value->command = 0;
+      entry = copy_function_def (value);
+      value->command = cmd;
 
-  dispose_words (nlist);
-  return (var);
+      elt = hash_insert (savestring (name), shell_function_defs, HASH_NOSRCH);
+      elt->data = (PTR_T *)entry;
+    }
 }
-#endif /* ARRAY_VARS */
+
+/* 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)
+     WORD_DESC *word;
+{
+  int offset;
+  char *name, *temp, *value;
+  SHELL_VAR *var;
+  const char *string;
+
+  string = word->word;
+
+  offset = assignment (string, 0);
+  name = savestring (string);
+  value = (char *)NULL;
+
+  if (name[offset] == '=')
+    {
+      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 (readonly_p (var))
+           err_readonly (name);
+         free (name);
+         return (0);
+       }
+
+      temp = name + offset + 1;
+#if 0
+      temp = (xstrchr (temp, '~') != 0) ? bash_tilde_expand (temp, 1) : savestring (temp);
+      value = expand_string_unsplit_to_string (temp, 0);
+      free (temp);
+#else
+      value = expand_assignment_string_to_string (temp, 0);
+#endif
+    }
+
+  if (temporary_env == 0)
+    temporary_env = hash_create (TEMPENV_HASH_BUCKETS);
+
+  var = hash_lookup (name, temporary_env);
+  if (var == 0)
+    var = make_new_variable (name, temporary_env);
+  else
+    FREE (value_cell (var));
+
+  if (value == 0)
+    {
+      value = (char *)xmalloc (1);     /* like do_assignment_internal */
+      value[0] = '\0';
+    }
+
+  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);
+
+  array_needs_making = 1;
+
+  if (ifsname (name))
+    setifs (var);
+
+  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);
+
+  free (name);
+  return 1;
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*                     Copying variables                           */
+/*                                                                 */
+/* **************************************************************** */
+
+#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))
+       var_setfunc (copy, copy_command (function_cell (var)));
+#if defined (ARRAY_VARS)
+      else if (array_p (var))
+       var_setarray (copy, dup_array (array_cell (var)));
+#endif
+      else if (value_cell (var))
+       var_setvalue (copy, savestring (value_cell (var)));
+      else
+       var_setvalue (copy, (char *)NULL);
+
+      copy->dynamic_value = var->dynamic_value;
+      copy->assign_func = var->assign_func;
+
+      copy->exportstr = COPY_EXPORTSTR (var);
+
+      copy->context = var->context;
+    }
+  return (copy);
+}
+#endif
+
+/* **************************************************************** */
+/*                                                                 */
+/*               Deleting and unsetting variables                  */
+/*                                                                 */
+/* **************************************************************** */
 
 /* Dispose of the information attached to VAR. */
 void
 dispose_variable (var)
      SHELL_VAR *var;
 {
-  if (!var)
+  if (var == 0)
     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));
 #endif
   else
     FREE (value_cell (var));
 
+  FREE_EXPORTSTR (var);
+
   free (var->name);
 
   if (exported_p (var))
@@ -1640,68 +2310,65 @@ 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. */
 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;  
 }
 
 /* Make the variable associated with NAME go away.  HASH_LIST is the
@@ -1709,52 +2376,59 @@ unbind_variable (name)
    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 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)
     {
-      old_var->attributes |= att_invisible;
-      new_elt = add_hash_item (savestring (old_var->name), hash_list);
-      new_elt->data = (char *)old_var;
+#if defined (ARRAY_VARS)
+      if (array_p (old_var))
+       array_dispose (array_cell (old_var));
+      else
+#endif
+       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 = 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. */
@@ -1766,87 +2440,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++)
-           {
-             var->attributes &= ~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. */
@@ -1854,101 +2479,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);
-
-      elt->data = (char *)new_shell_variable (name);
-      entry = (SHELL_VAR *)elt->data;
-      entry->dynamic_value = entry->assign_func = (DYNAMIC_FUNC *)NULL;
-
-      /* Functions are always made at the top level.  This allows a
-        function to define another function (like autoload). */
-      entry->context = 0;
-    }
-
-  if (entry->value)
-    dispose_command ((COMMAND *)entry->value);
-
-  entry->value = value ? (char *)copy_command (value) : (char *)NULL;
-  entry->attributes |= att_function;
-
-  if (mark_modified_vars)
-    entry->attributes |= att_exported;
-
-  entry->attributes &= ~att_invisible; /* Just to be sure */
-
-  if (exported_p (entry))
-    array_needs_making = 1;
-
-  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->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 \
@@ -1956,7 +2494,7 @@ copy_variable (var)
       entry = find_variable (name); \
       if (!entry) \
        { \
-         entry = bind_variable (name, ""); \
+         entry = bind_variable (name, "", 0); \
          if (!no_invisible_vars) entry->attributes |= att_invisible; \
        } \
     } \
@@ -1971,7 +2509,7 @@ set_var_read_only (name)
   SHELL_VAR *entry;
 
   FIND_OR_MAKE_VARIABLE (name, entry);
-  entry->attributes |= att_readonly;
+  VSETATTR (entry, att_readonly);
 }
 
 #ifdef INCLUDE_UNUSED
@@ -1979,13 +2517,13 @@ 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;
 
   entry = find_function (name);
   if (entry)
-    entry->attributes |= att_readonly;
+    VSETATTR (entry, att_readonly);
 }
 
 /* Make the variable associated with NAME be auto-exported.
@@ -2003,7 +2541,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;
 
@@ -2013,103 +2551,224 @@ 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;
+/* **************************************************************** */
+/*                                                                 */
+/*                  Creating lists of variables                    */
+/*                                                                 */
+/* **************************************************************** */
+
+static VARLIST *
+vlist_alloc (nentries)
+     int nentries;
 {
-  int count, c;
+  VARLIST  *vlist;
 
-  for (count = 1; count && (c = s[++i]); )
+  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;
+
+  return vlist;
+}
+
+static VARLIST *
+vlist_realloc (vlist, n)
+     VARLIST *vlist;
+     int n;
+{
+  if (vlist == 0)
+    return (vlist = vlist_alloc (n));
+  if (n > vlist->list_size)
     {
-      if (c == '[')
-       count++;
-      else if (c == ']')
-       count--;
+      vlist->list_size = n;
+      vlist->list = (SHELL_VAR **)xrealloc (vlist->list, (vlist->list_size + 1) * sizeof (SHELL_VAR *));
     }
-  return i;
+  return vlist;
 }
-#endif /* ARRAY_VARS */
 
-/* Returns non-zero if STRING is an assignment statement.  The returned value
-   is the index of the `=' sign. */
-int
-assignment (string)
-     char *string;
+static void
+vlist_add (vlist, var, flags)
+     VARLIST *vlist;
+     SHELL_VAR *var;
+     int flags;
+{
+  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;
+}
+
+/* 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;
 {
-  register int c, newi, indx;
+  VAR_CONTEXT *v;
+  VARLIST *vlist;
+  SHELL_VAR **ret;
+  int nentries;
 
-  c = string[indx = 0];
+  for (nentries = 0, v = vc; v; v = v->down)
+    nentries += HASH_ENTRIES (v->table);
 
-  if (legal_variable_starter (c) == 0)
-    return (0);
+  if (nentries == 0)
+    return (SHELL_VAR **)NULL;
 
-  while (c = string[indx])
-    {
-      /* The following is safe.  Note that '=' at the start of a word
-        is not an assignment statement. */
-      if (c == '=')
-       return (indx);
+  vlist = vlist_alloc (nentries);
 
-#if defined (ARRAY_VARS)
-      if (c == '[')
-       {
-         newi = skipsubscript (string, indx);
-         if (string[newi++] != ']')
-           return (0);
-         return ((string[newi] == '=') ? newi : 0);
-       }
-#endif /* ARRAY_VARS */
+  for (v = vc; v; v = v->down)
+    flatten (v->table, function, vlist, 0);
 
-      /* Variable names in assignment statements may contain only letters,
-        digits, and `_'. */
-      if (legal_variable_char (c) == 0)
-       return (0);
+  ret = vlist->list;
+  free (vlist);
+  return ret;
+}
+
+SHELL_VAR **
+map_over_funcs (function)
+     sh_var_map_func_t *function;
+{
+  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;
+}
+
+/* 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;
+{
+  register int i;
+  register BUCKET_CONTENTS *tlist;
+  int r;
+  SHELL_VAR *var;
 
-      indx++;
+  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);
+       }
     }
-  return (0);
 }
 
-#ifdef READLINE
+void
+sort_variables (array)
+     SHELL_VAR **array;
+{
+  qsort (array, strvec_len ((char **)array), sizeof (SHELL_VAR *), (QSFUNC *)qsort_var_comp);
+}
 
 static int
-visible_var (var)
-     SHELL_VAR *var;
+qsort_var_comp (var1, var2)
+     SHELL_VAR **var1, **var2;
 {
-  return (invisible_p (var) == 0);
+  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 **
-_visible_names (table)
-     HASH_TABLE *table;
+vapply (func)
+     sh_var_map_func_t *func;
 {
   SHELL_VAR **list;
 
-  list = map_over (visible_var, table);
-
+  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_visible_variables ()
+all_shell_variables ()
 {
-  return (_visible_names (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 (_visible_names (shell_functions));
+  return (fapply (visible_var));
 }
 
-#endif /* READLINE */
+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. */
@@ -2120,28 +2779,305 @@ visible_and_exported (var)
   return (invisible_p (var) == 0 && exported_p (var));
 }
 
-/* Make an array of assignment statements from the hash table
-   HASHED_VARS which contains SHELL_VARs.  Only visible, exported
-   variables are eligible. */
+/* 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 ()
+{
+  return (vapply (visible_and_exported));
+}
+
+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)
+/* Return non-zero if the variable VAR is visible and an array. */
+static int
+visible_array_vars (var)
+     SHELL_VAR *var;
+{
+  return (invisible_p (var) == 0 && array_p (var));
+}
+
+SHELL_VAR **
+all_array_variables ()
+{
+  return (vapply (visible_array_vars));
+}
+#endif /* ARRAY_VARS */
+
 char **
-make_var_array (hashed_vars)
-     HASH_TABLE *hashed_vars;
+all_variables_matching_prefix (prefix)
+     const char *prefix;
+{
+  SHELL_VAR **varlist;
+  char **rlist;
+  int vind, rind, plen;
+
+  plen = STRLEN (prefix);
+  varlist = all_visible_variables ();
+  for (vind = 0; varlist && varlist[vind]; vind++)
+    ;
+  if (varlist == 0 || vind == 0)
+    return ((char **)NULL);
+  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] = (char *)0;
+  free (varlist);
+
+  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);
+}
+
+/* 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;
+
+  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
+    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. */
+static void
+dispose_temporary_env (pushf)
+     sh_free_func_t *pushf;
+{
+  hash_flush (temporary_env, pushf);
+  hash_dispose (temporary_env);
+  temporary_env  = (HASH_TABLE *)NULL;
+
+  array_needs_making = 1;
+
+  sv_ifs ("IFS");              /* XXX here for now */
+}
+
+void
+dispose_used_env_vars ()
+{
+  if (temporary_env)
+    dispose_temporary_env (propagate_temp_var);
+}
+
+/* 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)
+     const char *name, *value;
+{
+  int name_len, value_len;
+  char *p;
+
+  name_len = strlen (name);
+  value_len = STRLEN (value);
+  p = (char *)xmalloc (2 + name_len + value_len);
+  strcpy (p, name);
+  p[name_len] = '=';
+  if (value && *value)
+    strcpy (p + name_len + 1, value);
+  else
+    p[name_len + 1] = '\0';
+  return (p);
+}
+
+#ifdef DEBUG
+/* Debugging */
+static int
+valid_exportstr (v)
+     SHELL_VAR *v;
+{
+  char *s;
+
+  s = v->exportstr;
+  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 ((unsigned char)*s) == 0)
+       {
+         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);
+      return (0);
+    }
+  return (1);
+}
+#endif
+
+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 = strvec_create ((1 + strvec_len ((char **)vars)));
 
-  list = (char **)xmalloc ((1 + array_len ((char **)vars)) * sizeof (char *));
+#define USE_EXPORTSTR (value == var->exportstr)
 
   for (i = 0, list_index = 0; var = vars[i]; i++)
     {
-      if (function_p (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)
       else if (array_p (var))
@@ -2156,428 +3092,623 @@ make_var_array (hashed_vars)
 
       if (value)
        {
-         int name_len, value_len;
-         char  *p;
-
-         name_len = strlen (var->name);
-         value_len = strlen (value);
-         p = list[list_index] = xmalloc (2 + name_len + value_len);
-         strcpy (p, var->name);
-         p[name_len] = '=';
-         strcpy (p + name_len + 1, value);
+         /* Gee, I'd like to get away with not using savestring() if we're
+            using the cached exportstr... */
+         list[list_index] = USE_EXPORTSTR ? savestring (value)
+                                          : mk_env_string (var->name, value);
+
+         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))
            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;
+  vars = map_over (visible_and_exported, vcxt);
 
-  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;
 
-  nlen = strlen (name);
-  vlen = value ? strlen (value) : 0;
-  temp = xmalloc (2 + nlen + vlen);
-  strcpy (temp, name);
-  temp[nlen] = '=';
-  temp[nlen + 1] = '\0';
-  if (value)
-    {
-      if (*value)
-       strcpy (temp + nlen + 1, 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;
+  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)
-     char *name;
+void
+maybe_make_export_env ()
 {
-  SHELL_VAR *var;
+  register char **temp_array;
+  int new_size;
+  VAR_CONTEXT *tcxt;
 
-  var = (SHELL_VAR *)NULL;
+  if (array_needs_making)
+    {
+      if (export_env)
+        strvec_flush (export_env);
 
-  if (temporary_env)
-    var = find_name_in_env_array (name, temporary_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;
 
-  /* 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);
+      /* 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);
 
-  if (!var && variable_context && function_env)
-    var = find_name_in_env_array (name, function_env);
+      if (tcxt != shell_variables)
+        free (tcxt);
 
-  return (var);
+#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 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;
+/* 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
+update_export_env_inplace (env_prefix, preflen, value)
+     char *env_prefix;
+     int preflen;
+     char *value;
 {
-  if (!*arrayp)
-    return;
+  char *evar;
 
-  free_array (*arrayp);
-  *arrayp = (char **)NULL;
-  array_needs_making = 1;
+  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 in the variable array for temporary
-   environment variables. */
+/* We always put _ in the environment as the name of this command. */
 void
-dispose_used_env_vars ()
+put_command_name_into_env (command_name)
+     char *command_name;
 {
-  dispose_temporary_vars (&temporary_env);
+  update_export_env_inplace ("_=", 2, command_name);
 }
 
-/* Free the storage used for temporary environment variables given to
-   commands when executing inside of a function body. */
+#if 0  /* UNUSED -- it caused too many problems */
 void
-dispose_function_env ()
+put_gnu_argv_flags_into_env (pid, flags_string)
+     intmax_t pid;
+     char *flags_string;
 {
-  dispose_temporary_vars (&function_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
 
-/* Free the storage used for temporary environment variables given to
-   commands when executing a builtin command such as "source". */
-void
-dispose_builtin_env ()
+/* **************************************************************** */
+/*                                                                 */
+/*                   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;
 {
-  dispose_temporary_vars (&builtin_env);
+  VAR_CONTEXT *vc;
+
+  vc = (VAR_CONTEXT *)xmalloc (sizeof (VAR_CONTEXT));
+  vc->name = name ? savestring (name) : (char *)NULL;
+  vc->scope = variable_context;
+  vc->flags = flags;
+
+  vc->up = vc->down = (VAR_CONTEXT *)NULL;
+  vc->table = (HASH_TABLE *)NULL;
+
+  return vc;
 }
 
-/* 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;
+/* Free a variable context and its data, including the hash table.  Dispose
+   all of the variables. */
+void
+dispose_var_context (vc)
+     VAR_CONTEXT *vc;
 {
-  register int i, l;
-  SHELL_VAR *temp;
-  char *val, *name;
-
-  if (env_array == 0)
-    return;
+  FREE (vc->name);
 
-  for (i = 0; env_array[i]; i++)
+  if (vc->table)
     {
-      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] = '=';
+      delete_all_variables (vc->table);
+      hash_dispose (vc->table);
     }
+
+  free (vc);
 }
 
-void
-merge_temporary_env ()
+/* Set VAR's scope level to the current variable context. */
+static int
+set_context (var)
+     SHELL_VAR *var;
 {
-  merge_env_array (temporary_env);
+  return (var->context = variable_context);
 }
 
-void
-merge_builtin_env ()
+/* 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;
 {
-  merge_env_array (builtin_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)))
+    {
+      /* 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;
+    }
 
-  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;
+    }
+
+  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);
     }
-  add_to_export_env (assign, do_alloc);
-  return (export_env);
+  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 = shell_variables->nentries + shell_functions->nentries + 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
-
-      array_needs_making = 0;
+    {
+      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;
     }
+
+  dispose_variable (var);
 }
 
-/* We always put _ in the environment as the name of this command. */
 void
-put_command_name_into_env (command_name)
-     char *command_name;
+pop_scope (is_special)
+     int is_special;
 {
-  char *dummy;
+  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;
+    }
 
-  dummy = xmalloc (4 + strlen (command_name));
+  ret = vcxt->down;
+  if (ret)
+    ret->up = (VAR_CONTEXT *)NULL;
 
-  /* These three statements replace a call to sprintf */
-  dummy[0] = '_';
-  dummy[1] = '=';
-  strcpy (dummy + 2, command_name);
-  export_env = add_or_supercede_exported_var (dummy, 0);
+  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 */
 }
 
-#if 0  /* UNUSED -- it caused too many problems */
+/* **************************************************************** */
+/*                                                                 */
+/*              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;
+
+/* XXX - we might want to consider pushing and popping the `getopts' state
+   when we modify the positional parameters. */
 void
-put_gnu_argv_flags_into_env (pid, flags_string)
-     int pid;
-     char *flags_string;
+push_context (name, is_subshell, tempvars)
+     char *name;       /* function name */
+     int is_subshell;
+     HASH_TABLE *tempvars;
 {
-  char *dummy, *pbuf;
-  int l, fl;
+  if (is_subshell == 0)
+    push_dollar_vars ();
+  variable_context++;
+  push_var_context (name, VC_FUNCENV, tempvars);
+}
 
-  pbuf = itos (pid);
-  l = strlen (pbuf);
+/* Only called when subshell == 0, so we don't need to check, and can
+   unconditionally pop the dollar vars off the stack. */
+void
+pop_context ()
+{
+  pop_dollar_vars ();
+  variable_context--;
+  pop_var_context ();
 
-  fl = strlen (flags_string);
+  sv_ifs ("IFS");              /* XXX here for now */
+}
 
-  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);
+/* Save the existing positional parameters on a stack. */
+void
+push_dollar_vars ()
+{
+  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;
+}
 
-  free (pbuf);
+/* Restore the positional parameters from our stack. */
+void
+pop_dollar_vars ()
+{
+  if (!dollar_arg_stack || dollar_arg_stack_index == 0)
+    return;
 
-  export_env = add_or_supercede_exported_var (dummy, 0);
+  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 ();
 }
-#endif
 
-/* Return a string denoting what our indirection level is. */
-static char indirection_string[100];
-
-char *
-indirection_level_string ()
+void
+dispose_saved_dollar_vars ()
 {
-  register int i, j;
-  char *ps4;
+  if (!dollar_arg_stack || dollar_arg_stack_index == 0)
+    return;
+
+  dispose_words (dollar_arg_stack[dollar_arg_stack_index]);
+  dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL;
+}
 
-  indirection_string[0] = '\0';
-  ps4 = get_string_value ("PS4");
+/* Manipulate the special BASH_ARGV and BASH_ARGC variables. */
 
-  if (ps4 == 0 || *ps4 == '\0')
-    return (indirection_string);
+void
+push_args (list)
+     WORD_LIST *list;
+{
+#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 */
 }
 
 /*************************************************
@@ -2590,7 +3721,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
 
@@ -2601,74 +3731,148 @@ 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 },
-
-  { "POSIXLY_CORRECT", sv_strict_posix },
-  { "GLOBIGNORE", sv_globignore },
+  sh_sv_func_t *function;
+};
 
-  /* Variables which only do something special when READLINE is defined. */
+static struct name_and_function special_vars[] = {
 #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
+
+  { "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 },
-  { "LANG", sv_locale },
+  { "LC_NUMERIC", sv_locale },
+  { "LC_TIME", sv_locale },
+
+#if defined (READLINE) && defined (STRICT_POSIX)
+  { "LINES", sv_winsize },
+#endif
+
+  { "MAIL", sv_mail },
+  { "MAILCHECK", sv_mail },
+  { "MAILPATH", sv_mail },
+
+  { "OPTERR", sv_opterr },
+  { "OPTIND", sv_optind },
+
+  { "PATH", sv_path },
+  { "POSIXLY_CORRECT", sv_strict_posix },
+
+#if defined (READLINE)
+  { "TERM", sv_terminal },
+  { "TERMCAP", sv_terminal },
+  { "TERMINFO", sv_terminal },
+#endif /* READLINE */
+
+  { "TEXTDOMAIN", sv_locale },
+  { "TEXTDOMAINDIR", sv_locale },
 
 #if defined (HAVE_TZSET) && defined (PROMPT_STRING_DECODE)
   { "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);
+}
+
+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. */
@@ -2677,7 +3881,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
@@ -2709,6 +3913,17 @@ sv_globignore (name)
 }
 
 #if defined (READLINE)
+void
+sv_comp_wordbreaks (name)
+     char *name;
+{
+  SHELL_VAR *sv;
+
+  sv = find_variable (name);
+  if (sv == 0)
+    rl_completer_word_break_characters = (char *)NULL;
+}
+
 /* 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. */
@@ -2724,10 +3939,60 @@ void
 sv_hostfile (name)
      char *name;
 {
-  hostname_list_initialized = 0;
+  SHELL_VAR *v;
+
+  v = find_variable (name);
+  if (v == 0)
+    clear_hostname_list ();
+  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 = winsize_assigned = 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
@@ -2740,17 +4005,19 @@ 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))
-        {
+       {
          if (name[4] == 'S')
            {
-             stifle_history (num);
+             hmax = num;
+             stifle_history (hmax);
              num = where_history ();
              if (history_lines_this_session > num)
                history_lines_this_session = num;
@@ -2781,18 +4048,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);
     }
 }
 
@@ -2823,6 +4100,16 @@ 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)
@@ -2914,13 +4201,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;
+  char *t, tbuf[INT_STRLEN_BOUND(int) + 1];
 
   v = find_variable ("PIPESTATUS");
   if (v == 0)
@@ -2928,13 +4217,50 @@ 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)
     {
-      t = itos (ps[i]);
-      array_add_element (a, i, t);
-      free (t);
+      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)
+    {
+      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);
+       }
     }
 }
 #endif
@@ -2947,6 +4273,6 @@ set_pipestatus_from_exit (s)
   static int v[2] = { 0, -1 };
 
   v[0] = s;
-  set_pipestatus_array (v);
+  set_pipestatus_array (v, 1);
 #endif
 }