Imported from ../bash-2.0.tar.gz.
[platform/upstream/bash.git] / variables.c
index 2c911c2..2c8b939 100644 (file)
    along with Bash; see the file COPYING.  If not, write to the Free
    Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
 
-#include <stdio.h>
+#include "config.h"
+
 #include "bashtypes.h"
 #include "posixstat.h"
+
+#if defined (HAVE_UNISTD_H)
+#  include <unistd.h>
+#endif
+
+#include <stdio.h>
 #include <ctype.h>
 #include <pwd.h>
-
 #include "bashansi.h"
+
 #include "shell.h"
-#include "hash.h"
 #include "flags.h"
 #include "execute_cmd.h"
+#include "mailcheck.h"
+#include "input.h"
 
 #include "builtins/common.h"
 #include <tilde/tilde.h>
 
+#if defined (HISTORY)
+#  include "bashhist.h"
+#endif /* HISTORY */
+
 /* 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, shell_level;
-extern int subshell_environment;
-extern int build_version;
-extern char *dist_version;
+extern int interactive, interactive_shell, login_shell;
+extern int subshell_environment, indirection_level;
+extern int build_version, patch_level;
+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 char *this_command_name;
 extern time_t shell_start_time;
 
 /* The list of shell variables that the user has created, or that came from
@@ -86,14 +100,17 @@ char **export_env = (char **)NULL;
 /* Non-zero means that we have to remake EXPORT_ENV. */
 int array_needs_making = 1;
 
-/* The list of variables that may not be unset in this shell. */
-char **non_unsettable_vars = (char **)NULL;
+/* The number of times BASH has been executed.  This is set
+   by initialize_variables (). */
+int shell_level = 0;
 
-static char *have_local_variables;             /* XXX */
-static int local_variable_stack_size = 0;      /* XXX */
+static char *have_local_variables;
+static int local_variable_stack_size;
 
 /* Some forward declarations. */
+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 ();
 
@@ -101,18 +118,14 @@ static int qsort_var_comp ();
 #define set_auto_export(var) \
   do { var->attributes |= att_exported; array_needs_making = 1; } while (0)
 
-#if defined (HISTORY)
-#  include "bashhist.h"
-#endif /* HISTORY */
-
 /* Initialize the shell variables from the current environment. */
 void
-initialize_shell_variables (env)
+initialize_shell_variables (env, no_functions)
      char **env;
+     int no_functions; /* If set, don't import functions from ENV. */
 {
-  char *name, *string, *current_dir;
-  int c, char_index;
-  int string_index = 0;
+  char *name, *string, *temp_string;
+  int c, char_index, string_index, string_length;
   SHELL_VAR *temp_var;
 
   if (!shell_variables)
@@ -121,10 +134,8 @@ initialize_shell_variables (env)
   if (!shell_functions)
     shell_functions = make_hash_table (0);
 
-  while (string = env[string_index++])
+  for (string_index = 0; string = env[string_index++]; )
     {
-      int string_length;
-
       char_index = 0;
 
       string_length = strlen (string);
@@ -136,33 +147,42 @@ initialize_shell_variables (env)
       name[char_index] = '\0';
 
       /* If exported function, define it now. */
-      if (!privileged_mode && STREQN ("() {", string, 4))
+      if (no_functions == 0 && STREQN ("() {", string, 4))
        {
-         SHELL_VAR *f;
-         char *eval_string;
-
-         eval_string = xmalloc (3 + string_length + strlen (name));
-         sprintf (eval_string, "%s %s", name, string);
+         temp_string = xmalloc (3 + string_length + strlen (name));
+         sprintf (temp_string, "%s %s", name, string);
 
-         parse_and_execute (eval_string, name, 0);
+         parse_and_execute (temp_string, name, 0);
 
          if (name[char_index - 1] == ')')
            name[char_index - 2] = '\0';
 
-         if (f = find_function (name))
+         if (temp_var = find_function (name))
            {
-             f->attributes |= (att_exported | att_imported);
+             temp_var->attributes |= (att_exported | att_imported);
              array_needs_making = 1;
            }
          else
            report_error ("error importing function definition for `%s'", name);
        }
+#if defined (ARRAY_VARS)
+#  if 0
+      /* Array variables may not yet be exported. */
+      else if (*string == '(' && string[1] == '[' && strchr (string, ')'))
+       {
+         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);
+         array_needs_making = 1;
+       }
+#  endif
+#endif
       else
        {
-         SHELL_VAR *v;
-
-         v = bind_variable (name, string);
-         v->attributes |= (att_exported | att_imported);
+         temp_var = bind_variable (name, string);
+         temp_var->attributes |= (att_exported | att_imported);
          array_needs_making = 1;
        }
       free (name);
@@ -170,23 +190,26 @@ initialize_shell_variables (env)
 
   /* 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 getwd () to fail on shell startup,
+     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) &&
-      (current_dir = value_cell (temp_var)) &&
-      same_file (current_dir, ".", (struct stat *)NULL, (struct stat *)NULL))
-    set_working_directory (current_dir);
+      (temp_string = value_cell (temp_var)) &&
+      same_file (temp_string, ".", (struct stat *)NULL, (struct stat *)NULL))
+    set_working_directory (temp_string);
   else
     {
-      current_dir = get_working_directory ("shell-init");
-      if (current_dir)
-        {
-         bind_variable ("PWD", current_dir);
-         free (current_dir);
-        }
+      temp_string = get_working_directory ("shell-init");
+      if (temp_string)
+       {
+         bind_variable ("PWD", temp_string);
+         free (temp_string);
+       }
     }
 
+  /* Set up initial value of $_ */
+  temp_var = bind_variable ("_", dollar_vars[0]);
+
   /* Remember this pid. */
   dollar_dollar_pid = (int)getpid ();
 
@@ -198,6 +221,7 @@ initialize_shell_variables (env)
   temp_var = set_if_not ("TERM", "dumb");
   set_auto_export (temp_var);
 
+  /* set up the prompts. */
   if (interactive_shell)
     {
       set_if_not ("PS1", primary_prompt);
@@ -205,19 +229,23 @@ initialize_shell_variables (env)
     }
   set_if_not ("PS4", "+ ");
 
-#if defined (INSECURITY)
-  set_if_not ("IFS", " \t\n");
-#else
-  bind_variable ("IFS", " \t\n");
-#endif /* INSECURITY */
+  /* Don't allow IFS to be imported from the environment. */
+  temp_var = bind_variable ("IFS", " \t\n");
 
   /* Magic machine types.  Pretty convenient. */
-  temp_var = set_if_not ("HOSTTYPE", HOSTTYPE);
+  temp_var = bind_variable ("HOSTTYPE", HOSTTYPE);
   set_auto_export (temp_var);
-  temp_var = set_if_not ("OSTYPE", OSTYPE);
+  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);
 
