1*4887Schin /***********************************************************************
2*4887Schin *                                                                      *
3*4887Schin *               This software is part of the ast package               *
4*4887Schin *           Copyright (c) 1982-2007 AT&T Knowledge Ventures            *
5*4887Schin *                      and is licensed under the                       *
6*4887Schin *                  Common Public License, Version 1.0                  *
7*4887Schin *                      by AT&T Knowledge Ventures                      *
8*4887Schin *                                                                      *
9*4887Schin *                A copy of the License is available at                 *
10*4887Schin *            http://www.opensource.org/licenses/cpl1.0.txt             *
11*4887Schin *         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
12*4887Schin *                                                                      *
13*4887Schin *              Information and Software Systems Research               *
14*4887Schin *                            AT&T Research                             *
15*4887Schin *                           Florham Park NJ                            *
16*4887Schin *                                                                      *
17*4887Schin *                  David Korn <dgk@research.att.com>                   *
18*4887Schin *                                                                      *
19*4887Schin ***********************************************************************/
20*4887Schin #pragma prototyped
21*4887Schin /*
22*4887Schin  * bash style history expansion
23*4887Schin  *
24*4887Schin  * Author:
25*4887Schin  * Karsten Fleischer
26*4887Schin  * Omnium Software Engineering
27*4887Schin  * An der Luisenburg 7
28*4887Schin  * D-51379 Leverkusen
29*4887Schin  * Germany
30*4887Schin  *
31*4887Schin  * <K.Fleischer@omnium.de>
32*4887Schin  */
33*4887Schin 
34*4887Schin 
35*4887Schin #include "defs.h"
36*4887Schin #include "edit.h"
37*4887Schin 
38*4887Schin #if ! SHOPT_HISTEXPAND
39*4887Schin 
40*4887Schin NoN(hexpand)
41*4887Schin 
42*4887Schin #else
43*4887Schin 
44*4887Schin #include <ctype.h>
45*4887Schin 
46*4887Schin static char *modifiers = "htrepqxs&";
47*4887Schin static int mod_flags[] = { 0, 0, 0, 0, HIST_PRINT, HIST_QUOTE, HIST_QUOTE|HIST_QUOTE_BR, 0, 0 };
48*4887Schin 
49*4887Schin #define	DONE()	{flag |= HIST_ERROR; cp = 0; stakseek(0); goto done;}
50*4887Schin 
51*4887Schin struct subst
52*4887Schin {
53*4887Schin 	char *str[2];	/* [0] is "old", [1] is "new" string */
54*4887Schin };
55*4887Schin 
56*4887Schin 
57*4887Schin /*
58*4887Schin  * parse an /old/new/ string, delimiter expected as first char.
59*4887Schin  * if "old" not specified, keep sb->str[0]
60*4887Schin  * if "new" not specified, set sb->str[1] to empty string
61*4887Schin  * read up to third delimeter char, \n or \0, whichever comes first.
62*4887Schin  * return adress is one past the last valid char in s:
63*4887Schin  * - the address containing \n or \0 or
64*4887Schin  * - one char beyond the third delimiter
65*4887Schin  */
66*4887Schin 
67*4887Schin static char *parse_subst(const char *s, struct subst *sb)
68*4887Schin {
69*4887Schin 	char	*cp,del;
70*4887Schin 	int	off,n = 0;
71*4887Schin 
72*4887Schin 	/* build the strings on the stack, mainly for '&' substition in "new" */
73*4887Schin 	off = staktell();
74*4887Schin 
75*4887Schin 	/* init "new" with empty string */
76*4887Schin 	if(sb->str[1])
77*4887Schin 		free(sb->str[1]);
78*4887Schin 	sb->str[1] = strdup("");
79*4887Schin 
80*4887Schin 	/* get delimiter */
81*4887Schin 	del = *s;
82*4887Schin 
83*4887Schin 	cp = (char*) s + 1;
84*4887Schin 
85*4887Schin 	while(n < 2)
86*4887Schin 	{
87*4887Schin 		if(*cp == del || *cp == '\n' || *cp == '\0')
88*4887Schin 		{
89*4887Schin 			/* delimiter or EOL */
90*4887Schin 			if(staktell() != off)
91*4887Schin 			{
92*4887Schin 				/* dupe string on stack and rewind stack */
93*4887Schin 				stakputc('\0');
94*4887Schin 				if(sb->str[n])
95*4887Schin 					free(sb->str[n]);
96*4887Schin 				sb->str[n] = strdup(stakptr(off));
97*4887Schin 				stakseek(off);
98*4887Schin 			}
99*4887Schin 			n++;
100*4887Schin 
101*4887Schin 			/* if not delimiter, we've reached EOL. Get outta here. */
102*4887Schin 			if(*cp != del)
103*4887Schin 				break;
104*4887Schin 		}
105*4887Schin 		else if(*cp == '\\')
106*4887Schin 		{
107*4887Schin 			if(*(cp+1) == del)	/* quote delimiter */
108*4887Schin 			{
109*4887Schin 				stakputc(del);
110*4887Schin 				cp++;
111*4887Schin 			}
112*4887Schin 			else if(*(cp+1) == '&' && n == 1)
113*4887Schin 			{		/* quote '&' only in "new" */
114*4887Schin 				stakputc('&');
115*4887Schin 				cp++;
116*4887Schin 			}
117*4887Schin 			else
118*4887Schin 				stakputc('\\');
119*4887Schin 		}
120*4887Schin 		else if(*cp == '&' && n == 1 && sb->str[0])
121*4887Schin 			/* substitute '&' with "old" in "new" */
122*4887Schin 			stakputs(sb->str[0]);
123*4887Schin 		else
124*4887Schin 			stakputc(*cp);
125*4887Schin 		cp++;
126*4887Schin 	}
127*4887Schin 
128*4887Schin 	/* rewind stack */
129*4887Schin 	stakseek(off);
130*4887Schin 
131*4887Schin 	return cp;
132*4887Schin }
133*4887Schin 
134*4887Schin /*
135*4887Schin  * history expansion main routine
136*4887Schin  */
137*4887Schin 
138*4887Schin int hist_expand(const char *ln, char **xp)
139*4887Schin {
140*4887Schin 	int	off,	/* stack offset */
141*4887Schin 		q,	/* quotation flags */
142*4887Schin 		p,	/* flag */
143*4887Schin 		c,	/* current char */
144*4887Schin 		flag=0;	/* HIST_* flags */
145*4887Schin 	Sfoff_t	n,	/* history line number, counter, etc. */
146*4887Schin 		i,	/* counter */
147*4887Schin 		w[2];	/* word range */
148*4887Schin 	char	*sp,	/* stack pointer */
149*4887Schin 		*cp,	/* current char in ln */
150*4887Schin 		*str,	/* search string */
151*4887Schin 		*evp,	/* event/word designator string, for error msgs */
152*4887Schin 		*cc=0,	/* copy of current line up to cp; temp ptr */
153*4887Schin 		hc[3],	/* default histchars */
154*4887Schin 		*qc="\'\"`";	/* quote characters */
155*4887Schin 	Sfio_t	*ref=0,	/* line referenced by event designator */
156*4887Schin 		*tmp=0,	/* temporary line buffer */
157*4887Schin 		*tmp2=0;/* temporary line buffer */
158*4887Schin 	Histloc_t hl;	/* history location */
159*4887Schin 	static Namval_t *np = 0;	/* histchars variable */
160*4887Schin 	static struct subst	sb = {0,0};	/* substition strings */
161*4887Schin 	static Sfio_t	*wm=0;	/* word match from !?string? event designator */
162*4887Schin 
163*4887Schin 	if(!wm)
164*4887Schin 		wm = sfopen(NULL, NULL, "swr");
165*4887Schin 
166*4887Schin 	hc[0] = '!';
167*4887Schin 	hc[1] = '^';
168*4887Schin 	hc[2] = 0;
169*4887Schin 	if((np = nv_open("histchars",sh.var_tree,0)) && (cp = nv_getval(np)))
170*4887Schin 	{
171*4887Schin 		if(cp[0])
172*4887Schin 		{
173*4887Schin 			hc[0] = cp[0];
174*4887Schin 			if(cp[1])
175*4887Schin 			{
176*4887Schin 				hc[1] = cp[1];
177*4887Schin 				if(cp[2])
178*4887Schin 					hc[2] = cp[2];
179*4887Schin 			}
180*4887Schin 		}
181*4887Schin 	}
182*4887Schin 
183*4887Schin 	/* save shell stack */
184*4887Schin 	if(off = staktell())
185*4887Schin 		sp = stakfreeze(0);
186*4887Schin 
187*4887Schin 	cp = (char*)ln;
188*4887Schin 
189*4887Schin 	while(cp && *cp)
190*4887Schin 	{
191*4887Schin 		/* read until event/quick substitution/comment designator */
192*4887Schin 		if((*cp != hc[0] && *cp != hc[1] && *cp != hc[2])
193*4887Schin 		   || (*cp == hc[1] && cp != ln))
194*4887Schin 		{
195*4887Schin 			if(*cp == '\\')	/* skip escaped designators */
196*4887Schin 				stakputc(*cp++);
197*4887Schin 			else if(*cp == '\'') /* skip quoted designators */
198*4887Schin 			{
199*4887Schin 				do
200*4887Schin 					stakputc(*cp);
201*4887Schin 				while(*++cp && *cp != '\'');
202*4887Schin 			}
203*4887Schin 			stakputc(*cp++);
204*4887Schin 			continue;
205*4887Schin 		}
206*4887Schin 
207*4887Schin 		if(hc[2] && *cp == hc[2]) /* history comment designator, skip rest of line */
208*4887Schin 		{
209*4887Schin 			stakputc(*cp++);
210*4887Schin 			stakputs(cp);
211*4887Schin 			DONE();
212*4887Schin 		}
213*4887Schin 
214*4887Schin 		n = -1;
215*4887Schin 		str = 0;
216*4887Schin 		flag &= HIST_EVENT; /* save event flag for returning later */
217*4887Schin 		evp = cp;
218*4887Schin 		ref = 0;
219*4887Schin 
220*4887Schin 		if(*cp == hc[1]) /* shortcut substitution */
221*4887Schin 		{
222*4887Schin 			flag |= HIST_QUICKSUBST;
223*4887Schin 			goto getline;
224*4887Schin 		}
225*4887Schin 
226*4887Schin 		if(*cp == hc[0] && *(cp+1) == hc[0]) /* refer to line -1 */
227*4887Schin 		{
228*4887Schin 			cp += 2;
229*4887Schin 			goto getline;
230*4887Schin 		}
231*4887Schin 
232*4887Schin 		switch(c = *++cp) {
233*4887Schin 		case ' ':
234*4887Schin 		case '\t':
235*4887Schin 		case '\n':
236*4887Schin 		case '\0':
237*4887Schin 		case '=':
238*4887Schin 		case '(':
239*4887Schin 			stakputc(hc[0]);
240*4887Schin 			continue;
241*4887Schin 		case '#': /* the line up to current position */
242*4887Schin 			flag |= HIST_HASH;
243*4887Schin 			cp++;
244*4887Schin 			n = staktell(); /* terminate string and dup */
245*4887Schin 			stakputc('\0');
246*4887Schin 			cc = strdup(stakptr(0));
247*4887Schin 			stakseek(n); /* remove null byte again */
248*4887Schin 			ref = sfopen(ref, cc, "s"); /* open as file */
249*4887Schin 			n = 0; /* skip history file referencing */
250*4887Schin 			break;
251*4887Schin 		case '-': /* back reference by number */
252*4887Schin 			if(!isdigit(*(cp+1)))
253*4887Schin 				goto string_event;
254*4887Schin 			cp++;
255*4887Schin 		case '0': /* reference by number */
256*4887Schin 		case '1':
257*4887Schin 		case '2':
258*4887Schin 		case '3':
259*4887Schin 		case '4':
260*4887Schin 		case '5':
261*4887Schin 		case '6':
262*4887Schin 		case '7':
263*4887Schin 		case '8':
264*4887Schin 		case '9':
265*4887Schin 			n = 0;
266*4887Schin 			while(isdigit(*cp))
267*4887Schin 				n = n * 10 + (*cp++) - '0';
268*4887Schin 			if(c == '-')
269*4887Schin 				n = -n;
270*4887Schin 			break;
271*4887Schin 		case '$':
272*4887Schin 			n = -1;
273*4887Schin 		case ':':
274*4887Schin 			break;
275*4887Schin 		case '?':
276*4887Schin 			cp++;
277*4887Schin 			flag |= HIST_QUESTION;
278*4887Schin 		string_event:
279*4887Schin 		default:
280*4887Schin 			/* read until end of string or word designator/modifier */
281*4887Schin 			str = cp;
282*4887Schin 			while(*cp)
283*4887Schin 			{
284*4887Schin 				cp++;
285*4887Schin 				if((!(flag&HIST_QUESTION) &&
286*4887Schin 				   (*cp == ':' || isspace(*cp)
287*4887Schin 				    || *cp == '^' || *cp == '$'
288*4887Schin 				    || *cp == '*' || *cp == '-'
289*4887Schin 				    || *cp == '%')
290*4887Schin 				   )
291*4887Schin 				   || ((flag&HIST_QUESTION) && (*cp == '?' || *cp == '\n')))
292*4887Schin 				{
293*4887Schin 					c = *cp;
294*4887Schin 					*cp = '\0';
295*4887Schin 				}
296*4887Schin 			}
297*4887Schin 			break;
298*4887Schin 		}
299*4887Schin 
300*4887Schin getline:
301*4887Schin 		flag |= HIST_EVENT;
302*4887Schin 		if(str)	/* !string or !?string? event designator */
303*4887Schin 		{
304*4887Schin 
305*4887Schin 			/* search history for string */
306*4887Schin 			hl = hist_find(sh.hist_ptr, str,
307*4887Schin 				       sh.hist_ptr->histind,
308*4887Schin 				       flag&HIST_QUESTION, -1);
309*4887Schin 			if((n = hl.hist_command) == -1)
310*4887Schin 				n = 0;	/* not found */
311*4887Schin 		}
312*4887Schin 		if(n)
313*4887Schin 		{
314*4887Schin 			if(n < 0) /* determine index for backref */
315*4887Schin 				n = sh.hist_ptr->histind + n;
316*4887Schin 			/* search and use history file if found */
317*4887Schin 			if(n > 0 && hist_seek(sh.hist_ptr, n) != -1)
318*4887Schin 				ref = sh.hist_ptr->histfp;
319*4887Schin 
320*4887Schin 		}
321*4887Schin 		if(!ref)
322*4887Schin 		{
323*4887Schin 			/* string not found or command # out of range */
324*4887Schin 			c = *cp;
325*4887Schin 			*cp = '\0';
326*4887Schin 			errormsg(SH_DICT, ERROR_ERROR, "%s: event not found", evp);
327*4887Schin 			*cp = c;
328*4887Schin 			DONE();
329*4887Schin 		}
330*4887Schin 
331*4887Schin 		if(str) /* string search: restore orig. line */
332*4887Schin 		{
333*4887Schin 			if(flag&HIST_QUESTION)
334*4887Schin 				*cp++ = c; /* skip second question mark */
335*4887Schin 			else
336*4887Schin 				*cp = c;
337*4887Schin 		}
338*4887Schin 
339*4887Schin 		/* colon introduces either word designators or modifiers */
340*4887Schin 		if(*(evp = cp) == ':')
341*4887Schin 			cp++;
342*4887Schin 
343*4887Schin 		w[0] = 0; /* -1 means last word, -2 means match from !?string? */
344*4887Schin 		w[1] = -1; /* -1 means last word, -2 means suppress last word */
345*4887Schin 
346*4887Schin 		if(flag & HIST_QUICKSUBST) /* shortcut substitution */
347*4887Schin 			goto getsel;
348*4887Schin 
349*4887Schin 		n = 0;
350*4887Schin 		while(n < 2)
351*4887Schin 		{
352*4887Schin 			switch(c = *cp++) {
353*4887Schin 			case '^': /* first word */
354*4887Schin 				if(n == 0)
355*4887Schin 				{
356*4887Schin 					w[0] = w[1] = 1;
357*4887Schin 					goto skip;
358*4887Schin 				}
359*4887Schin 				else
360*4887Schin 					goto skip2;
361*4887Schin 			case '$': /* last word */
362*4887Schin 				w[n] = -1;
363*4887Schin 				goto skip;
364*4887Schin 			case '%': /* match from !?string? event designator */
365*4887Schin 				if(n == 0)
366*4887Schin 				{
367*4887Schin 					if(!str)
368*4887Schin 					{
369*4887Schin 						w[0] = 0;
370*4887Schin 						w[1] = -1;
371*4887Schin 						ref = wm;
372*4887Schin 					}
373*4887Schin 					else
374*4887Schin 					{
375*4887Schin 						w[0] = -2;
376*4887Schin 						w[1] = sftell(ref) + hl.hist_char;
377*4887Schin 					}
378*4887Schin 					sfseek(wm, 0, SEEK_SET);
379*4887Schin 					goto skip;
380*4887Schin 				}
381*4887Schin 			default:
382*4887Schin 			skip2:
383*4887Schin 				cp--;
384*4887Schin 				n = 2;
385*4887Schin 				break;
386*4887Schin 			case '*': /* until last word */
387*4887Schin 				if(n == 0)
388*4887Schin 					w[0] = 1;
389*4887Schin 				w[1] = -1;
390*4887Schin 			skip:
391*4887Schin 				flag |= HIST_WORDDSGN;
392*4887Schin 				n = 2;
393*4887Schin 				break;
394*4887Schin 			case '-': /* until last word or specified index */
395*4887Schin 				w[1] = -2;
396*4887Schin 				flag |= HIST_WORDDSGN;
397*4887Schin 				n = 1;
398*4887Schin 				break;
399*4887Schin 			case '0':
400*4887Schin 			case '1':
401*4887Schin 			case '2':
402*4887Schin 			case '3':
403*4887Schin 			case '4':
404*4887Schin 			case '5':
405*4887Schin 			case '6':
406*4887Schin 			case '7':
407*4887Schin 			case '8':
408*4887Schin 			case '9': /* specify index */
409*4887Schin 				if((*evp == ':') || w[1] == -2)
410*4887Schin 				{
411*4887Schin 					w[n] = c - '0';
412*4887Schin 					while(isdigit(c=*cp++))
413*4887Schin 						w[n] = w[n] * 10 + c - '0';
414*4887Schin 					flag |= HIST_WORDDSGN;
415*4887Schin 					if(n == 0)
416*4887Schin 						w[1] = w[0];
417*4887Schin 					n++;
418*4887Schin 				}
419*4887Schin 				else
420*4887Schin 					n = 2;
421*4887Schin 				cp--;
422*4887Schin 				break;
423*4887Schin 			}
424*4887Schin 		}
425*4887Schin 
426*4887Schin 		if(w[0] != -2 && w[1] > 0 && w[0] > w[1])
427*4887Schin 		{
428*4887Schin 			c = *cp;
429*4887Schin 			*cp = '\0';
430*4887Schin 			errormsg(SH_DICT, ERROR_ERROR, "%s: bad word specifier", evp);
431*4887Schin 			*cp = c;
432*4887Schin 			DONE();
433*4887Schin 		}
434*4887Schin 
435*4887Schin 		/* no valid word designator after colon, rewind */
436*4887Schin 		if(!(flag & HIST_WORDDSGN) && (*evp == ':'))
437*4887Schin 			cp = evp;
438*4887Schin 
439*4887Schin getsel:
440*4887Schin 		/* open temp buffer, let sfio do the (re)allocation */
441*4887Schin 		tmp = sfopen(NULL, NULL, "swr");
442*4887Schin 
443*4887Schin 		/* push selected words into buffer, squash
444*4887Schin 		   whitespace into single blank or a newline */
445*4887Schin 		n = i = q = 0;
446*4887Schin 
447*4887Schin 		while((c = sfgetc(ref)) > 0)
448*4887Schin 		{
449*4887Schin 			if(isspace(c))
450*4887Schin 			{
451*4887Schin 				flag |= (c == '\n' ? HIST_NEWLINE : 0);
452*4887Schin 				continue;
453*4887Schin 			}
454*4887Schin 
455*4887Schin 			if(n >= w[0] && ((w[0] != -2) ? (w[1] < 0 || n <= w[1]) : 1))
456*4887Schin 			{
457*4887Schin 				if(w[0] < 0)
458*4887Schin 					sfseek(tmp, 0, SEEK_SET);
459*4887Schin 				else
460*4887Schin 					i = sftell(tmp);
461*4887Schin 
462*4887Schin 				if(i > 0)
463*4887Schin 					sfputc(tmp, flag & HIST_NEWLINE ? '\n' : ' ');
464*4887Schin 
465*4887Schin 				flag &= ~HIST_NEWLINE;
466*4887Schin 				p = 1;
467*4887Schin 			}
468*4887Schin 			else
469*4887Schin 				p = 0;
470*4887Schin 
471*4887Schin 			do
472*4887Schin 			{
473*4887Schin 				cc = strchr(qc, c);
474*4887Schin 				q ^= cc ? 1<<(int)(cc - qc) : 0;
475*4887Schin 				if(p)
476*4887Schin 					sfputc(tmp, c);
477*4887Schin 			}
478*4887Schin 			while((c = sfgetc(ref)) > 0  && (!isspace(c) || q));
479*4887Schin 
480*4887Schin 			if(w[0] == -2 && sftell(ref) > w[1])
481*4887Schin 				break;
482*4887Schin 
483*4887Schin 			flag |= (c == '\n' ? HIST_NEWLINE : 0);
484*4887Schin 			n++;
485*4887Schin 		}
486*4887Schin 		if(w[0] != -2 && w[1] >= 0 && w[1] >= n)
487*4887Schin 		{
488*4887Schin 			c = *cp;
489*4887Schin 			*cp = '\0';
490*4887Schin 			errormsg(SH_DICT, ERROR_ERROR, "%s: bad word specifier", evp);
491*4887Schin 			*cp = c;
492*4887Schin 			DONE();
493*4887Schin 		}
494*4887Schin 		else if(w[1] == -2)	/* skip last word */
495*4887Schin 			sfseek(tmp, i, SEEK_SET);
496*4887Schin 
497*4887Schin 		/* remove trailing newline */
498*4887Schin 		if(sftell(tmp))
499*4887Schin 		{
500*4887Schin 			sfseek(tmp, -1, SEEK_CUR);
501*4887Schin 			if(sfgetc(tmp) == '\n')
502*4887Schin 				sfungetc(tmp, '\n');
503*4887Schin 		}
504*4887Schin 
505*4887Schin 		sfputc(tmp, '\0');
506*4887Schin 
507*4887Schin 		if(str)
508*4887Schin 		{
509*4887Schin 			if(wm)
510*4887Schin 				sfclose(wm);
511*4887Schin 			wm = tmp;
512*4887Schin 		}
513*4887Schin 
514*4887Schin 		if(cc && (flag&HIST_HASH))
515*4887Schin 		{
516*4887Schin 			/* close !# temp file */
517*4887Schin 			sfclose(ref);
518*4887Schin 			flag &= ~HIST_HASH;
519*4887Schin 			free(cc);
520*4887Schin 			cc = 0;
521*4887Schin 		}
522*4887Schin 
523*4887Schin 		evp = cp;
524*4887Schin 
525*4887Schin 		/* selected line/words are now in buffer, now go for the modifiers */
526*4887Schin 		while(*cp == ':' || (flag & HIST_QUICKSUBST))
527*4887Schin 		{
528*4887Schin 			if(flag & HIST_QUICKSUBST)
529*4887Schin 			{
530*4887Schin 				flag &= ~HIST_QUICKSUBST;
531*4887Schin 				c = 's';
532*4887Schin 				cp--;
533*4887Schin 			}
534*4887Schin 			else
535*4887Schin 				c = *++cp;
536*4887Schin 
537*4887Schin 			sfseek(tmp, 0, SEEK_SET);
538*4887Schin 			tmp2 = sfopen(tmp2, NULL, "swr");
539*4887Schin 
540*4887Schin 			if(c == 'g') /* global substitution */
541*4887Schin 			{
542*4887Schin 				flag |= HIST_GLOBALSUBST;
543*4887Schin 				c = *++cp;
544*4887Schin 			}
545*4887Schin 
546*4887Schin 			if(cc = strchr(modifiers, c))
547*4887Schin 				flag |= mod_flags[cc - modifiers];
548*4887Schin 			else
549*4887Schin 			{
550*4887Schin 				errormsg(SH_DICT, ERROR_ERROR, "%c: unrecognized history modifier", c);
551*4887Schin 				DONE();
552*4887Schin 			}
553*4887Schin 
554*4887Schin 			if(c == 'h' || c == 'r') /* head or base */
555*4887Schin 			{
556*4887Schin 				n = -1;
557*4887Schin 				while((c = sfgetc(tmp)) > 0)
558*4887Schin 				{	/* remember position of / or . */
559*4887Schin 					if((c == '/' && *cp == 'h') || (c == '.' && *cp == 'r'))
560*4887Schin 						n = sftell(tmp2);
561*4887Schin 					sfputc(tmp2, c);
562*4887Schin 				}
563*4887Schin 				if(n > 0)
564*4887Schin 				{	 /* rewind to last / or . */
565*4887Schin 					sfseek(tmp2, n, SEEK_SET);
566*4887Schin 					/* end string there */
567*4887Schin 					sfputc(tmp2, '\0');
568*4887Schin 				}
569*4887Schin 			}
570*4887Schin 			else if(c == 't' || c == 'e') /* tail or suffix */
571*4887Schin 			{
572*4887Schin 				n = 0;
573*4887Schin 				while((c = sfgetc(tmp)) > 0)
574*4887Schin 				{	/* remember position of / or . */
575*4887Schin 					if((c == '/' && *cp == 't') || (c == '.' && *cp == 'e'))
576*4887Schin 						n = sftell(tmp);
577*4887Schin 				}
578*4887Schin 				/* rewind to last / or . */
579*4887Schin 				sfseek(tmp, n, SEEK_SET);
580*4887Schin 				/* copy from there on */
581*4887Schin 				while((c = sfgetc(tmp)) > 0)
582*4887Schin 					sfputc(tmp2, c);
583*4887Schin 			}
584*4887Schin 			else if(c == 's' || c == '&')
585*4887Schin 			{
586*4887Schin 				cp++;
587*4887Schin 
588*4887Schin 				if(c == 's')
589*4887Schin 				{
590*4887Schin 					/* preset old with match from !?string? */
591*4887Schin 					if(!sb.str[0] && wm)
592*4887Schin 						sb.str[0] = strdup(sfsetbuf(wm, (Void_t*)1, 0));
593*4887Schin 					cp = parse_subst(cp, &sb);
594*4887Schin 				}
595*4887Schin 
596*4887Schin 				if(!sb.str[0] || !sb.str[1])
597*4887Schin 				{
598*4887Schin 					c = *cp;
599*4887Schin 					*cp = '\0';
600*4887Schin 					errormsg(SH_DICT, ERROR_ERROR,
601*4887Schin 						 "%s%s: no previous substitution",
602*4887Schin 						(flag & HIST_QUICKSUBST) ? ":s" : "",
603*4887Schin 						evp);
604*4887Schin 					*cp = c;
605*4887Schin 					DONE();
606*4887Schin 				}
607*4887Schin 
608*4887Schin 				/* need pointer for strstr() */
609*4887Schin 				str = sfsetbuf(tmp, (Void_t*)1, 0);
610*4887Schin 
611*4887Schin 				flag |= HIST_SUBSTITUTE;
612*4887Schin 				while(flag & HIST_SUBSTITUTE)
613*4887Schin 				{
614*4887Schin 					/* find string */
615*4887Schin 					if(cc = strstr(str, sb.str[0]))
616*4887Schin 					{	/* replace it */
617*4887Schin 						c = *cc;
618*4887Schin 						*cc = '\0';
619*4887Schin 						sfputr(tmp2, str, -1);
620*4887Schin 						sfputr(tmp2, sb.str[1], -1);
621*4887Schin 						*cc = c;
622*4887Schin 						str = cc + strlen(sb.str[0]);
623*4887Schin 					}
624*4887Schin 					else if(!sftell(tmp2))
625*4887Schin 					{	/* not successfull */
626*4887Schin 						c = *cp;
627*4887Schin 						*cp = '\0';
628*4887Schin 						errormsg(SH_DICT, ERROR_ERROR,
629*4887Schin 							 "%s%s: substitution failed",
630*4887Schin 							(flag & HIST_QUICKSUBST) ? ":s" : "",
631*4887Schin 							evp);
632*4887Schin 						*cp = c;
633*4887Schin 						DONE();
634*4887Schin 					}
635*4887Schin 					/* loop if g modifier specified */
636*4887Schin 					if(!cc || !(flag & HIST_GLOBALSUBST))
637*4887Schin 						flag &= ~HIST_SUBSTITUTE;
638*4887Schin 				}
639*4887Schin 				/* output rest of line */
640*4887Schin 				sfputr(tmp2, str, -1);
641*4887Schin 				if(*cp)
642*4887Schin 					cp--;
643*4887Schin 			}
644*4887Schin 
645*4887Schin 			if(sftell(tmp2))
646*4887Schin 			{ /* if any substitions done, swap buffers */
647*4887Schin 				if(wm != tmp)
648*4887Schin 					sfclose(tmp);
649*4887Schin 				tmp = tmp2;
650*4887Schin 				tmp2 = 0;
651*4887Schin 			}
652*4887Schin 			cc = 0;
653*4887Schin 			if(*cp)
654*4887Schin 				cp++;
655*4887Schin 		}
656*4887Schin 
657*4887Schin 		/* flush temporary buffer to stack */
658*4887Schin 		if(tmp)
659*4887Schin 		{
660*4887Schin 			sfseek(tmp, 0, SEEK_SET);
661*4887Schin 
662*4887Schin 			if(flag & HIST_QUOTE)
663*4887Schin 				stakputc('\'');
664*4887Schin 
665*4887Schin 			while((c = sfgetc(tmp)) > 0)
666*4887Schin 			{
667*4887Schin 				if(isspace(c))
668*4887Schin 				{
669*4887Schin 					flag = flag & ~HIST_NEWLINE;
670*4887Schin 
671*4887Schin 					/* squash white space to either a
672*4887Schin 					   blank or a newline */
673*4887Schin 					do
674*4887Schin 						flag |= (c == '\n' ? HIST_NEWLINE : 0);
675*4887Schin 					while((c = sfgetc(tmp)) > 0 && isspace(c));
676*4887Schin 
677*4887Schin 					sfungetc(tmp, c);
678*4887Schin 
679*4887Schin 					c = (flag & HIST_NEWLINE) ? '\n' : ' ';
680*4887Schin 
681*4887Schin 					if(flag & HIST_QUOTE_BR)
682*4887Schin 					{
683*4887Schin 						stakputc('\'');
684*4887Schin 						stakputc(c);
685*4887Schin 						stakputc('\'');
686*4887Schin 					}
687*4887Schin 					else
688*4887Schin 						stakputc(c);
689*4887Schin 				}
690*4887Schin 				else if((c == '\'') && (flag & HIST_QUOTE))
691*4887Schin 				{
692*4887Schin 					stakputc('\'');
693*4887Schin 					stakputc('\\');
694*4887Schin 					stakputc(c);
695*4887Schin 					stakputc('\'');
696*4887Schin 				}
697*4887Schin 				else
698*4887Schin 					stakputc(c);
699*4887Schin 			}
700*4887Schin 			if(flag & HIST_QUOTE)
701*4887Schin 				stakputc('\'');
702*4887Schin 		}
703*4887Schin 	}
704*4887Schin 
705*4887Schin 	stakputc('\0');
706*4887Schin 
707*4887Schin done:
708*4887Schin 	if(cc && (flag&HIST_HASH))
709*4887Schin 	{
710*4887Schin 		/* close !# temp file */
711*4887Schin 		sfclose(ref);
712*4887Schin 		free(cc);
713*4887Schin 		cc = 0;
714*4887Schin 	}
715*4887Schin 
716*4887Schin 	/* error? */
717*4887Schin 	if(staktell() && !(flag & HIST_ERROR))
718*4887Schin 		*xp = strdup(stakfreeze(1));
719*4887Schin 
720*4887Schin 	/* restore shell stack */
721*4887Schin 	if(off)
722*4887Schin 		stakset(sp,off);
723*4887Schin 	else
724*4887Schin 		stakseek(0);
725*4887Schin 
726*4887Schin 	/* drop temporary files */
727*4887Schin 
728*4887Schin 	if(tmp && tmp != wm)
729*4887Schin 		sfclose(tmp);
730*4887Schin 	if(tmp2)
731*4887Schin 		sfclose(tmp2);
732*4887Schin 
733*4887Schin 	return (flag & HIST_ERROR ? HIST_ERROR : flag & HIST_FLAG_RETURN_MASK);
734*4887Schin }
735*4887Schin 
736*4887Schin #endif
737