xref: /onnv-gate/usr/src/lib/libshell/common/edit/hexpand.c (revision 12068:08a39a083754)
14887Schin /***********************************************************************
24887Schin *                                                                      *
34887Schin *               This software is part of the ast package               *
4*12068SRoger.Faulkner@Oracle.COM *          Copyright (c) 1982-2010 AT&T Intellectual Property          *
54887Schin *                      and is licensed under the                       *
64887Schin *                  Common Public License, Version 1.0                  *
78462SApril.Chin@Sun.COM *                    by AT&T Intellectual Property                     *
84887Schin *                                                                      *
94887Schin *                A copy of the License is available at                 *
104887Schin *            http://www.opensource.org/licenses/cpl1.0.txt             *
114887Schin *         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
124887Schin *                                                                      *
134887Schin *              Information and Software Systems Research               *
144887Schin *                            AT&T Research                             *
154887Schin *                           Florham Park NJ                            *
164887Schin *                                                                      *
174887Schin *                  David Korn <dgk@research.att.com>                   *
184887Schin *                                                                      *
194887Schin ***********************************************************************/
204887Schin #pragma prototyped
214887Schin /*
224887Schin  * bash style history expansion
234887Schin  *
244887Schin  * Author:
254887Schin  * Karsten Fleischer
264887Schin  * Omnium Software Engineering
274887Schin  * An der Luisenburg 7
284887Schin  * D-51379 Leverkusen
294887Schin  * Germany
304887Schin  *
314887Schin  * <K.Fleischer@omnium.de>
324887Schin  */
334887Schin 
344887Schin 
354887Schin #include "defs.h"
364887Schin #include "edit.h"
374887Schin 
384887Schin #if ! SHOPT_HISTEXPAND
394887Schin 
404887Schin NoN(hexpand)
414887Schin 
424887Schin #else
434887Schin 
444887Schin static char *modifiers = "htrepqxs&";
454887Schin static int mod_flags[] = { 0, 0, 0, 0, HIST_PRINT, HIST_QUOTE, HIST_QUOTE|HIST_QUOTE_BR, 0, 0 };
464887Schin 
474887Schin #define	DONE()	{flag |= HIST_ERROR; cp = 0; stakseek(0); goto done;}
484887Schin 
494887Schin struct subst
504887Schin {
514887Schin 	char *str[2];	/* [0] is "old", [1] is "new" string */
524887Schin };
534887Schin 
544887Schin 
554887Schin /*
564887Schin  * parse an /old/new/ string, delimiter expected as first char.
574887Schin  * if "old" not specified, keep sb->str[0]
584887Schin  * if "new" not specified, set sb->str[1] to empty string
594887Schin  * read up to third delimeter char, \n or \0, whichever comes first.
604887Schin  * return adress is one past the last valid char in s:
614887Schin  * - the address containing \n or \0 or
624887Schin  * - one char beyond the third delimiter
634887Schin  */
644887Schin 
654887Schin static char *parse_subst(const char *s, struct subst *sb)
664887Schin {
674887Schin 	char	*cp,del;
684887Schin 	int	off,n = 0;
694887Schin 
704887Schin 	/* build the strings on the stack, mainly for '&' substition in "new" */
714887Schin 	off = staktell();
724887Schin 
734887Schin 	/* init "new" with empty string */
744887Schin 	if(sb->str[1])
754887Schin 		free(sb->str[1]);
764887Schin 	sb->str[1] = strdup("");
774887Schin 
784887Schin 	/* get delimiter */
794887Schin 	del = *s;
804887Schin 
814887Schin 	cp = (char*) s + 1;
824887Schin 
834887Schin 	while(n < 2)
844887Schin 	{
854887Schin 		if(*cp == del || *cp == '\n' || *cp == '\0')
864887Schin 		{
874887Schin 			/* delimiter or EOL */
884887Schin 			if(staktell() != off)
894887Schin 			{
904887Schin 				/* dupe string on stack and rewind stack */
914887Schin 				stakputc('\0');
924887Schin 				if(sb->str[n])
934887Schin 					free(sb->str[n]);
944887Schin 				sb->str[n] = strdup(stakptr(off));
954887Schin 				stakseek(off);
964887Schin 			}
974887Schin 			n++;
984887Schin 
994887Schin 			/* if not delimiter, we've reached EOL. Get outta here. */
1004887Schin 			if(*cp != del)
1014887Schin 				break;
1024887Schin 		}
1034887Schin 		else if(*cp == '\\')
1044887Schin 		{
1054887Schin 			if(*(cp+1) == del)	/* quote delimiter */
1064887Schin 			{
1074887Schin 				stakputc(del);
1084887Schin 				cp++;
1094887Schin 			}
1104887Schin 			else if(*(cp+1) == '&' && n == 1)
1114887Schin 			{		/* quote '&' only in "new" */
1124887Schin 				stakputc('&');
1134887Schin 				cp++;
1144887Schin 			}
1154887Schin 			else
1164887Schin 				stakputc('\\');
1174887Schin 		}
1184887Schin 		else if(*cp == '&' && n == 1 && sb->str[0])
1194887Schin 			/* substitute '&' with "old" in "new" */
1204887Schin 			stakputs(sb->str[0]);
1214887Schin 		else
1224887Schin 			stakputc(*cp);
1234887Schin 		cp++;
1244887Schin 	}
1254887Schin 
1264887Schin 	/* rewind stack */
1274887Schin 	stakseek(off);
1284887Schin 
1294887Schin 	return cp;
1304887Schin }
1314887Schin 
1324887Schin /*
1334887Schin  * history expansion main routine
1344887Schin  */
1354887Schin 
1364887Schin int hist_expand(const char *ln, char **xp)
1374887Schin {
1384887Schin 	int	off,	/* stack offset */
1394887Schin 		q,	/* quotation flags */
1404887Schin 		p,	/* flag */
1414887Schin 		c,	/* current char */
1424887Schin 		flag=0;	/* HIST_* flags */
1434887Schin 	Sfoff_t	n,	/* history line number, counter, etc. */
1444887Schin 		i,	/* counter */
1454887Schin 		w[2];	/* word range */
1464887Schin 	char	*sp,	/* stack pointer */
1474887Schin 		*cp,	/* current char in ln */
1484887Schin 		*str,	/* search string */
1494887Schin 		*evp,	/* event/word designator string, for error msgs */
1504887Schin 		*cc=0,	/* copy of current line up to cp; temp ptr */
1514887Schin 		hc[3],	/* default histchars */
1524887Schin 		*qc="\'\"`";	/* quote characters */
1534887Schin 	Sfio_t	*ref=0,	/* line referenced by event designator */
1544887Schin 		*tmp=0,	/* temporary line buffer */
1554887Schin 		*tmp2=0;/* temporary line buffer */
1564887Schin 	Histloc_t hl;	/* history location */
1574887Schin 	static Namval_t *np = 0;	/* histchars variable */
1584887Schin 	static struct subst	sb = {0,0};	/* substition strings */
1594887Schin 	static Sfio_t	*wm=0;	/* word match from !?string? event designator */
1604887Schin 
1614887Schin 	if(!wm)
1624887Schin 		wm = sfopen(NULL, NULL, "swr");
1634887Schin 
1644887Schin 	hc[0] = '!';
1654887Schin 	hc[1] = '^';
1664887Schin 	hc[2] = 0;
1674887Schin 	if((np = nv_open("histchars",sh.var_tree,0)) && (cp = nv_getval(np)))
1684887Schin 	{
1694887Schin 		if(cp[0])
1704887Schin 		{
1714887Schin 			hc[0] = cp[0];
1724887Schin 			if(cp[1])
1734887Schin 			{
1744887Schin 				hc[1] = cp[1];
1754887Schin 				if(cp[2])
1764887Schin 					hc[2] = cp[2];
1774887Schin 			}
1784887Schin 		}
1794887Schin 	}
1804887Schin 
1814887Schin 	/* save shell stack */
1824887Schin 	if(off = staktell())
1834887Schin 		sp = stakfreeze(0);
1844887Schin 
1854887Schin 	cp = (char*)ln;
1864887Schin 
1874887Schin 	while(cp && *cp)
1884887Schin 	{
1894887Schin 		/* read until event/quick substitution/comment designator */
1904887Schin 		if((*cp != hc[0] && *cp != hc[1] && *cp != hc[2])
1914887Schin 		   || (*cp == hc[1] && cp != ln))
1924887Schin 		{
1934887Schin 			if(*cp == '\\')	/* skip escaped designators */
1944887Schin 				stakputc(*cp++);
1954887Schin 			else if(*cp == '\'') /* skip quoted designators */
1964887Schin 			{
1974887Schin 				do
1984887Schin 					stakputc(*cp);
1994887Schin 				while(*++cp && *cp != '\'');
2004887Schin 			}
2014887Schin 			stakputc(*cp++);
2024887Schin 			continue;
2034887Schin 		}
2044887Schin 
2054887Schin 		if(hc[2] && *cp == hc[2]) /* history comment designator, skip rest of line */
2064887Schin 		{
2074887Schin 			stakputc(*cp++);
2084887Schin 			stakputs(cp);
2094887Schin 			DONE();
2104887Schin 		}
2114887Schin 
2124887Schin 		n = -1;
2134887Schin 		str = 0;
2144887Schin 		flag &= HIST_EVENT; /* save event flag for returning later */
2154887Schin 		evp = cp;
2164887Schin 		ref = 0;
2174887Schin 
2184887Schin 		if(*cp == hc[1]) /* shortcut substitution */
2194887Schin 		{
2204887Schin 			flag |= HIST_QUICKSUBST;
2214887Schin 			goto getline;
2224887Schin 		}
2234887Schin 
2244887Schin 		if(*cp == hc[0] && *(cp+1) == hc[0]) /* refer to line -1 */
2254887Schin 		{
2264887Schin 			cp += 2;
2274887Schin 			goto getline;
2284887Schin 		}
2294887Schin 
2304887Schin 		switch(c = *++cp) {
2314887Schin 		case ' ':
2324887Schin 		case '\t':
2334887Schin 		case '\n':
2344887Schin 		case '\0':
2354887Schin 		case '=':
2364887Schin 		case '(':
2374887Schin 			stakputc(hc[0]);
2384887Schin 			continue;
2394887Schin 		case '#': /* the line up to current position */
2404887Schin 			flag |= HIST_HASH;
2414887Schin 			cp++;
2424887Schin 			n = staktell(); /* terminate string and dup */
2434887Schin 			stakputc('\0');
2444887Schin 			cc = strdup(stakptr(0));
2454887Schin 			stakseek(n); /* remove null byte again */
2464887Schin 			ref = sfopen(ref, cc, "s"); /* open as file */
2474887Schin 			n = 0; /* skip history file referencing */
2484887Schin 			break;
2494887Schin 		case '-': /* back reference by number */
2504887Schin 			if(!isdigit(*(cp+1)))
2514887Schin 				goto string_event;
2524887Schin 			cp++;
2534887Schin 		case '0': /* reference by number */
2544887Schin 		case '1':
2554887Schin 		case '2':
2564887Schin 		case '3':
2574887Schin 		case '4':
2584887Schin 		case '5':
2594887Schin 		case '6':
2604887Schin 		case '7':
2614887Schin 		case '8':
2624887Schin 		case '9':
2634887Schin 			n = 0;
2644887Schin 			while(isdigit(*cp))
2654887Schin 				n = n * 10 + (*cp++) - '0';
2664887Schin 			if(c == '-')
2674887Schin 				n = -n;
2684887Schin 			break;
2694887Schin 		case '$':
2704887Schin 			n = -1;
2714887Schin 		case ':':
2724887Schin 			break;
2734887Schin 		case '?':
2744887Schin 			cp++;
2754887Schin 			flag |= HIST_QUESTION;
2764887Schin 		string_event:
2774887Schin 		default:
2784887Schin 			/* read until end of string or word designator/modifier */
2794887Schin 			str = cp;
2804887Schin 			while(*cp)
2814887Schin 			{
2824887Schin 				cp++;
2834887Schin 				if((!(flag&HIST_QUESTION) &&
2844887Schin 				   (*cp == ':' || isspace(*cp)
2854887Schin 				    || *cp == '^' || *cp == '$'
2864887Schin 				    || *cp == '*' || *cp == '-'
2874887Schin 				    || *cp == '%')
2884887Schin 				   )
2894887Schin 				   || ((flag&HIST_QUESTION) && (*cp == '?' || *cp == '\n')))
2904887Schin 				{
2914887Schin 					c = *cp;
2924887Schin 					*cp = '\0';
2934887Schin 				}
2944887Schin 			}
2954887Schin 			break;
2964887Schin 		}
2974887Schin 
2984887Schin getline:
2994887Schin 		flag |= HIST_EVENT;
3004887Schin 		if(str)	/* !string or !?string? event designator */
3014887Schin 		{
3024887Schin 
3034887Schin 			/* search history for string */
3044887Schin 			hl = hist_find(sh.hist_ptr, str,
3054887Schin 				       sh.hist_ptr->histind,
3064887Schin 				       flag&HIST_QUESTION, -1);
3074887Schin 			if((n = hl.hist_command) == -1)
3084887Schin 				n = 0;	/* not found */
3094887Schin 		}
3104887Schin 		if(n)
3114887Schin 		{
3124887Schin 			if(n < 0) /* determine index for backref */
3134887Schin 				n = sh.hist_ptr->histind + n;
3144887Schin 			/* search and use history file if found */
3154887Schin 			if(n > 0 && hist_seek(sh.hist_ptr, n) != -1)
3164887Schin 				ref = sh.hist_ptr->histfp;
3174887Schin 
3184887Schin 		}
3194887Schin 		if(!ref)
3204887Schin 		{
3214887Schin 			/* string not found or command # out of range */
3224887Schin 			c = *cp;
3234887Schin 			*cp = '\0';
3244887Schin 			errormsg(SH_DICT, ERROR_ERROR, "%s: event not found", evp);
3254887Schin 			*cp = c;
3264887Schin 			DONE();
3274887Schin 		}
3284887Schin 
3294887Schin 		if(str) /* string search: restore orig. line */
3304887Schin 		{
3314887Schin 			if(flag&HIST_QUESTION)
3324887Schin 				*cp++ = c; /* skip second question mark */
3334887Schin 			else
3344887Schin 				*cp = c;
3354887Schin 		}
3364887Schin 
3374887Schin 		/* colon introduces either word designators or modifiers */
3384887Schin 		if(*(evp = cp) == ':')
3394887Schin 			cp++;
3404887Schin 
3414887Schin 		w[0] = 0; /* -1 means last word, -2 means match from !?string? */
3424887Schin 		w[1] = -1; /* -1 means last word, -2 means suppress last word */
3434887Schin 
3444887Schin 		if(flag & HIST_QUICKSUBST) /* shortcut substitution */
3454887Schin 			goto getsel;
3464887Schin 
3474887Schin 		n = 0;
3484887Schin 		while(n < 2)
3494887Schin 		{
3504887Schin 			switch(c = *cp++) {
3514887Schin 			case '^': /* first word */
3524887Schin 				if(n == 0)
3534887Schin 				{
3544887Schin 					w[0] = w[1] = 1;
3554887Schin 					goto skip;
3564887Schin 				}
3574887Schin 				else
3584887Schin 					goto skip2;
3594887Schin 			case '$': /* last word */
3604887Schin 				w[n] = -1;
3614887Schin 				goto skip;
3624887Schin 			case '%': /* match from !?string? event designator */
3634887Schin 				if(n == 0)
3644887Schin 				{
3654887Schin 					if(!str)
3664887Schin 					{
3674887Schin 						w[0] = 0;
3684887Schin 						w[1] = -1;
3694887Schin 						ref = wm;
3704887Schin 					}
3714887Schin 					else
3724887Schin 					{
3734887Schin 						w[0] = -2;
3744887Schin 						w[1] = sftell(ref) + hl.hist_char;
3754887Schin 					}
3764887Schin 					sfseek(wm, 0, SEEK_SET);
3774887Schin 					goto skip;
3784887Schin 				}
3794887Schin 			default:
3804887Schin 			skip2:
3814887Schin 				cp--;
3824887Schin 				n = 2;
3834887Schin 				break;
3844887Schin 			case '*': /* until last word */
3854887Schin 				if(n == 0)
3864887Schin 					w[0] = 1;
3874887Schin 				w[1] = -1;
3884887Schin 			skip:
3894887Schin 				flag |= HIST_WORDDSGN;
3904887Schin 				n = 2;
3914887Schin 				break;
3924887Schin 			case '-': /* until last word or specified index */
3934887Schin 				w[1] = -2;
3944887Schin 				flag |= HIST_WORDDSGN;
3954887Schin 				n = 1;
3964887Schin 				break;
3974887Schin 			case '0':
3984887Schin 			case '1':
3994887Schin 			case '2':
4004887Schin 			case '3':
4014887Schin 			case '4':
4024887Schin 			case '5':
4034887Schin 			case '6':
4044887Schin 			case '7':
4054887Schin 			case '8':
4064887Schin 			case '9': /* specify index */
4074887Schin 				if((*evp == ':') || w[1] == -2)
4084887Schin 				{
4094887Schin 					w[n] = c - '0';
4104887Schin 					while(isdigit(c=*cp++))
4114887Schin 						w[n] = w[n] * 10 + c - '0';
4124887Schin 					flag |= HIST_WORDDSGN;
4134887Schin 					if(n == 0)
4144887Schin 						w[1] = w[0];
4154887Schin 					n++;
4164887Schin 				}
4174887Schin 				else
4184887Schin 					n = 2;
4194887Schin 				cp--;
4204887Schin 				break;
4214887Schin 			}
4224887Schin 		}
4234887Schin 
4244887Schin 		if(w[0] != -2 && w[1] > 0 && w[0] > w[1])
4254887Schin 		{
4264887Schin 			c = *cp;
4274887Schin 			*cp = '\0';
4284887Schin 			errormsg(SH_DICT, ERROR_ERROR, "%s: bad word specifier", evp);
4294887Schin 			*cp = c;
4304887Schin 			DONE();
4314887Schin 		}
4324887Schin 
4334887Schin 		/* no valid word designator after colon, rewind */
4344887Schin 		if(!(flag & HIST_WORDDSGN) && (*evp == ':'))
4354887Schin 			cp = evp;
4364887Schin 
4374887Schin getsel:
4384887Schin 		/* open temp buffer, let sfio do the (re)allocation */
4394887Schin 		tmp = sfopen(NULL, NULL, "swr");
4404887Schin 
4414887Schin 		/* push selected words into buffer, squash
4424887Schin 		   whitespace into single blank or a newline */
4434887Schin 		n = i = q = 0;
4444887Schin 
4454887Schin 		while((c = sfgetc(ref)) > 0)
4464887Schin 		{
4474887Schin 			if(isspace(c))
4484887Schin 			{
4494887Schin 				flag |= (c == '\n' ? HIST_NEWLINE : 0);
4504887Schin 				continue;
4514887Schin 			}
4524887Schin 
4534887Schin 			if(n >= w[0] && ((w[0] != -2) ? (w[1] < 0 || n <= w[1]) : 1))
4544887Schin 			{
4554887Schin 				if(w[0] < 0)
4564887Schin 					sfseek(tmp, 0, SEEK_SET);
4574887Schin 				else
4584887Schin 					i = sftell(tmp);
4594887Schin 
4604887Schin 				if(i > 0)
4614887Schin 					sfputc(tmp, flag & HIST_NEWLINE ? '\n' : ' ');
4624887Schin 
4634887Schin 				flag &= ~HIST_NEWLINE;
4644887Schin 				p = 1;
4654887Schin 			}
4664887Schin 			else
4674887Schin 				p = 0;
4684887Schin 
4694887Schin 			do
4704887Schin 			{
4714887Schin 				cc = strchr(qc, c);
4724887Schin 				q ^= cc ? 1<<(int)(cc - qc) : 0;
4734887Schin 				if(p)
4744887Schin 					sfputc(tmp, c);
4754887Schin 			}
4764887Schin 			while((c = sfgetc(ref)) > 0  && (!isspace(c) || q));
4774887Schin 
4784887Schin 			if(w[0] == -2 && sftell(ref) > w[1])
4794887Schin 				break;
4804887Schin 
4814887Schin 			flag |= (c == '\n' ? HIST_NEWLINE : 0);
4824887Schin 			n++;
4834887Schin 		}
4844887Schin 		if(w[0] != -2 && w[1] >= 0 && w[1] >= n)
4854887Schin 		{
4864887Schin 			c = *cp;
4874887Schin 			*cp = '\0';
4884887Schin 			errormsg(SH_DICT, ERROR_ERROR, "%s: bad word specifier", evp);
4894887Schin 			*cp = c;
4904887Schin 			DONE();
4914887Schin 		}
4924887Schin 		else if(w[1] == -2)	/* skip last word */
4934887Schin 			sfseek(tmp, i, SEEK_SET);
4944887Schin 
4954887Schin 		/* remove trailing newline */
4964887Schin 		if(sftell(tmp))
4974887Schin 		{
4984887Schin 			sfseek(tmp, -1, SEEK_CUR);
4994887Schin 			if(sfgetc(tmp) == '\n')
5004887Schin 				sfungetc(tmp, '\n');
5014887Schin 		}
5024887Schin 
5034887Schin 		sfputc(tmp, '\0');
5044887Schin 
5054887Schin 		if(str)
5064887Schin 		{
5074887Schin 			if(wm)
5084887Schin 				sfclose(wm);
5094887Schin 			wm = tmp;
5104887Schin 		}
5114887Schin 
5124887Schin 		if(cc && (flag&HIST_HASH))
5134887Schin 		{
5144887Schin 			/* close !# temp file */
5154887Schin 			sfclose(ref);
5164887Schin 			flag &= ~HIST_HASH;
5174887Schin 			free(cc);
5184887Schin 			cc = 0;
5194887Schin 		}
5204887Schin 
5214887Schin 		evp = cp;
5224887Schin 
5234887Schin 		/* selected line/words are now in buffer, now go for the modifiers */
5244887Schin 		while(*cp == ':' || (flag & HIST_QUICKSUBST))
5254887Schin 		{
5264887Schin 			if(flag & HIST_QUICKSUBST)
5274887Schin 			{
5284887Schin 				flag &= ~HIST_QUICKSUBST;
5294887Schin 				c = 's';
5304887Schin 				cp--;
5314887Schin 			}
5324887Schin 			else
5334887Schin 				c = *++cp;
5344887Schin 
5354887Schin 			sfseek(tmp, 0, SEEK_SET);
5364887Schin 			tmp2 = sfopen(tmp2, NULL, "swr");
5374887Schin 
5384887Schin 			if(c == 'g') /* global substitution */
5394887Schin 			{
5404887Schin 				flag |= HIST_GLOBALSUBST;
5414887Schin 				c = *++cp;
5424887Schin 			}
5434887Schin 
5444887Schin 			if(cc = strchr(modifiers, c))
5454887Schin 				flag |= mod_flags[cc - modifiers];
5464887Schin 			else
5474887Schin 			{
5484887Schin 				errormsg(SH_DICT, ERROR_ERROR, "%c: unrecognized history modifier", c);
5494887Schin 				DONE();
5504887Schin 			}
5514887Schin 
5524887Schin 			if(c == 'h' || c == 'r') /* head or base */
5534887Schin 			{
5544887Schin 				n = -1;
5554887Schin 				while((c = sfgetc(tmp)) > 0)
5564887Schin 				{	/* remember position of / or . */
5574887Schin 					if((c == '/' && *cp == 'h') || (c == '.' && *cp == 'r'))
5584887Schin 						n = sftell(tmp2);
5594887Schin 					sfputc(tmp2, c);
5604887Schin 				}
5614887Schin 				if(n > 0)
5624887Schin 				{	 /* rewind to last / or . */
5634887Schin 					sfseek(tmp2, n, SEEK_SET);
5644887Schin 					/* end string there */
5654887Schin 					sfputc(tmp2, '\0');
5664887Schin 				}
5674887Schin 			}
5684887Schin 			else if(c == 't' || c == 'e') /* tail or suffix */
5694887Schin 			{
5704887Schin 				n = 0;
5714887Schin 				while((c = sfgetc(tmp)) > 0)
5724887Schin 				{	/* remember position of / or . */
5734887Schin 					if((c == '/' && *cp == 't') || (c == '.' && *cp == 'e'))
5744887Schin 						n = sftell(tmp);
5754887Schin 				}
5764887Schin 				/* rewind to last / or . */
5774887Schin 				sfseek(tmp, n, SEEK_SET);
5784887Schin 				/* copy from there on */
5794887Schin 				while((c = sfgetc(tmp)) > 0)
5804887Schin 					sfputc(tmp2, c);
5814887Schin 			}
5824887Schin 			else if(c == 's' || c == '&')
5834887Schin 			{
5844887Schin 				cp++;
5854887Schin 
5864887Schin 				if(c == 's')
5874887Schin 				{
5884887Schin 					/* preset old with match from !?string? */
5894887Schin 					if(!sb.str[0] && wm)
5904887Schin 						sb.str[0] = strdup(sfsetbuf(wm, (Void_t*)1, 0));
5914887Schin 					cp = parse_subst(cp, &sb);
5924887Schin 				}
5934887Schin 
5944887Schin 				if(!sb.str[0] || !sb.str[1])
5954887Schin 				{
5964887Schin 					c = *cp;
5974887Schin 					*cp = '\0';
5984887Schin 					errormsg(SH_DICT, ERROR_ERROR,
5994887Schin 						 "%s%s: no previous substitution",
6004887Schin 						(flag & HIST_QUICKSUBST) ? ":s" : "",
6014887Schin 						evp);
6024887Schin 					*cp = c;
6034887Schin 					DONE();
6044887Schin 				}
6054887Schin 
6064887Schin 				/* need pointer for strstr() */
6074887Schin 				str = sfsetbuf(tmp, (Void_t*)1, 0);
6084887Schin 
6094887Schin 				flag |= HIST_SUBSTITUTE;
6104887Schin 				while(flag & HIST_SUBSTITUTE)
6114887Schin 				{
6124887Schin 					/* find string */
6134887Schin 					if(cc = strstr(str, sb.str[0]))
6144887Schin 					{	/* replace it */
6154887Schin 						c = *cc;
6164887Schin 						*cc = '\0';
6174887Schin 						sfputr(tmp2, str, -1);
6184887Schin 						sfputr(tmp2, sb.str[1], -1);
6194887Schin 						*cc = c;
6204887Schin 						str = cc + strlen(sb.str[0]);
6214887Schin 					}
6224887Schin 					else if(!sftell(tmp2))
6234887Schin 					{	/* not successfull */
6244887Schin 						c = *cp;
6254887Schin 						*cp = '\0';
6264887Schin 						errormsg(SH_DICT, ERROR_ERROR,
6274887Schin 							 "%s%s: substitution failed",
6284887Schin 							(flag & HIST_QUICKSUBST) ? ":s" : "",
6294887Schin 							evp);
6304887Schin 						*cp = c;
6314887Schin 						DONE();
6324887Schin 					}
6334887Schin 					/* loop if g modifier specified */
6344887Schin 					if(!cc || !(flag & HIST_GLOBALSUBST))
6354887Schin 						flag &= ~HIST_SUBSTITUTE;
6364887Schin 				}
6374887Schin 				/* output rest of line */
6384887Schin 				sfputr(tmp2, str, -1);
6394887Schin 				if(*cp)
6404887Schin 					cp--;
6414887Schin 			}
6424887Schin 
6434887Schin 			if(sftell(tmp2))
6444887Schin 			{ /* if any substitions done, swap buffers */
6454887Schin 				if(wm != tmp)
6464887Schin 					sfclose(tmp);
6474887Schin 				tmp = tmp2;
6484887Schin 				tmp2 = 0;
6494887Schin 			}
6504887Schin 			cc = 0;
6514887Schin 			if(*cp)
6524887Schin 				cp++;
6534887Schin 		}
6544887Schin 
6554887Schin 		/* flush temporary buffer to stack */
6564887Schin 		if(tmp)
6574887Schin 		{
6584887Schin 			sfseek(tmp, 0, SEEK_SET);
6594887Schin 
6604887Schin 			if(flag & HIST_QUOTE)
6614887Schin 				stakputc('\'');
6624887Schin 
6634887Schin 			while((c = sfgetc(tmp)) > 0)
6644887Schin 			{
6654887Schin 				if(isspace(c))
6664887Schin 				{
6674887Schin 					flag = flag & ~HIST_NEWLINE;
6684887Schin 
6694887Schin 					/* squash white space to either a
6704887Schin 					   blank or a newline */
6714887Schin 					do
6724887Schin 						flag |= (c == '\n' ? HIST_NEWLINE : 0);
6734887Schin 					while((c = sfgetc(tmp)) > 0 && isspace(c));
6744887Schin 
6754887Schin 					sfungetc(tmp, c);
6764887Schin 
6774887Schin 					c = (flag & HIST_NEWLINE) ? '\n' : ' ';
6784887Schin 
6794887Schin 					if(flag & HIST_QUOTE_BR)
6804887Schin 					{
6814887Schin 						stakputc('\'');
6824887Schin 						stakputc(c);
6834887Schin 						stakputc('\'');
6844887Schin 					}
6854887Schin 					else
6864887Schin 						stakputc(c);
6874887Schin 				}
6884887Schin 				else if((c == '\'') && (flag & HIST_QUOTE))
6894887Schin 				{
6904887Schin 					stakputc('\'');
6914887Schin 					stakputc('\\');
6924887Schin 					stakputc(c);
6934887Schin 					stakputc('\'');
6944887Schin 				}
6954887Schin 				else
6964887Schin 					stakputc(c);
6974887Schin 			}
6984887Schin 			if(flag & HIST_QUOTE)
6994887Schin 				stakputc('\'');
7004887Schin 		}
7014887Schin 	}
7024887Schin 
7034887Schin 	stakputc('\0');
7044887Schin 
7054887Schin done:
7064887Schin 	if(cc && (flag&HIST_HASH))
7074887Schin 	{
7084887Schin 		/* close !# temp file */
7094887Schin 		sfclose(ref);
7104887Schin 		free(cc);
7114887Schin 		cc = 0;
7124887Schin 	}
7134887Schin 
7144887Schin 	/* error? */
7154887Schin 	if(staktell() && !(flag & HIST_ERROR))
7164887Schin 		*xp = strdup(stakfreeze(1));
7174887Schin 
7184887Schin 	/* restore shell stack */
7194887Schin 	if(off)
7204887Schin 		stakset(sp,off);
7214887Schin 	else
7224887Schin 		stakseek(0);
7234887Schin 
7244887Schin 	/* drop temporary files */
7254887Schin 
7264887Schin 	if(tmp && tmp != wm)
7274887Schin 		sfclose(tmp);
7284887Schin 	if(tmp2)
7294887Schin 		sfclose(tmp2);
7304887Schin 
7314887Schin 	return (flag & HIST_ERROR ? HIST_ERROR : flag & HIST_FLAG_RETURN_MASK);
7324887Schin }
7334887Schin 
7344887Schin #endif
735