-  /* Default MAILCHECK for interactive shells. */
+  /* 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
+     default only if neither is set. */
   if (interactive_shell)
     set_if_not ("MAILCHECK", "60");
 
@@ -227,90 +255,82 @@ initialize_shell_variables (env)
   adjust_shell_level (1);
 
   /* Make a variable $PPID, which holds the pid of the shell's parent.  */
-  {
-    char *ppid;
-    SHELL_VAR *v;
-
-    ppid = itos ((int) getppid ());
-    v = find_variable ("PPID");
-
-    if (v)
-      v->attributes &= ~(att_readonly | att_exported);
-
-    v = bind_variable ("PPID", ppid);
-    v->attributes |= (att_readonly | att_integer);
-
-    non_unsettable ("PPID");
-    free (ppid);
-  }
+  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);
 
-#if defined (GETOPTS_BUILTIN)
   /* Initialize the `getopts' stuff. */
   bind_variable ("OPTIND", "1");
+  sv_optind ("OPTIND");
   bind_variable ("OPTERR", "1");
-#endif /* GETOPTS_BUILTIN */
+  sv_opterr ("OPTERR");
 
   /* Get the full pathname to THIS shell, and set the BASH variable
      to it. */
-  {
-    char *tname;
-
-    if ((login_shell == 1) && (*shell_name != '/'))
-      {
-       /* If HOME doesn't exist, set it. */
-       temp_var = set_if_not ("HOME", current_user.home_dir);
-       temp_var->attributes |= att_exported;
-
-       name = savestring (current_user.shell);
-      }
-    else if (*shell_name == '/')
-      name = savestring (shell_name);
-    else
-      {
-       int s;
-
-       tname = find_user_command (shell_name);
-       if (tname == 0)
-         {
-           /* Try the current directory.  If there is not an executable
-              there, just punt and use the login shell. */
-           s = file_status (shell_name);
-           if (s & FS_EXECABLE)
-             {
-               tname = make_absolute (shell_name, get_string_value ("PWD"));
-               if (*shell_name == '.')
-                 {
-                   name = canonicalize_pathname (tname);
+  if ((login_shell == 1) && (*shell_name != '/'))
+    {
+      /* If HOME doesn't exist, set it. */
+      temp_var = set_if_not ("HOME", current_user.home_dir);
+      temp_var->attributes |= att_exported;
+
+      name = savestring (current_user.shell);
+    }
+  else if (*shell_name == '/')
+    name = savestring (shell_name);
+  else
+    {
+      char *tname;
+      int s;
+
+      tname = find_user_command (shell_name);
+
+      if (tname == 0)
+       {
+         /* Try the current directory.  If there is not an executable
+            there, just punt and use the login shell. */
+         s = file_status (shell_name);
+         if (s & FS_EXECABLE)
+           {
+             tname = make_absolute (shell_name, get_string_value ("PWD"));
+             if (*shell_name == '.')
+               {
+                 name = canonicalize_pathname (tname);
+                 if (name == 0)
+                   name = tname;
+                 else
                    free (tname);
-                 }
-               else
-                 name = tname;
-             }
-           else
-             name = savestring (current_user.shell);
-         }
-       else
-         {
-           name = full_pathname (tname);
-           free (tname);
-         }
-      }
-
-    /* Make the exported environment variable SHELL be the user's login
-       shell.  Note that the `tset' command looks at this variable
-       to determine what style of commands to output; if it ends in "csh",
-       then C-shell commands are output, else Bourne shell commands. */
-    temp_var = set_if_not ("SHELL", current_user.shell);
-    set_auto_export (temp_var);
-
-    /* Make a variable called BASH, which is the name of THIS shell. */
-    temp_var = bind_variable ("BASH", name);
-
-    free (name);
-  }
+               }
+            else
+               name = tname;
+           }
+         else
+           name = savestring (current_user.shell);
+       }
+      else
+       {
+         name = full_pathname (tname);
+         free (tname);
+       }
+    }
+  temp_var = bind_variable ("BASH", name);
+  free (name);
+
+  /* Make the exported environment variable SHELL be the user's login
+     shell.  Note that the `tset' command looks at this variable
+     to determine what style of commands to output; if it ends in "csh",
+     then C-shell commands are output, else Bourne shell commands. */
+  temp_var = set_if_not ("SHELL", current_user.shell);
+  set_auto_export (temp_var);
 
   /* Make a variable called BASH_VERSION which contains the version info. */
   bind_variable ("BASH_VERSION", shell_version_string ());
+#if defined (ARRAY_VARS)
+  make_vers_array ();
+#endif
 
   /* Find out if we're supposed to be in Posix.2 mode via an
      environment variable. */
@@ -326,10 +346,7 @@ initialize_shell_variables (env)
      that we are remembering commands on the history list. */
   if (remember_on_history)
     {
-      if (posixly_correct)
-       name = tilde_expand ("~/.sh_history");
-      else
-       name = tilde_expand ("~/.bash_history");
+      name = bash_tilde_expand (posixly_correct ? "~/.sh_history" : "~/.bash_history");
 
       set_if_not ("HISTFILE", name);
       free (name);
@@ -340,13 +357,11 @@ initialize_shell_variables (env)
 #endif /* HISTORY */
 
   /* Seed the random number generator. */
-  sbrand (dollar_dollar_pid);
+  sbrand (dollar_dollar_pid + (long)shell_start_time);
 
   /* Handle some "special" variables that we may have inherited from a
      parent shell. */
 
-  noclobber = find_variable ("noclobber") != (SHELL_VAR *)NULL;
-
   temp_var = find_variable ("IGNOREEOF");
   if (!temp_var)
     temp_var = find_variable ("ignoreeof");
@@ -356,36 +371,16 @@ initialize_shell_variables (env)
 #if defined (HISTORY)
   if (interactive_shell && remember_on_history)
     {
-      sv_command_oriented_history ("command_oriented_history");
-      if (find_variable ("history_control"))
-       sv_history_control ("history_control"); /* gone in next release */
-      else
-       sv_history_control ("HISTCONTROL");
+      sv_history_control ("HISTCONTROL");
+      sv_histignore ("HISTIGNORE");
     }
 #endif /* HISTORY */
 
+  /* Get the user's real and effective user ids. */
+  uidset ();
+
   /* Initialize the dynamic variables, and seed their values. */
   initialize_dynamic_variables ();
-
-  non_unsettable ("PATH");
-  non_unsettable ("IFS");
-
-  if (interactive_shell)
-    {
-      non_unsettable ("PS1");
-      non_unsettable ("PS2");
-    }
-
-  /* Get the users real user id, and save that in a readonly variable.
-     To make the variable *really* readonly, we have added it to a special
-     list of vars. */
-
-  sv_uids ();
-  set_var_read_only ("UID");
-  set_var_read_only ("EUID");
-
-  non_unsettable ("EUID");
-  non_unsettable ("UID");
 }
 
 void
@@ -409,28 +404,81 @@ adjust_shell_level (change)
   free (new_level);
 }
 
