| This file is fc.def, from which is created fc.c. |
| It implements the builtin "fc" in Bash. |
| |
| Copyright (C) 1987-2010 Free Software Foundation, Inc. |
| |
| This file is part of GNU Bash, the Bourne Again SHell. |
| |
| Bash is free software: you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation, either version 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/>. |
| |
| $PRODUCES fc.c |
| |
| $BUILTIN fc |
| $FUNCTION fc_builtin |
| $DEPENDS_ON HISTORY |
| $SHORT_DOC fc [-e ename] [-lnr] [first] [last] or fc -s [pat=rep] [command] |
| Display or execute commands from the history list. |
| |
| fc is used to list or edit and re-execute commands from the history list. |
| FIRST and LAST can be numbers specifying the range, or FIRST can be a |
| string, which means the most recent command beginning with that |
| string. |
| |
| Options: |
| -e ENAME select which editor to use. Default is FCEDIT, then EDITOR, |
| then vi |
| -l list lines instead of editing |
| -n omit line numbers when listing |
| -r reverse the order of the lines (newest listed first) |
| |
| With the `fc -s [pat=rep ...] [command]' format, COMMAND is |
| re-executed after the substitution OLD=NEW is performed. |
| |
| A useful alias to use with this is r='fc -s', so that typing `r cc' |
| runs the last command beginning with `cc' and typing `r' re-executes |
| the last command. |
| |
| Exit Status: |
| Returns success or status of executed command; non-zero if an error occurs. |
| $END |
| |
| #include <config.h> |
| |
| #if defined (HISTORY) |
| #ifndef _MINIX |
| # include <sys/param.h> |
| #endif |
| #include "../bashtypes.h" |
| #include "posixstat.h" |
| #if ! defined(_MINIX) && defined (HAVE_SYS_FILE_H) |
| # include <sys/file.h> |
| #endif |
| |
| #if defined (HAVE_UNISTD_H) |
| # include <unistd.h> |
| #endif |
| |
| #include <stdio.h> |
| #include <chartypes.h> |
| |
| #include "../bashansi.h" |
| #include "../bashintl.h" |
| #include <errno.h> |
| |
| #include "../shell.h" |
| #include "../builtins.h" |
| #include "../flags.h" |
| #include "../bashhist.h" |
| #include "maxpath.h" |
| #include <readline/history.h> |
| #include "bashgetopt.h" |
| #include "common.h" |
| |
| #if !defined (errno) |
| extern int errno; |
| #endif /* !errno */ |
| |
| extern int current_command_line_count, saved_command_line_count; |
| extern int literal_history; |
| extern int posixly_correct; |
| extern int subshell_environment, interactive_shell; |
| |
| extern int unlink __P((const char *)); |
| |
| extern FILE *sh_mktmpfp __P((char *, int, char **)); |
| |
| /* **************************************************************** */ |
| /* */ |
| /* The K*rn shell style fc command (Fix Command) */ |
| /* */ |
| /* **************************************************************** */ |
| |
| /* fc builtin command (fix command) for Bash for those who |
| like K*rn-style history better than csh-style. |
| |
| fc [-e ename] [-nlr] [first] [last] |
| |
| FIRST and LAST can be numbers specifying the range, or FIRST can be |
| a string, which means the most recent command beginning with that |
| string. |
| |
| -e ENAME selects which editor to use. Default is FCEDIT, then EDITOR, |
| then the editor which corresponds to the current readline editing |
| mode, then vi. |
| |
| -l means list lines instead of editing. |
| -n means no line numbers listed. |
| -r means reverse the order of the lines (making it newest listed first). |
| |
| fc -e - [pat=rep ...] [command] |
| fc -s [pat=rep ...] [command] |
| |
| Equivalent to !command:sg/pat/rep execpt there can be multiple PAT=REP's. |
| */ |
| |
| /* Data structure describing a list of global replacements to perform. */ |
| typedef struct repl { |
| struct repl *next; |
| char *pat; |
| char *rep; |
| } REPL; |
| |
| /* Accessors for HIST_ENTRY lists that are called HLIST. */ |
| #define histline(i) (hlist[(i)]->line) |
| #define histdata(i) (hlist[(i)]->data) |
| |
| #define FREE_RLIST() \ |
| do { \ |
| for (rl = rlist; rl; ) { \ |
| REPL *r; \ |
| r = rl->next; \ |
| if (rl->pat) \ |
| free (rl->pat); \ |
| if (rl->rep) \ |
| free (rl->rep); \ |
| free (rl); \ |
| rl = r; \ |
| } \ |
| } while (0) |
| |
| static char *fc_dosubs __P((char *, REPL *)); |
| static char *fc_gethist __P((char *, HIST_ENTRY **)); |
| static int fc_gethnum __P((char *, HIST_ENTRY **)); |
| static int fc_number __P((WORD_LIST *)); |
| static void fc_replhist __P((char *)); |
| #ifdef INCLUDE_UNUSED |
| static char *fc_readline __P((FILE *)); |
| static void fc_addhist __P((char *)); |
| #endif |
| |
| /* String to execute on a file that we want to edit. */ |
| #define FC_EDIT_COMMAND "${FCEDIT:-${EDITOR:-vi}}" |
| #if defined (STRICT_POSIX) |
| # define POSIX_FC_EDIT_COMMAND "${FCEDIT:-ed}" |
| #else |
| # define POSIX_FC_EDIT_COMMAND "${FCEDIT:-${EDITOR:-ed}}" |
| #endif |
| |
| int |
| fc_builtin (list) |
| WORD_LIST *list; |
| { |
| register int i; |
| register char *sep; |
| int numbering, reverse, listing, execute; |
| int histbeg, histend, last_hist, retval, opt, rh; |
| FILE *stream; |
| REPL *rlist, *rl; |
| char *ename, *command, *newcom, *fcedit; |
| HIST_ENTRY **hlist; |
| char *fn; |
| |
| numbering = 1; |
| reverse = listing = execute = 0; |
| ename = (char *)NULL; |
| |
| /* Parse out the options and set which of the two forms we're in. */ |
| reset_internal_getopt (); |
| lcurrent = list; /* XXX */ |
| while (fc_number (loptend = lcurrent) == 0 && |
| (opt = internal_getopt (list, ":e:lnrs")) != -1) |
| { |
| switch (opt) |
| { |
| case 'n': |
| numbering = 0; |
| break; |
| |
| case 'l': |
| listing = 1; |
| break; |
| |
| case 'r': |
| reverse = 1; |
| break; |
| |
| case 's': |
| execute = 1; |
| break; |
| |
| case 'e': |
| ename = list_optarg; |
| break; |
| |
| default: |
| builtin_usage (); |
| return (EX_USAGE); |
| } |
| } |
| |
| list = loptend; |
| |
| if (ename && (*ename == '-') && (ename[1] == '\0')) |
| execute = 1; |
| |
| /* The "execute" form of the command (re-run, with possible string |
| substitutions). */ |
| if (execute) |
| { |
| rlist = (REPL *)NULL; |
| while (list && ((sep = (char *)strchr (list->word->word, '=')) != NULL)) |
| { |
| *sep++ = '\0'; |
| rl = (REPL *)xmalloc (sizeof (REPL)); |
| rl->next = (REPL *)NULL; |
| rl->pat = savestring (list->word->word); |
| rl->rep = savestring (sep); |
| |
| if (rlist == NULL) |
| rlist = rl; |
| else |
| { |
| rl->next = rlist; |
| rlist = rl; |
| } |
| list = list->next; |
| } |
| |
| /* If we have a list of substitutions to do, then reverse it |
| to get the replacements in the proper order. */ |
| |
| rlist = REVERSE_LIST (rlist, REPL *); |
| |
| hlist = history_list (); |
| |
| /* If we still have something in list, it is a command spec. |
| Otherwise, we use the most recent command in time. */ |
| command = fc_gethist (list ? list->word->word : (char *)NULL, hlist); |
| |
| if (command == NULL) |
| { |
| builtin_error (_("no command found")); |
| if (rlist) |
| FREE_RLIST (); |
| |
| return (EXECUTION_FAILURE); |
| } |
| |
| if (rlist) |
| { |
| newcom = fc_dosubs (command, rlist); |
| free (command); |
| FREE_RLIST (); |
| command = newcom; |
| } |
| |
| fprintf (stderr, "%s\n", command); |
| fc_replhist (command); /* replace `fc -s' with command */ |
| /* Posix says that the re-executed commands should be entered into the |
| history. */ |
| return (parse_and_execute (command, "fc", SEVAL_NOHIST)); |
| } |
| |
| /* This is the second form of the command (the list-or-edit-and-rerun |
| form). */ |
| hlist = history_list (); |
| if (hlist == 0) |
| return (EXECUTION_SUCCESS); |
| for (i = 0; hlist[i]; i++); |
| |
| /* With the Bash implementation of history, the current command line |
| ("fc blah..." and so on) is already part of the history list by |
| the time we get to this point. This just skips over that command |
| and makes the last command that this deals with be the last command |
| the user entered before the fc. We need to check whether the |
| line was actually added (HISTIGNORE may have caused it to not be), |
| so we check hist_last_line_added. */ |
| |
| /* Even though command substitution through parse_and_execute turns off |
| remember_on_history, command substitution in a shell when set -o history |
| has been enabled (interactive or not) should use it in the last_hist |
| calculation as if it were on. */ |
| rh = remember_on_history || ((subshell_environment & SUBSHELL_COMSUB) && enable_history_list); |
| last_hist = i - rh - hist_last_line_added; |
| |
| /* XXX */ |
| if (i == last_hist && hlist[last_hist] == 0) |
| while (last_hist >= 0 && hlist[last_hist] == 0) |
| last_hist--; |
| if (last_hist < 0) |
| { |
| sh_erange ((char *)NULL, _("history specification")); |
| return (EXECUTION_FAILURE); |
| } |
| |
| if (list) |
| { |
| histbeg = fc_gethnum (list->word->word, hlist); |
| list = list->next; |
| |
| if (list) |
| histend = fc_gethnum (list->word->word, hlist); |
| else |
| histend = listing ? last_hist : histbeg; |
| } |
| else |
| { |
| /* The default for listing is the last 16 history items. */ |
| if (listing) |
| { |
| histend = last_hist; |
| histbeg = histend - 16 + 1; /* +1 because loop below uses >= */ |
| if (histbeg < 0) |
| histbeg = 0; |
| } |
| else |
| /* For editing, it is the last history command. */ |
| histbeg = histend = last_hist; |
| } |
| |
| /* "When not listing, the fc command that caused the editing shall not be |
| entered into the history list." */ |
| if (listing == 0 && hist_last_line_added) |
| { |
| bash_delete_last_history (); |
| /* If we're editing a single command -- the last command in the |
| history -- and we just removed the dummy command added by |
| edit_and_execute_command (), we need to check whether or not we |
| just removed the last command in the history and need to back |
| the pointer up. remember_on_history is off because we're running |
| in parse_and_execute(). */ |
| if (histbeg == histend && histend == last_hist && hlist[last_hist] == 0) |
| last_hist = histbeg = --histend; |
| } |
| |
| /* We print error messages for line specifications out of range. */ |
| if ((histbeg < 0) || (histend < 0)) |
| { |
| sh_erange ((char *)NULL, _("history specification")); |
| return (EXECUTION_FAILURE); |
| } |
| |
| if (histend < histbeg) |
| { |
| i = histend; |
| histend = histbeg; |
| histbeg = i; |
| |
| reverse = 1; |
| } |
| |
| if (listing) |
| stream = stdout; |
| else |
| { |
| numbering = 0; |
| stream = sh_mktmpfp ("bash-fc", MT_USERANDOM|MT_USETMPDIR, &fn); |
| if (stream == 0) |
| { |
| builtin_error (_("%s: cannot open temp file: %s"), fn ? fn : "", strerror (errno)); |
| FREE (fn); |
| return (EXECUTION_FAILURE); |
| } |
| } |
| |
| for (i = reverse ? histend : histbeg; reverse ? i >= histbeg : i <= histend; reverse ? i-- : i++) |
| { |
| QUIT; |
| if (numbering) |
| fprintf (stream, "%d", i + history_base); |
| if (listing) |
| { |
| if (posixly_correct) |
| fputs ("\t", stream); |
| else |
| fprintf (stream, "\t%c", histdata (i) ? '*' : ' '); |
| } |
| fprintf (stream, "%s\n", histline (i)); |
| } |
| |
| if (listing) |
| return (sh_chkwrite (EXECUTION_SUCCESS)); |
| |
| fflush (stream); |
| if (ferror (stream)) |
| { |
| sh_wrerror (); |
| fclose (stream); |
| return (EXECUTION_FAILURE); |
| } |
| fclose (stream); |
| |
| /* Now edit the file of commands. */ |
| if (ename) |
| { |
| command = (char *)xmalloc (strlen (ename) + strlen (fn) + 2); |
| sprintf (command, "%s %s", ename, fn); |
| } |
| else |
| { |
| fcedit = posixly_correct ? POSIX_FC_EDIT_COMMAND : FC_EDIT_COMMAND; |
| command = (char *)xmalloc (3 + strlen (fcedit) + strlen (fn)); |
| sprintf (command, "%s %s", fcedit, fn); |
| } |
| retval = parse_and_execute (command, "fc", SEVAL_NOHIST); |
| if (retval != EXECUTION_SUCCESS) |
| { |
| unlink (fn); |
| free (fn); |
| return (EXECUTION_FAILURE); |
| } |
| |
| /* Make sure parse_and_execute doesn't turn this off, even though a |
| call to parse_and_execute farther up the function call stack (e.g., |
| if this is called by vi_edit_and_execute_command) may have already |
| called bash_history_disable. */ |
| remember_on_history = 1; |
| |
| /* Turn on the `v' flag while fc_execute_file runs so the commands |
| will be echoed as they are read by the parser. */ |
| begin_unwind_frame ("fc builtin"); |
| add_unwind_protect ((Function *)xfree, fn); |
| add_unwind_protect (unlink, fn); |
| unwind_protect_int (echo_input_at_read); |
| echo_input_at_read = 1; |
| |
| retval = fc_execute_file (fn); |
| |
| run_unwind_frame ("fc builtin"); |
| |
| return (retval); |
| } |
| |
| /* Return 1 if LIST->word->word is a legal number for fc's use. */ |
| static int |
| fc_number (list) |
| WORD_LIST *list; |
| { |
| char *s; |
| |
| if (list == 0) |
| return 0; |
| s = list->word->word; |
| if (*s == '-') |
| s++; |
| return (legal_number (s, (intmax_t *)NULL)); |
| } |
| |
| /* Return an absolute index into HLIST which corresponds to COMMAND. If |
| COMMAND is a number, then it was specified in relative terms. If it |
| is a string, then it is the start of a command line present in HLIST. */ |
| static int |
| fc_gethnum (command, hlist) |
| char *command; |
| HIST_ENTRY **hlist; |
| { |
| int sign, n, clen, rh; |
| register int i, j, last_hist; |
| register char *s; |
| |
| sign = 1; |
| /* Count history elements. */ |
| for (i = 0; hlist[i]; i++); |
| |
| /* With the Bash implementation of history, the current command line |
| ("fc blah..." and so on) is already part of the history list by |
| the time we get to this point. This just skips over that command |
| and makes the last command that this deals with be the last command |
| the user entered before the fc. We need to check whether the |
| line was actually added (HISTIGNORE may have caused it to not be), |
| so we check hist_last_line_added. This needs to agree with the |
| calculation of last_hist in fc_builtin above. */ |
| /* Even though command substitution through parse_and_execute turns off |
| remember_on_history, command substitution in a shell when set -o history |
| has been enabled (interactive or not) should use it in the last_hist |
| calculation as if it were on. */ |
| rh = remember_on_history || ((subshell_environment & SUBSHELL_COMSUB) && enable_history_list); |
| last_hist = i - rh - hist_last_line_added; |
| |
| if (i == last_hist && hlist[last_hist] == 0) |
| while (last_hist >= 0 && hlist[last_hist] == 0) |
| last_hist--; |
| if (last_hist < 0) |
| return (-1); |
| |
| i = last_hist; |
| |
| /* No specification defaults to most recent command. */ |
| if (command == NULL) |
| return (i); |
| |
| /* Otherwise, there is a specification. It can be a number relative to |
| the current position, or an absolute history number. */ |
| s = command; |
| |
| /* Handle possible leading minus sign. */ |
| if (s && (*s == '-')) |
| { |
| sign = -1; |
| s++; |
| } |
| |
| if (s && DIGIT(*s)) |
| { |
| n = atoi (s); |
| n *= sign; |
| |
| /* If the value is negative or zero, then it is an offset from |
| the current history item. */ |
| if (n < 0) |
| { |
| n += i + 1; |
| return (n < 0 ? 0 : n); |
| } |
| else if (n == 0) |
| return (i); |
| else |
| { |
| n -= history_base; |
| return (i < n ? i : n); |
| } |
| } |
| |
| clen = strlen (command); |
| for (j = i; j >= 0; j--) |
| { |
| if (STREQN (command, histline (j), clen)) |
| return (j); |
| } |
| return (-1); |
| } |
| |
| /* Locate the most recent history line which begins with |
| COMMAND in HLIST, and return a malloc()'ed copy of it. */ |
| static char * |
| fc_gethist (command, hlist) |
| char *command; |
| HIST_ENTRY **hlist; |
| { |
| int i; |
| |
| if (hlist == 0) |
| return ((char *)NULL); |
| |
| i = fc_gethnum (command, hlist); |
| |
| if (i >= 0) |
| return (savestring (histline (i))); |
| else |
| return ((char *)NULL); |
| } |
| |
| #ifdef INCLUDE_UNUSED |
| /* Read the edited history lines from STREAM and return them |
| one at a time. This can read unlimited length lines. The |
| caller should free the storage. */ |
| static char * |
| fc_readline (stream) |
| FILE *stream; |
| { |
| register int c; |
| int line_len = 0, lindex = 0; |
| char *line = (char *)NULL; |
| |
| while ((c = getc (stream)) != EOF) |
| { |
| if ((lindex + 2) >= line_len) |
| line = (char *)xrealloc (line, (line_len += 128)); |
| |
| if (c == '\n') |
| { |
| line[lindex++] = '\n'; |
| line[lindex++] = '\0'; |
| return (line); |
| } |
| else |
| line[lindex++] = c; |
| } |
| |
| if (!lindex) |
| { |
| if (line) |
| free (line); |
| |
| return ((char *)NULL); |
| } |
| |
| if (lindex + 2 >= line_len) |
| line = (char *)xrealloc (line, lindex + 3); |
| |
| line[lindex++] = '\n'; /* Finish with newline if none in file */ |
| line[lindex++] = '\0'; |
| return (line); |
| } |
| #endif |
| |
| /* Perform the SUBS on COMMAND. |
| SUBS is a list of substitutions, and COMMAND is a simple string. |
| Return a pointer to a malloc'ed string which contains the substituted |
| command. */ |
| static char * |
| fc_dosubs (command, subs) |
| char *command; |
| REPL *subs; |
| { |
| register char *new, *t; |
| register REPL *r; |
| |
| for (new = savestring (command), r = subs; r; r = r->next) |
| { |
| t = strsub (new, r->pat, r->rep, 1); |
| free (new); |
| new = t; |
| } |
| return (new); |
| } |
| |
| /* Use `command' to replace the last entry in the history list, which, |
| by this time, is `fc blah...'. The intent is that the new command |
| become the history entry, and that `fc' should never appear in the |
| history list. This way you can do `r' to your heart's content. */ |
| static void |
| fc_replhist (command) |
| char *command; |
| { |
| int n; |
| |
| if (command == 0 || *command == '\0') |
| return; |
| |
| n = strlen (command); |
| if (command[n - 1] == '\n') |
| command[n - 1] = '\0'; |
| |
| if (command && *command) |
| { |
| bash_delete_last_history (); |
| maybe_add_history (command); /* Obeys HISTCONTROL setting. */ |
| } |
| } |
| |
| #ifdef INCLUDE_UNUSED |
| /* Add LINE to the history, after removing a single trailing newline. */ |
| static void |
| fc_addhist (line) |
| char *line; |
| { |
| register int n; |
| |
| if (line == 0 || *line == 0) |
| return; |
| |
| n = strlen (line); |
| |
| if (line[n - 1] == '\n') |
| line[n - 1] = '\0'; |
| |
| if (line && *line) |
| maybe_add_history (line); /* Obeys HISTCONTROL setting. */ |
| } |
| #endif |
| |
| #endif /* HISTORY */ |