]> git.ipfire.org Git - thirdparty/bash.git/commitdiff
commit bash-20100318 snapshot
authorChet Ramey <chet.ramey@case.edu>
Tue, 13 Dec 2011 02:56:08 +0000 (21:56 -0500)
committerChet Ramey <chet.ramey@case.edu>
Tue, 13 Dec 2011 02:56:08 +0000 (21:56 -0500)
20 files changed:
MANIFEST
MANIFEST~
bashhist.c
bashhist.c~ [new file with mode: 0644]
builtins/read.def
doc/bash.1
doc/bash.1~
doc/bashref.texi
doc/bashref.texi~
parse.y
parse.y~
po/zh_CN.po
print_cmd.c
print_cmd.c~
redir.c~ [new file with mode: 0644]
tests/RUN-ONE-TEST
tests/type.right
tests/type.tests
tests/type.tests~ [new file with mode: 0644]
tests/type3.sub [new file with mode: 0644]

index 483c7f9539f8aee4df4ec4d00344d1c5e263942b..02d3112da3594a1e4f13a017162f461603b96641 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -1066,6 +1066,7 @@ tests/type.tests  f
 tests/type.right       f
 tests/type1.sub                f
 tests/type2.sub                f
+tests/type3.sub                f
 tests/varenv.right     f
 tests/varenv.sh                f
 tests/varenv1.sub      f
index eaa44df080e65acafede9edb04db1d840137f0bc..483c7f9539f8aee4df4ec4d00344d1c5e263942b 100644 (file)
--- a/MANIFEST~
+++ b/MANIFEST~
@@ -415,6 +415,7 @@ lib/sh/snprintf.c   f
 lib/sh/spell.c         f
 lib/sh/strcasecmp.c    f
 lib/sh/strcasestr.c    f
+lib/sh/strchrnul.c     f
 lib/sh/strerror.c      f
 lib/sh/strftime.c      f
 lib/sh/stringlist.c    f
index a80968fd5c6269a09c445f04581689f3955cf33e..5a8b9c564f0d75760447fb8cfbd1ed0f1636d8d0 100644 (file)
@@ -751,6 +751,9 @@ bash_add_history (line)
              chars_to_add = "";
            }
 