-/* Add NAME to the list of variables that cannot be unset
-   if it isn't already there. */
-void
-non_unsettable (name)
-     char *name;
+static void
+uidset ()
 {
-  register int i;
+  char *buff;
+  register SHELL_VAR *v;
+
+  buff = itos (current_user.uid);
+  v = find_variable ("UID");
+  if (v)
+    v->attributes &= ~att_readonly;
+
+  v = bind_variable ("UID", buff);
+  v->attributes |= (att_readonly | att_integer);
 
-  if (!non_unsettable_vars)
+  if (current_user.euid != current_user.uid)
     {
-      non_unsettable_vars = (char **)xmalloc (1 * sizeof (char *));
-      non_unsettable_vars[0] = (char *)NULL;
+      free (buff);
+      buff = itos (current_user.euid);
     }
 
-  for (i = 0; non_unsettable_vars[i]; i++)
-    if (STREQ (non_unsettable_vars[i], name))
-      return;
+  v = find_variable ("EUID");
+  if (v)
+    v->attributes &= ~att_readonly;
 
-  non_unsettable_vars = (char **)
-    xrealloc (non_unsettable_vars, (2 + i) * sizeof (char *));
-  non_unsettable_vars[i] = savestring (name);
-  non_unsettable_vars[i + 1] = (char *)NULL;
+  v = bind_variable ("EUID", buff);
+  v->attributes |= (att_readonly | att_integer);
+  free (buff);
+}
+
+#if defined (ARRAY_VARS)
+static void
+make_vers_array ()
+{
+  SHELL_VAR *vv;
+  ARRAY *av;
+  char *s, d[16];
+
+  makunbound ("BASH_VERSINFO", shell_variables);
+
+  vv = make_new_array_variable ("BASH_VERSINFO");
+  av = array_cell (vv);
+  strcpy (d, dist_version);
+  s = strchr (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;
+}
+#endif /* ARRAY_VARS */
+
+/* Set the environment variables $LINES and $COLUMNS in response to
+   a window size change. */
+void
+set_lines_and_columns (lines, cols)
+     int lines, cols;
+{
+  char *val;
+
+  val = itos (lines);
+  bind_variable ("LINES", val);
+  free (val);
+
+  val = itos (cols);
+  bind_variable ("COLUMNS", val);
+  free (val);
 }
 
 /* Set NAME to VALUE if NAME has no value. */
@@ -438,8 +486,9 @@ SHELL_VAR *
 set_if_not (name, value)
      char *name, *value;
 {
-  SHELL_VAR *v = find_variable (name);
+  SHELL_VAR *v;
 
+  v = find_variable (name);
   if (!v)
     v = bind_variable (name, value);
   return (v);
@@ -509,7 +558,7 @@ all_vars (table)
   SHELL_VAR **list;
 
   list = map_over ((Function *)NULL, table);
-  if (list)
+  if (list && posixly_correct)
     sort_variables (list);
   return (list);
 }
@@ -545,6 +594,7 @@ print_var_list (list)
 /* 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;
 {
@@ -570,22 +620,40 @@ print_assignment (var)
       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);
+      print_var_value (var, 1);
       printf ("\n");
     }
 }
 
 /* Print the value cell of VAR, a shell variable.  Do not print
-   the name, nor leading/trailing newline. */
+   the name, nor leading/trailing newline.  If QUOTE is non-zero,
+   and the value contains shell metacharacters, quote the value
+   in such a way that it can be read back in. */
 void
-print_var_value (var)
+print_var_value (var, quote)
      SHELL_VAR *var;
+     int quote;
 {
+  char *t;
+
   if (var->value)
-    printf ("%s", var->value);
+    {
+      if (quote && contains_shell_metas (var->value))
+       {
+         t = single_quote (var->value);
+         printf ("%s", t);
+         free (t);
+       }
+      else
+       printf ("%s", var->value);
+    }
 }
 
 /* Print the function cell of VAR, a shell variable.  Do not
@@ -598,38 +666,61 @@ print_var_function (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 Variable Extension                         */
+/*                                                                 */
 /* **************************************************************** */
 
 /* DYNAMIC VARIABLES
-   
+
    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)
    and the (SHELL_VAR *)temp is returned as the value of bind_variable.  It
    is usually ENTRY (self).
-   
+
    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);
-   
+
    Sometimes `tempvar' will replace the value of `var'.  Other times, the
    shell will simply use the string value.  Pretty object-oriented, huh?
-   
+
    Be warned, though: if you `unset' a special variable, it loses its
    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
    stupidly_hack_special_variables, but I wanted the changes as
@@ -638,14 +729,14 @@ print_var_function (var)
 /* The value of $SECONDS.  This is the number of seconds since shell
    invocation, or, the number of seconds since the last assignment + the
    value of the last assignment. */
-static long seconds_value_assigned = (long)0;
+static long seconds_value_assigned;
 
 static SHELL_VAR *
 assign_seconds (self, value)
      SHELL_VAR *self;
      char *value;
 {
-  seconds_value_assigned = atol (value);
+  seconds_value_assigned = string_to_long (value);
   shell_start_time = NOW;
   return (self);
 }
@@ -669,6 +760,7 @@ get_seconds (var)
 
 /* The random number seed.  You can change this by setting RANDOM. */
 static unsigned long rseed = 1;
+static unsigned long last_random_value;
 
 /* A linear congruential random number generator based on the ANSI
    C standard.  A more complicated one is overkill.  */
@@ -694,9 +786,7 @@ assign_random (self, value)
      SHELL_VAR *self;
      char *value;
 {
-  int s = atoi (value);
-
-  sbrand (s);
+  sbrand (atoi (value));
   return (self);
 }
 
@@ -707,7 +797,15 @@ get_random (var)
   int rv;
   char *p;
 
-  rv = brand ();
+  /* Reset for command and process substitution. */
+  if (subshell_environment)
+    sbrand ((int)(getpid() + NOW));
+
+  do
+    rv = brand ();
+  while (rv == (int)last_random_value);
+
+  last_random_value = rv;
   p = itos ((int)rv);
 
   FREE (var->value);
@@ -723,13 +821,24 @@ get_lineno (var)
      SHELL_VAR *var;
 {
   char *p;
+  int ln;
 
-  p = itos (line_number);
+  ln = executing_line_number ();
+  p = itos (ln);
   FREE (var->value);
   var->value = p;
   return (var);
 }
 
+static SHELL_VAR *
+assign_lineno (var, value)
+     SHELL_VAR *var;
+     char *value;
+{
+  line_number = atoi (value);
+  return var;
+}
+
 #if defined (HISTORY)
 static SHELL_VAR *
 get_histcmd (var)
@@ -744,6 +853,32 @@ get_histcmd (var)
 }
 #endif
 
