1 /***********************************************************************
3 * This software is part of the ast package *
4 * Copyright (c) 1982-2010 AT&T Intellectual Property *
5 * and is licensed under the *
6 * Common Public License, Version 1.0 *
7 * by AT&T Intellectual Property *
9 * A copy of the License is available at *
10 * http://www.opensource.org/licenses/cpl1.0.txt *
11 * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) *
13 * Information and Software Systems Research *
17 * David Korn <dgk@research.att.com> *
19 ***********************************************************************/
22 * bash style history expansion
26 * Omnium Software Engineering
31 * <K.Fleischer@omnium.de>
38 #if ! SHOPT_HISTEXPAND
44 static char *modifiers
= "htrepqxs&";
45 static int mod_flags
[] = { 0, 0, 0, 0, HIST_PRINT
, HIST_QUOTE
, HIST_QUOTE
|HIST_QUOTE_BR
, 0, 0 };
47 #define DONE() {flag |= HIST_ERROR; cp = 0; stakseek(0); goto done;}
51 char *str
[2]; /* [0] is "old", [1] is "new" string */
56 * parse an /old/new/ string, delimiter expected as first char.
57 * if "old" not specified, keep sb->str[0]
58 * if "new" not specified, set sb->str[1] to empty string
59 * read up to third delimeter char, \n or \0, whichever comes first.
60 * return adress is one past the last valid char in s:
61 * - the address containing \n or \0 or
62 * - one char beyond the third delimiter
65 static char *parse_subst(const char *s
, struct subst
*sb
)
70 /* build the strings on the stack, mainly for '&' substition in "new" */
73 /* init "new" with empty string */
76 sb
->str
[1] = strdup("");
85 if(*cp
== del
|| *cp
== '\n' || *cp
== '\0')
87 /* delimiter or EOL */
90 /* dupe string on stack and rewind stack */
94 sb
->str
[n
] = strdup(stakptr(off
));
99 /* if not delimiter, we've reached EOL. Get outta here. */
105 if(*(cp
+1) == del
) /* quote delimiter */
110 else if(*(cp
+1) == '&' && n
== 1)
111 { /* quote '&' only in "new" */
118 else if(*cp
== '&' && n
== 1 && sb
->str
[0])
119 /* substitute '&' with "old" in "new" */
120 stakputs(sb
->str
[0]);
133 * history expansion main routine
136 int hist_expand(const char *ln
, char **xp
)
138 int off
, /* stack offset */
139 q
, /* quotation flags */
141 c
, /* current char */
142 flag
=0; /* HIST_* flags */
143 Sfoff_t n
, /* history line number, counter, etc. */
145 w
[2]; /* word range */
146 char *sp
, /* stack pointer */
147 *cp
, /* current char in ln */
148 *str
, /* search string */
149 *evp
, /* event/word designator string, for error msgs */
150 *cc
=0, /* copy of current line up to cp; temp ptr */
151 hc
[3], /* default histchars */
152 *qc
="\'\"`"; /* quote characters */
153 Sfio_t
*ref
=0, /* line referenced by event designator */
154 *tmp
=0, /* temporary line buffer */
155 *tmp2
=0;/* temporary line buffer */
156 Histloc_t hl
; /* history location */
157 static Namval_t
*np
= 0; /* histchars variable */
158 static struct subst sb
= {0,0}; /* substition strings */
159 static Sfio_t
*wm
=0; /* word match from !?string? event designator */
162 wm
= sfopen(NULL
, NULL
, "swr");
167 if((np
= nv_open("histchars",sh
.var_tree
,0)) && (cp
= nv_getval(np
)))
181 /* save shell stack */
189 /* read until event/quick substitution/comment designator */
190 if((*cp
!= hc
[0] && *cp
!= hc
[1] && *cp
!= hc
[2])
191 || (*cp
== hc
[1] && cp
!= ln
))
193 if(*cp
== '\\') /* skip escaped designators */
195 else if(*cp
== '\'') /* skip quoted designators */
199 while(*++cp
&& *cp
!= '\'');
205 if(hc
[2] && *cp
== hc
[2]) /* history comment designator, skip rest of line */
214 flag
&= HIST_EVENT
; /* save event flag for returning later */
218 if(*cp
== hc
[1]) /* shortcut substitution */
220 flag
|= HIST_QUICKSUBST
;
224 if(*cp
== hc
[0] && *(cp
+1) == hc
[0]) /* refer to line -1 */
239 case '#': /* the line up to current position */
242 n
= staktell(); /* terminate string and dup */
244 cc
= strdup(stakptr(0));
245 stakseek(n
); /* remove null byte again */
246 ref
= sfopen(ref
, cc
, "s"); /* open as file */
247 n
= 0; /* skip history file referencing */
249 case '-': /* back reference by number */
250 if(!isdigit(*(cp
+1)))
253 case '0': /* reference by number */
265 n
= n
* 10 + (*cp
++) - '0';
275 flag
|= HIST_QUESTION
;
278 /* read until end of string or word designator/modifier */
283 if((!(flag
&HIST_QUESTION
) &&
284 (*cp
== ':' || isspace(*cp
)
285 || *cp
== '^' || *cp
== '$'
286 || *cp
== '*' || *cp
== '-'
289 || ((flag
&HIST_QUESTION
) && (*cp
== '?' || *cp
== '\n')))
300 if(str
) /* !string or !?string? event designator */
303 /* search history for string */
304 hl
= hist_find(sh
.hist_ptr
, str
,
305 sh
.hist_ptr
->histind
,
306 flag
&HIST_QUESTION
, -1);
307 if((n
= hl
.hist_command
) == -1)
308 n
= 0; /* not found */
312 if(n
< 0) /* determine index for backref */
313 n
= sh
.hist_ptr
->histind
+ n
;
314 /* search and use history file if found */
315 if(n
> 0 && hist_seek(sh
.hist_ptr
, n
) != -1)
316 ref
= sh
.hist_ptr
->histfp
;
321 /* string not found or command # out of range */
324 errormsg(SH_DICT
, ERROR_ERROR
, "%s: event not found", evp
);
329 if(str
) /* string search: restore orig. line */
331 if(flag
&HIST_QUESTION
)
332 *cp
++ = c
; /* skip second question mark */
337 /* colon introduces either word designators or modifiers */
338 if(*(evp
= cp
) == ':')
341 w
[0] = 0; /* -1 means last word, -2 means match from !?string? */
342 w
[1] = -1; /* -1 means last word, -2 means suppress last word */
344 if(flag
& HIST_QUICKSUBST
) /* shortcut substitution */
351 case '^': /* first word */
359 case '$': /* last word */
362 case '%': /* match from !?string? event designator */
374 w
[1] = sftell(ref
) + hl
.hist_char
;
376 sfseek(wm
, 0, SEEK_SET
);
384 case '*': /* until last word */
389 flag
|= HIST_WORDDSGN
;
392 case '-': /* until last word or specified index */
394 flag
|= HIST_WORDDSGN
;
406 case '9': /* specify index */
407 if((*evp
== ':') || w
[1] == -2)
410 while(isdigit(c
=*cp
++))
411 w
[n
] = w
[n
] * 10 + c
- '0';
412 flag
|= HIST_WORDDSGN
;
424 if(w
[0] != -2 && w
[1] > 0 && w
[0] > w
[1])
428 errormsg(SH_DICT
, ERROR_ERROR
, "%s: bad word specifier", evp
);
433 /* no valid word designator after colon, rewind */
434 if(!(flag
& HIST_WORDDSGN
) && (*evp
== ':'))
438 /* open temp buffer, let sfio do the (re)allocation */
439 tmp
= sfopen(NULL
, NULL
, "swr");
441 /* push selected words into buffer, squash
442 whitespace into single blank or a newline */
445 while((c
= sfgetc(ref
)) > 0)
449 flag
|= (c
== '\n' ? HIST_NEWLINE
: 0);
453 if(n
>= w
[0] && ((w
[0] != -2) ? (w
[1] < 0 || n
<= w
[1]) : 1))
456 sfseek(tmp
, 0, SEEK_SET
);
461 sfputc(tmp
, flag
& HIST_NEWLINE
? '\n' : ' ');
463 flag
&= ~HIST_NEWLINE
;
472 q
^= cc
? 1<<(int)(cc
- qc
) : 0;
476 while((c
= sfgetc(ref
)) > 0 && (!isspace(c
) || q
));
478 if(w
[0] == -2 && sftell(ref
) > w
[1])
481 flag
|= (c
== '\n' ? HIST_NEWLINE
: 0);
484 if(w
[0] != -2 && w
[1] >= 0 && w
[1] >= n
)
488 errormsg(SH_DICT
, ERROR_ERROR
, "%s: bad word specifier", evp
);
492 else if(w
[1] == -2) /* skip last word */
493 sfseek(tmp
, i
, SEEK_SET
);
495 /* remove trailing newline */
498 sfseek(tmp
, -1, SEEK_CUR
);
499 if(sfgetc(tmp
) == '\n')
512 if(cc
&& (flag
&HIST_HASH
))
514 /* close !# temp file */
523 /* selected line/words are now in buffer, now go for the modifiers */
524 while(*cp
== ':' || (flag
& HIST_QUICKSUBST
))
526 if(flag
& HIST_QUICKSUBST
)
528 flag
&= ~HIST_QUICKSUBST
;
535 sfseek(tmp
, 0, SEEK_SET
);
536 tmp2
= sfopen(tmp2
, NULL
, "swr");
538 if(c
== 'g') /* global substitution */
540 flag
|= HIST_GLOBALSUBST
;
544 if(cc
= strchr(modifiers
, c
))
545 flag
|= mod_flags
[cc
- modifiers
];
548 errormsg(SH_DICT
, ERROR_ERROR
, "%c: unrecognized history modifier", c
);
552 if(c
== 'h' || c
== 'r') /* head or base */
555 while((c
= sfgetc(tmp
)) > 0)
556 { /* remember position of / or . */
557 if((c
== '/' && *cp
== 'h') || (c
== '.' && *cp
== 'r'))
562 { /* rewind to last / or . */
563 sfseek(tmp2
, n
, SEEK_SET
);
564 /* end string there */
568 else if(c
== 't' || c
== 'e') /* tail or suffix */
571 while((c
= sfgetc(tmp
)) > 0)
572 { /* remember position of / or . */
573 if((c
== '/' && *cp
== 't') || (c
== '.' && *cp
== 'e'))
576 /* rewind to last / or . */
577 sfseek(tmp
, n
, SEEK_SET
);
578 /* copy from there on */
579 while((c
= sfgetc(tmp
)) > 0)
582 else if(c
== 's' || c
== '&')
588 /* preset old with match from !?string? */
590 sb
.str
[0] = strdup(sfsetbuf(wm
, (Void_t
*)1, 0));
591 cp
= parse_subst(cp
, &sb
);
594 if(!sb
.str
[0] || !sb
.str
[1])
598 errormsg(SH_DICT
, ERROR_ERROR
,
599 "%s%s: no previous substitution",
600 (flag
& HIST_QUICKSUBST
) ? ":s" : "",
606 /* need pointer for strstr() */
607 str
= sfsetbuf(tmp
, (Void_t
*)1, 0);
609 flag
|= HIST_SUBSTITUTE
;
610 while(flag
& HIST_SUBSTITUTE
)
613 if(cc
= strstr(str
, sb
.str
[0]))
617 sfputr(tmp2
, str
, -1);
618 sfputr(tmp2
, sb
.str
[1], -1);
620 str
= cc
+ strlen(sb
.str
[0]);
622 else if(!sftell(tmp2
))
623 { /* not successfull */
626 errormsg(SH_DICT
, ERROR_ERROR
,
627 "%s%s: substitution failed",
628 (flag
& HIST_QUICKSUBST
) ? ":s" : "",
633 /* loop if g modifier specified */
634 if(!cc
|| !(flag
& HIST_GLOBALSUBST
))
635 flag
&= ~HIST_SUBSTITUTE
;
637 /* output rest of line */
638 sfputr(tmp2
, str
, -1);
644 { /* if any substitions done, swap buffers */
655 /* flush temporary buffer to stack */
658 sfseek(tmp
, 0, SEEK_SET
);
660 if(flag
& HIST_QUOTE
)
663 while((c
= sfgetc(tmp
)) > 0)
667 flag
= flag
& ~HIST_NEWLINE
;
669 /* squash white space to either a
670 blank or a newline */
672 flag
|= (c
== '\n' ? HIST_NEWLINE
: 0);
673 while((c
= sfgetc(tmp
)) > 0 && isspace(c
));
677 c
= (flag
& HIST_NEWLINE
) ? '\n' : ' ';
679 if(flag
& HIST_QUOTE_BR
)
688 else if((c
== '\'') && (flag
& HIST_QUOTE
))
698 if(flag
& HIST_QUOTE
)
706 if(cc
&& (flag
&HIST_HASH
))
708 /* close !# temp file */
715 if(staktell() && !(flag
& HIST_ERROR
))
716 *xp
= strdup(stakfreeze(1));
718 /* restore shell stack */
724 /* drop temporary files */
731 return (flag
& HIST_ERROR
? HIST_ERROR
: flag
& HIST_FLAG_RETURN_MASK
);