4 * only implements in-memory history.
9 * a) the original in-memory history mechanism
10 * b) a simple file saving history mechanism done by sjg@zen
19 # define HISTFILE "/SDK/Data/abc-shell/history"
22 void histappend(const char *, int);
24 static int hist_execute(char *);
25 static int hist_replace(char **, const char *, const char *, int);
26 static char **hist_get(const char *, int, int);
27 static char **hist_get_oldest(void);
28 static void histbackup(void);
30 static char **current
; /* current postition in history[] */
31 static char *hname
; /* current name of history file */
32 static int hstarted
; /* set after hist_init() called */
33 static Source
*hist_source
;
40 struct temp
*tf
= NULL
;
41 char *p
, *editor
= (char *) 0;
42 int gflag
= 0, lflag
= 0, nflag
= 0, sflag
= 0, rflag
= 0;
44 char *first
= (char *) 0, *last
= (char *) 0;
45 char **hfirst
, **hlast
, **hp
;
47 if (!Flag(FTALKING_I
)) {
48 bi_errorf("history functions not available");
52 while ((optc
= ksh_getopt(wp
, &builtin_opt
,
53 "e:glnrs0,1,2,3,4,5,6,7,8,9,")) != -1)
56 p
= builtin_opt
.optarg
;
57 if (strcmp(p
, "-") == 0)
60 size_t len
= strlen(p
) + 4;
61 editor
= str_nsave(p
, len
, ATEMP
);
62 strlcat(editor
, " $_", len
);
65 case 'g': /* non-at&t ksh */
77 case 's': /* posix version of -e - */
80 /* kludge city - accept -num as -- -num (kind of) */
81 case '0': case '1': case '2': case '3': case '4':
82 case '5': case '6': case '7': case '8': case '9':
83 p
= shf_smprintf("-%c%s",
84 optc
, builtin_opt
.optarg
);
90 bi_errorf("too many arguments");
97 wp
+= builtin_opt
.optind
;
99 /* Substitute and execute command */
101 char *pat
= (char *) 0, *rep
= (char *) 0;
103 if (editor
|| lflag
|| nflag
|| rflag
) {
104 bi_errorf("can't use -e, -l, -n, -r with -s (-e -)");
108 /* Check for pattern replacement argument */
109 if (*wp
&& **wp
&& (p
= strchr(*wp
+ 1, '='))) {
110 pat
= str_save(*wp
, ATEMP
);
116 /* Check for search prefix */
117 if (!first
&& (first
= *wp
))
120 bi_errorf("too many arguments");
124 hp
= first
? hist_get(first
, false, false) :
125 hist_get_newest(false);
128 return hist_replace(hp
, pat
, rep
, gflag
);
131 if (editor
&& (lflag
|| nflag
)) {
132 bi_errorf("can't use -l, -n with -e");
136 if (!first
&& (first
= *wp
))
138 if (!last
&& (last
= *wp
))
141 bi_errorf("too many arguments");
145 hfirst
= lflag
? hist_get("-16", true, true) :
146 hist_get_newest(false);
149 /* can't fail if hfirst didn't fail */
150 hlast
= hist_get_newest(false);
152 /* POSIX says not an error if first/last out of bounds
153 * when range is specified; at&t ksh and pdksh allow out of
154 * bounds for -l as well.
156 hfirst
= hist_get(first
, (lflag
|| last
) ? true : false,
157 lflag
? true : false);
160 hlast
= last
? hist_get(last
, true, lflag
? true : false) :
161 (lflag
? hist_get_newest(false) : hfirst
);
165 if (hfirst
> hlast
) {
168 temp
= hfirst
; hfirst
= hlast
; hlast
= temp
;
169 rflag
= !rflag
; /* POSIX */
175 const char *nfmt
= nflag
? "\t" : "%d\t";
177 for (hp
= rflag
? hlast
: hfirst
;
178 hp
>= hfirst
&& hp
<= hlast
; hp
+= rflag
? -1 : 1) {
179 shf_fprintf(shl_stdout
, nfmt
,
180 hist_source
->line
- (int) (histptr
- hp
));
181 /* print multi-line commands correctly */
182 for (s
= *hp
; (t
= strchr(s
, '\n')); s
= t
)
183 shf_fprintf(shl_stdout
, "%.*s\t", ++t
- s
, s
);
184 shf_fprintf(shl_stdout
, "%s\n", s
);
186 shf_flush(shl_stdout
);
190 /* Run editor on selected lines, then run resulting commands */
192 tf
= maketemp(ATEMP
, TT_HIST_EDIT
, &e
->temps
);
193 if (!(shf
= tf
->shf
)) {
194 bi_errorf("cannot create temp file %s - %s",
195 tf
->name
, strerror(errno
));
198 for (hp
= rflag
? hlast
: hfirst
;
199 hp
>= hfirst
&& hp
<= hlast
; hp
+= rflag
? -1 : 1)
200 shf_fprintf(shf
, "%s\n", *hp
);
201 if (shf_close(shf
) == EOF
) {
202 bi_errorf("error writing temporary file - %s", strerror(errno
));
206 /* Ignore setstr errors here (arbitrary) */
207 setstr(local("_", false), tf
->name
, KSH_RETURN_ERROR
);
209 /* XXX: source should not get trashed by this.. */
211 Source
*sold
= source
;
214 ret
= command(editor
? editor
: "${FCEDIT:-/bin/ed} $_");
226 if (!(shf
= shf_open(tf
->name
, O_RDONLY
, 0, 0))) {
227 bi_errorf("cannot open temp file %s", tf
->name
);
231 n
= fstat(shf_fileno(shf
), &statb
) < 0 ? 128 :
233 Xinit(xs
, xp
, n
, hist_source
->areap
);
234 while ((n
= shf_read(xp
, Xnleft(xs
, xp
), shf
)) > 0) {
236 if (Xnleft(xs
, xp
) <= 0)
237 XcheckN(xs
, xp
, Xlength(xs
, xp
));
240 bi_errorf("error reading temp file %s - %s",
241 tf
->name
, strerror(shf_errno(shf
)));
247 strip_nuls(Xstring(xs
, xp
), Xlength(xs
, xp
));
248 return hist_execute(Xstring(xs
, xp
));
252 /* Save cmd in history, execute cmd (cmd gets trashed) */
254 hist_execute(char *cmd
)
262 for (p
= cmd
; p
; p
= q
) {
263 if ((q
= strchr(p
, '\n'))) {
264 *q
++ = '\0'; /* kill the newline */
265 if (!*q
) /* ignore trailing newline */
271 histsave(++(hist_source
->line
), p
, 1);
273 shellf("%s\n", p
); /* POSIX doesn't say this is done... */
274 if ((p
= q
)) /* restore \n (trailing \n not restored) */
278 /* Commands are executed here instead of pushing them onto the
279 * input 'cause posix says the redirection and variable assignments
281 * X=y fc -e - 42 2> /dev/null
282 * are to effect the repeated commands environment.
284 /* XXX: source should not get trashed by this.. */
292 hist_replace(char **hp
, const char *pat
, const char *rep
, int global
)
297 line
= str_save(*hp
, ATEMP
);
300 int pat_len
= strlen(pat
);
301 int rep_len
= strlen(rep
);
307 Xinit(xs
, xp
, 128, ATEMP
);
308 for (s
= *hp
; (s1
= strstr(s
, pat
)) && (!any_subst
|| global
);
312 XcheckN(xs
, xp
, len
+ rep_len
);
313 memcpy(xp
, s
, len
); /* first part */
315 memcpy(xp
, rep
, rep_len
); /* replacement */
319 bi_errorf("substitution failed");
323 XcheckN(xs
, xp
, len
);
326 line
= Xclose(xs
, xp
);
328 return hist_execute(line
);
332 * get pointer to history given pattern
333 * pattern is a number or string
336 hist_get(const char *str
, int approx
, int allow_cur
)
338 char **hp
= (char **) 0;
342 hp
= histptr
+ (n
< 0 ? n
: (n
- hist_source
->line
));
345 hp
= hist_get_oldest();
347 bi_errorf("%s: not in history", str
);
350 } else if (hp
> histptr
) {
352 hp
= hist_get_newest(allow_cur
);
354 bi_errorf("%s: not in history", str
);
357 } else if (!allow_cur
&& hp
== histptr
) {
358 bi_errorf("%s: invalid range", str
);
362 int anchored
= *str
== '?' ? (++str
, 0) : 1;
364 /* the -1 is to avoid the current fc command */
365 n
= findhist(histptr
- history
- 1, 0, str
, anchored
);
367 bi_errorf("%s: not in history", str
);
375 /* Return a pointer to the newest command in the history */
377 hist_get_newest(int allow_cur
)
379 if (histptr
< history
|| (!allow_cur
&& histptr
== history
)) {
380 bi_errorf("no history (yet)");
388 /* Return a pointer to the newest command in the history */
390 hist_get_oldest(void)
392 if (histptr
<= history
) {
393 bi_errorf("no history (yet)");
399 /******************************/
400 /* Back up over last histsave */
401 /******************************/
405 static int last_line
= -1;
407 if (histptr
>= history
&& last_line
!= hist_source
->line
) {
409 afree((void*)*histptr
, APERM
);
411 last_line
= hist_source
->line
;
416 * Return the current position.
427 int last
= histptr
- history
;
429 if (n
< 0 || n
>= last
) {
433 current
= &history
[n
];
439 * This will become unnecessary if hist_get is modified to allow
440 * searching from positions other than the end, and in either
444 findhist(int start
, int fwd
, const char *str
, int anchored
)
447 int maxhist
= histptr
- history
;
448 int incr
= fwd
? 1 : -1;
449 int len
= strlen(str
);
451 if (start
< 0 || start
>= maxhist
)
454 hp
= &history
[start
];
455 for (; hp
>= history
&& hp
<= histptr
; hp
+= incr
)
456 if ((anchored
&& strncmp(*hp
, str
, len
) == 0) ||
457 (!anchored
&& strstr(*hp
, str
)))
464 findhistrel(const char *str
)
466 int maxhist
= histptr
- history
;
467 int start
= maxhist
- 1;
479 return start
+ rec
+ 1;
484 * this means reallocating the dataspace
489 if (n
> 0 && n
!= histsize
) {
490 int cursize
= histptr
- history
;
492 /* save most recent history */
494 memmove(history
, histptr
- n
, n
* sizeof(char *));
498 history
= (char **)aresize(history
, n
*sizeof(char *), APERM
);
501 histptr
= history
+ cursize
;
507 * This can mean reloading/resetting/starting history file
511 sethistfile(const char *name
)
513 /* if not started then nothing to do */
517 /* if the name is the same as the name we have */
518 if (hname
&& strcmp(hname
, name
) == 0)
522 * its a new name - possibly
529 hist_init(hist_source
);
533 * initialise the history vector
538 if (history
== (char **)NULL
) {
539 histsize
= HISTORYSIZE
;
540 history
= (char **)alloc(histsize
*sizeof (char *), APERM
);
541 histptr
= history
- 1;
546 * save command in history
549 histsave(int lno
, const char *cmd
, int dowrite
)
555 /* don't save if command is the same as last one */
556 if (hp
>= history
&& *hp
!= NULL
) {
558 if (strcmp(*hp
, cmd
) == 0)
560 else if ((cmd
[l
-1] == '\n')
561 && (strlen(*hp
) == l
-1)
562 && strncmp(*hp
, cmd
, l
- 1) == 0)
566 if (++hp
>= history
+ histsize
) { /* remove oldest command */
567 afree((void*)history
[0], APERM
);
568 memmove(history
, history
+ 1,
569 sizeof(history
[0]) * (histsize
- 1));
570 hp
= &history
[histsize
- 1];
572 *hp
= str_save(cmd
, APERM
);
573 /* trash trailing newline but allow imbedded newlines */
574 cp
= *hp
+ strlen(*hp
);
575 if (cp
> *hp
&& cp
[-1] == '\n')
581 * Append an entry to the last saved command. Used for multiline
585 histappend(const char *cmd
, int nl_separate
)
590 hlen
= strlen(*histptr
);
592 if (clen
> 0 && cmd
[clen
-1] == '\n')
594 p
= *histptr
= (char *) aresize(*histptr
, hlen
+ clen
+ 2, APERM
);
598 memcpy(p
, cmd
, clen
);
604 * A simple history file implementation.
605 * At present we only save the history when we exit.
606 * This can cause problems when there are multiple shells are
607 * running under the same user-id. The last shell to exit gets
608 * to save its history.
616 if (Flag(FTALKING
) == 0)
623 if ((f
= str_val(global("HISTFILE"))) == NULL
|| *f
== '\0') {
624 # if 1 /* Don't use history file unless the user asks for it */
628 char *home
= str_val(global("HOME"));
634 hname
= alloc(len
= strlen(home
) + strlen(f
) + 2, APERM
);
635 shf_snprintf(hname
, len
, "%s/%s", home
, f
);
638 hname
= str_save(f
, APERM
);
640 if ((fh
= fopen(hname
, "r"))) {
641 int pos
= 0, nread
= 0;
642 int contin
= 0; /* continuation of previous command */
644 char hline
[LINE
+ 1];
649 nread
= fread(hline
, 1, LINE
, fh
);
654 end
= strchr(hline
+ pos
, '\n'); /* will always succeed - 0, but not \n!!! */
655 if(end
!= NULL
) /* in case sth put \0 in .history...) */
658 end
= strchr(hline
+ pos
, '\0'); /* THIS will always succeed */
660 histappend(hline
+ pos
, 0);
663 histsave(0, hline
+ pos
, 0);
665 pos
= end
- hline
+ 1;
666 contin
= end
== &hline
[nread
];
674 * We check that we do not have more than we are allowed.
675 * If the history file is read-only we do nothing.
676 * Handy for having all shells start with a useful history set.
689 /* check how many we have */
690 i
= histptr
- history
;
692 hp
= &histptr
[-histsize
];
695 if (hname
&& (fh
= fopen(hname
, "w")))
697 for (i
= 0; hp
+ i
<= histptr
&& hp
[i
]; i
++)
698 fprintf(fh
, "%s\n", hp
[i
]);
705 /* No history to be compiled in: dummy routines to avoid lots more ifdefs */
722 histsave(int lno
, const char *cmd
, int dowrite
)
724 errorf("history not enabled");