+#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS)
+static SHELL_VAR *
+get_dirstack (self)
+     SHELL_VAR *self;
+{
+  ARRAY *a;
+  WORD_LIST *l;
+
+  l = get_directory_stack ();
+  a = word_list_to_array (l);
+  dispose_array (array_cell (self));
+  self->value = (char *)a;
+  return self;
+}
+
+static  SHELL_VAR *
+assign_dirstack (self, ind, value)
+     SHELL_VAR *self;
+     int ind;
+     char *value;
+{
+  set_dirstack_element (ind, 1, value);
+  return self;
+}
+#endif /* PUSHD AND POPD && ARRAY_VARS */
+
 static void
 initialize_dynamic_variables ()
 {
@@ -759,13 +894,19 @@ initialize_dynamic_variables ()
 
   v = bind_variable ("LINENO", (char *)NULL);
   v->dynamic_value = get_lineno;
-  v->assign_func = (DYNAMIC_FUNC *)NULL;
+  v->assign_func = assign_lineno;
 
 #if defined (HISTORY)
   v = bind_variable ("HISTCMD", (char *)NULL);
   v->dynamic_value = get_histcmd;
   v->assign_func = (DYNAMIC_FUNC *)NULL;
 #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;
+#endif /* PUSHD_AND_POPD && ARRAY_VARS */
 }
 
 /* How to get a pointer to the shell variable or function named NAME.
@@ -779,11 +920,7 @@ var_lookup (name, hashed_vars)
   BUCKET_CONTENTS *bucket;
 
   bucket = find_hash_item (name, hashed_vars);
-
-  if (bucket)
-    return ((SHELL_VAR *)bucket->data);
-  else
-    return ((SHELL_VAR *)NULL);
+  return (bucket ? (SHELL_VAR *)bucket->data : (SHELL_VAR *)NULL);
 }
 
 /* Look up the variable entry named NAME.  If SEARCH_TEMPENV is non-zero,
@@ -810,10 +947,7 @@ find_variable_internal (name, search_tempenv)
   if (!var)
     return ((SHELL_VAR *)NULL);
 
-  if (var->dynamic_value)
-    return ((*(var->dynamic_value)) (var));
-  else
-    return (var);
+  return (var->dynamic_value ? (*(var->dynamic_value)) (var) : var);
 }
 
 /* Look up the variable entry named NAME.  Returns the entry or NULL. */
@@ -845,6 +979,10 @@ get_string_value (var_name)
 
   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);
 }
@@ -883,15 +1021,13 @@ make_local_variable (name)
       new_var = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR));
 
       new_var->name = savestring (name);
-      new_var->value = savestring ("");
+      new_var->value = xmalloc (1);
+      new_var->value[0] = '\0';
 
       new_var->dynamic_value = (DYNAMIC_FUNC *)NULL;
       new_var->assign_func = (DYNAMIC_FUNC *)NULL;
 
-      new_var->attributes = 0;
-
-      if (exported_p (old_var))
-       new_var->attributes |= att_exported;
+      new_var->attributes = exported_p (old_var) ? att_exported : 0;
 
       new_var->prev_context = old_var;
       elt = add_hash_item (savestring (name), shell_variables);
@@ -899,15 +1035,14 @@ make_local_variable (name)
     }
 
   new_var->context = variable_context;
+  new_var->attributes |= att_local;
 
   /* XXX */
-  if (local_variable_stack_size <= variable_context)
+  if (variable_context >= local_variable_stack_size)
     {
       int old_size = local_variable_stack_size;
-      while (local_variable_stack_size <= variable_context)
-        local_variable_stack_size += 8;
-      have_local_variables =
-        xrealloc (have_local_variables, 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);
     }
@@ -916,95 +1051,156 @@ make_local_variable (name)
   return (new_var);
 }
 
-/* Bind a variable NAME to VALUE.  This conses up the name
-   and value strings. */
+#if defined (ARRAY_VARS)
 SHELL_VAR *
-bind_variable (name, value)
-     char *name, *value;
+make_local_array_variable (name)
+     char *name;
+{
+  SHELL_VAR *var;
+  ARRAY *array;
+
+  var = make_local_variable (name);
+  array = new_array ();
+
+  FREE (value_cell(var));
+  var->value = (char *)array;
+  var->attributes |= 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;
 {
-  SHELL_VAR *entry = var_lookup (name, shell_variables);
+  SHELL_VAR *entry;
   BUCKET_CONTENTS *elt;
 
-  if (!entry)
-    {
-      /* Make a new entry for this variable.  Then do the binding. */
-      entry = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR));
+  entry = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR));
 
-      entry->attributes = 0;
-      entry->name = savestring (name);
+  entry->attributes = 0;
+  entry->name = savestring (name);
+  entry->value = (char *)NULL;
 
-      if (value)
+  entry->dynamic_value = (DYNAMIC_FUNC *)NULL;
+  entry->assign_func = (DYNAMIC_FUNC *)NULL;
+
+  /* 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;
+}
+
+#if defined (ARRAY_VARS)
+SHELL_VAR *
+make_new_array_variable (name)
+     char *name;
+{
+  SHELL_VAR *entry;
+  ARRAY *array;
+
+  entry = make_new_variable (name);
+  array = new_array ();
+  entry->value = (char *)array;
+  entry->attributes |= att_array;
+  return entry;
+}
+#endif
+
+char *
+make_variable_value (var, value)
+     SHELL_VAR *var;
+     char *value;
+{
+  char *retval;
+  long lval;
+
+  /* If this variable has had its type set to integer (via `declare -i'),
+     then do expression evaluation on it and store the result.  The
+     functions in expr.c (evalexp and bind_int_variable) are responsible
+     for turning off the integer flag if they don't want further
+     evaluation done. */
+  if (integer_p (var))
+    {
+      lval = evalexp (value);
+      retval = itos (lval);
+    }
+  else if (value)
+    {
+      if (*value)
+       retval = savestring (value);
+      else
        {
-         if (*value)
-           entry->value = savestring (value);
-         else
-           {
-             entry->value = xmalloc (1);
-             entry->value[0] = '\0';
-           }
+         retval = xmalloc (1);
+         retval[0] = '\0';
        }
-      else
-       entry->value = (char *)NULL;
+    }
+  else
+    retval = (char *)NULL;
 
-      entry->dynamic_value = (DYNAMIC_FUNC *)NULL;
-      entry->assign_func = (DYNAMIC_FUNC *)NULL;
+  return retval;
+}
 
