]>
Commit | Line | Data |
---|---|---|
726f6388 JA |
1 | This file is fc.def, from which is created fc.c. |
2 | It implements the builtin "fc" in Bash. | |
3 | ||
4 | Copyright (C) 1987, 1989, 1991 Free Software Foundation, Inc. | |
5 | ||
6 | This file is part of GNU Bash, the Bourne Again SHell. | |
7 | ||
8 | Bash is free software; you can redistribute it and/or modify it under | |
9 | the terms of the GNU General Public License as published by the Free | |
10 | Software Foundation; either version 1, or (at your option) any later | |
11 | version. | |
12 | ||
13 | Bash is distributed in the hope that it will be useful, but WITHOUT ANY | |
14 | WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
15 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
16 | for more details. | |
17 | ||
18 | You should have received a copy of the GNU General Public License along | |
19 | with Bash; see the file COPYING. If not, write to the Free Software | |
20 | Foundation, 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 | ||
29 | FIRST and LAST can be numbers specifying the range, or FIRST can be a | |
30 | string, which means the most recent command beginning with that | |
31 | string. | |
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 | ||
41 | With the `fc -s [pat=rep ...] [command]' format, the command is | |
42 | re-executed after the substitution OLD=NEW is performed. | |
43 | ||
44 | A useful alias to use with this is r='fc -s', so that typing `r cc' | |
45 | runs the last command beginning with `cc' and typing `r' re-executes | |
46 | the 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) | |
67 | extern int errno; | |
68 | #endif /* !errno */ | |
69 | ||
70 | extern int echo_input_at_read; | |
71 | ||
72 | extern 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 | ||
103 | static char *fc_dosubs (), *fc_replace (), *fc_gethist (), *fc_readline (); | |
104 | static int fc_gethnum (); | |
105 | static void fc_replhist (), fc_addhist (); | |
106 | ||
107 | /* Data structure describing a list of global replacements to perform. */ | |
108 | typedef 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 | ||
137 | int | |
138 | fc_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. */ | |
441 | static int | |
442 | fc_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. */ | |
506 | static char * | |
507 | fc_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. */ | |
527 | static char * | |
528 | fc_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. */ | |
570 | static char * | |
571 | fc_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. */ | |
591 | static char * | |
592 | fc_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. */ | |
631 | static void | |
632 | fc_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. */ | |
677 | static void | |
678 | fc_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 */ |