Bash-4.3 distribution sources and documentation
[platform/upstream/bash.git] / builtins / history.def
index b1e3ab3..4cf7308 100644 (file)
@@ -1,54 +1,69 @@
 This file is history.def, from which is created history.c.
 It implements the builtin "history" in Bash.
 
-Copyright (C) 1987, 1989, 1991 Free Software Foundation, Inc.
+Copyright (C) 1987-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 1, or (at your option) any later
-version.
+Bash is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
 
-Bash is distributed in the hope that it will be useful, but WITHOUT ANY
-WARRANTY; without even the implied warranty of MERCHANTABILITY or
-FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
-for more details.
+Bash is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
 
-You should have received a copy of the GNU General Public License along
-with Bash; see the file COPYING.  If not, write to the Free Software
-Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+You should have received a copy of the GNU General Public License
+along with Bash.  If not, see <http://www.gnu.org/licenses/>.
 
 $PRODUCES history.c
 
 $BUILTIN history
 $FUNCTION history_builtin
 $DEPENDS_ON HISTORY
-$SHORT_DOC history [-c] [n] or history -awrn [filename] or history -ps arg [arg...]
-Display the history list with line numbers.  Lines listed with
-with a `*' have been modified.  Argument of N says to list only
-the last N lines.  The -c option causes the history list to be
-cleared by deleting all of the entries.  The `-w' option writes out the
-current history to the history file;  `-r' means to read the file and
-append the contents to the history list instead.  `-a' means
-to append history lines from this session to the history file.
-Argument `-n' means to read all history lines not already read
-from the history file and append them to the history list.  If
-FILENAME is given, then that is used as the history file else
+$SHORT_DOC history [-c] [-d offset] [n] or history -anrw [filename] or history -ps arg [arg...]
+Display or manipulate the history list.
+
+Display the history list with line numbers, prefixing each modified
+entry with a `*'.  An argument of N lists only the last N entries.
+
+Options:
+  -c   clear the history list by deleting all of the entries
+  -d offset    delete the history entry at offset OFFSET.
+
+  -a   append history lines from this session to the history file
+  -n   read all history lines not already read from the history file
+  -r   read the history file and append the contents to the history
+       list
+  -w   write the current history to the history file
+       and append them to the history list
+
+  -p   perform history expansion on each ARG and display the result
+       without storing it in the history list
+  -s   append the ARGs to the history list as a single entry
+
+If FILENAME is given, it is used as the history file.  Otherwise,
 if $HISTFILE has a value, that is used, else ~/.bash_history.
-If the -s option is supplied, the non-option ARGs are appended to
-the history list as a single entry.  The -p option means to perform
-history expansion on each ARG and display the result, without storing
-anything in the history list.
+
+If the $HISTTIMEFORMAT variable is set and not null, its value is used
+as a format string for strftime(3) to print the time stamp associated
+with each displayed history entry.  No time stamps are printed otherwise.
+
+Exit Status:
+Returns success unless an invalid option is given or an error occurs.
 $END
 
 #include <config.h>
 
 #if defined (HISTORY)
 #include "../bashtypes.h"
-#include <sys/file.h>
-#include "../posixstat.h"
-#include "../filecntl.h"
+#if ! defined(_MINIX) && defined (HAVE_SYS_FILE_H)
+#  include <sys/file.h>
+#endif
+#include "posixstat.h"
+#include "filecntl.h"
 #include <errno.h>
 #include <stdio.h>
 #if defined (HAVE_UNISTD_H)
@@ -56,6 +71,7 @@ $END
 #endif
 
 #include "../bashansi.h"
+#include "../bashintl.h"
 
 #include "../shell.h"
 #include "../bashhist.h"
@@ -67,9 +83,13 @@ $END
 extern int errno;
 #endif
 
-static void display_history ();
-static void push_history ();
-static int expand_and_print_history ();
+extern int current_command_line_count;
+extern int force_append_history;       /* shopt -s histappend */
+
+static char *histtime __P((HIST_ENTRY *, const char *));
+static int display_history __P((WORD_LIST *));
+static void push_history __P((WORD_LIST *));
+static int expand_and_print_history __P((WORD_LIST *));
 
 #define AFLAG  0x01
 #define RFLAG  0x02
@@ -78,17 +98,19 @@ static int expand_and_print_history ();
 #define SFLAG  0x10
 #define PFLAG  0x20
 #define CFLAG  0x40