+if (dstack.delimiter_depth == 0 && current->line[curlen - 1] == '\n' && *chars_to_add == ';')
+  chars_to_add++;
+
          new_line = (char *)xmalloc (1
                                      + curlen
                                      + strlen (line)
diff --git a/bashhist.c~ b/bashhist.c~
new file mode 100644 (file)
index 0000000..a80968f
--- /dev/null
@@ -0,0 +1,898 @@
+/* bashhist.c -- bash interface to the GNU history library. */
+
+/* Copyright (C) 1993-2009 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 3 of the License, or
+   (at your option) any later version.
+
+   Bash is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with Bash.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+
+#if defined (HISTORY)
+
+#if defined (HAVE_UNISTD_H)
+#  ifdef _MINIX
+#    include <sys/types.h>
+#  endif
+#  include <unistd.h>
+#endif
+
+#include "bashtypes.h"
+#include <stdio.h>
+#include <errno.h>
+#include "bashansi.h"
+#include "posixstat.h"
+#include "filecntl.h"
+
+#include "bashintl.h"
+
+#if defined (SYSLOG_HISTORY)
+#  include <syslog.h>
+#endif
+
+#include "shell.h"
+#include "flags.h"
+#include "input.h"
+#include "parser.h"    /* for the struct dstack stuff. */
+#include "pathexp.h"   /* for the struct ignorevar stuff */
+#include "bashhist.h"  /* matching prototypes and declarations */
+#include "builtins/common.h"
+
+#include <readline/history.h>
+#include <glob/glob.h>
+#include <glob/strmatch.h>
+
+#if defined (READLINE)
+#  include "bashline.h"
+extern int rl_done, rl_dispatching;    /* should really include readline.h */
+#endif
+
+#if !defined (errno)
+extern int errno;
+#endif
+
+static int histignore_item_func __P((struct ign *));
+static int check_history_control __P((char *));
+static void hc_erasedups __P((char *));
+static void really_add_history __P((char *));
+
+static struct ignorevar histignore =
+{
+  "HISTIGNORE",
+  (struct ign *)0,
+  0,
+  (char *)0,
+  (sh_iv_item_func_t *)histignore_item_func,
+};
+
+#define HIGN_EXPAND 0x01
+
+/* Declarations of bash history variables. */
+/* Non-zero means to remember lines typed to the shell on the history
+   list.  This is different than the user-controlled behaviour; this
+   becomes zero when we read lines from a file, for example. */
+int remember_on_history = 1;
+int enable_history_list = 1;   /* value for `set -o history' */
+
+/* The number of lines that Bash has added to this history session.  The
+   difference between the number of the top element in the history list
+   (offset from history_base) and the number of lines in the history file.
+   Appending this session's history to the history file resets this to 0. */
+int history_lines_this_session;
+
+/* The number of lines that Bash has read from the history file. */
+int history_lines_in_file;
+
+#if defined (BANG_HISTORY)
+/* Non-zero means do no history expansion on this line, regardless
+   of what history_expansion says. */
+int history_expansion_inhibited;
+#endif
+
+/* With the old default, every line was saved in the history individually.
+   I.e., if the user enters:
+       bash$ for i in a b c
+       > do
+       > echo $i
+       > done
+   Each line will be individually saved in the history.
+       bash$ history
+       10  for i in a b c
+       11  do
+       12  echo $i
+       13  done
+       14  history
+   If the variable command_oriented_history is set, multiple lines
+   which form one command will be saved as one history entry.
+       bash$ for i in a b c
+       > do
+       > echo $i
+       > done
+       bash$ history
+       10  for i in a b c
+    do
+    echo $i
+    done
+       11  history
+   The user can then recall the whole command all at once instead
+   of just being able to recall one line at a time.
+
+   This is now enabled by default.
+   */
+int command_oriented_history = 1;
+
+/* Set to 1 if the first line of a possibly-multi-line command was saved
+   in the history list.  Managed by maybe_add_history(), but global so
+   the history-manipluating builtins can see it. */
+int current_command_first_line_saved = 0;
+
+/* Non-zero means to store newlines in the history list when using
+   command_oriented_history rather than trying to use semicolons. */
+int literal_history;
+
+/* Non-zero means to append the history to the history file at shell
+   exit, even if the history has been stifled. */
+int force_append_history;
+
+/* A nit for picking at history saving.  Flags have the following values:
+
+   Value == 0 means save all lines parsed by the shell on the history.
+   Value & HC_IGNSPACE means save all lines that do not start with a space.
+   Value & HC_IGNDUPS means save all lines that do not match the last
+   line saved.
+   Value & HC_ERASEDUPS means to remove all other matching lines from the
+   history list before saving the latest line. */
+int history_control;
+
+/* Set to 1 if the last command was added to the history list successfully
+   as a separate history entry; set to 0 if the line was ignored or added
+   to a previous entry as part of command-oriented-history processing. */
+int hist_last_line_added;
+
+/* Set to 1 if builtins/history.def:push_history added the last history
+   entry. */
+int hist_last_line_pushed;
+
+#if defined (READLINE)
+/* If non-zero, and readline is being used, the user is offered the
+   chance to re-edit a failed history expansion. */
+int history_reediting;
+
+/* If non-zero, and readline is being used, don't directly execute a
+   line with history substitution.  Reload it into the editing buffer
+   instead and let the user further edit and confirm with a newline. */
+int hist_verify;
+
+#endif /* READLINE */
+
+/* Non-zero means to not save function definitions in the history list. */
+int dont_save_function_defs;
+
+/* Variables declared in other files used here. */
+extern int current_command_line_count;
+
+extern struct dstack dstack;
+
+static int bash_history_inhibit_expansion __P((char *, int));
+#if defined (READLINE)
+static void re_edit __P((char *));
+#endif
+static int history_expansion_p __P((char *));
+static int shell_comment __P((char *));
+static int should_expand __P((char *));
+static HIST_ENTRY *last_history_entry __P((void));
+static char *expand_histignore_pattern __P((char *));
+static int history_should_ignore __P((char *));
+
+/* Is the history expansion starting at string[i] one that should not
+   be expanded? */
+static int
+bash_history_inhibit_expansion (string, i)
+     char *string;
+     int i;
+{
+  /* The shell uses ! as a pattern negation character in globbing [...]
+     expressions, so let those pass without expansion. */
+  if (i > 0 && (string[i - 1] == '[') && member (']', string + i + 1))
+    return (1);
+  /* The shell uses ! as the indirect expansion character, so let those
+     expansions pass as well. */
+  else if (i > 1 && string[i - 1] == '{' && string[i - 2] == '$' &&
+            member ('}', string + i + 1))
+    return (1);
+#if defined (EXTENDED_GLOB)
+  else if (extended_glob && i > 1 && string[i+1] == '(' && member (')', string + i + 2))
+    return (1);
+#endif
+  else
+    return (0);
+}
+
+void
+bash_initialize_history ()
+{
+  history_quotes_inhibit_expansion = 1;
+  history_search_delimiter_chars = ";&()|<>";
+  history_inhibit_expansion_function = bash_history_inhibit_expansion;
+#if defined (BANG_HISTORY)
+  sv_histchars ("histchars");
+#endif
+}
+
+void
+bash_history_reinit (interact)
+     int interact;
+{
+#if defined (BANG_HISTORY)
+  history_expansion = interact != 0;
+  history_expansion_inhibited = 1;
+#endif
+  remember_on_history = enable_history_list = interact != 0;
+  history_inhibit_expansion_function = bash_history_inhibit_expansion;
+}
+
+void
+bash_history_disable ()
+{
+  remember_on_history = 0;
+#if defined (BANG_HISTORY)
+  history_expansion_inhibited = 1;
+#endif
+}
+
+void
+bash_history_enable ()
+{
+  remember_on_history = 1;
+#if defined (BANG_HISTORY)
+  history_expansion_inhibited = 0;
+#endif
+  history_inhibit_expansion_function = bash_history_inhibit_expansion;
+  sv_history_control ("HISTCONTROL");
+  sv_histignore ("HISTIGNORE");
+}
+
+/* Load the history list from the history file. */
+void
+load_history ()
+{
+  char *hf;
+
+  /* Truncate history file for interactive shells which desire it.
+     Note that the history file is automatically truncated to the
+     size of HISTSIZE if the user does not explicitly set the size
+     differently. */
+  set_if_not ("HISTSIZE", "500");
+  sv_histsize ("HISTSIZE");
+
+  set_if_not ("HISTFILESIZE", get_string_value ("HISTSIZE"));
+  sv_histsize ("HISTFILESIZE");
+
+  /* Read the history in HISTFILE into the history list. */
+  hf = get_string_value ("HISTFILE");
+
+  if (hf && *hf && file_exists (hf))
+    {
+      read_history (hf);
+      using_history ();
+      history_lines_in_file = where_history ();
+    }
+}
+
+void
+bash_clear_history ()
+{
+  clear_history ();
+  history_lines_this_session = 0;
+}
+
+/* Delete and free the history list entry at offset I. */
+int
+bash_delete_histent (i)
+     int i;
+{
+  HIST_ENTRY *discard;
+
+  discard = remove_history (i);
+  if (discard)
+    free_history_entry (discard);
+  history_lines_this_session--;
+
+  return 1;
+}
+
+int
+bash_delete_last_history ()
+{
+  register int i;
+  HIST_ENTRY **hlist, *histent;
+  int r;
+
+  hlist = history_list ();
+  if (hlist == NULL)
+    return 0;
+
+  for (i = 0; hlist[i]; i++)
+    ;
+  i--;
+
+  /* History_get () takes a parameter that must be offset by history_base. */
+  histent = history_get (history_base + i);    /* Don't free this */
+  if (histent == NULL)
+    return 0;
+
+  r = bash_delete_histent (i);
+
+  if (where_history () > history_length)
+    history_set_pos (history_length);
+
+  return r;
+}
+
+#ifdef INCLUDE_UNUSED
+/* Write the existing history out to the history file. */
+void
+save_history ()
+{
+  char *hf;
+
+  hf = get_string_value ("HISTFILE");
+  if (hf && *hf && file_exists (hf))
+    {
+      /* Append only the lines that occurred this session to
+        the history file. */
+      using_history ();
+
+      if (history_lines_this_session < where_history () || force_append_history)
+       append_history (history_lines_this_session, hf);
+      else
+       write_history (hf);
+      sv_histsize ("HISTFILESIZE");
+    }
+}
+#endif
+
+int
+maybe_append_history (filename)
+     char *filename;
+{
+  int fd, result;
+  struct stat buf;
+
+  result = EXECUTION_SUCCESS;
+  if (history_lines_this_session && (history_lines_this_session < where_history ()))
+    {
+      /* If the filename was supplied, then create it if necessary. */
+      if (stat (filename, &buf) == -1 && errno == ENOENT)
+       {
+         fd = open (filename, O_WRONLY|O_CREAT, 0600);
+         if (fd < 0)
+           {
+             builtin_error (_("%s: cannot create: %s"), filename, strerror (errno));
+             return (EXECUTION_FAILURE);
+           }
+         close (fd);
+       }
+      result = append_history (history_lines_this_session, filename);
+      history_lines_in_file += history_lines_this_session;
+      history_lines_this_session = 0;
+    }
+  return (result);
+}
+
+/* If this is an interactive shell, then append the lines executed
+   this session to the history file. */
+int
+maybe_save_shell_history ()
+{
+  int result;
+  char *hf;
+
+  result = 0;
+  if (history_lines_this_session)
+    {
+      hf = get_string_value ("HISTFILE");
+
+      if (hf && *hf)
+       {
+         /* If the file doesn't exist, then create it. */
+         if (file_exists (hf) == 0)
+           {
+             int file;
+             file = open (hf, O_CREAT | O_TRUNC | O_WRONLY, 0600);
+             if (file != -1)
+               close (file);
+           }
+
+         /* Now actually append the lines if the history hasn't been
+            stifled.  If the history has been stifled, rewrite the
+            history file. */
+         using_history ();
+         if (history_lines_this_session <= where_history () || force_append_history)
+           {
+             result = append_history (history_lines_this_session, hf);
+             history_lines_in_file += history_lines_this_session;
+           }
+         else
+           {
+             result = write_history (hf);
+             history_lines_in_file = history_lines_this_session;
+           }
+         history_lines_this_session = 0;
+
+         sv_histsize ("HISTFILESIZE");
+       }
+    }
+  return (result);
+}
+
+#if defined (READLINE)
+/* Tell readline () that we have some text for it to edit. */
+static void
+re_edit (text)
+     char *text;
+{
+  if (bash_input.type == st_stdin)
+    bash_re_edit (text);
+}
+#endif /* READLINE */
+
+/* Return 1 if this line needs history expansion. */
+static int
+history_expansion_p (line)
+     char *line;
+{
+  register char *s;
+
+  for (s = line; *s; s++)
+    if (*s == history_expansion_char || *s == history_subst_char)
+      return 1;
+  return 0;
+}
+
+/* Do pre-processing on LINE.  If PRINT_CHANGES is non-zero, then
+   print the results of expanding the line if there were any changes.
+   If there is an error, return NULL, otherwise the expanded line is
+   returned.  If ADDIT is non-zero the line is added to the history
+   list after history expansion.  ADDIT is just a suggestion;
+   REMEMBER_ON_HISTORY can veto, and does.
+   Right now this does history expansion. */
+char *
+pre_process_line (line, print_changes, addit)
+     char *line;
+     int print_changes, addit;
+{
+  char *history_value;
+  char *return_value;
+  int expanded;
+
+  return_value = line;
+  expanded = 0;
+
+#  if defined (BANG_HISTORY)
+  /* History expand the line.  If this results in no errors, then
+     add that line to the history if ADDIT is non-zero. */
+  if (!history_expansion_inhibited && history_expansion && history_expansion_p (line))
+    {
+      expanded = history_expand (line, &history_value);
+
+      if (expanded)
+       {
+         if (print_changes)
+           {
+             if (expanded < 0)
+               internal_error ("%s", history_value);
+#if defined (READLINE)
+             else if (hist_verify == 0 || expanded == 2)
+#else
+             else
+#endif
+               fprintf (stderr, "%s\n", history_value);
+           }
+
+         /* If there was an error, return NULL. */
+         if (expanded < 0 || expanded == 2)    /* 2 == print only */
+           {
+#    if defined (READLINE)
+             if (expanded == 2 && rl_dispatching == 0 && *history_value)
+#    else            
+             if (expanded == 2 && *history_value)
+#    endif /* !READLINE */
+               maybe_add_history (history_value);
+
+             free (history_value);
+
+#    if defined (READLINE)
+             /* New hack.  We can allow the user to edit the
+                failed history expansion. */
+             if (history_reediting && expanded < 0 && rl_done)
+               re_edit (line);
+#    endif /* READLINE */
+             return ((char *)NULL);
+           }
+
+#    if defined (READLINE)
+         if (hist_verify && expanded == 1)
+           {
+             re_edit (history_value);
+             return ((char *)NULL);
+           }
+#    endif
+       }
+
+      /* Let other expansions know that return_value can be free'ed,
+        and that a line has been added to the history list.  Note
+        that we only add lines that have something in them. */
+      expanded = 1;
+      return_value = history_value;
+    }
+#  endif /* BANG_HISTORY */
+
+  if (addit && remember_on_history && *return_value)
+    maybe_add_history (return_value);
+
+#if 0
+  if (expanded == 0)
+    return_value = savestring (line);
+#endif
+
+  return (return_value);
+}
+
+/* Return 1 if the first non-whitespace character in LINE is a `#', indicating
+ * that the line is a shell comment. */
+static int
+shell_comment (line)
+     char *line;
+{
+  char *p;
+
+  for (p = line; p && *p && whitespace (*p); p++)
+    ;
+  return (p && *p == '#');
+}
+
+#ifdef INCLUDE_UNUSED
+/* Remove shell comments from LINE.  A `#' and anything after it is a comment.
+   This isn't really useful yet, since it doesn't handle quoting. */
+static char *
+filter_comments (line)
+     char *line;
+{
+  char *p;
+
+  for (p = line; p && *p && *p != '#'; p++)
+    ;
+  if (p && *p == '#')
+    *p = '\0';
+  return (line);
+}
+#endif
+
+/* Check LINE against what HISTCONTROL says to do.  Returns 1 if the line
+   should be saved; 0 if it should be discarded. */
+static int
+check_history_control (line)
+     char *line;
+{
+  HIST_ENTRY *temp;
+  int r;
+
+  if (history_control == 0)
+    return 1;
+
+  /* ignorespace or ignoreboth */
+  if ((history_control & HC_IGNSPACE) && *line == ' ')
+    return 0;
+
+  /* ignoredups or ignoreboth */
+  if (history_control & HC_IGNDUPS)
+    {
+      using_history ();
+      temp = previous_history ();
+
+      r = (temp == 0 || STREQ (temp->line, line) == 0);
+
+      using_history ();
+
+      if (r == 0)
+       return r;
+    }
+
+  return 1;
+}
+
+/* Remove all entries matching LINE from the history list.  Triggered when
+   HISTCONTROL includes `erasedups'. */
+static void
+hc_erasedups (line)
+     char *line;
+{
+  HIST_ENTRY *temp;
+  int r;
+
+  using_history ();
+  while (temp = previous_history ())
+    {
+      if (STREQ (temp->line, line))
+       {
+         r = where_history ();
+         remove_history (r);
+       }
+    }
+  using_history ();
+}
+
+/* Add LINE to the history list, handling possibly multi-line compound
+   commands.  We note whether or not we save the first line of each command
+   (which is usually the entire command and history entry), and don't add
+   the second and subsequent lines of a multi-line compound command if we
+   didn't save the first line.  We don't usually save shell comment lines in
+   compound commands in the history, because they could have the effect of
+   commenting out the rest of the command when the entire command is saved as
+   a single history entry (when COMMAND_ORIENTED_HISTORY is enabled).  If
+   LITERAL_HISTORY is set, we're saving lines in the history with embedded
+   newlines, so it's OK to save comment lines.  We also make sure to save
+   multiple-line quoted strings or other constructs. */
+void
+maybe_add_history (line)
+     char *line;
+{
+  hist_last_line_added = 0;
+
+  /* Don't use the value of history_control to affect the second
+     and subsequent lines of a multi-line command (old code did
+     this only when command_oriented_history is enabled). */
+  if (current_command_line_count > 1)
+    {
+      if (current_command_first_line_saved &&
+         (literal_history || dstack.delimiter_depth != 0 || shell_comment (line) == 0))
+       bash_add_history (line);
+      return;
+    }
+
+  /* This is the first line of a (possible multi-line) command.  Note whether
+     or not we should save the first line and remember it. */
+  current_command_first_line_saved = check_add_history (line, 0);
+}
+
+/* Just check LINE against HISTCONTROL and HISTIGNORE and add it to the
+   history if it's OK.  Used by `history -s' as well as maybe_add_history().
+   Returns 1 if the line was saved in the history, 0 otherwise. */
+int
+check_add_history (line, force)
+     char *line;
+     int force;
+{
+  if (check_history_control (line) && history_should_ignore (line) == 0)
+    {
+      /* We're committed to saving the line.  If the user has requested it,
+        remove other matching lines from the history. */
+      if (history_control & HC_ERASEDUPS)
+       hc_erasedups (line);
+        
+      if (force)
+       {
+         really_add_history (line);
+         using_history ();
+       }
+      else
+       bash_add_history (line);
+      return 1;
+    }
+  return 0;
+}
+
+#if defined (SYSLOG_HISTORY)
+#define SYSLOG_MAXLEN 600
+
+void
+bash_syslog_history (line)
+     const char *line;
+{
+  char trunc[SYSLOG_MAXLEN];
+
+  if (strlen(line) < SYSLOG_MAXLEN)
+    syslog (SYSLOG_FACILITY|SYSLOG_LEVEL, "HISTORY: PID=%d UID=%d %s", getpid(), current_user.uid, line);
+  else
+    {
+      strncpy (trunc, line, SYSLOG_MAXLEN);
+      trunc[SYSLOG_MAXLEN - 1] = '\0';
+      syslog (SYSLOG_FACILITY|SYSLOG_LEVEL, "HISTORY (TRUNCATED): PID=%d UID=%d %s", getpid(), current_user.uid, trunc);
+    }
+}
+#endif
+       
+/* Add a line to the history list.
+   The variable COMMAND_ORIENTED_HISTORY controls the style of history
+   remembering;  when non-zero, and LINE is not the first line of a
+   complete parser construct, append LINE to the last history line instead
+   of adding it as a new line. */
+void
+bash_add_history (line)
+     char *line;
+{
+  int add_it, offset, curlen;
+  HIST_ENTRY *current, *old;
+  char *chars_to_add, *new_line;
+
+  add_it = 1;
+  if (command_oriented_history && current_command_line_count > 1)
+    {
+      chars_to_add = literal_history ? "\n" : history_delimiting_chars ();
+
+      using_history ();
+      current = previous_history ();
+
+      if (current)
+       {
+         /* If the previous line ended with an escaped newline (escaped
+            with backslash, but otherwise unquoted), then remove the quoted
+            newline, since that is what happens when the line is parsed. */
+         curlen = strlen (current->line);
+
+         if (dstack.delimiter_depth == 0 && current->line[curlen - 1] == '\\' &&
+             current->line[curlen - 2] != '\\')
+           {
+             current->line[curlen - 1] = '\0';
+             curlen--;
+             chars_to_add = "";
+           }
+
+         new_line = (char *)xmalloc (1
+                                     + curlen
+                                     + strlen (line)
+                                     + strlen (chars_to_add));
+         sprintf (new_line, "%s%s%s", current->line, chars_to_add, line);
+         offset = where_history ();
+         old = replace_history_entry (offset, new_line, current->data);
+         free (new_line);
+
+         if (old)
+           free_history_entry (old);
+
+         add_it = 0;
+       }
+    }
+
+  if (add_it)
+    really_add_history (line);
+
+#if defined (SYSLOG_HISTORY)
+  bash_syslog_history (line);
+#endif
+
+  using_history ();
+}
+
+static void
+really_add_history (line)
+     char *line;
+{
+  hist_last_line_added = 1;
+  hist_last_line_pushed = 0;
+  add_history (line);
+  history_lines_this_session++;
+}
+
+int
+history_number ()
+{
+  using_history ();
+  return (remember_on_history ? history_base + where_history () : 1);
+}
+
+static int
+should_expand (s)
+     char *s;
+{
+  char *p;
+
+  for (p = s; p && *p; p++)
+    {
+      if (*p == '\\')
+       p++;
+      else if (*p == '&')
+       return 1;
+    }
+  return 0;
+}
+
+static int
+histignore_item_func (ign)
+     struct ign *ign;
+{
+  if (should_expand (ign->val))
+    ign->flags |= HIGN_EXPAND;
+  return (0);
+}
+
+void
+setup_history_ignore (varname)
+     char *varname;
+{
+  setup_ignore_patterns (&histignore);
+}
+
+static HIST_ENTRY *
+last_history_entry ()
+{
+  HIST_ENTRY *he;
+
+  using_history ();
+  he = previous_history ();
+  using_history ();
+  return he;
+}
+
+char *
+last_history_line ()
+{
+  HIST_ENTRY *he;
+
+  he = last_history_entry ();
+  if (he == 0)
+    return ((char *)NULL);
+  return he->line;
+}
+
+static char *
+expand_histignore_pattern (pat)
+     char *pat;
+{
+  HIST_ENTRY *phe;
+  char *ret;
+
+  phe = last_history_entry ();
+
+  if (phe == (HIST_ENTRY *)0)
+    return (savestring (pat));
+
+  ret = strcreplace (pat, '&', phe->line, 1);
+
+  return ret;
+}
+
+/* Return 1 if we should not put LINE into the history according to the
+   patterns in HISTIGNORE. */
+static int
+history_should_ignore (line)
+     char *line;
+{
+  register int i, match;
+  char *npat;
+
+  if (histignore.num_ignores == 0)
+    return 0;
+
+  for (i = match = 0; i < histignore.num_ignores; i++)
+    {
+      if (histignore.ignores[i].flags & HIGN_EXPAND)
+       npat = expand_histignore_pattern (histignore.ignores[i].val);
+      else
+       npat = histignore.ignores[i].val;
+
+      match = strmatch (npat, line, FNMATCH_EXTFLAG) != FNM_NOMATCH;
+
+      if (histignore.ignores[i].flags & HIGN_EXPAND)
+       free (npat);
+
+      if (match)
+       break;
+    }
+
+  return match;
+}
+#endif /* HISTORY */
index 2bd9550003ce0a882dfeb29ba551b3ad3230a9db..20860befa92bc30420cc424ecb03934c3240dcbf 100644 (file)
@@ -615,13 +615,14 @@ add_char:
   if (unbuffered_read == 0)
     zsyncfd (fd);
 
+  discard_unwind_frame ("read_builtin");
+
   retval = eof ? EXECUTION_FAILURE : EXECUTION_SUCCESS;
 
 assign_vars:
 
   interrupt_immediately--;
   terminate_immediately--;
-  discard_unwind_frame ("read_builtin");
 
 #if defined (ARRAY_VARS)
   /* If -a was given, take the string read, break it into a list of words,
index 21ace22babc95fba234f8a0b8c7917124719f335..89a22e44c8fb4b753f73352ef7fdfe9594c4125d 100644 (file)
@@ -3902,7 +3902,7 @@ turned on to be used in an expression.
 .PP
 Constants with a leading 0 are interpreted as octal numbers.
 A leading 0x or 0X denotes hexadecimal.
-Otherwise, numbers take the form [\fIbase#\fP]n, where \fIbase\fP
+Otherwise, numbers take the form [\fIbase#\fP]n, where the optional \fIbase\fP
 is a decimal number between 2 and 64 representing the arithmetic
 base, and \fIn\fP is a number in that base.
 If \fIbase#\fP is omitted, then base 10 is used.
index 3c99d1f98d58c0268e82fad7893101ef646ce43b..21ace22babc95fba234f8a0b8c7917124719f335 100644 (file)
@@ -6055,7 +6055,7 @@ exit status of 124.  If a shell function returns 124, and changes
 the compspec associated with the command on which completion is being
 attempted (supplied as the first argument when the function is executed),
 programmable completion restarts from the beginning, with an
-attempt to find a compspec for that command.  This allows a set of
+attempt to find a new compspec for that command.  This allows a set of
 completions to be built dynamically as completion is attempted, rather than
 being loaded all at once.
 .PP
@@ -7642,12 +7642,13 @@ It returns false if the end of options is encountered or an
 error occurs.
 .TP
 \fBhash\fP [\fB\-lr\fP] [\fB\-p\fP \fIfilename\fP] [\fB\-dt\fP] [\fIname\fP]
-For each
-.IR name ,
-the full file name of the command is determined by searching
+Each time \fBhash\fP is invoked,
+the full pathname of the command 
+.I name
+is determined by searching
 the directories in
 .B $PATH
-and remembered.
+and remembered.  Any previously-remembered pathname is discarded.
 If the
 .B \-p
 option is supplied, no path search is performed, and
index 6203624f59e65b38efee28df9e82014183e9afcb..042ca92fbc838a0a21d37bec18308bd0d621d3ca 100644 (file)
@@ -5966,7 +5966,7 @@ to be used in an expression.
 
 Constants with a leading 0 are interpreted as octal numbers.
 A leading @samp{0x} or @samp{0X} denotes hexadecimal.  Otherwise,
-numbers take the form [@var{base}@code{#}]@var{n}, where @var{base}
+numbers take the form [@var{base}@code{#}]@var{n}, where the optional @var{base}
 is a decimal number between 2 and 64 representing the arithmetic
 base, and @var{n} is a number in that base.  If @var{base}@code{#} is
 omitted, then base 10 is used.
index b9e6f73942b922ffc98d9eacccb131f5939dee74..6203624f59e65b38efee28df9e82014183e9afcb 100644 (file)
@@ -2986,10 +2986,12 @@ If @code{getopts} is silent, then a colon (@samp{:}) is placed in
 @example
 hash [-r] [-p @var{filename}] [-dt] [@var{name}]
 @end example
-Remember the full pathnames of commands specified as @var{name} arguments,
+Each time @code{hash} is invoked, it remembers the full pathnames of the
+commands specified as @var{name} arguments,
 so they need not be searched for on subsequent invocations.
 The commands are found by searching through the directories listed in
 @env{$PATH}.
+Any previously-remembered pathname is discarded.
 The @option{-p} option inhibits the path search, and @var{filename} is
 used as the location of @var{name}.
 The @option{-r} option causes the shell to forget all remembered locations.
@@ -5316,8 +5318,9 @@ bash [long-opt] [-abefhkmnptuvxdBCDHP] [-o @var{option}] [-O @var{shopt_option}]
 bash [long-opt] -s [-abefhkmnptuvxdBCDHP] [-o @var{option}] [-O @var{shopt_option}] [@var{argument} @dots{}]
 @end example
 
-In addition to the single-character shell command-line options
-(@pxref{The Set Builtin}), there are several multi-character
+All of the single-character options used with the @code{set} builtin
+(@pxref{The Set Builtin}) can be used as options when the shell is invoked.
+In addition, there are several multi-character
 options that you can use.  These options must appear on the command
 line before the single-character options to be recognized. 
 
@@ -5326,9 +5329,7 @@ line before the single-character options to be recognized.
 Arrange for the debugger profile to be executed before the shell
 starts.  Turns on extended debugging mode (see @ref{The Shopt Builtin}
 for a description of the @code{extdebug} option to the @code{shopt}
-builtin) and shell function tracing
-(see @ref{The Set Builtin} for a description of the @code{-o functrace}
-option).
+builtin).
 
 @item --dump-po-strings
 A list of all double-quoted strings preceded by @samp{$}
diff --git a/parse.y b/parse.y
index b786fa7dc805648bd27da5c29aed65878a123135..92b65aab547a92527877cae32ce7330f6e58814d 100644 (file)
--- a/parse.y
+++ b/parse.y
@@ -4816,7 +4816,15 @@ history_delimiting_chars ()
      add the first line of the body of the here document (the second line
      of the command). */
   if (parser_state & PST_HEREDOC)
+#if 0
+{
+  if (last_read_token == WORD && (token_before_that == LESS_LESS || token_before_that == LESS_LESS_MINUS))
+    return ("\n");     /* XXX -- need to clean up in bash_add_history */
+#endif
     return (current_command_line_count == 2 ? "\n" : "");
+#if 0
+}
+#endif
 
   /* First, handle some special cases. */
   /*(*/
index 7f9fce76f970449b849f43a1f5d0a7fb2f7d165e..00dd12a441adb676febd38d12e7bd2043631450d 100644 (file)
--- a/parse.y~
+++ b/parse.y~
@@ -4816,7 +4816,11 @@ history_delimiting_chars ()
      add the first line of the body of the here document (the second line
      of the command). */
   if (parser_state & PST_HEREDOC)
+{
+  if (last_read_token == WORD && (token_before_that == LESS_LESS || token_before_that == LESS_LESS_MINUS))
+    return ("\n");     /* XXX -- need to clean up in bash_add_history */
     return (current_command_line_count == 2 ? "\n" : "");
+}
 
   /* First, handle some special cases. */
   /*(*/
@@ -5780,10 +5784,6 @@ sh_parser_state_t *
 save_parser_state (ps)
      sh_parser_state_t *ps;
 {
-#if defined (ARRAY_VARS)
-  SHELL_VAR *v;
-#endif
-
   if (ps == 0)
     ps = (sh_parser_state_t *)xmalloc (sizeof (sh_parser_state_t));
   if (ps == 0)
@@ -5806,11 +5806,7 @@ save_parser_state (ps)
 
   ps->last_command_exit_value = last_command_exit_value;
 #if defined (ARRAY_VARS)
-  v = find_variable ("PIPESTATUS");
-  if (v && array_p (v) && array_cell (v))
-    ps->pipestatus = array_copy (array_cell (v));
-  else
-    ps->pipestatus = (ARRAY *)NULL;
+  ps->pipestatus = save_pipestatus_array ();
 #endif
     
   ps->last_shell_builtin = last_shell_builtin;
@@ -5826,10 +5822,6 @@ void
 restore_parser_state (ps)
      sh_parser_state_t *ps;
 {
-#if defined (ARRAY_VARS)
-  SHELL_VAR *v;
-#endif
-
   if (ps == 0)
     return;
 
@@ -5854,12 +5846,7 @@ restore_parser_state (ps)
 
   last_command_exit_value = ps->last_command_exit_value;
 #if defined (ARRAY_VARS)
-  v = find_variable ("PIPESTATUS");
-  if (v && array_p (v) && array_cell (v))
-    {
-      array_dispose (array_cell (v));
-      var_setarray (v, ps->pipestatus);
-    }
+  restore_pipestatus_array (ps->pipestatus);
 #endif
 
   last_shell_builtin = ps->last_shell_builtin;
index c6bc1bda3055cbcec1415649334eef233bce7a0f..193d6242bcf51b8e22683f376ba532b90df50d32 100644 (file)
@@ -8,7 +8,7 @@ msgstr ""
 "Project-Id-Version: bash 4.1\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2009-12-30 08:25-0500\n"
-"PO-Revision-Date: 2010-01-28 10:27+0800\n"
+"PO-Revision-Date: 2010-03-14 16:16+0800\n"
 "Last-Translator: Xin Ye <alyex.ye@gmail.com>\n"
 "Language-Team: Chinese (simplified) <i18n-zh@googlegroups.com>\n"
 "MIME-Version: 1.0\n"
@@ -101,7 +101,7 @@ msgstr "%s 未与任何键绑定。\n"
 #: builtins/bind.def:307
 #, c-format
 msgid "%s can be invoked via "
-msgstr ""
+msgstr "%s 可以被调用,通过"
 
 #: builtins/break.def:77 builtins/break.def:117
 msgid "loop count"
@@ -439,17 +439,17 @@ msgstr "%s: 选项需要一个参数 -- %c\n"
 
 #: builtins/hash.def:92
 msgid "hashing disabled"
-msgstr ""
+msgstr "哈希已禁用"
 
 #: builtins/hash.def:138
 #, c-format
 msgid "%s: hash table empty\n"
-msgstr ""
+msgstr "%s: 哈希表为空\n"
 
 #: builtins/hash.def:244
 #, c-format
 msgid "hits\tcommand\n"
-msgstr ""
+msgstr "命中\t命令\n"
 
 #: builtins/help.def:130
 #, c-format
@@ -495,7 +495,7 @@ msgstr "%s: 历史扩展失败"
 #: builtins/inlib.def:71
 #, c-format
 msgid "%s: inlib failed"
-msgstr ""
+msgstr "%s: inlib 失败"
 
 #: builtins/jobs.def:109
 msgid "no other options allowed with `-x'"
@@ -537,12 +537,12 @@ msgstr "%s: 无效的行数"
 #: builtins/mapfile.def:277
 #, c-format
 msgid "%s: invalid array origin"
-msgstr ""
+msgstr "%s: 无效的数组基数"
 
 #: builtins/mapfile.def:294
 #, c-format
 msgid "%s: invalid callback quantum"
-msgstr ""
+msgstr "%s: 无效的回调量子"
 
 #: builtins/mapfile.def:326
 msgid "empty array variable name"
@@ -569,7 +569,7 @@ msgstr "警告: %s: %s"
 
 #: builtins/printf.def:757
 msgid "missing hex digit for \\x"
-msgstr ""
+msgstr "使用了\\x但缺少十六进制数"
 
 #: builtins/pushd.def:195
 msgid "no other directory"
@@ -695,7 +695,7 @@ msgstr "%s: 不是函数"
 
 #: builtins/shift.def:71 builtins/shift.def:77
 msgid "shift count"
-msgstr ""
+msgstr "位移计数"
 
 #: builtins/shopt.def:260
 msgid "cannot set and unset shell options simultaneously"
@@ -720,7 +720,6 @@ msgid "cannot suspend"
 msgstr "无法挂起"
 
 #: builtins/suspend.def:111
-#, fuzzy
 msgid "cannot suspend a login shell"
 msgstr "无法挂起一个登录shell"
 
@@ -752,12 +751,12 @@ msgstr "%s 是 %s\n"
 #: builtins/type.def:337
 #, c-format
 msgid "%s is hashed (%s)\n"
-msgstr ""
+msgstr "%s 已被哈希 (%s)\n"
 
 #: builtins/ulimit.def:372
 #, c-format
 msgid "%s: invalid limit argument"
-msgstr ""
+msgstr "%s: 无效的 limit 参数"
 
 #: builtins/ulimit.def:398
 #, c-format
@@ -767,16 +766,16 @@ msgstr "`%c': 坏的命令"
 #: builtins/ulimit.def:427
 #, c-format
 msgid "%s: cannot get limit: %s"
-msgstr ""
+msgstr "%s: 无法获取 limit 值: %s"
 
 #: builtins/ulimit.def:453
 msgid "limit"
-msgstr ""
+msgstr "limit"
 
 #: builtins/ulimit.def:465 builtins/ulimit.def:765
 #, c-format
 msgid "%s: cannot modify limit: %s"
-msgstr ""
+msgstr "%s: 无法修改 limit 值: %s"
 
 #: builtins/umask.def:118
 msgid "octal number"
@@ -785,12 +784,12 @@ msgstr "八进制数"
 #: builtins/umask.def:231
 #, c-format
 msgid "`%c': invalid symbolic mode operator"
-msgstr ""
+msgstr "`%c': 无效的符号状态运算符"
 
 #: builtins/umask.def:286
 #, c-format
 msgid "`%c': invalid symbolic mode character"
-msgstr ""
+msgstr "`%c': 无效的符号状态字符"
 
 #: error.c:90 error.c:321 error.c:323 error.c:325
 msgid " line "
@@ -816,11 +815,11 @@ msgstr "坏的命令类型"
 
 #: error.c:408
 msgid "bad connector"
-msgstr ""
+msgstr "坏的条件连接符"
 
 #: error.c:409
 msgid "bad jump"
-msgstr ""
+msgstr "坏的跳转"
 
 #: error.c:447
 #, c-format
@@ -830,7 +829,7 @@ msgstr "%s: 为绑定变量"
 #: eval.c:181
 #, c-format
 msgid "\atimed out waiting for input: auto-logout\n"
-msgstr ""
+msgstr "\a 等待输入超时:自动登出\n"
 
 #: execute_cmd.c:497
 #, c-format
@@ -864,7 +863,7 @@ msgstr "%s: %s: 坏的解释器"
 #: execute_cmd.c:4976
 #, c-format
 msgid "cannot duplicate fd %d to fd %d"
-msgstr ""
+msgstr "无法复制文件描述符 %d 到 文件描述符 %d"
 
 #: expr.c:241
 msgid "expression recursion level exceeded"
@@ -872,7 +871,7 @@ msgstr "表达式递归层次越界"
 
 #: expr.c:265
 msgid "recursion stack underflow"
-msgstr ""
+msgstr "递归栈下溢"
 
 #: expr.c:379
 msgid "syntax error in expression"
@@ -888,7 +887,7 @@ msgstr "除0"
 
 #: expr.c:471
 msgid "bug: bad expassign token"
-msgstr ""
+msgstr "错误:坏的表达式赋值符号"
 
 #: expr.c:513
 msgid "`:' expected for conditional expression"
@@ -896,11 +895,11 @@ msgstr "条件表达式期待 `:'"
 
 #: expr.c:781
 msgid "exponent less than 0"
-msgstr ""
+msgstr "指数小于0"
 
 #: expr.c:826
 msgid "identifier expected after pre-increment or pre-decrement"
-msgstr ""
+msgstr "预增符或预减符后应跟有标识符"
 
 #: expr.c:854
 msgid "missing `)'"
@@ -917,15 +916,15 @@ msgstr "语法错误: 无效的算术运算符"
 #: expr.c:1202
 #, c-format
 msgid "%s%s%s: %s (error token is \"%s\")"
-msgstr ""
+msgstr "%s%s%s: %s (错误符号是 \"%s\")"
 
 #: expr.c:1260
 msgid "invalid arithmetic base"
-msgstr ""
+msgstr "无效的算数进制"
 
 #: expr.c:1280
 msgid "value too great for base"
-msgstr ""
+msgstr "数值太大不可为算数进制的基"
 
 #: expr.c:1329
 #, c-format
@@ -939,17 +938,17 @@ msgstr "getcwd: 无法访问父目录"
 #: input.c:94 subst.c:4857
 #, c-format
 msgid "cannot reset nodelay mode for fd %d"
-msgstr ""
+msgstr "无法为文件描述符 %d 重置nodelay模式"
 
 #: input.c:258
 #, c-format
 msgid "cannot allocate new file descriptor for bash input from fd %d"
-msgstr ""
+msgstr "无法从文件描述符 %d 为 bash 的输入获取一个新的文件描述符"
 
 #: input.c:266
 #, c-format
 msgid "save_bash_input: buffer already exists for new fd %d"
-msgstr ""
+msgstr "save_bash_input: 已经存在新的文件描述符 %d 的缓冲区"
 
 #: jobs.c:466
 msgid "start_pipeline: pgrp pipe"
@@ -1203,7 +1202,6 @@ msgid "setlocale: %s: cannot change locale (%s): %s"
 msgstr ""
 
 #: mailcheck.c:433
-#, fuzzy
 msgid "You have mail in $_"
 msgstr "您在 $_ 中有邮件"
 
@@ -1837,17 +1835,17 @@ msgstr ""
 
 #: version.c:47
 msgid "License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n"
-msgstr ""
+msgstr "许可证 GPLv3+: GNU GPL 许可证版本3或者更高 <http://gnu.org/licenses/gpl.html>\n"
 
 #: version.c:86 version2.c:83
 #, c-format
 msgid "GNU bash, version %s (%s)\n"
-msgstr ""
+msgstr "GNU bash, 版本 %s (%s)\n"
 
 #: version.c:91 version2.c:88
 #, c-format
 msgid "This is free software; you are free to change and redistribute it.\n"
-msgstr ""
+msgstr "这是自由软件,您可以自由地更改和重新发布。\n"
 
 #: version.c:92 version2.c:89
 #, c-format
@@ -1862,7 +1860,7 @@ msgstr ""
 #: version2.c:87
 #, c-format
 msgid "License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl.html>\n"
-msgstr ""
+msgstr "许可证 GPLv2+: GNU GPL 许可证版本2或者更高 <http://gnu.org/licenses/gpl.html>\n"
 
 #: xmalloc.c:91
 #, c-format
index 5a5ff6d79c12c20ab44403919fd322c773e4639c..53d5adb1c72f9cf08ea47ad5efda1f49f41c56b5 100644 (file)
@@ -113,6 +113,12 @@ FILE *xtrace_fp = 0;
 
 #define CHECK_XTRACE_FP        xtrace_fp = (xtrace_fp ? xtrace_fp : stderr)
 
+#define PRINT_DEFERRED_HEREDOCS(x) \
+  do { \
+    if (deferred_heredocs) \
+      print_deferred_heredocs (x); \
+  } while (0)
+
 /* Non-zero means the stuff being printed is inside of a function def. */
 static int inside_function_def;
 static int skip_this_indent;
@@ -293,8 +299,7 @@ make_command_string_internal (command)
            }
 
          make_command_string_internal (command->value.Connection->second);
-         if (deferred_heredocs)
-           print_deferred_heredocs ("");
+         PRINT_DEFERRED_HEREDOCS ("");
          printing_connection--;                  
          break;
 
@@ -560,13 +565,15 @@ print_for_command (for_command)
      FOR_COM *for_command;
 {
   print_for_command_head (for_command);
-
   cprintf (";");
   newline ("do\n");
+
   indentation += indentation_amount;
   make_command_string_internal (for_command->action);
+  PRINT_DEFERRED_HEREDOCS ("");
   semicolon ();
   indentation -= indentation_amount;
+
   newline ("done");
 }
 
@@ -620,6 +627,7 @@ print_select_command (select_command)
   newline ("do\n");
   indentation += indentation_amount;
   make_command_string_internal (select_command->action);
+  PRINT_DEFERRED_HEREDOCS ("");
   semicolon ();
   indentation -= indentation_amount;
   newline ("done");
@@ -703,6 +711,7 @@ print_case_clauses (clauses)
       indentation += indentation_amount;
       make_command_string_internal (clauses->action);
       indentation -= indentation_amount;
+      PRINT_DEFERRED_HEREDOCS ("");
       if (clauses->flags & CASEPAT_FALLTHROUGH)
        newline (";&");
       else if (clauses->flags & CASEPAT_TESTNEXT)
@@ -736,10 +745,12 @@ print_until_or_while (while_command, which)
   cprintf ("%s ", which);
   skip_this_indent++;
   make_command_string_internal (while_command->test);
+  PRINT_DEFERRED_HEREDOCS ("");
   semicolon ();
   cprintf (" do\n");   /* was newline ("do\n"); */
   indentation += indentation_amount;
   make_command_string_internal (while_command->action);
+  PRINT_DEFERRED_HEREDOCS ("");
   indentation -= indentation_amount;
   semicolon ();
   newline ("done");
@@ -756,6 +767,7 @@ print_if_command (if_command)
   cprintf (" then\n");
   indentation += indentation_amount;
   make_command_string_internal (if_command->true_case);
+  PRINT_DEFERRED_HEREDOCS ("");
   indentation -= indentation_amount;
 
   if (if_command->false_case)
@@ -764,6 +776,7 @@ print_if_command (if_command)
       newline ("else\n");
       indentation += indentation_amount;
       make_command_string_internal (if_command->false_case);
+      PRINT_DEFERRED_HEREDOCS ("");
       indentation -= indentation_amount;
     }
   semicolon ();
@@ -943,7 +956,7 @@ print_deferred_heredocs (cstring)
       cprintf (" ");
       print_heredoc_header (hdtail);
     }