-      /* 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;
+/* Bind a variable NAME to VALUE.  This conses up the name
+   and value strings. */
+SHELL_VAR *
+bind_variable (name, value)
+     char *name, *value;
+{
+  char *newval;
+  SHELL_VAR *entry;
 
-      elt = add_hash_item (savestring (name), shell_variables);
-      elt->data = (char *)entry;
+  entry = var_lookup (name, shell_variables);
+
+  if (entry == 0)
+    {
+      entry = make_new_variable (name);
+      entry->value = make_variable_value (entry, value);
     }
+#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))
        {
-         report_error ("%s: read-only variable", name);
+         report_error ("%s: readonly variable", name);
          return (entry);
        }
 
       /* Variables which are bound are visible. */
       entry->attributes &= ~att_invisible;
 
-      /* If this variable has had its type set to integer (via `declare -i'),
-        then do expression evaluation on it and store the result.  The
-        functions in expr.c (evalexp and bind_int_variable) are responsible
-        for turning off the integer flag if they don't want further
-        evaluation done. */
-      if (integer_p (entry))
-       {
-         long val;
+      newval = make_variable_value (entry, value);
 
-         val = evalexp (value);
-         /* We cannot free () entry->value before this; what if the string
-            we are working is `even=even+2'?  We need the original value
-            around while we are doing the evaluation to handle any possible
-            recursion. */
-         FREE (entry->value);
-         entry->value = itos (val);
-       }
+#if defined (ARRAY_VARS)
+      /* XXX -- this bears looking at again -- XXX */
+      /* If an existing array variable x is being assigned to with x=b or
+        `read x' or something of that nature, silently convert it to
+        x[0]=b or `read x[0]'. */
+      if (array_p (entry))
+        array_add_element (array_cell (entry), 0, newval);
       else
        {
          FREE (entry->value);
-
-         if (value)
-           {
-             if (*value)
-               entry->value = savestring (value);
-             else
-               {
-                 entry->value = xmalloc (1);
-                 entry->value[0] = '\0';
-               }
-           }
-         else
-           entry->value = (char *)NULL;
+         entry->value = newval;
        }
+#else
+      FREE (entry->value);
+      entry->value = newval;
+#endif
     }
 
   if (mark_modified_vars)
@@ -1016,6 +1212,188 @@ bind_variable (name, value)
   return (entry);
 }
 
+#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;
+{
+  char *oldval;
+  ARRAY *array;
+
+  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;
+
+  return var;
+}
+
+/* Perform an array assignment name[ind]=value.  If NAME already exists and
+   is not an array, and IND is 0, perform name=value instead.  If NAME exists
+   and is not an array, and IND is not 0, convert it into an array with the
+   existing value as name[0].
+
+   If NAME does not exist, just create an array variable, no matter what
+   IND's value may be. */
+SHELL_VAR *
+bind_array_variable (name, ind, value)
+     char *name;
+     int ind;
+     char *value;
+{
+  SHELL_VAR *entry;
+  char *newval;
+
+  entry = var_lookup (name, shell_variables);
+
+  if (entry == (SHELL_VAR *) 0)
+    entry = make_new_array_variable (name);
+  else if (readonly_p (entry))
+    {
+      report_error ("%s: readonly variable", name);
+      return (entry);
+    }
+  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);
+
+  return (entry);
+}
+
+SHELL_VAR *
+assign_array_from_string (name, value)
+     char *name, *value;
+{
+  SHELL_VAR *var;
+
+  var = find_variable (name);
+  if (var == 0)
+    var = make_new_array_variable (name);
+  else if (array_p (var) == 0)
+    var = convert_var_to_array (var);
+
+  return (assign_array_var_from_string (var, value));
+}
+
+SHELL_VAR *
+assign_array_var_from_word_list (var, list)
+     SHELL_VAR *var;
+     WORD_LIST *list;
+{
+  register int i;
+  register WORD_LIST *l;
+  ARRAY *a;
+
+  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;
+}
+
+SHELL_VAR *
+assign_array_var_from_string (var, value)
+     SHELL_VAR *var;
+     char *value;
+{
+  ARRAY *a;
+  WORD_LIST *list, *nlist;
+  char *w, *val, *nval;
+  int ni, len, ind, last_ind;
+
+  a = array_cell (var);
+
+  /* Expand the value string into a list of words, performing all the
+     shell expansions including word splitting. */
+  if (*value == '(')
+    {
+      ni = 1;
+      val = extract_array_assignment_list (value, &ni);
+      if (val == 0)
+       return var;
+      nlist = expand_string (val, 0);
+      free (val);
+    }
+  else
+    nlist = expand_string (value, 0);
+
+  for (last_ind = 0, list = nlist; list; list = list->next)
+    {
+      w = list->word->word;
+
+      /* We have a word of the form [ind]=value */
+      if (w[0] == '[')
+       {
+         len = skipsubscript (w, 0);
+
+         if (w[len] != ']' || w[len+1] != '=')
+           {
+             nval = make_variable_value (var, w);
+             if (var->assign_func)
+               (*var->assign_func) (var, last_ind, nval);
+             else
+               array_add_element (a, last_ind, nval);
+             FREE (nval);
+             last_ind++;
+             continue;
+           }
+
+         if (len == 1)
+           {
+             report_error ("%s: bad array subscript", w);
+             continue;
+           }
+
+         if (ALL_ELEMENT_SUB (w[1]) && len == 2)
+           {
+             report_error ("%s: cannot assign to non-numeric index", w);
+             continue;
+           }
+
+         ind = array_expand_index (w + 1, len);
+         if (ind < 0)
+           {
+             report_error ("%s: bad array subscript", w);
+             continue;
+           }
+         last_ind = ind;
+         val = w + len + 2;
+       }
+      else             /* No [ind]=value, just a stray `=' */
+       {
+         ind = last_ind;
+         val = w;
+       }
+
+      if (integer_p (var))
+        this_command_name = (char *)NULL;      /* no command name for errors */
+      nval = make_variable_value (var, val);
+      if (var->assign_func)
+       (*var->assign_func) (var, ind, nval);
+      else
+       array_add_element (a, ind, nval);
+      FREE (nval);
+      last_ind++;
+    }
+
+  dispose_words (nlist);
+  return (var);
+}
+#endif /* ARRAY_VARS */
+
 /* Dispose of the information attached to VAR. */
 void
 dispose_variable (var)
@@ -1025,9 +1403,13 @@ dispose_variable (var)
     return;
 
   if (function_p (var))
-    dispose_command ((COMMAND *)var->value);
-  else if (var->value)
-    free (var->value);
+    dispose_command (function_cell (var));
+#if defined (ARRAY_VARS)
+  else if (array_p (var))
+    dispose_array (array_cell (var));
+#endif
+  else
+    FREE (value_cell (var));
 
   free (var->name);
 
@@ -1037,7 +1419,45 @@ 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. */
+int
+unbind_array_element (var, sub)
+     SHELL_VAR *var;
+     char *sub;
+{
+  int len, ind;
+  ARRAY_ELEMENT *ae;
+
+  len = skipsubscript (sub, 0);
+  if (sub[len] != ']' || len == 0)
+    {
+      builtin_error ("%s[%s: bad array subscript", var->name, sub);
+      return -1;
+    }
+  sub[len] = '\0';
+
+  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)
+    {
+      builtin_error ("[%s: bad array subscript", sub);
+      return -1;
+    }
+  ae = array_delete_element (array_cell (var), ind);
+  if (ae)
+    destroy_array_element (ae);
+  return 0;
+}
+#endif
+
 /* Unset the variable referenced by NAME. */
