]> git.ipfire.org Git - thirdparty/bash.git/blame - builtins/fc.def
Imported from ../bash-1.14.7.tar.gz.
[thirdparty/bash.git] / builtins / fc.def
CommitLineData
726f6388
JA
1This file is fc.def, from which is created fc.c.
2It implements the builtin "fc" in Bash.
3
4Copyright (C) 1987, 1989, 1991 Free Software Foundation, Inc.
5
6This file is part of GNU Bash, the Bourne Again SHell.
7
8Bash is free software; you can redistribute it and/or modify it under
9the terms of the GNU General Public License as published by the Free
10Software Foundation; either version 1, or (at your option) any later
11version.
12
13Bash is distributed in the hope that it will be useful, but WITHOUT ANY
14WARRANTY; without even the implied warranty of MERCHANTABILITY or
15FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16for more details.
17
18You should have received a copy of the GNU General Public License along
19with Bash; see the file COPYING. If not, write to the Free Software
20Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
21
22$PRODUCES fc.c
23
24$BUILTIN fc
25$FUNCTION fc_builtin
26$DEPENDS_ON HISTORY
27$SHORT_DOC fc [-e ename] [-nlr] [first] [last] or fc -s [pat=rep] [cmd]
28
29FIRST and LAST can be numbers specifying the range, or FIRST can be a
30string, which means the most recent command beginning with that
31string.
32
33 -e ENAME selects which editor to use. Default is FCEDIT, then EDITOR,
34 then the editor which corresponds to the current readline editing
35 mode, then vi.
36
37 -l means list lines instead of editing.
38 -n means no line numbers listed.
39 -r means reverse the order of the lines (making it newest listed first).
40
41With the `fc -s [pat=rep ...] [command]' format, the command is
42re-executed after the substitution OLD=NEW is performed.
43
44A useful alias to use with this is r='fc -s', so that typing `r cc'
45runs the last command beginning with `cc' and typing `r' re-executes
46the last command.
47$END
48
49#include <stdio.h>
50#include "../bashansi.h"
51#include "../shell.h"
52#if defined (HISTORY)
53#include <sys/param.h>
54#include <sys/types.h>
55#include <sys/stat.h>
56#include <sys/file.h>
57#include <errno.h>
58#include "../builtins.h"
59#include "../flags.h"
60#include "../maxpath.h"
61#include "../bashhist.h"
62#include <readline/history.h>
63#include "bashgetopt.h"
64
65/* Not all systems declare ERRNO in errno.h... and some systems #define it! */
66#if !defined (errno)
67extern int errno;
68#endif /* !errno */
69
70extern int echo_input_at_read;
71
72extern int unlink ();
73
74/* **************************************************************** */
75/* */
76/* The K*rn shell style fc command (Fix Command) */
77/* */
78/* **************************************************************** */
79
80/* fc builtin command (fix command) for Bash for those who
81 like K*rn-style history better than csh-style.
82
83 fc [-e ename] [-nlr] [first] [last]
84
85 FIRST and LAST can be numbers specifying the range, or FIRST can be
86 a string, which means the most recent command beginning with that
87 string.
88
89 -e ENAME selects which editor to use. Default is FCEDIT, then EDITOR,
90 then the editor which corresponds to the current readline editing
91 mode, then vi.
92
93 -l means list lines instead of editing.
94 -n means no line numbers listed.
95 -r means reverse the order of the lines (making it newest listed first).
96
97 fc -e - [pat=rep ...] [command]
98 fc -s [pat=rep ...] [command]
99
100 Equivalent to !command:sg/pat/rep execpt there can be multiple PAT=REP's.
101*/
102
103static char *fc_dosubs (), *fc_replace (), *fc_gethist (), *fc_readline ();
104static int fc_gethnum ();
105static void fc_replhist (), fc_addhist ();
106
107/* Data structure describing a list of global replacements to perform. */
108typedef struct repl {
109 struct repl *next;
110 char *pat;
111 char *rep;
112} REPL;
113
114#define USAGE "usage: fc [-e ename] [-nlr] [first] [last] or fc -s [pat=rep] [command]"
115
116/* Accessors for HIST_ENTRY lists that are called HLIST. */
117#define histline(i) (hlist[(i)]->line)
118#define histdata(i) (hlist[(i)]->data)
119
120#define FREE_RLIST() \
121 do { \
122 for (rl = rlist; rl; ) { \
123 REPL *r; \
124 r = rl->next; \
125 if (rl->pat) \
126 free (rl->pat); \
127 if (rl->rep) \
128 free (rl->rep); \
129 free (rl); \
130 rl = r; \
131 } \
132 } while (0)
133
134/* String to execute on a file that we want to edit. */
135#define FC_EDIT_COMMAND "${FCEDIT:-${EDITOR:-vi}}"
136
137int
138fc_builtin (list)
139 WORD_LIST *list;
140{
141 register int i;
142 register char *sep;
143 int numbering, reverse, listing, execute;
144 int histbeg, histend, last_hist, retval, first, opt;
145 FILE *stream;
146 REPL *rlist = (REPL *) NULL, *rl;
147 char *ename = NULL, *command, *newcom, *line;
148 HIST_ENTRY **hlist;
149 char fn[MAXPATHLEN];
150
151 numbering = 1;
152 reverse = listing = execute = 0;
153
154 /* Parse out the options and set which of the two forms we're in. */
155
156 while (list && *list->word->word == '-')
157 {
158 register char *s = &((list->word->word)[1]);
159
160 if (!isletter (*s))
161 break;
162
163 while (opt = *s++)
164 {
165 switch (opt)
166 {
167 case 'n':
168 numbering = 0;
169 break;
170
171 case 'l':
172 listing = 1;
173 break;
174
175 case 'r':
176 reverse = 1;
177 break;
178
179 case 's':
180 execute = 1;
181 break;
182
183 case 'e':
184 list = list->next;
185 if (list == NULL)
186 {
187 builtin_error (USAGE);
188 return (EX_USAGE);
189 }
190 ename = list->word->word;
191 break;
192
193 default:
194 builtin_error (USAGE);
195 return (EX_USAGE);
196 }
197 }
198 list = list->next;
199 }
200
201 if (ename && (*ename == '-') && (ename[1] == '\0'))
202 execute = 1;
203
204 /* The "execute" form of the command (re-run, with possible string
205 substitutions). */
206 if (execute)
207 {
208 while (list && ((sep = (char *)strchr (list->word->word, '=')) != NULL))
209 {
210 *sep++ = '\0';
211 rl = (REPL *)xmalloc (sizeof (REPL));
212 rl->next = (REPL *)NULL;
213 rl->pat = savestring (list->word->word);
214 rl->rep = savestring (sep);
215
216 if (rlist == NULL)
217 rlist = rl;
218 else
219 {
220 rl->next = rlist;
221 rlist = rl;
222 }
223 list = list->next;
224 }
225
226 /* If we have a list of substitutions to do, then reverse it
227 to get the replacements in the proper order. */
228
229 if (rlist && rlist->next)
230 rlist = (REPL *) reverse_list ((GENERIC_LIST *) rlist);
231
232 hlist = history_list ();
233
234 /* If we still have something in list, it is a command spec.
235 Otherwise, we use the most recent command in time. */
236 if (list)
237 command = fc_gethist (list->word->word, hlist);
238 else
239 command = fc_gethist ((char *) NULL, hlist);
240
241 if (command == NULL)
242 {
243 builtin_error ("no command found");
244 if (rlist)
245 FREE_RLIST ();
246
247 return (EXECUTION_FAILURE);
248 }
249
250 if (rlist)
251 {
252 newcom = fc_dosubs (command, rlist);
253 free (command);
254 FREE_RLIST ();
255 command = newcom;
256 }
257
258 printf ("%s\n", command);
259 fc_replhist (command); /* replace `fc -e -' with command */
260 return (parse_and_execute (command, "fc", -1));
261 }
262
263 /* This is the second form of the command (the list-or-edit-and-rerun
264 form). */
265 hlist = history_list ();
266 if (hlist == 0)
267 return (EXECUTION_SUCCESS);
268 for (i = 0; hlist[i]; i++);
269
270 /* With the Bash implementation of history, the current command line
271 ("fc blah..." and so on) is already part of the history list by
272 the time we get to this point. This just skips over that command
273 and makes the last command that this deals with be the last command
274 the user entered before the fc. */
275
276 last_hist = i - 2;
277
278 if (list)
279 {
280 histbeg = fc_gethnum (list->word->word, hlist);
281 list = list->next;
282
283 if (list)
284 histend = fc_gethnum (list->word->word, hlist);
285 else
286 {
287 if (listing)
288 histend = last_hist;
289 else
290 histend = histbeg;
291 }
292 }
293 else
294 {
295 /* The default for listing is the last 16 history items. */
296 if (listing)
297 {
298 histend = last_hist;
299 histbeg = histend - 16;
300 if (histbeg < 0)
301 histbeg = 0;
302 }
303 else
304 {
305 /* For editing, it is the last history command. */
306 histbeg = histend = last_hist;
307 }
308 }
309
310 /* We print error messages for line specifications out of range. */
311 if ((histbeg < 0) || (histend < 0) ||
312 (histbeg > last_hist) || (histend > last_hist))
313 {
314 builtin_error ("history specification out of range");
315 return (EXECUTION_FAILURE);
316 }
317
318 if (histend < histbeg)
319 {
320 int t = histend;
321
322 histend = histbeg;
323 histbeg = t;
324 reverse = 1;
325 }
326
327 if (listing)
328 stream = stdout;
329 else
330 {
331 numbering = 0;
332 sprintf (fn, "/tmp/bash%d", (int)time ((long *) 0) + (int)getpid ());
333
334 stream = fopen (fn, "w");
335
336 if (!stream)
337 {
338 builtin_error ("cannot open temp file %s", fn);
339 return (EXECUTION_FAILURE);
340 }
341 }
342
343 if (!reverse)
344 {
345 for (i = histbeg; i <= histend; i++)
346 {
347 QUIT;
348 if (numbering)
349 fprintf (stream, "%d", i + history_base);
350 if (listing)
351 fprintf (stream, "\t%c", histdata (i) ? '*' : ' ');
352 fprintf (stream, "%s\n", histline (i));
353 }
354 }
355 else
356 {
357 for (i = histend; i >= histbeg; i--)
358 {
359 QUIT;
360 if (numbering)
361 fprintf (stream, "%d", i + history_base);
362 if (listing)
363 fprintf (stream, "\t%c", histdata (i) ? '*' : ' ');
364 fprintf (stream, "%s\n", histline (i));
365 }
366 }
367
368 if (listing)
369 return (EXECUTION_SUCCESS);
370
371 fclose (stream);
372
373 /* Now edit the file of commands. */
374 if (ename)
375 {
376 command = (char *)xmalloc (strlen (ename) + strlen (fn) + 2);
377 sprintf (command, "%s %s", ename, fn);
378 }
379 else
380 {
381 command = (char *)xmalloc (3 + strlen (FC_EDIT_COMMAND) + strlen (fn));
382 sprintf (command, "%s %s", FC_EDIT_COMMAND, fn);
383 }
384 parse_and_execute (command, "fc", -1);
385
386 /* Now reopen the file and execute the edited commands. */
387
388 stream = fopen (fn, "r");
389
390 if (stream == NULL)
391 {
392 builtin_error ("cannot reopen temp file %s", fn);
393 unlink (fn);
394 return (EXECUTION_FAILURE);
395 }
396
397 retval = EXECUTION_SUCCESS;
398 first = 1;
399
400 /* First, write the commands to the history file. This will not happen
401 when we call parse_and_execute, since parse_and_execute disables
402 the command line history while it executes. */
403
404 while ((line = fc_readline (stream)) != NULL)
405 {
406 if (line[0] == '\n')
407 {
408 free (line);
409 continue; /* Skip blank lines. */
410 }
411
412 if (first)
413 {
414 first = 0;
415 fc_replhist (line);
416 }
417 else
418 fc_addhist (line);
419
420 free (line);
421 }
422 fclose (stream);
423
424 /* Turn on the `v' flag while maybe_execute_file runs so the commands
425 will be echoed as they are read by the parser. */
426 begin_unwind_frame ("fc builtin");
427 add_unwind_protect (unlink, fn);
428 unwind_protect_int (echo_input_at_read);
429 echo_input_at_read = 1;
430
431 retval = maybe_execute_file (fn, 0);
432
433 run_unwind_frame ("fc builtin");
434
435 return (retval);
436}
437
438/* Return an absolute index into HLIST which corresponds to COMMAND. If
439 COMMAND is a number, then it was specified in relative terms. If it
440 is a string, then it is the start of a command line present in HLIST. */
441static int
442fc_gethnum (command, hlist)
443 char *command;
444 HIST_ENTRY **hlist;
445{
446 int sign = 1, n, clen;
447 register int i, j;
448 register char *s;
449
450 /* Count history elements. */
451 for (i = 0; hlist[i]; i++);
452
453 /* With the Bash implementation of history, the current command line
454 ("fc blah..." and so on) is already part of the history list by
455 the time we get to this point. This just skips over that command
456 and makes the last command that this deals with be the last command
457 the user entered before the fc. */
458 i -= 2;
459
460 /* No specification defaults to most recent command. */
461 if (command == NULL)
462 return (i);
463
464 /* Otherwise, there is a specification. It can be a number relative to
465 the current position, or an absolute history number. */
466 s = command;
467
468 /* Handle possible leading minus sign. */
469 if (s && (*s == '-'))
470 {
471 sign = -1;
472 s++;
473 }
474
475 if (s && digit(*s))
476 {
477 n = atoi (s);
478 n *= sign;
479
480 /* Anything specified greater than the last history element that we
481 deal with is an error. */
482 if (n > i + history_base)
483 return (-1);
484
485 /* If the value is negative or zero, then it is an offset from
486 the current history item. */
487 if (n < 0)
488 return (i + n + 1);
489 else if (n == 0)
490 return (i);
491 else
492 return (n - history_base);
493 }
494
495 clen = strlen (command);
496 for (j = i; j >= 0; j--)
497 {
498 if (STREQN (command, histline (j), clen))
499 return (j);
500 }
501 return (-1);
502}
503
504/* Locate the most recent history line which begins with
505 COMMAND in HLIST, and return a malloc()'ed copy of it. */
506static char *
507fc_gethist (command, hlist)
508 char *command;
509 HIST_ENTRY **hlist;
510{
511 int i;
512
513 if (!hlist)
514 return ((char *)NULL);
515
516 i = fc_gethnum (command, hlist);
517
518 if (i >= 0)
519 return (savestring (histline (i)));
520 else
521 return ((char *)NULL);
522}
523
524/* Read the edited history lines from STREAM and return them
525 one at a time. This can read unlimited length lines. The
526 caller should free the storage. */
527static char *
528fc_readline (stream)
529 FILE *stream;
530{
531 register int c;
532 int line_len = 0, lindex = 0;
533 char *line = (char *)NULL;
534
535 while ((c = getc (stream)) != EOF)
536 {
537 if ((lindex + 2) >= line_len)
538 line = (char *) xrealloc (line, (line_len += 128));
539
540 if (c == '\n')
541 {
542 line[lindex++] = '\n';
543 line[lindex++] = '\0';
544 return (line);
545 }
546 else
547 line[lindex++] = c;
548 }
549
550 if (!lindex)
551 {
552 if (line)
553 free (line);
554
555 return ((char *)NULL);
556 }
557
558 if (lindex + 2 >= line_len)
559 line = (char *)xrealloc (line, lindex + 3);
560
561 line[lindex++] = '\n'; /* Finish with newline if none in file */
562 line[lindex++] = '\0';
563 return (line);
564}
565
566/* Perform the SUBS on COMMAND.
567 SUBS is a list of substitutions, and COMMAND is a simple string.
568 Return a pointer to a malloc'ed string which contains the substituted
569 command. */
570static char *
571fc_dosubs (command, subs)
572 char *command;
573 REPL *subs;
574{
575 register char *new = savestring (command);
576 register REPL *r;
577
578 for (r = subs; r; r = r->next)
579 {
580 register char *t;
581
582 t = fc_replace (r->pat, r->rep, new);
583 free (new);
584 new = t;
585 }
586 return (new);
587}
588
589/* Replace the occurrences of PAT with REP in COMMAND.
590 This returns a new string; the caller should free it. */
591static char *
592fc_replace (pat, rep, command)
593 char *pat, *rep, *command;
594{
595 register int i;
596 int patlen, replen, templen;
597 char *new, *temp;
598
599 patlen = strlen (pat);
600 replen = strlen (rep);
601
602 temp = savestring (command);
603 templen = strlen (temp);
604 i = 0;
605
606 for (; (i + patlen) <= templen; i++)
607 {
608 if (STREQN (temp + i, pat, patlen))
609 {
610 new = (char *) xmalloc (1 + (replen - patlen) + templen);
611
612 strncpy (new, temp, i);
613 strncpy (new + i, rep, replen);
614 strncpy (new + i + replen,
615 temp + i + patlen, templen - (i + patlen));
616 new[templen + (replen - patlen)] = '\0'; /* just in case */
617
618 free (temp);
619 temp = new;
620 i += replen;
621 templen = strlen (temp);
622 }
623 }
624 return (temp);
625}
626
627/* Use `command' to replace the last entry in the history list, which,
628 by this time, is `fc blah...'. The intent is that the new command
629 become the history entry, and that `fc' should never appear in the
630 history list. This way you can do `r' to your heart's content. */
631static void
632fc_replhist (command)
633 char *command;
634{
635 register int i;
636 HIST_ENTRY **hlist, *histent, *discard;
637 char *data;
638 int n;
639
640 if (!command || !*command)
641 return;
642
643 hlist = history_list ();
644
645 if (hlist == NULL)
646 return;
647
648 for (i = 0; hlist[i]; i++);
649 i--;
650
651 /* History_get () takes a parameter that should be
652 offset by history_base. */
653
654 histent = history_get (history_base + i); /* Don't free this */
655 if (histent == NULL)
656 return;
657
658 n = strlen (command);
659
660 if (command[n - 1] == '\n')
661 command[n - 1] = '\0';
662
663 if (command && *command)
664 {
665 discard = remove_history (i);
666 if (discard)
667 {
668 if (discard->line)
669 free (discard->line);
670 free ((char *) discard);
671 }
672 maybe_add_history (command); /* Obeys HISTCONTROL setting. */
673 }
674}
675
676/* Add LINE to the history, after removing a single trailing newline. */
677static void
678fc_addhist (line)
679 char *line;
680{
681 register int n;
682
683 n = strlen (line);
684
685 if (line[n - 1] == '\n')
686 line[n - 1] = '\0';
687
688 if (line && *line)
689 maybe_add_history (line);
690}
691#endif /* HISTORY */