-  if (cstring[0] != ';' || cstring[1])
+  if (cstring[0] && (cstring[0] != ';' || cstring[1]))
     cprintf ("%s", cstring); 
   if (deferred_heredocs)
     cprintf ("\n");
index bf405c68fffc47d7a5669f89237e1b80d4835278..fa0317448b4ed471ec0c682163bf4a5af63e9391 100644 (file)
@@ -155,7 +155,7 @@ static void
 make_command_string_internal (command)
      COMMAND *command;
 {
-  char s[3], *op;
+  char s[3];
 
   if (command == 0)
     cprintf ("");
@@ -565,6 +565,8 @@ print_for_command (for_command)
   newline ("do\n");
   indentation += indentation_amount;
   make_command_string_internal (for_command->action);
+  if (deferred_heredocs)
+    print_deferred_heredocs ("");
   semicolon ();
   indentation -= indentation_amount;
   newline ("done");
@@ -1052,10 +1054,9 @@ static void
 print_redirection (redirect)
      REDIRECT *redirect;
 {
-  int kill_leading, redirector, redir_fd;
+  int redirector, redir_fd;
   WORD_DESC *redirectee, *redir_word;
 
-  kill_leading = 0;
   redirectee = redirect->redirectee.filename;
   redir_fd = redirect->redirectee.dest;
 
diff --git a/redir.c~ b/redir.c~
new file mode 100644 (file)
index 0000000..744306f
--- /dev/null
+++ b/redir.c~
@@ -0,0 +1,1279 @@
+/* redir.c -- Functions to perform input and output redirection. */
+
+/* Copyright (C) 1997-2009 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 3 of the License, or
+   (at your option) any later version.
+
+   Bash is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with Bash.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+
+#if !defined (__GNUC__) && !defined (HAVE_ALLOCA_H) && defined (_AIX)
+  #pragma alloca
+#endif /* _AIX && RISC6000 && !__GNUC__ */
+
+#include <stdio.h>
+#include "bashtypes.h"
+#if !defined (_MINIX) && defined (HAVE_SYS_FILE_H)
+#  include <sys/file.h>
+#endif
+#include "filecntl.h"
+#include "posixstat.h"
+
+#if defined (HAVE_UNISTD_H)
+#  include <unistd.h>
+#endif
+
+#include <errno.h>
+
+#if !defined (errno)
+extern int errno;
+#endif
+
+#include "bashansi.h"
+#include "bashintl.h"
+#include "memalloc.h"
+
+#define NEED_FPURGE_DECL
+
+#include "shell.h"
+#include "flags.h"
+#include "execute_cmd.h"
+#include "redir.h"
+
+#if defined (BUFFERED_INPUT)
+#  include "input.h"
+#endif
+
+#define SHELL_FD_BASE  10
+
+int expanding_redir;
+
+extern int posixly_correct;
+extern REDIRECT *redirection_undo_list;
+extern REDIRECT *exec_redirection_undo_list;
+
+/* Static functions defined and used in this file. */
+static void add_undo_close_redirect __P((int));
+static void add_exec_redirect __P((REDIRECT *));
+static int add_undo_redirect __P((int, enum r_instruction, int));
+static int expandable_redirection_filename __P((REDIRECT *));
+static int stdin_redirection __P((enum r_instruction, int));
+static int undoablefd __P((int));
+static int do_redirection_internal __P((REDIRECT *, int));
+
+static int write_here_document __P((int, WORD_DESC *));
+static int write_here_string __P((int, WORD_DESC *));
+static int here_document_to_fd __P((WORD_DESC *, enum r_instruction));
+
+static int redir_special_open __P((int, char *, int, int, enum r_instruction));
+static int noclobber_open __P((char *, int, int, enum r_instruction));
+static int redir_open __P((char *, int, int, enum r_instruction));
+
+static int redir_varassign __P((REDIRECT *, int));
+static int redir_varvalue __P((REDIRECT *));
+
+/* Spare redirector used when translating [N]>&WORD[-] or [N]<&WORD[-] to
+   a new redirection and when creating the redirection undo list. */
+static REDIRECTEE rd;
+
+/* Set to errno when a here document cannot be created for some reason.
+   Used to print a reasonable error message. */
+static int heredoc_errno;
+
+void
+redirection_error (temp, error)
+     REDIRECT *temp;
+     int error;
+{
+  char *filename, *allocname;
+  int oflags;
+
+  allocname = 0;
+  if (temp->rflags & REDIR_VARASSIGN)
+    filename = savestring (temp->redirector.filename->word);
+  else if (temp->redirector.dest < 0)
+    /* This can happen when read_token_word encounters overflow, like in
+       exec 4294967297>x */
+    filename = _("file descriptor out of range");
+#ifdef EBADF
+  /* This error can never involve NOCLOBBER */
+  else if (error != NOCLOBBER_REDIRECT && temp->redirector.dest >= 0 && error == EBADF)
+    {
+      /* If we're dealing with two file descriptors, we have to guess about
+         which one is invalid; in the cases of r_{duplicating,move}_input and
+         r_{duplicating,move}_output we're here because dup2() failed. */
+      switch (temp->instruction)
+        {
+        case r_duplicating_input:
+        case r_duplicating_output:
+        case r_move_input:
+        case r_move_output:
+         filename = allocname = itos (temp->redirectee.dest);
+         break;
+       case r_duplicating_input_word:
+         if (temp->redirector.dest == 0)       /* Guess */
+           filename = temp->redirectee.filename->word; /* XXX */
+         else
+           filename = allocname = itos (temp->redirector.dest);
+         break;
+       case r_duplicating_output_word:
+         if (temp->redirector.dest == 1)       /* Guess */
+           filename = temp->redirectee.filename->word; /* XXX */
+         else
+           filename = allocname = itos (temp->redirector.dest);
+         break;
+       default:
+         filename = allocname = itos (temp->redirector.dest);
+         break;
+        }
+    }
+#endif
+  else if (expandable_redirection_filename (temp))
+    {
+expandable_filename:
+      if (posixly_correct && interactive_shell == 0)
+       {
+         oflags = temp->redirectee.filename->flags;
+         temp->redirectee.filename->flags |= W_NOGLOB;
+       }
+      filename = allocname = redirection_expand (temp->redirectee.filename);
+      if (posixly_correct && interactive_shell == 0)
+       temp->redirectee.filename->flags = oflags;
+      if (filename == 0)
+       filename = temp->redirectee.filename->word;
+    }
+  else if (temp->redirectee.dest < 0)
+    filename = "file descriptor out of range";
+  else
+    filename = allocname = itos (temp->redirectee.dest);
+
+  switch (error)
+    {
+    case AMBIGUOUS_REDIRECT:
+      internal_error (_("%s: ambiguous redirect"), filename);
+      break;
+
+    case NOCLOBBER_REDIRECT:
+      internal_error (_("%s: cannot overwrite existing file"), filename);
+      break;
+
+#if defined (RESTRICTED_SHELL)
+    case RESTRICTED_REDIRECT:
+      internal_error (_("%s: restricted: cannot redirect output"), filename);
+      break;
+#endif /* RESTRICTED_SHELL */
+
+    case HEREDOC_REDIRECT:
+      internal_error (_("cannot create temp file for here-document: %s"), strerror (heredoc_errno));
+      break;
+
+    case BADVAR_REDIRECT:
+      internal_error (_("%s: cannot assign fd to variable"), filename);
+      break;
+
+    default:
+      internal_error ("%s: %s", filename, strerror (error));
+      break;
+    }
+
+  FREE (allocname);
+}
+
+/* Perform the redirections on LIST.  If flags & RX_ACTIVE, then actually
+   make input and output file descriptors, otherwise just do whatever is
+   neccessary for side effecting.  flags & RX_UNDOABLE says to remember
+   how to undo the redirections later, if non-zero.  If flags & RX_CLEXEC
+   is non-zero, file descriptors opened in do_redirection () have their
+   close-on-exec flag set. */
+int
+do_redirections (list, flags)
+     REDIRECT *list;
+     int flags;
+{
+  int error;
+  REDIRECT *temp;
+
+  if (flags & RX_UNDOABLE)
+    {
+      if (redirection_undo_list)
+       {
+         dispose_redirects (redirection_undo_list);
+         redirection_undo_list = (REDIRECT *)NULL;
+       }
+      if (exec_redirection_undo_list)
+       dispose_exec_redirects ();
+    }
+
+  for (temp = list; temp; temp = temp->next)
+    {
+      error = do_redirection_internal (temp, flags);
+      if (error)
+       {
+         redirection_error (temp, error);
+         return (error);
+       }
+    }
+  return (0);
+}
+
+/* Return non-zero if the redirection pointed to by REDIRECT has a
+   redirectee.filename that can be expanded. */
+static int
+expandable_redirection_filename (redirect)
+     REDIRECT *redirect;
+{
+  switch (redirect->instruction)
+    {
+    case r_output_direction:
+    case r_appending_to:
+    case r_input_direction:
+    case r_inputa_direction:
+    case r_err_and_out:
+    case r_append_err_and_out:
+    case r_input_output:
+    case r_output_force:
+    case r_duplicating_input_word:
+    case r_duplicating_output_word:
+    case r_move_input_word:
+    case r_move_output_word:
+      return 1;
+
+    default:
+      return 0;
+    }
+}
+
+/* Expand the word in WORD returning a string.  If WORD expands to
+   multiple words (or no words), then return NULL. */
+char *
+redirection_expand (word)
+     WORD_DESC *word;
+{
+  char *result;
+  WORD_LIST *tlist1, *tlist2;
+  WORD_DESC *w;
+
+  w = copy_word (word);
+  if (posixly_correct)
+    w->flags |= W_NOSPLIT;
+
+  tlist1 = make_word_list (w, (WORD_LIST *)NULL);
+  expanding_redir = 1;
+  tlist2 = expand_words_no_vars (tlist1);
+  expanding_redir = 0;
+  dispose_words (tlist1);
+
+  if (!tlist2 || tlist2->next)
+    {
+      /* We expanded to no words, or to more than a single word.
+        Dispose of the word list and return NULL. */
+      if (tlist2)
+       dispose_words (tlist2);
+      return ((char *)NULL);
+    }
+  result = string_list (tlist2);  /* XXX savestring (tlist2->word->word)? */
+  dispose_words (tlist2);
+  return (result);
+}
+
+static int
+write_here_string (fd, redirectee)
+     int fd;
+     WORD_DESC *redirectee;
+{
+  char *herestr;
+  int herelen, n, e;
+
+  expanding_redir = 1;
+  herestr = expand_string_to_string (redirectee->word, 0);
+  expanding_redir = 0;
+  herelen = STRLEN (herestr);
+
+  n = write (fd, herestr, herelen);
+  if (n == herelen)
+    {
+      n = write (fd, "\n", 1);
+      herelen = 1;
+    }
+  e = errno;
+  FREE (herestr);
+  if (n != herelen)
+    {
+      if (e == 0)
+       e = ENOSPC;
+      return e;
+    }
+  return 0;
+}  
+
+/* Write the text of the here document pointed to by REDIRECTEE to the file
+   descriptor FD, which is already open to a temp file.  Return 0 if the
+   write is successful, otherwise return errno. */
+static int
+write_here_document (fd, redirectee)
+     int fd;
+     WORD_DESC *redirectee;
+{
+  char *document;
+  int document_len, fd2;
+  FILE *fp;
+  register WORD_LIST *t, *tlist;
+
+  /* Expand the text if the word that was specified had
+     no quoting.  The text that we expand is treated
+     exactly as if it were surrounded by double quotes. */
+
+  if (redirectee->flags & W_QUOTED)
+    {
+      document = redirectee->word;
+      document_len = strlen (document);
+      /* Set errno to something reasonable if the write fails. */
+      if (write (fd, document, document_len) < document_len)
+       {
+         if (errno == 0)
+           errno = ENOSPC;
+         return (errno);
+       }
+      else
+       return 0;
+    }
+
+  expanding_redir = 1;
+  tlist = expand_string (redirectee->word, Q_HERE_DOCUMENT);
+  expanding_redir = 0;
+
+  if (tlist)
+    {
+      /* Try using buffered I/O (stdio) and writing a word
+        at a time, letting stdio do the work of buffering
+        for us rather than managing our own strings.  Most
+        stdios are not particularly fast, however -- this
+        may need to be reconsidered later. */
+      if ((fd2 = dup (fd)) < 0 || (fp = fdopen (fd2, "w")) == NULL)
+       {
+         if (fd2 >= 0)
+           close (fd2);
+         return (errno);
+       }
+      errno = 0;
+      for (t = tlist; t; t = t->next)
+       {
+         /* This is essentially the body of
+            string_list_internal expanded inline. */
+         document = t->word->word;
+         document_len = strlen (document);
+         if (t != tlist)
+           putc (' ', fp);     /* separator */
+         fwrite (document, document_len, 1, fp);
+         if (ferror (fp))
+           {
+             if (errno == 0)
+               errno = ENOSPC;
+             fd2 = errno;
+             fclose(fp);
+             dispose_words (tlist);
+             return (fd2);
+           }
+       }
+      dispose_words (tlist);
+      if (fclose (fp) != 0)
+       {
+         if (errno == 0)
+           errno = ENOSPC;
+         return (errno);
+       }
+    }
+  return 0;
+}
+
+/* Create a temporary file holding the text of the here document pointed to
+   by REDIRECTEE, and return a file descriptor open for reading to the temp
+   file.  Return -1 on any error, and make sure errno is set appropriately. */
+static int
+here_document_to_fd (redirectee, ri)
+     WORD_DESC *redirectee;
+     enum r_instruction ri;
+{
+  char *filename;
+  int r, fd, fd2;
+
+  fd = sh_mktmpfd ("sh-thd", MT_USERANDOM|MT_USETMPDIR, &filename);
+
+  /* If we failed for some reason other than the file existing, abort */
+  if (fd < 0)
+    {
+      FREE (filename);
+      return (fd);
+    }
+
+  errno = r = 0;               /* XXX */
+  /* write_here_document returns 0 on success, errno on failure. */
+  if (redirectee->word)
+    r = (ri != r_reading_string) ? write_here_document (fd, redirectee)
+                                : write_here_string (fd, redirectee);
+
+  if (r)
+    {
+      close (fd);
+      unlink (filename);
+      free (filename);
+      errno = r;
+      return (-1);
+    }
+
+  /* In an attempt to avoid races, we close the first fd only after opening
+     the second. */
+  /* Make the document really temporary.  Also make it the input. */
+  fd2 = open (filename, O_RDONLY, 0600);
+
+itrace("here_document_to_fd: %s open read-only to fd %d", filename, fd2);
+  if (fd2 < 0)
+    {
+      r = errno;
+      unlink (filename);
+      free (filename);
+      close (fd);
+      errno = r;
+      return -1;
+    }
+
+  close (fd);
+  if (unlink (filename) < 0)
+    {
+      r = errno;
+#if defined (__CYGWIN__)
+      /* Under CygWin 1.1.0, the unlink will fail if the file is
+        open. This hack will allow the previous action of silently
+        ignoring the error, but will still leave the file there. This
+        needs some kind of magic. */
+      if (r == EACCES)
+       return (fd2);
+#endif /* __CYGWIN__ */
+      close (fd2);
+      free (filename);
+      errno = r;
+      return (-1);
+    }
+
+  free (filename);
+  return (fd2);
+}
+
+#define RF_DEVFD       1
+#define RF_DEVSTDERR   2
+#define RF_DEVSTDIN    3
+#define RF_DEVSTDOUT   4
+#define RF_DEVTCP      5
+#define RF_DEVUDP      6
+
+/* A list of pattern/value pairs for filenames that the redirection
+   code handles specially. */
+static STRING_INT_ALIST _redir_special_filenames[] = {
+#if !defined (HAVE_DEV_FD)
+  { "/dev/fd/[0-9]*", RF_DEVFD },
+#endif
+#if !defined (HAVE_DEV_STDIN)
+  { "/dev/stderr", RF_DEVSTDERR },
+  { "/dev/stdin", RF_DEVSTDIN },
+  { "/dev/stdout", RF_DEVSTDOUT },
+#endif
+#if defined (NETWORK_REDIRECTIONS)
+  { "/dev/tcp/*/*", RF_DEVTCP },
+  { "/dev/udp/*/*", RF_DEVUDP },
+#endif
+  { (char *)NULL, -1 }
+};
+
+static int
+redir_special_open (spec, filename, flags, mode, ri)
+     int spec;
+     char *filename;
+     int flags, mode;
+     enum r_instruction ri;
+{
+  int fd;
+#if !defined (HAVE_DEV_FD)
+  intmax_t lfd;
+#endif
+
+  fd = -1;
+  switch (spec)
+    {
+#if !defined (HAVE_DEV_FD)
+    case RF_DEVFD:
+      if (all_digits (filename+8) && legal_number (filename+8, &lfd) && lfd == (int)lfd)
+       {
+         fd = lfd;
+         fd = fcntl (fd, F_DUPFD, SHELL_FD_BASE);
+       }
+      else
+       fd = AMBIGUOUS_REDIRECT;
+      break;
+#endif
+
+#if !defined (HAVE_DEV_STDIN)
+    case RF_DEVSTDIN:
+      fd = fcntl (0, F_DUPFD, SHELL_FD_BASE);
+      break;
+    case RF_DEVSTDOUT:
+      fd = fcntl (1, F_DUPFD, SHELL_FD_BASE);
+      break;
+    case RF_DEVSTDERR:
+      fd = fcntl (2, F_DUPFD, SHELL_FD_BASE);
+      break;
+#endif
+
+#if defined (NETWORK_REDIRECTIONS)
+    case RF_DEVTCP:
+    case RF_DEVUDP:
+#if defined (HAVE_NETWORK)
+      fd = netopen (filename);
+#else
+      internal_warning (_("/dev/(tcp|udp)/host/port not supported without networking"));
+      fd = open (filename, flags, mode);
+#endif
+      break;
+#endif /* NETWORK_REDIRECTIONS */
+    }
+
+  return fd;
+}
+      
+/* Open FILENAME with FLAGS in noclobber mode, hopefully avoiding most
+   race conditions and avoiding the problem where the file is replaced
+   between the stat(2) and open(2). */
+static int
+noclobber_open (filename, flags, mode, ri)
+     char *filename;
+     int flags, mode;
+     enum r_instruction ri;
+{
+  int r, fd;
+  struct stat finfo, finfo2;
+
+  /* If the file exists and is a regular file, return an error
+     immediately. */
+  r = stat (filename, &finfo);
+  if (r == 0 && (S_ISREG (finfo.st_mode)))
+    return (NOCLOBBER_REDIRECT);
+
+  /* If the file was not present (r != 0), make sure we open it
+     exclusively so that if it is created before we open it, our open
+     will fail.  Make sure that we do not truncate an existing file.
+     Note that we don't turn on O_EXCL unless the stat failed -- if
+     the file was not a regular file, we leave O_EXCL off. */
+  flags &= ~O_TRUNC;
+  if (r != 0)
+    {
+      fd = open (filename, flags|O_EXCL, mode);
+      return ((fd < 0 && errno == EEXIST) ? NOCLOBBER_REDIRECT : fd);
+    }
+  fd = open (filename, flags, mode);
+
+  /* If the open failed, return the file descriptor right away. */
+  if (fd < 0)
+    return (errno == EEXIST ? NOCLOBBER_REDIRECT : fd);
+
+  /* OK, the open succeeded, but the file may have been changed from a
+     non-regular file to a regular file between the stat and the open.
+     We are assuming that the O_EXCL open handles the case where FILENAME
+     did not exist and is symlinked to an existing file between the stat
+     and open. */
+
+  /* If we can open it and fstat the file descriptor, and neither check
+     revealed that it was a regular file, and the file has not been replaced,
+     return the file descriptor. */
+  if ((fstat (fd, &finfo2) == 0) && (S_ISREG (finfo2.st_mode) == 0) &&
+      r == 0 && (S_ISREG (finfo.st_mode) == 0) &&
+      same_file (filename, filename, &finfo, &finfo2))
+    return fd;
+
+  /* The file has been replaced.  badness. */
+  close (fd);  
+  errno = EEXIST;
+  return (NOCLOBBER_REDIRECT);
+}
+
+static int
+redir_open (filename, flags, mode, ri)
+     char *filename;
+     int flags, mode;
+     enum r_instruction ri;
+{
+  int fd, r;
+
+  r = find_string_in_alist (filename, _redir_special_filenames, 1);
+  if (r >= 0)
+    return (redir_special_open (r, filename, flags, mode, ri));
+
+  /* If we are in noclobber mode, you are not allowed to overwrite
+     existing files.  Check before opening. */
+  if (noclobber && CLOBBERING_REDIRECT (ri))
+    {
+      fd = noclobber_open (filename, flags, mode, ri);
+      if (fd == NOCLOBBER_REDIRECT)
+       return (NOCLOBBER_REDIRECT);
+    }
+  else
+    {
+      fd = open (filename, flags, mode);
+#if defined (AFS)
+      if ((fd < 0) && (errno == EACCES))
+       {
+         fd = open (filename, flags & ~O_CREAT, mode);
+         errno = EACCES;       /* restore errno */
+       }
+#endif /* AFS */
+    }
+
+itrace("redir_open: %s opens to fd %d", filename, fd);
+  return fd;
+}
+
+static int
+undoablefd (fd)
+     int fd;
+{
+  int clexec;
+
+  clexec = fcntl (fd, F_GETFD, 0);
+  if (clexec == -1 || (fd >= SHELL_FD_BASE && clexec == 1))
+    return 0;
+  return 1;
+}
+
+/* Do the specific redirection requested.  Returns errno or one of the
+   special redirection errors (*_REDIRECT) in case of error, 0 on success.
+   If flags & RX_ACTIVE is zero, then just do whatever is neccessary to
+   produce the appropriate side effects.   flags & RX_UNDOABLE, if non-zero,
+   says to remember how to undo each redirection.  If flags & RX_CLEXEC is
+   non-zero, then we set all file descriptors > 2 that we open to be
+   close-on-exec.  */
+static int
+do_redirection_internal (redirect, flags)
+     REDIRECT *redirect;
+     int flags;
+{
+  WORD_DESC *redirectee;
+  int redir_fd, fd, redirector, r, oflags;
+  intmax_t lfd;
+  char *redirectee_word;
+  enum r_instruction ri;
+  REDIRECT *new_redirect;
+  REDIRECTEE sd;
+
+  redirectee = redirect->redirectee.filename;
+  redir_fd = redirect->redirectee.dest;
+  redirector = redirect->redirector.dest;
+  ri = redirect->instruction;
+
+  if (redirect->flags & RX_INTERNAL)
+    flags |= RX_INTERNAL;
+
+  if (TRANSLATE_REDIRECT (ri))
+    {
+      /* We have [N]>&WORD[-] or [N]<&WORD[-] (or {V}>&WORD[-] or {V}<&WORD-).
+         and WORD, then translate the redirection into a new one and 
+        continue. */
+      redirectee_word = redirection_expand (redirectee);
+
+      /* XXX - what to do with [N]<&$w- where w is unset or null?  ksh93
+              closes N. */
+      if (redirectee_word == 0)
+       return (AMBIGUOUS_REDIRECT);
+      else if (redirectee_word[0] == '-' && redirectee_word[1] == '\0')
+       {
+         sd = redirect->redirector;
+         rd.dest = 0;
+         new_redirect = make_redirection (sd, r_close_this, rd, 0);
+       }
+      else if (all_digits (redirectee_word))
+       {
+         sd = redirect->redirector;
+         if (legal_number (redirectee_word, &lfd) && (int)lfd == lfd)
+           rd.dest = lfd;
+         else
+           rd.dest = -1;       /* XXX */
+         switch (ri)
+           {
+           case r_duplicating_input_word:
+             new_redirect = make_redirection (sd, r_duplicating_input, rd, 0);
+             break;
+           case r_duplicating_output_word:
+             new_redirect = make_redirection (sd, r_duplicating_output, rd, 0);
+             break;
+           case r_move_input_word:
+             new_redirect = make_redirection (sd, r_move_input, rd, 0);
+             break;
+           case r_move_output_word:
+             new_redirect = make_redirection (sd, r_move_output, rd, 0);
+             break;
+           }
+       }
+      else if (ri == r_duplicating_output_word && (redirect->rflags & REDIR_VARASSIGN) == 0 && redirector == 1)
+       {
+         sd = redirect->redirector;
+         rd.filename = make_bare_word (redirectee_word);
+         new_redirect = make_redirection (sd, r_err_and_out, rd, 0);
+       }
+      else
+       {
+         free (redirectee_word);
+         return (AMBIGUOUS_REDIRECT);
+       }
+
+      free (redirectee_word);
+
+      /* Set up the variables needed by the rest of the function from the
+        new redirection. */
+      if (new_redirect->instruction == r_err_and_out)
+       {
+         char *alloca_hack;
+
+         /* Copy the word without allocating any memory that must be
+            explicitly freed. */
+         redirectee = (WORD_DESC *)alloca (sizeof (WORD_DESC));
+         xbcopy ((char *)new_redirect->redirectee.filename,
+                (char *)redirectee, sizeof (WORD_DESC));
+
+         alloca_hack = (char *)
+           alloca (1 + strlen (new_redirect->redirectee.filename->word));
+         redirectee->word = alloca_hack;
+         strcpy (redirectee->word, new_redirect->redirectee.filename->word);
+       }
+      else
+       /* It's guaranteed to be an integer, and shouldn't be freed. */
+       redirectee = new_redirect->redirectee.filename;
+
+      redir_fd = new_redirect->redirectee.dest;
+      redirector = new_redirect->redirector.dest;
+      ri = new_redirect->instruction;
+
+      /* Overwrite the flags element of the old redirect with the new value. */
+      redirect->flags = new_redirect->flags;
+      dispose_redirects (new_redirect);
+    }
+
+  switch (ri)
+    {
+    case r_output_direction:
+    case r_appending_to:
+    case r_input_direction:
+    case r_inputa_direction:
+    case r_err_and_out:                /* command &>filename */
+    case r_append_err_and_out: /* command &>> filename */
+    case r_input_output:
+    case r_output_force:
+      if (posixly_correct && interactive_shell == 0)
+       {
+         oflags = redirectee->flags;
+         redirectee->flags |= W_NOGLOB;
+       }
+      redirectee_word = redirection_expand (redirectee);
+      if (posixly_correct && interactive_shell == 0)
+       redirectee->flags = oflags;
+
+      if (redirectee_word == 0)
+       return (AMBIGUOUS_REDIRECT);
+
+#if defined (RESTRICTED_SHELL)
+      if (restricted && (WRITE_REDIRECT (ri)))
+       {
+         free (redirectee_word);
+         return (RESTRICTED_REDIRECT);
+       }
+#endif /* RESTRICTED_SHELL */
+
+      fd = redir_open (redirectee_word, redirect->flags, 0666, ri);
+      free (redirectee_word);
+
+      if (fd == NOCLOBBER_REDIRECT)
+       return (fd);
+
+      if (fd < 0)
+       return (errno);
+
+      if (flags & RX_ACTIVE)
+       {
+         if (redirect->rflags & REDIR_VARASSIGN)
+           redirector = fcntl (fd, F_DUPFD, SHELL_FD_BASE);            /* XXX try this for now */
+
+         if (flags & RX_UNDOABLE)
+           {
+             /* Only setup to undo it if the thing to undo is active. */
+             if ((fd != redirector) && (fcntl (redirector, F_GETFD, 0) != -1))
+               add_undo_redirect (redirector, ri, -1);
+             else
+               add_undo_close_redirect (redirector);
+           }
+
+#if defined (BUFFERED_INPUT)
+         check_bash_input (redirector);
+#endif
+
+         /* Make sure there is no pending output before we change the state
+            of the underlying file descriptor, since the builtins use stdio
+            for output. */
+         if (redirector == 1 && fileno (stdout) == redirector)
+           {
+             fflush (stdout);
+             fpurge (stdout);
+           }
+         else if (redirector == 2 && fileno (stderr) == redirector)
+           {
+             fflush (stderr);
+             fpurge (stderr);
+           }
+
+         if (redirect->rflags & REDIR_VARASSIGN)
+           {
+             if ((r = redir_varassign (redirect, redirector)) < 0)
+               {
+                 close (redirector);
+                 close (fd);
+                 return (r);   /* XXX */
+               }
+           }
+         else if ((fd != redirector) && (dup2 (fd, redirector) < 0))
+           return (errno);
+
+#if defined (BUFFERED_INPUT)
+         /* Do not change the buffered stream for an implicit redirection
+            of /dev/null to fd 0 for asynchronous commands without job
+            control (r_inputa_direction). */
+         if (ri == r_input_direction || ri == r_input_output)
+           duplicate_buffered_stream (fd, redirector);
+#endif /* BUFFERED_INPUT */
+
+         /*
+          * If we're remembering, then this is the result of a while, for
+          * or until loop with a loop redirection, or a function/builtin
+          * executing in the parent shell with a redirection.  In the
+          * function/builtin case, we want to set all file descriptors > 2
+          * to be close-on-exec to duplicate the effect of the old
+          * for i = 3 to NOFILE close(i) loop.  In the case of the loops,
+          * both sh and ksh leave the file descriptors open across execs.
+          * The Posix standard mentions only the exec builtin.
+          */
+         if ((flags & RX_CLEXEC) && (redirector > 2))
+           SET_CLOSE_ON_EXEC (redirector);
+       }
+
+      if (fd != redirector)
+       {
+#if defined (BUFFERED_INPUT)
+         if (INPUT_REDIRECT (ri))
+           close_buffered_fd (fd);
+         else
+#endif /* !BUFFERED_INPUT */
+           close (fd);         /* Don't close what we just opened! */
+       }
+
+      /* If we are hacking both stdout and stderr, do the stderr
+        redirection here.  XXX - handle {var} here? */
+      if (ri == r_err_and_out || ri == r_append_err_and_out)
+       {
+         if (flags & RX_ACTIVE)
+           {
+             if (flags & RX_UNDOABLE)
+               add_undo_redirect (2, ri, -1);
+             if (dup2 (1, 2) < 0)
+               return (errno);
+           }
+       }
+      break;
+
+    case r_reading_until:
+    case r_deblank_reading_until:
+    case r_reading_string:
+      /* REDIRECTEE is a pointer to a WORD_DESC containing the text of
+        the new input.  Place it in a temporary file. */
+      if (redirectee)
+       {
+         fd = here_document_to_fd (redirectee, ri);
+
+         if (fd < 0)
+           {
+             heredoc_errno = errno;
+             return (HEREDOC_REDIRECT);
+           }
+
+         if (redirect->rflags & REDIR_VARASSIGN)
+           redirector = fcntl (fd, F_DUPFD, SHELL_FD_BASE);            /* XXX try this for now */
+
+         if (flags & RX_ACTIVE)
+           {
+             if (flags & RX_UNDOABLE)
+               {
+                 /* Only setup to undo it if the thing to undo is active. */
+                 if ((fd != redirector) && (fcntl (redirector, F_GETFD, 0) != -1))
+                   add_undo_redirect (redirector, ri, -1);
+                 else
+                   add_undo_close_redirect (redirector);
+               }
+
+#if defined (BUFFERED_INPUT)
+             check_bash_input (redirector);
+#endif
+             if (redirect->rflags & REDIR_VARASSIGN)
+               {
+                 if ((r = redir_varassign (redirect, redirector)) < 0)
+                   {
+                     close (redirector);
+                     close (fd);
+                     return (r);       /* XXX */
+                   }
+               }
+             else if (fd != redirector && dup2 (fd, redirector) < 0)
+               {
+                 r = errno;
+                 close (fd);
+                 return (r);
+               }
+
+#if defined (BUFFERED_INPUT)
+             duplicate_buffered_stream (fd, redirector);
+#endif
+
+             if ((flags & RX_CLEXEC) && (redirector > 2))
+               SET_CLOSE_ON_EXEC (redirector);
+           }
+
+         if (fd != redirector)
+#if defined (BUFFERED_INPUT)
+           close_buffered_fd (fd);
+#else
+           close (fd);
+#endif
+       }
+      break;
+
+    case r_duplicating_input:
+    case r_duplicating_output:
+    case r_move_input:
+    case r_move_output:
+      if ((flags & RX_ACTIVE) && (redirect->rflags & REDIR_VARASSIGN))
+       redirector = fcntl (redir_fd, F_DUPFD, SHELL_FD_BASE);          /* XXX try this for now */
+
+      if ((flags & RX_ACTIVE) && (redir_fd != redirector))
+       {
+         if (flags & RX_UNDOABLE)
+           {
+             /* Only setup to undo it if the thing to undo is active. */
+             if (fcntl (redirector, F_GETFD, 0) != -1)
+               add_undo_redirect (redirector, ri, redir_fd);
+             else
+               add_undo_close_redirect (redirector);
+           }
+#if defined (BUFFERED_INPUT)
+         check_bash_input (redirector);
+#endif
+         if (redirect->rflags & REDIR_VARASSIGN)
+           {
+             if ((r = redir_varassign (redirect, redirector)) < 0)
+               {
+                 close (redirector);
+                 return (r);   /* XXX */
+               }
+           }
+         /* This is correct.  2>&1 means dup2 (1, 2); */
+         else if (dup2 (redir_fd, redirector) < 0)
+           return (errno);
+
+#if defined (BUFFERED_INPUT)
+         if (ri == r_duplicating_input || ri == r_move_input)
+           duplicate_buffered_stream (redir_fd, redirector);
+#endif /* BUFFERED_INPUT */
+
+         /* First duplicate the close-on-exec state of redirectee.  dup2
+            leaves the flag unset on the new descriptor, which means it
+            stays open.  Only set the close-on-exec bit for file descriptors
+            greater than 2 in any case, since 0-2 should always be open
+            unless closed by something like `exec 2<&-'.  It should always
+            be safe to set fds > 2 to close-on-exec if they're being used to
+            save file descriptors < 2, since we don't need to preserve the
+            state of the close-on-exec flag for those fds -- they should
+            always be open. */
+         /* if ((already_set || set_unconditionally) && (ok_to_set))
+               set_it () */
+#if 0
+         if (((fcntl (redir_fd, F_GETFD, 0) == 1) || redir_fd < 2 || (flags & RX_CLEXEC)) &&
+              (redirector > 2))
+#else
+         if (((fcntl (redir_fd, F_GETFD, 0) == 1) || (redir_fd < 2 && (flags & RX_INTERNAL)) || (flags & RX_CLEXEC)) &&
+              (redirector > 2))
+#endif
+           SET_CLOSE_ON_EXEC (redirector);
+
+         /* When undoing saving of non-standard file descriptors (>=3) using
+            file descriptors >= SHELL_FD_BASE, we set the saving fd to be
+            close-on-exec and use a flag to decide how to set close-on-exec
+            when the fd is restored. */
+         if ((redirect->flags & RX_INTERNAL) && (redirect->flags & RX_SAVCLEXEC) && redirector >= 3 && redir_fd >= SHELL_FD_BASE)
+           SET_OPEN_ON_EXEC (redirector);
+           
+         /* dup-and-close redirection */
+         if (ri == r_move_input || ri == r_move_output)
+           {
+             xtrace_fdchk (redir_fd);
+
+             close (redir_fd);
+#if defined (COPROCESS_SUPPORT)
+             coproc_fdchk (redir_fd);  /* XXX - loses coproc fds */
+#endif
+           }
+       }
+      break;
+
+    case r_close_this:
+      if (flags & RX_ACTIVE)
+       {
+         if (redirect->rflags & REDIR_VARASSIGN)
+           {
+             redirector = redir_varvalue (redirect);
+             if (redirector < 0)
+               return AMBIGUOUS_REDIRECT;
+           }
+
+         if ((flags & RX_UNDOABLE) && (fcntl (redirector, F_GETFD, 0) != -1))
+           add_undo_redirect (redirector, ri, -1);
+
+#if defined (COPROCESS_SUPPORT)
+         coproc_fdchk (redirector);
+#endif
+         xtrace_fdchk (redirector);
+
+#if defined (BUFFERED_INPUT)
+         check_bash_input (redirector);
+         close_buffered_fd (redirector);
+#else /* !BUFFERED_INPUT */
+         close (redirector);
+#endif /* !BUFFERED_INPUT */
+       }
+      break;
+
+    case r_duplicating_input_word:
+    case r_duplicating_output_word:
+      break;
+    }
+  return (0);
+}
+
+/* Remember the file descriptor associated with the slot FD,
+   on REDIRECTION_UNDO_LIST.  Note that the list will be reversed
+   before it is executed.  Any redirections that need to be undone
+   even if REDIRECTION_UNDO_LIST is discarded by the exec builtin
+   are also saved on EXEC_REDIRECTION_UNDO_LIST.  FDBASE says where to
+   start the duplicating.  If it's less than SHELL_FD_BASE, we're ok,
+   and can use SHELL_FD_BASE (-1 == don't care).  If it's >= SHELL_FD_BASE,
+   we have to make sure we don't use fdbase to save a file descriptor,
+   since we're going to use it later (e.g., make sure we don't save fd 0
+   to fd 10 if we have a redirection like 0<&10).  If the value of fdbase
+   puts the process over its fd limit, causing fcntl to fail, we try
+   again with SHELL_FD_BASE. */
+static int
+add_undo_redirect (fd, ri, fdbase)
+     int fd;
+     enum r_instruction ri;
+     int fdbase;
+{
+  int new_fd, clexec_flag;
+  REDIRECT *new_redirect, *closer, *dummy_redirect;
+  REDIRECTEE sd;
+
+  new_fd = fcntl (fd, F_DUPFD, (fdbase < SHELL_FD_BASE) ? SHELL_FD_BASE : fdbase+1);
+  if (new_fd < 0)
+    new_fd = fcntl (fd, F_DUPFD, SHELL_FD_BASE);
+
+  if (new_fd < 0)
+    {
+      sys_error (_("redirection error: cannot duplicate fd"));
+      return (-1);
+    }
+
+  clexec_flag = fcntl (fd, F_GETFD, 0);
+
+  sd.dest = new_fd;
+  rd.dest = 0;
+  closer = make_redirection (sd, r_close_this, rd, 0);
+  closer->flags |= RX_INTERNAL;
+  dummy_redirect = copy_redirects (closer);
+
+  sd.dest = fd;
+  rd.dest = new_fd;
+  if (fd == 0)
+    new_redirect = make_redirection (sd, r_duplicating_input, rd, 0);
+  else
+    new_redirect = make_redirection (sd, r_duplicating_output, rd, 0);
+  new_redirect->flags |= RX_INTERNAL;
+  if (clexec_flag == 0 && fd >= 3 && new_fd >= SHELL_FD_BASE)
+    new_redirect->flags |= RX_SAVCLEXEC;
+  new_redirect->next = closer;
+
+  closer->next = redirection_undo_list;
+  redirection_undo_list = new_redirect;
+
+  /* Save redirections that need to be undone even if the undo list
+     is thrown away by the `exec' builtin. */
+  add_exec_redirect (dummy_redirect);
+
+  /* experimental:  if we're saving a redirection to undo for a file descriptor
+     above SHELL_FD_BASE, add a redirection to be undone if the exec builtin
+     causes redirections to be discarded.  There needs to be a difference
+     between fds that are used to save other fds and then are the target of
+     user redirctions and fds that are just the target of user redirections.
+     We use the close-on-exec flag to tell the difference; fds > SHELL_FD_BASE
+     that have the close-on-exec flag set are assumed to be fds used internally
+     to save others. */
+  if (fd >= SHELL_FD_BASE && ri != r_close_this && clexec_flag)
+    {
+      sd.dest = fd;
+      rd.dest = new_fd;
+      new_redirect = make_redirection (sd, r_duplicating_output, rd, 0);
+      new_redirect->flags |= RX_INTERNAL;
+
+      add_exec_redirect (new_redirect);
+    }
+
+  /* File descriptors used only for saving others should always be
+     marked close-on-exec.  Unfortunately, we have to preserve the
+     close-on-exec state of the file descriptor we are saving, since
+     fcntl (F_DUPFD) sets the new file descriptor to remain open
+     across execs.  If, however, the file descriptor whose state we
+     are saving is <= 2, we can just set the close-on-exec flag,
+     because file descriptors 0-2 should always be open-on-exec,
+     and the restore above in do_redirection() will take care of it. */
+  if (clexec_flag || fd < 3)
+    SET_CLOSE_ON_EXEC (new_fd);
+  else if (redirection_undo_list->flags & RX_SAVCLEXEC)
+    SET_CLOSE_ON_EXEC (new_fd);
+
+  return (0);
+}
+
+/* Set up to close FD when we are finished with the current command
+   and its redirections. */
+static void
+add_undo_close_redirect (fd)
+     int fd;
+{
+  REDIRECT *closer;
+  REDIRECTEE sd;
+
+  sd.dest = fd;
+  rd.dest = 0;
+  closer = make_redirection (sd, r_close_this, rd, 0);
+  closer->flags |= RX_INTERNAL;
+  closer->next = redirection_undo_list;
+  redirection_undo_list = closer;
+}
+
+static void
+add_exec_redirect (dummy_redirect)
+     REDIRECT *dummy_redirect;
+{
+  dummy_redirect->next = exec_redirection_undo_list;
+  exec_redirection_undo_list = dummy_redirect;
+}
+
+/* Return 1 if the redirection specified by RI and REDIRECTOR alters the
+   standard input. */
+static int
+stdin_redirection (ri, redirector)
+     enum r_instruction ri;
+     int redirector;
+{
+  switch (ri)
+    {
+    case r_input_direction:
+    case r_inputa_direction:
+    case r_input_output:
+    case r_reading_until:
+    case r_deblank_reading_until:
+    case r_reading_string:
+      return (1);
+    case r_duplicating_input:
+    case r_duplicating_input_word:
+    case r_close_this:
+      return (redirector == 0);
+    case r_output_direction:
+    case r_appending_to:
+    case r_duplicating_output:
+    case r_err_and_out:
+    case r_append_err_and_out:
+    case r_output_force:
+    case r_duplicating_output_word:
+      return (0);
+    }
+  return (0);
+}
+
+/* Return non-zero if any of the redirections in REDIRS alter the standard
+   input. */
+int
+stdin_redirects (redirs)
+     REDIRECT *redirs;
+{
+  REDIRECT *rp;
+  int n;
+
+  for (n = 0, rp = redirs; rp; rp = rp->next)
+    if ((rp->rflags & REDIR_VARASSIGN) == 0)
+      n += stdin_redirection (rp->instruction, rp->redirector.dest);
+  return n;
+}
+
+/* These don't yet handle array references */
+static int
+redir_varassign (redir, fd)
+     REDIRECT *redir;
+     int fd;
+{
+  WORD_DESC *w;
+  SHELL_VAR *v;
+
+  w = redir->redirector.filename;
+  v = bind_var_to_int (w->word, fd);
+  if (v == 0 || readonly_p (v) || noassign_p (v))
+    return BADVAR_REDIRECT;
+
+  return 0;
+}
+
+static int
+redir_varvalue (redir)
+     REDIRECT *redir;
+{
+  SHELL_VAR *v;
+  char *val;
+  intmax_t vmax;
+  int i;
+
+  /* XXX - handle set -u here? */
+  v = find_variable (redir->redirector.filename->word);
+  if (v == 0 || invisible_p (v))
+    return -1;
+
+  val = get_variable_value (v);
+  if (val == 0 || *val == 0)
+    return -1;
+
+  if (legal_number (val, &vmax) < 0)
+    return -1;
+
+  i = vmax;    /* integer truncation */
+  return i;
+}
index 3efcf32d68e9722024b6ca9d67f9e81b2aa5ac04..72ec06a2c1fd8dde92acea5e8ac773e35f1d061b 100755 (executable)
@@ -1,4 +1,4 @@
-BUILD_DIR=/usr/local/build/chet/bash/bash-current
+BUILD_DIR=/usr/local/build/bash/bash-current
 THIS_SH=$BUILD_DIR/bash
 PATH=$PATH:$BUILD_DIR
 
index a628231ce7d675092303de0abb69c4a418112208..6dbce6437ae7f595df8cf68109ad9dd849533362 100644 (file)
@@ -80,3 +80,19 @@ qux
 
 bar
 qux
+foo is a function
+foo () 
+{ 
+    rm -f a b c;
+    for f in a b c;
+    do
+        cat  >> ${f} <<-EOF
+file
+EOF
+
+    done
+    grep . a b c
+}
+a:file
+b:file
+c:file
index 6caa243ea16ce676e47b24f7fd28894ccfa0d212..2df4efd7f1a47482c51b8756470be4f580058513 100644 (file)
@@ -91,3 +91,5 @@ type f | cat -v
 ${THIS_SH} type1.sub
 
 ${THIS_SH} type2.sub
+
+${THIS_SH} type3.sub
diff --git a/tests/type.tests~ b/tests/type.tests~
new file mode 100644 (file)
index 0000000..6caa243
--- /dev/null
@@ -0,0 +1,93 @@
+set +o posix
+
+hash -r
+unalias -a
+
+# this should echo nothing
+type
+# this should be a usage error
+type -r ${THIS_SH}
+
+# these should behave identically
+type notthere
+command -v notthere
+
+alias m=more
+
+unset -f func 2>/dev/null
+func() { echo this is func; }
+
+type -t func
+type -t while
+type -t builtin
+type -t /bin/sh
+type -t ${THIS_SH}
+type -t mv
+
+type func
+# the following two should produce identical output
+type while
+type -a while
+type builtin
+type /bin/sh
+
+command -v func
+command -V func
+command -v while
+command -V while
+
+# the following two lines should produce the same output
+# post-3.0 patch makes command -v silent, as posix specifies
+# first test with alias expansion off (should all fail or produce no output)
+type -t m
+type m
+command -v m
+alias -p
+alias m
+
+# then test with alias expansion on 
+shopt -s expand_aliases
+type m
+type -t m
+command -v m
+alias -p
+alias m
+
+command -V m
+shopt -u expand_aliases
+
+command -v builtin
+command -V builtin
+command -v /bin/sh
+command -V /bin/sh
+
+unset -f func
+type func
+unalias m
+type m
+
+hash -r
+
+hash -p /bin/sh sh
+type -p sh
+
+SHBASE=${THIS_SH##*/}
+hash -p /tmp/$SHBASE $SHBASE
+type -p $SHBASE
+type $SHBASE
+
+type -t $SHBASE
+
+# make sure the hash table looks right
+hash
+
+# bug in versions of bash up to and including bash-3.2
+f() {
+        v=$'\001'
+    }
+
+type f | cat -v
+
+${THIS_SH} type1.sub
+
+${THIS_SH} type2.sub
diff --git a/tests/type3.sub b/tests/type3.sub
new file mode 100644 (file)
index 0000000..5eb3feb
--- /dev/null
@@ -0,0 +1,16 @@
+foo() {
+       rm -f a b c
+       for f in a b c; do
+               cat <<-EOF >> ${f}
+               file
+               EOF
+       done
+       grep . a b c
+}
+
+type foo
+
+eval "$(type foo | sed 1d)"
+foo
+
+rm -f a b c