+int
 unbind_variable (name)
      char *name;
 {
@@ -1046,7 +1466,12 @@ unbind_variable (name)
   if (!var)
     return (-1);
 
+  /* 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;
@@ -1061,6 +1486,7 @@ unbind_variable (name)
    hash table from which this variable should be deleted (either
    shell_variables or shell_functions).
    Returns non-zero if the variable couldn't be found. */
+int
 makunbound (name, hash_list)
      char *name;
      HASH_TABLE *hash_list;
@@ -1080,6 +1506,21 @@ makunbound (name, hash_list)
   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. */
+  if (old_var && local_p (old_var) && variable_context == old_var->context)
+    {
+      old_var->attributes |= att_invisible;
+      elt = add_hash_item (savestring (old_var->name), hash_list);
+      elt->data = (char *)old_var;
+      stupidly_hack_special_variables (old_var->name);
+      return (0);
+    }
+
   if (new_var)
     {
       /* Has to be a variable, functions don't have previous contexts. */
@@ -1089,7 +1530,7 @@ makunbound (name, hash_list)
       new_elt->data = (char *)new_var;
 
       if (exported_p (new_var))
-       set_var_auto_export (new_var->name);
+       set_auto_export (new_var);
     }
 
   /* Have to save a copy of name here, because it might refer to
@@ -1108,12 +1549,13 @@ makunbound (name, hash_list)
 
 /* 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 = find_variable (name);
 
-  if (temp && (temp->context == variable_context))
+  if (temp && temp->context == variable_context)
     {
       makunbound (name, shell_variables);
       return (0);
@@ -1136,8 +1578,15 @@ kill_all_local_variables ()
   register SHELL_VAR *var, **list;
   HASH_TABLE *varlist;
 
-  /* XXX */
-  if (!have_local_variables || have_local_variables[variable_context] == 0)
+  /* 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++)
@@ -1149,8 +1598,10 @@ kill_all_local_variables ()
       if (list)
        {
          for (i = 0; var = list[i]; i++)
-           makunbound (var->name, varlist);
-
+           {
+             var->attributes &= ~att_local;
+             makunbound (var->name, varlist);
+           }
          free (list);
        }
     }
@@ -1158,40 +1609,27 @@ kill_all_local_variables ()
   have_local_variables[variable_context] = 0;          /* XXX */
 }
 
+static void
+free_variable_hash_data (data)
+     char *data;
+{
+  SHELL_VAR *var, *prev;
+
+  var = (SHELL_VAR *)data;
+  while (var)
+    {
+      prev = var->prev_context;
+      dispose_variable (var);
+      var = prev;
+    }
+}
+
 /* Delete the entire contents of the hash table. */
 void
 delete_all_variables (hashed_vars)
      HASH_TABLE *hashed_vars;
 {
-  register int i;
-  register BUCKET_CONTENTS *bucket;
-
-  for (i = 0; i < hashed_vars->nbuckets; i++)
-    {
-      bucket = hashed_vars->bucket_array[i];
-
-      while (bucket)
-       {
-         BUCKET_CONTENTS *temp = bucket;
-         SHELL_VAR *var, *prev;
-
-         bucket = bucket->next;
-
-         var = (SHELL_VAR *)temp->data;
-
-         while (var)
-           {
-             prev = var->prev_context;
-             dispose_variable (var);
-
-             var = prev;
-           }
-
-         free (temp->key);
-         free (temp);
-       }
-      hashed_vars->bucket_array[i] = (BUCKET_CONTENTS *)NULL;
-    }
+  flush_hash_table (hashed_vars, free_variable_hash_data);
 }
 
 static SHELL_VAR *
@@ -1214,8 +1652,9 @@ bind_function (name, value)
      char *name;
      COMMAND *value;
 {
-  SHELL_VAR *entry = find_function (name);
+  SHELL_VAR *entry;
 
+  entry = find_function (name);
   if (!entry)
     {
       BUCKET_CONTENTS *elt;
@@ -1224,8 +1663,7 @@ bind_function (name, value)
 
       elt->data = (char *)new_shell_variable (name);
       entry = (SHELL_VAR *)elt->data;
-      entry->dynamic_value = (DYNAMIC_FUNC *)NULL;
-      entry->assign_func = (DYNAMIC_FUNC *)NULL;
+      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). */
@@ -1235,11 +1673,7 @@ bind_function (name, value)
   if (entry->value)
     dispose_command ((COMMAND *)entry->value);
 
-  if (value)   /* I don't think this can happen anymore */
-    entry->value = (char *)copy_command (value);
-  else
-    entry->value = (char *)NULL;
-
+  entry->value = value ? (char *)copy_command (value) : (char *)NULL;
   entry->attributes |= att_function;
 
   if (mark_modified_vars)
@@ -1247,7 +1681,8 @@ bind_function (name, value)
 
   entry->attributes &= ~att_invisible; /* Just to be sure */
 
-  array_needs_making = 1;
+  if (exported_p (entry))
+    array_needs_making = 1;
 
   return (entry);
 }
@@ -1267,9 +1702,13 @@ copy_variable (var)
       copy->name = savestring (var->name);
 
       if (function_p (var))
-       copy->value = (char *)copy_command ((COMMAND *)var->value);
-      else if (var->value)
-       copy->value = savestring (var->value);
+       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;
 
@@ -1284,24 +1723,31 @@ copy_variable (var)
   return (copy);
 }
 
-/* Make the variable associated with NAME be read-only.
+#define FIND_OR_MAKE_VARIABLE(name, entry) \
+  do \
+    { \
+      entry = find_variable (name); \
+      if (!entry) \
+       { \
+         entry = bind_variable (name, ""); \
+         if (!no_invisible_vars) entry->attributes |= att_invisible; \
+       } \
+    } \
+  while (0)
+
+/* Make the variable associated with NAME be readonly.
    If NAME does not exist yet, create it. */
 void
 set_var_read_only (name)
      char *name;
 {
-  SHELL_VAR *entry = find_variable (name);
+  SHELL_VAR *entry;
 
-  if (!entry)
-    {
-      entry = bind_variable (name, "");
-      if (!no_invisible_vars)
-       entry->attributes |= att_invisible;
-    }
+  FIND_OR_MAKE_VARIABLE (name, entry);
   entry->attributes |= att_readonly;
 }
 
-/* Make the function associated with NAME be read-only.
+/* Make the function associated with NAME be readonly.
    If NAME does not exist, we just punt, like auto_export code below. */
 void
 set_func_read_only (name)
