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