+#define DFLAG  0x80
 
 int
 history_builtin (list)
      WORD_LIST *list;
 {
-  int flags, opt, result;
-  char *filename;
+  int flags, opt, result, old_history_lines, obase;
+  char *filename, *delete_arg;
+  intmax_t delete_offset;
 
   flags = 0;
   reset_internal_getopt ();
-  while ((opt = internal_getopt (list, "acnpsrw")) != -1)
+  while ((opt = internal_getopt (list, "acd:npsrw")) != -1)
     {
       switch (opt)
        {
@@ -110,6 +132,10 @@ history_builtin (list)
        case 's':
          flags |= SFLAG;
          break;
+       case 'd':
+         flags |= DFLAG;
+         delete_arg = list_optarg;
+         break;
        case 'p':
 #if defined (BANG_HISTORY)
          flags |= PFLAG;
@@ -125,14 +151,14 @@ history_builtin (list)
   opt = flags & (AFLAG|RFLAG|WFLAG|NFLAG);
   if (opt && opt != AFLAG && opt != RFLAG && opt != WFLAG && opt != NFLAG)
     {
-      builtin_error ("cannot use more than one of -anrw");
+      builtin_error (_("cannot use more than one of -anrw"));
       return (EXECUTION_FAILURE);
     }
 
   /* clear the history, but allow other arguments to add to it again. */
   if (flags & CFLAG)
     {
-      clear_history ();
+      bash_clear_history ();
       if (list == 0)
        return (EXECUTION_SUCCESS);
     }
@@ -147,14 +173,31 @@ history_builtin (list)
   else if (flags & PFLAG)
     {
       if (list)
-        return (expand_and_print_history (list));
-      return (EXECUTION_SUCCESS);
+       return (expand_and_print_history (list));
+      return (sh_chkwrite (EXECUTION_SUCCESS));
     }
 #endif
+  else if (flags & DFLAG)
+    {
+      if ((legal_number (delete_arg, &delete_offset) == 0)
+         || (delete_offset < history_base)
+         || (delete_offset > (history_base + history_length)))
+       {
+         sh_erange (delete_arg, _("history position"));
+         return (EXECUTION_FAILURE);
+       }
+      opt = delete_offset;
+      result = bash_delete_histent (opt - history_base);
+      /* Since remove_history changes history_length, this can happen if
+        we delete the last history entry. */
+      if (where_history () > history_length)
+       history_set_pos (history_length);
+      return (result ? EXECUTION_SUCCESS : EXECUTION_FAILURE);
+    }
   else if ((flags & (AFLAG|RFLAG|NFLAG|WFLAG|CFLAG)) == 0)
     {
-      display_history (list);
-      return (EXECUTION_SUCCESS);
+      result = display_history (list);
+      return (sh_chkwrite (result));
     }
 
   filename = list ? list->word->word : get_string_value ("HISTFILE");
@@ -169,10 +212,28 @@ history_builtin (list)
   else if (flags & NFLAG)      /* Read `new' history from file. */
     {
       /* Read all of the lines in the file that we haven't already read. */
+      old_history_lines = history_lines_in_file;
+      obase = history_base;
+
       using_history ();
       result = read_history_range (filename, history_lines_in_file, -1);
       using_history ();
+
       history_lines_in_file = where_history ();
+
+      /* If we're rewriting the history file at shell exit rather than just
+        appending the lines from this session to it, the question is whether
+        we reset history_lines_this_session to 0, losing any history entries
+        we had before we read the new entries from the history file, or
+        whether we count the new entries we just read from the file as
+        history lines added during this session.
+        Right now, we do the latter.  This will cause these history entries
+        to be written to the history file along with any intermediate entries
+        we add when we do a `history -a', but the alternative is losing
+        them altogether. */
+      if (force_append_history == 0)
+       history_lines_this_session += history_lines_in_file - old_history_lines +
+                                   history_base - obase;
     }
 
   return (result ? EXECUTION_FAILURE : EXECUTION_SUCCESS);
@@ -182,21 +243,41 @@ history_builtin (list)
 #define histline(i) (hlist[(i)]->line)
 #define histdata(i) (hlist[(i)]->data)
 
-static void
+static char *
+histtime (hlist, histtimefmt)
+     HIST_ENTRY *hlist;
+     const char *histtimefmt;
+{
+  static char timestr[128];
+  time_t t;
+
+  t = history_get_time (hlist);
+  if (t)
+    strftime (timestr, sizeof (timestr), histtimefmt, localtime (&t));
+  else
+    strcpy (timestr, "??");
+  return timestr;
+}
+
+static int
 display_history (list)
      WORD_LIST *list;
 {
   register int i;
-  int limited, limit;
+  intmax_t limit;
   HIST_ENTRY **hlist;
+  char *histtimefmt, *timestr;
 
   if (list)
     {
-      limited = 1;
-      limit = get_numeric_arg (list, 0);
+      if (get_numeric_arg (list, 0, &limit) == 0)
+       return (EXECUTION_FAILURE);
+
+      if (limit < 0)
+       limit = -limit;
     }
   else
-    limited = limit = 0;
+    limit = -1;
 
   hlist = history_list ();
 
@@ -205,50 +286,27 @@ display_history (list)
       for (i = 0;  hlist[i]; i++)
        ;
 
-      if (limit < 0)
-       limit = -limit;
-
-      if ((limited == 0)  || ((i -= limit) < 0))
+      if (0 <= limit && limit < i)
+       i -= limit;
+      else
        i = 0;
 
+      histtimefmt = get_string_value ("HISTTIMEFORMAT");
+
       while (hlist[i])
        {
          QUIT;
-         printf ("%5d%c %s\n", i + history_base,
+
+         timestr = (histtimefmt && *histtimefmt) ? histtime (hlist[i], histtimefmt) : (char *)NULL;
+         printf ("%5d%c %s%s\n", i + history_base,
                  histdata(i) ? '*' : ' ',
+                 ((timestr && *timestr) ? timestr : ""),
                  histline(i));
          i++;
        }
     }
-}
 
-static int
-delete_last_history ()
-{
-  register int i;
-  HIST_ENTRY **hlist, *histent, *discard;
-
-  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;
-
-  discard = remove_history (i);
-  if (discard)
-    {
-      if (discard->line)
-       free (discard->line);
-      free ((char *) discard);
-    }
-  return (1);
+  return (EXECUTION_SUCCESS);
 }
 
 /* Remove the last entry in the history list and add each argument in
@@ -259,10 +317,33 @@ push_history (list)
 {
   char *s;
 
-  if (hist_last_line_added && delete_last_history () == 0)
-    return;
+  /* Delete the last history entry if it was a single entry added to the
+     history list (generally the `history -s' itself), or if `history -s'
+     is being used in a compound command and the compound command was
+     added to the history as a single element (command-oriented history).
+     If you don't want history -s to remove the compound command from the
+     history, change #if 0 to #if 1 below. */
+#if 0
+  if (remember_on_history && hist_last_line_pushed == 0 &&
+       hist_last_line_added && bash_delete_last_history () == 0)
+#else
+  if (remember_on_history && hist_last_line_pushed == 0 &&
+       (hist_last_line_added ||
+         (current_command_line_count > 0 && current_command_first_line_saved && command_oriented_history))
+      && bash_delete_last_history () == 0)
+#endif
+      return;
+
   s = string_list (list);
-  maybe_add_history (s);       /* Obeys HISTCONTROL setting. */
+  /* Call check_add_history with FORCE set to 1 to skip the check against
+     current_command_line_count.  If history -s is used in a compound
+     command, the above code will delete the compound command's history
+     entry and this call will add the line to the history as a separate
+     entry.  Without FORCE=1, if current_command_line_count were > 1, the
+     line would be appended to the entry before the just-deleted entry. */
+  check_add_history (s, 1);    /* obeys HISTCONTROL, HISTIGNORE */
+
+  hist_last_line_pushed = 1;   /* XXX */
   free (s);
 }
 
@@ -274,7 +355,7 @@ expand_and_print_history (list)
   char *s;
   int r, result;
 
-  if (hist_last_line_added && delete_last_history () == 0)
+  if (hist_last_line_pushed == 0 && hist_last_line_added && bash_delete_last_history () == 0)
     return EXECUTION_FAILURE;
   result = EXECUTION_SUCCESS;
   while (list)
@@ -282,14 +363,14 @@ expand_and_print_history (list)
       r = history_expand (list->word->word, &s);
       if (r < 0)
        {
-         builtin_error ("%s: history expansion failed", list->word->word);
+         builtin_error (_("%s: history expansion failed"), list->word->word);
          result = EXECUTION_FAILURE;
        }
       else
-        {
+       {
          fputs (s, stdout);
          putchar ('\n');
-        }
+       }
       FREE (s);
       list = list->next;
     }