@@ -1319,15 +1765,9 @@ void
 set_var_auto_export (name)
      char *name;
 {
-  SHELL_VAR *entry = find_variable (name);
-
-  if (!entry)
-    {
-      entry = bind_variable (name, "");
-      if (!no_invisible_vars)
-       entry->attributes |= att_invisible;
-    }
+  SHELL_VAR *entry;
 
+  FIND_OR_MAKE_VARIABLE (name, entry);
   set_auto_export (entry);
 }
 
@@ -1336,25 +1776,45 @@ void
 set_func_auto_export (name)
      char *name;
 {
-  SHELL_VAR *entry = find_function (name);
+  SHELL_VAR *entry;
 
+  entry = find_function (name);
   if (entry)
+    set_auto_export (entry);
+}
+
+#if defined (ARRAY_VARS)
+/* This function assumes s[i] == '['; returns with s[ret] == ']' if
+   an array subscript is correctly parsed. */
+int
+skipsubscript (s, i)
+     char *s;
+     int i;
+{
+  int count, c;
+
+  for (count = 1; count && (c = s[++i]); )
     {
-      entry->attributes |= att_exported;
-      array_needs_making = 1;
+      if (c == '[')
+       count++;
+      else if (c == ']')
+       count--;
     }
+  return i;
 }
+#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;
 {
-  register int c, indx = 0;
+  register int c, newi, indx;
 
-  c = string[indx];
+  c = string[indx = 0];
 
-  if (!isletter (c) && c != '_')
+  if (legal_variable_starter (c) == 0)
     return (0);
 
   while (c = string[indx])
@@ -1364,7 +1824,19 @@ assignment (string)
       if (c == '=')
        return (indx);
 
-      if (!isletter (c) && !digit (c) && c != '_')
+#if defined (ARRAY_VARS)
+      if (c == '[')
+       {
+         newi = skipsubscript (string, indx);
+         if (string[newi++] != ']')
+           return (0);
+         return ((string[newi] == '=') ? newi : 0);
+       }
+#endif /* ARRAY_VARS */
+
+      /* Variable names in assignment statements may contain only letters,
+        digits, and `_'. */
+      if (legal_variable_char (c) == 0)
        return (0);
 
       indx++;
@@ -1376,41 +1848,42 @@ static int
 visible_var (var)
      SHELL_VAR *var;
 {
-  return (!invisible_p (var));
+  return (invisible_p (var) == 0);
 }
 
-SHELL_VAR **
-all_visible_variables ()
+static SHELL_VAR **
+_visible_names (table)
+     HASH_TABLE *table;
 {
   SHELL_VAR **list;
 
-  list = map_over (visible_var, shell_variables);
+  list = map_over (visible_var, table);
 
-  if (list)
+  if (list && posixly_correct)
     sort_variables (list);
 
   return (list);
 }
 
 SHELL_VAR **
-all_visible_functions ()
+all_visible_variables ()
 {
-  SHELL_VAR **list;
-
-  list = map_over (visible_var, shell_functions);
-
-  if (list)
-    sort_variables (list);
+  return (_visible_names (shell_variables));
+}
 
-  return (list);
+SHELL_VAR **
+all_visible_functions ()
+{
+  return (_visible_names (shell_functions));
 }
 
-/* Return non-zero if the variable VAR is visible and exported. */
+/* Return non-zero if the variable VAR is visible and exported.  Array
+   variables cannot be exported. */
 static int
 visible_and_exported (var)
      SHELL_VAR *var;
 {
-  return (!invisible_p (var) && exported_p (var));
+  return (invisible_p (var) == 0 && exported_p (var));
 }
 
 /* Make an array of assignment statements from the hash table
@@ -1422,9 +1895,10 @@ make_var_array (hashed_vars)
 {
   register int i, list_index;
   register SHELL_VAR *var;
-  char **list = (char **)NULL;
+  char **list, *value;
   SHELL_VAR **vars;
 
+  list = (char **)NULL;
   vars = map_over (visible_and_exported, hashed_vars);
 
   if (!vars)
@@ -1434,25 +1908,35 @@ make_var_array (hashed_vars)
 
   for (i = 0, list_index = 0; var = vars[i]; i++)
     {
-      char *value;
-
       if (function_p (var))
-       value = named_function_string
-         ((char *)NULL, (COMMAND *)function_cell (var), 0);
+       value = named_function_string ((char *)NULL, function_cell (var), 0);
+#if defined (ARRAY_VARS)
+      else if (array_p (var))
+#  if 0
+       value = array_to_assignment_string (array_cell (var));
+#  else
+       continue;       /* XXX array vars cannot yet be exported */
+#  endif
+#endif
       else
        value = value_cell (var);
 
       if (value)
        {
-         int name_len = strlen (var->name);
-         int value_len = strlen (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);
          list_index++;
+#if defined (ARRAY_VARS)
+         if (array_p (var))
+           free (value);
+#endif
        }
     }
 
@@ -1463,23 +1947,35 @@ make_var_array (hashed_vars)
 
 /* 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;
 {
-  int size;
-
-  int offset = assignment (string);
-  char *name = savestring (string);
-  char *temp, *value = (char *)NULL;
+  int size, offset;
+  char *name, *temp, *value;
   int nlen, vlen;
+  WORD_LIST *list;
+  SHELL_VAR *var;
+
+  offset = assignment (string);
+  name = savestring (string);
+  value = (char *)NULL;
 
+#define freetemp nlen
   if (name[offset] == '=')
     {
-      WORD_LIST *list;
-
       name[offset] = 0;
+
+      var = find_variable (name);
+      if (var && readonly_p (var))
+       {
+         report_error ("%s: readonly variable", name);
+         return (0);
+       }
       temp = name + offset + 1;
-      temp = tilde_expand (temp);
+      freetemp = strchr (temp, '~') != 0;
+      if (freetemp)
+       temp = bash_tilde_expand (temp);
 
       list = expand_string_unsplit (temp, 0);
       value = string_list (list);
@@ -1487,22 +1983,26 @@ assign_in_env (string)
       if (list)
        dispose_words (list);
 
-      free (temp);
+      if (freetemp)
+       free (temp);
     }
-
-  if (!value)
-    value = savestring ("");
+#undef freetemp
 
   nlen = strlen (name);
-  vlen = strlen (value);
+  vlen = value ? strlen (value) : 0;
   temp = xmalloc (2 + nlen + vlen);
   strcpy (temp, name);
   temp[nlen] = '=';
-  strcpy (temp + nlen + 1, value);
+  temp[nlen + 1] = '\0';
+  if (value)
+    {
+      if (*value)
+       strcpy (temp + nlen + 1, value);
+      free (value);
+    }
   free (name);
-  free (value);
 
-  if (!temporary_env)
+  if (temporary_env == 0)
     {
       temporary_env = (char **)xmalloc (sizeof (char *));
       temporary_env [0] = (char *)NULL;
@@ -1512,13 +2012,13 @@ assign_in_env (string)
   temporary_env = (char **)
     xrealloc (temporary_env, (size + 2) * (sizeof (char *)));
 
-  temporary_env[size] = (temp);
+  temporary_env[size] = temp;
   temporary_env[size + 1] = (char *)NULL;
   array_needs_making = 1;
 
   if (echo_command_at_execute)
     {
-      /* The K*rn shell prints the `+ ' in front of assignment statements,
+      /* 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);
@@ -1535,30 +2035,28 @@ find_name_in_env_array (name, array)
      char *name;
      char **array;
 {
-  register int i, l = strlen (name);
+  register int i, l;
 
-  if (!array)
+  if (array == 0)
     return ((SHELL_VAR *)NULL);
 
-  for (i = 0; array[i]; i++)
+  for (i = 0, l = strlen (name); array[i]; i++)
     {
       if (STREQN (array[i], name, l) && array[i][l] == '=')
        {
          SHELL_VAR *temp;
+         char *w;
 
          temp = new_shell_variable (name);
+         w = array[i] + l + 1;
 
-         if (array[i][l + 1])
-           temp->value = savestring (&array[i][l + 1]);
-         else
-           temp->value = (char *) NULL;
+         temp->value = *w ? savestring (w) : (char *)NULL;
 
          temp->attributes = att_exported;
          temp->context = 0;
          temp->prev_context = (SHELL_VAR *)NULL;
 
-         temp->dynamic_value = (DYNAMIC_FUNC *)NULL;
-         temp->assign_func = (DYNAMIC_FUNC *)NULL;
+         temp->dynamic_value = temp->assign_func = (DYNAMIC_FUNC *)NULL;
 
          return (temp);
        }
@@ -1631,13 +2129,40 @@ dispose_builtin_env ()
   dispose_temporary_vars (&builtin_env);
 }
 
-/* Sort ARRAY, a null terminated array of pointers to strings. */
+/* 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;
+{
+  register int i, l;
+  SHELL_VAR *temp;
+  char *w, *name;
+
+  if (env_array == 0)
+    return;
+
+  for (i = 0; env_array[i]; i++)
+    {
+      l = assignment (env_array[i]);
+      name = env_array[i];
+      w = env_array[i] + l + 1;
+      name[l] = '\0';
+      temp = bind_variable (name, w);
+      name[l] = '=';
+    }
+}
+
 void
-sort_char_array (array)
-     char **array;
+merge_temporary_env ()
 {
-  qsort (array, array_len (array), sizeof (char *),
-        (Function *)qsort_string_compare);
+  merge_env_array (temporary_env);
+}
+
+void
+merge_builtin_env ()
+{
+  merge_env_array (builtin_env);
 }
 
 #define ISFUNC(s, o) ((s[o + 1] == '(')  && (s[o + 2] == ')'))
@@ -1690,21 +2215,9 @@ maybe_make_export_env ()
       if (export_env)
        free_array (export_env);
 
-#ifdef SHADOWED_ENV
-      export_env =
-       (char **)xmalloc ((1 + array_len (shell_environment)) * sizeof (char *));
-
-      for (i = 0; shell_environment[i]; i++)
-       export_env[i] = savestring (shell_environment[i]);
-      export_env[i] = (char *)NULL;
-
-#else /* !SHADOWED_ENV */
-
       export_env = (char **)xmalloc (sizeof (char *));
       export_env[0] = (char *)NULL;
 
-#endif /* SHADOWED_ENV */
-
       temp_array = make_var_array (shell_variables);
       for (i = 0; temp_array && temp_array[i]; i++)
        export_env = add_or_supercede (temp_array[i], export_env);
@@ -1723,9 +2236,11 @@ maybe_make_export_env ()
        for (i = 0; temporary_env[i]; i++)
          export_env = add_or_supercede (temporary_env[i], export_env);
 
+#if 0
       /* If we changed the array, then sort it alphabetically. */
-      if (temporary_env || function_env)
+      if (posixly_correct == 0 && (temporary_env || function_env))
        sort_char_array (export_env);
+#endif
 
       array_needs_making = 0;
     }
@@ -1748,57 +2263,56 @@ put_command_name_into_env (command_name)
   free (dummy);
 }
 
-/* We supply our own version of getenv () because we want library
-   routines to get the changed values of exported variables. */
+void
+put_gnu_argv_flags_into_env (pid, flags_string)
+     int pid;
+     char *flags_string;
+{
+  char *dummy, *pbuf;
+  int l, fl;
+
+  pbuf = itos (pid);
+  l = strlen (pbuf);
+
+  fl = strlen (flags_string);
+
+  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);
+
+  free (pbuf);
+
+  export_env = add_or_supercede (dummy, export_env);
+  free (dummy);
+}
 
-/* The NeXT C library has getenv () defined and used in the same file.
-   This screws our scheme.  However, Bash will run on the NeXT using
-   the C library getenv (), since right now the only environment variable
-   that we care about is HOME, and that is already defined.  */
-#if !defined (NeXT) && !defined (HPOSF1)
-static char *last_tempenv_value = (char *)NULL;
-extern char **environ;
+/* Return a string denoting what our indirection level is. */
+static char indirection_string[100];
 
 char *
-getenv (name)
-#if defined (Linux) || defined (__bsdi__) || defined (convex)
-     const char *name;
-#else
-     char const *name;
-#endif /* !Linux */
+indirection_level_string ()
 {
-  SHELL_VAR *var = find_tempenv_variable ((char *)name);
+  register int i, j;
+  char *ps4;
 
-  if (var)
-    {
-      FREE (last_tempenv_value);
+  indirection_string[0] = '\0';
+  ps4 = get_string_value ("PS4");
 
-      last_tempenv_value = savestring (value_cell (var));
-      dispose_variable (var);
-      return (last_tempenv_value);
-    }
-  else if (shell_variables)
-    {
-      var = find_variable ((char *)name);
-      if (var && exported_p (var))
-       return (value_cell (var));
-    }
-  else
-    {
-      register int i, len = strlen (name);
+  if (ps4 == 0 || *ps4 == '\0')
+    return (indirection_string);
 
-      /* In some cases, s5r3 invokes getenv() before main(); BSD systems
-         using gprof also exhibit this behavior.  This means that
-         shell_variables will be 0 when this is invoked.  We look up the
-        variable in the real environment in that case. */
+  ps4 = decode_prompt_string (ps4);
 
-      for (i = 0; environ[i]; i++)
-       {
-         if ((STREQN (environ[i], name, len)) && (environ[i][len] == '='))
-           return (environ[i] + len + 1);
-       }
-    }
+  for (i = 0; *ps4 && i < indirection_level && i < 99; i++)
+    indirection_string[i] = *ps4;
+
+  for (j = 1; *ps4 && ps4[j] && i < 99; i++, j++)
+    indirection_string[i] = ps4[j];
 
-  return ((char *)NULL);
+  indirection_string[i] = '\0';
+  free (ps4);
+  return (indirection_string);
 }
-#endif /* !NeXT && !HPOSF1 */