xref: /original-bsd/bin/csh/dol.c (revision f955cb91)
1 /*-
2  * Copyright (c) 1980, 1991 The Regents of the University of California.
3  * All rights reserved.
4  *
5  * %sccs.include.redist.c%
6  */
7 
8 #ifndef lint
9 static char sccsid[] = "@(#)dol.c	5.15 (Berkeley) 07/22/91";
10 #endif /* not lint */
11 
12 #include <sys/types.h>
13 #include <fcntl.h>
14 #include <errno.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <unistd.h>
18 #if __STDC__
19 # include <stdarg.h>
20 #else
21 # include <varargs.h>
22 #endif
23 
24 #include "csh.h"
25 #include "extern.h"
26 
27 /*
28  * These routines perform variable substitution and quoting via ' and ".
29  * To this point these constructs have been preserved in the divided
30  * input words.  Here we expand variables and turn quoting via ' and " into
31  * QUOTE bits on characters (which prevent further interpretation).
32  * If the `:q' modifier was applied during history expansion, then
33  * some QUOTEing may have occurred already, so we dont "trim()" here.
34  */
35 
36 static int Dpeekc, Dpeekrd;	/* Peeks for DgetC and Dreadc */
37 static Char *Dcp, **Dvp;	/* Input vector for Dreadc */
38 
39 #define	DEOF	-1
40 
41 #define	unDgetC(c)	Dpeekc = c
42 
43 #define QUOTES		(_Q|_Q1|_ESC)	/* \ ' " ` */
44 
45 /*
46  * The following variables give the information about the current
47  * $ expansion, recording the current word position, the remaining
48  * words within this expansion, the count of remaining words, and the
49  * information about any : modifier which is being applied.
50  */
51 #define MAXWLEN (BUFSIZ - 4)
52 #define MAXMOD MAXWLEN		/* This cannot overflow	*/
53 static Char *dolp;		/* Remaining chars from this word */
54 static Char **dolnxt;		/* Further words */
55 static int dolcnt;		/* Count of further words */
56 static Char dolmod[MAXMOD];	/* : modifier character */
57 static int dolnmod;		/* Number of modifiers */
58 static int dolmcnt;		/* :gx -> 10000, else 1 */
59 
60 static void	 Dfix2 __P((Char **));
61 static Char 	*Dpack __P((Char *, Char *));
62 static int	 Dword __P((void));
63 static void	 dolerror __P((Char *));
64 static int	 DgetC __P((int));
65 static void	 Dgetdol __P((void));
66 static void	 fixDolMod __P((void));
67 static void	 setDolp __P((Char *));
68 static void	 unDredc __P((int));
69 static int	 Dredc __P((void));
70 static void	 Dtestq __P((int));
71 
72 
73 /*
74  * Fix up the $ expansions and quotations in the
75  * argument list to command t.
76  */
77 void
78 Dfix(t)
79     register struct command *t;
80 {
81     register Char **pp;
82     register Char *p;
83 
84     if (noexec)
85 	return;
86     /* Note that t_dcom isn't trimmed thus !...:q's aren't lost */
87     for (pp = t->t_dcom; p = *pp++;)
88 	for (; *p; p++) {
89 	    if (cmap(*p, _DOL | QUOTES)) {	/* $, \, ', ", ` */
90 		Dfix2(t->t_dcom);	/* found one */
91 		blkfree(t->t_dcom);
92 		t->t_dcom = gargv;
93 		gargv = 0;
94 		return;
95 	    }
96 	}
97 }
98 
99 /*
100  * $ substitute one word, for i/o redirection
101  */
102 Char   *
103 Dfix1(cp)
104     register Char *cp;
105 {
106     Char   *Dv[2];
107 
108     if (noexec)
109 	return (0);
110     Dv[0] = cp;
111     Dv[1] = NULL;
112     Dfix2(Dv);
113     if (gargc != 1) {
114 	setname(short2str(cp));
115 	stderror(ERR_NAME | ERR_AMBIG);
116     }
117     cp = Strsave(gargv[0]);
118     blkfree(gargv), gargv = 0;
119     return (cp);
120 }
121 
122 /*
123  * Subroutine to do actual fixing after state initialization.
124  */
125 static void
126 Dfix2(v)
127     Char  **v;
128 {
129     ginit();			/* Initialize glob's area pointers */
130     Dvp = v;
131     Dcp = STRNULL;		/* Setup input vector for Dreadc */
132     unDgetC(0);
133     unDredc(0);			/* Clear out any old peeks (at error) */
134     dolp = 0;
135     dolcnt = 0;			/* Clear out residual $ expands (...) */
136     while (Dword())
137 	continue;
138 }
139 
140 /*
141  * Pack up more characters in this word
142  */
143 static Char *
144 Dpack(wbuf, wp)
145     Char   *wbuf, *wp;
146 {
147     register int c;
148     register int i = MAXWLEN - (wp - wbuf);
149 
150     for (;;) {
151 	c = DgetC(DODOL);
152 	if (c == '\\') {
153 	    c = DgetC(0);
154 	    if (c == DEOF) {
155 		unDredc(c);
156 		*wp = 0;
157 		Gcat(STRNULL, wbuf);
158 		return (NULL);
159 	    }
160 	    if (c == '\n')
161 		c = ' ';
162 	    else
163 		c |= QUOTE;
164 	}
165 	if (c == DEOF) {
166 	    unDredc(c);
167 	    *wp = 0;
168 	    Gcat(STRNULL, wbuf);
169 	    return (NULL);
170 	}
171 	if (cmap(c, _SP | _NL | _Q | _Q1)) {	/* sp \t\n'"` */
172 	    unDgetC(c);
173 	    if (cmap(c, QUOTES))
174 		return (wp);
175 	    *wp++ = 0;
176 	    Gcat(STRNULL, wbuf);
177 	    return (NULL);
178 	}
179 	if (--i <= 0)
180 	    stderror(ERR_WTOOLONG);
181 	*wp++ = c;
182     }
183 }
184 
185 /*
186  * Get a word.  This routine is analogous to the routine
187  * word() in sh.lex.c for the main lexical input.  One difference
188  * here is that we don't get a newline to terminate our expansion.
189  * Rather, DgetC will return a DEOF when we hit the end-of-input.
190  */
191 static int
192 Dword()
193 {
194     register int c, c1;
195     Char    wbuf[BUFSIZ];
196     register Char *wp = wbuf;
197     register int i = MAXWLEN;
198     register bool dolflg;
199     bool    sofar = 0, done = 0;
200 
201     while (!done) {
202 	done = 1;
203 	c = DgetC(DODOL);
204 	switch (c) {
205 
206 	case DEOF:
207 	    if (sofar == 0)
208 		return (0);
209 	    /* finish this word and catch the code above the next time */
210 	    unDredc(c);
211 	    /* fall into ... */
212 
213 	case '\n':
214 	    *wp = 0;
215 	    Gcat(STRNULL, wbuf);
216 	    return (1);
217 
218 	case ' ':
219 	case '\t':
220 	    done = 0;
221 	    break;
222 
223 	case '`':
224 	    /* We preserve ` quotations which are done yet later */
225 	    *wp++ = c, --i;
226 	case '\'':
227 	case '"':
228 	    /*
229 	     * Note that DgetC never returns a QUOTES character from an
230 	     * expansion, so only true input quotes will get us here or out.
231 	     */
232 	    c1 = c;
233 	    dolflg = c1 == '"' ? DODOL : 0;
234 	    for (;;) {
235 		c = DgetC(dolflg);
236 		if (c == c1)
237 		    break;
238 		if (c == '\n' || c == DEOF)
239 		    stderror(ERR_UNMATCHED, c1);
240 		if ((c & (QUOTE | TRIM)) == ('\n' | QUOTE))
241 		    --wp, ++i;
242 		if (--i <= 0)
243 		    stderror(ERR_WTOOLONG);
244 		switch (c1) {
245 
246 		case '"':
247 		    /*
248 		     * Leave any `s alone for later. Other chars are all
249 		     * quoted, thus `...` can tell it was within "...".
250 		     */
251 		    *wp++ = c == '`' ? '`' : c | QUOTE;
252 		    break;
253 
254 		case '\'':
255 		    /* Prevent all further interpretation */
256 		    *wp++ = c | QUOTE;
257 		    break;
258 
259 		case '`':
260 		    /* Leave all text alone for later */
261 		    *wp++ = c;
262 		    break;
263 		}
264 	    }
265 	    if (c1 == '`')
266 		*wp++ = '`', --i;
267 	    sofar = 1;
268 	    if ((wp = Dpack(wbuf, wp)) == NULL)
269 		return (1);
270 	    else {
271 		i = MAXWLEN - (wp - wbuf);
272 		done = 0;
273 	    }
274 	    break;
275 
276 	case '\\':
277 	    c = DgetC(0);	/* No $ subst! */
278 	    if (c == '\n' || c == DEOF) {
279 		done = 0;
280 		break;
281 	    }
282 	    c |= QUOTE;
283 	    break;
284 	}
285 	if (done) {
286 	    unDgetC(c);
287 	    sofar = 1;
288 	    if ((wp = Dpack(wbuf, wp)) == NULL)
289 		return (1);
290 	    else {
291 		i = MAXWLEN - (wp - wbuf);
292 		done = 0;
293 	    }
294 	}
295     }
296     /* Really NOTREACHED */
297     return (0);
298 }
299 
300 
301 /*
302  * Get a character, performing $ substitution unless flag is 0.
303  * Any QUOTES character which is returned from a $ expansion is
304  * QUOTEd so that it will not be recognized above.
305  */
306 static int
307 DgetC(flag)
308     register int flag;
309 {
310     register int c;
311 
312 top:
313     if (c = Dpeekc) {
314 	Dpeekc = 0;
315 	return (c);
316     }
317     if (lap) {
318 	c = *lap++ & (QUOTE | TRIM);
319 	if (c == 0) {
320 	    lap = 0;
321 	    goto top;
322 	}
323 quotspec:
324 	if (cmap(c, QUOTES))
325 	    return (c | QUOTE);
326 	return (c);
327     }
328     if (dolp) {
329 	if (c = *dolp++ & (QUOTE | TRIM))
330 	    goto quotspec;
331 	if (dolcnt > 0) {
332 	    setDolp(*dolnxt++);
333 	    --dolcnt;
334 	    return (' ');
335 	}
336 	dolp = 0;
337     }
338     if (dolcnt > 0) {
339 	setDolp(*dolnxt++);
340 	--dolcnt;
341 	goto top;
342     }
343     c = Dredc();
344     if (c == '$' && flag) {
345 	Dgetdol();
346 	goto top;
347     }
348     return (c);
349 }
350 
351 static Char *nulvec[] = {0};
352 static struct varent nulargv = {nulvec, STRargv, 0};
353 
354 static void
355 dolerror(s)
356     Char   *s;
357 {
358     setname(short2str(s));
359     stderror(ERR_NAME | ERR_RANGE);
360 }
361 
362 /*
363  * Handle the multitudinous $ expansion forms.
364  * Ugh.
365  */
366 static void
367 Dgetdol()
368 {
369     register Char *np;
370     register struct varent *vp = NULL;
371     Char    name[4 * MAXVARLEN + 1];
372     int     c, sc;
373     int     subscr = 0, lwb = 1, upb = 0;
374     bool    dimen = 0, bitset = 0;
375     char    tnp;
376     Char    wbuf[BUFSIZ];
377 
378     dolnmod = dolmcnt = 0;
379     c = sc = DgetC(0);
380     if (c == '{')
381 	c = DgetC(0);		/* sc is { to take } later */
382     if ((c & TRIM) == '#')
383 	dimen++, c = DgetC(0);	/* $# takes dimension */
384     else if (c == '?')
385 	bitset++, c = DgetC(0);	/* $? tests existence */
386     switch (c) {
387 
388     case '$':
389 	if (dimen || bitset)
390 	    stderror(ERR_SYNTAX);
391 	setDolp(doldol);
392 	goto eatbrac;
393 
394     case '<' | QUOTE:
395 	if (bitset)
396 	    stderror(ERR_NOTALLOWED, "$?<");
397 	if (dimen)
398 	    stderror(ERR_NOTALLOWED, "$?#");
399 	for (np = wbuf; read(OLDSTD, &tnp, 1) == 1; np++) {
400 	    *np = tnp;
401 	    if (np >= &wbuf[BUFSIZ - 1])
402 		stderror(ERR_LTOOLONG);
403 	    if (SIGN_EXTEND_CHAR(tnp) <= 0 || tnp == '\n')
404 		break;
405 	}
406 	*np = 0;
407 	/*
408 	 * KLUDGE: dolmod is set here because it will cause setDolp to call
409 	 * domod and thus to copy wbuf. Otherwise setDolp would use it
410 	 * directly. If we saved it ourselves, no one would know when to free
411 	 * it. The actual function of the 'q' causes filename expansion not to
412 	 * be done on the interpolated value.
413 	 */
414 	dolmod[dolnmod++] = 'q';
415 	dolmcnt = 10000;
416 	setDolp(wbuf);
417 	goto eatbrac;
418 
419     case DEOF:
420     case '\n':
421 	stderror(ERR_SYNTAX);
422 	/* NOTREACHED */
423 	break;
424 
425     case '*':
426 	(void) Strcpy(name, STRargv);
427 	vp = adrof(STRargv);
428 	subscr = -1;		/* Prevent eating [...] */
429 	break;
430 
431     default:
432 	np = name;
433 	if (Isdigit(c)) {
434 	    if (dimen)
435 		stderror(ERR_NOTALLOWED, "$#<num>");
436 	    subscr = 0;
437 	    do {
438 		subscr = subscr * 10 + c - '0';
439 		c = DgetC(0);
440 	    } while (Isdigit(c));
441 	    unDredc(c);
442 	    if (subscr < 0) {
443 		dolerror(vp->v_name);
444 		return;
445 	    }
446 	    if (subscr == 0) {
447 		if (bitset) {
448 		    dolp = ffile ? STR1 : STR0;
449 		    goto eatbrac;
450 		}
451 		if (ffile == 0)
452 		    stderror(ERR_DOLZERO);
453 		fixDolMod();
454 		setDolp(ffile);
455 		goto eatbrac;
456 	    }
457 	    if (bitset)
458 		stderror(ERR_DOLQUEST);
459 	    vp = adrof(STRargv);
460 	    if (vp == 0) {
461 		vp = &nulargv;
462 		goto eatmod;
463 	    }
464 	    break;
465 	}
466 	if (!alnum(c))
467 	    stderror(ERR_VARALNUM);
468 	for (;;) {
469 	    *np++ = c;
470 	    c = DgetC(0);
471 	    if (!alnum(c))
472 		break;
473 	    if (np >= &name[MAXVARLEN])
474 		stderror(ERR_VARTOOLONG);
475 	}
476 	*np++ = 0;
477 	unDredc(c);
478 	vp = adrof(name);
479     }
480     if (bitset) {
481 	dolp = (vp || getenv(short2str(name))) ? STR1 : STR0;
482 	goto eatbrac;
483     }
484     if (vp == 0) {
485 	np = str2short(getenv(short2str(name)));
486 	if (np) {
487 	    fixDolMod();
488 	    setDolp(np);
489 	    goto eatbrac;
490 	}
491 	udvar(name);
492 	/* NOTREACHED */
493     }
494     c = DgetC(0);
495     upb = blklen(vp->vec);
496     if (dimen == 0 && subscr == 0 && c == '[') {
497 	np = name;
498 	for (;;) {
499 	    c = DgetC(DODOL);	/* Allow $ expand within [ ] */
500 	    if (c == ']')
501 		break;
502 	    if (c == '\n' || c == DEOF)
503 		stderror(ERR_INCBR);
504 	    if (np >= &name[sizeof(name) / sizeof(Char) - 2])
505 		stderror(ERR_VARTOOLONG);
506 	    *np++ = c;
507 	}
508 	*np = 0, np = name;
509 	if (dolp || dolcnt)	/* $ exp must end before ] */
510 	    stderror(ERR_EXPORD);
511 	if (!*np)
512 	    stderror(ERR_SYNTAX);
513 	if (Isdigit(*np)) {
514 	    int     i;
515 
516 	    for (i = 0; Isdigit(*np); i = i * 10 + *np++ - '0');
517 	    if ((i < 0 || i > upb) && !any("-*", *np)) {
518 		dolerror(vp->v_name);
519 		return;
520 	    }
521 	    lwb = i;
522 	    if (!*np)
523 		upb = lwb, np = STRstar;
524 	}
525 	if (*np == '*')
526 	    np++;
527 	else if (*np != '-')
528 	    stderror(ERR_MISSING, '-');
529 	else {
530 	    register int i = upb;
531 
532 	    np++;
533 	    if (Isdigit(*np)) {
534 		i = 0;
535 		while (Isdigit(*np))
536 		    i = i * 10 + *np++ - '0';
537 		if (i < 0 || i > upb) {
538 		    dolerror(vp->v_name);
539 		    return;
540 		}
541 	    }
542 	    if (i < lwb)
543 		upb = lwb - 1;
544 	    else
545 		upb = i;
546 	}
547 	if (lwb == 0) {
548 	    if (upb != 0) {
549 		dolerror(vp->v_name);
550 		return;
551 	    }
552 	    upb = -1;
553 	}
554 	if (*np)
555 	    stderror(ERR_SYNTAX);
556     }
557     else {
558 	if (subscr > 0)
559 	    if (subscr > upb)
560 		lwb = 1, upb = 0;
561 	    else
562 		lwb = upb = subscr;
563 	unDredc(c);
564     }
565     if (dimen) {
566 	Char   *cp = putn(upb - lwb + 1);
567 
568 	addla(cp);
569 	xfree((ptr_t) cp);
570     }
571     else {
572 eatmod:
573 	fixDolMod();
574 	dolnxt = &vp->vec[lwb - 1];
575 	dolcnt = upb - lwb + 1;
576     }
577 eatbrac:
578     if (sc == '{') {
579 	c = Dredc();
580 	if (c != '}')
581 	    stderror(ERR_MISSING, '}');
582     }
583 }
584 
585 static void
586 fixDolMod()
587 {
588     register int c;
589 
590     c = DgetC(0);
591     if (c == ':') {
592 	do {
593 	    c = DgetC(0), dolmcnt = 1;
594 	    if (c == 'g')
595 		c = DgetC(0), dolmcnt = 10000;
596 	    if (!any("htrqxe", c))
597 		stderror(ERR_BADMOD, c);
598 	    dolmod[dolnmod++] = c;
599 	    if (c == 'q')
600 		dolmcnt = 10000;
601 	}
602 	while ((c = DgetC(0)) == ':');
603 	unDredc(c);
604     }
605     else
606 	unDredc(c);
607 }
608 
609 static void
610 setDolp(cp)
611     register Char *cp;
612 {
613     register Char *dp;
614     int i;
615 
616     if (dolnmod == 0 || dolmcnt == 0) {
617 	dolp = cp;
618 	return;
619     }
620     dp = cp = Strsave(cp);
621     for (i = 0; i < dolnmod; i++)
622 	if ((dp = domod(cp, dolmod[i]))) {
623 	    xfree((ptr_t) cp);
624 	    cp = dp;
625 	    dolmcnt--;
626 	}
627 	else {
628 	    dp = cp;
629 	    break;
630 	}
631 
632     if (dp) {
633 	addla(dp);
634 	xfree((ptr_t) dp);
635     }
636     dolp = STRNULL;
637     if (seterr)
638 	stderror(ERR_OLD);
639 }
640 
641 static void
642 unDredc(c)
643     int     c;
644 {
645 
646     Dpeekrd = c;
647 }
648 
649 static int
650 Dredc()
651 {
652     register int c;
653 
654     if (c = Dpeekrd) {
655 	Dpeekrd = 0;
656 	return (c);
657     }
658     if (Dcp && (c = *Dcp++))
659 	return (c & (QUOTE | TRIM));
660     if (*Dvp == 0) {
661 	Dcp = 0;
662 	return (DEOF);
663     }
664     Dcp = *Dvp++;
665     return (' ');
666 }
667 
668 static void
669 Dtestq(c)
670     register int c;
671 {
672 
673     if (cmap(c, QUOTES))
674 	gflag = 1;
675 }
676 
677 /*
678  * Form a shell temporary file (in unit 0) from the words
679  * of the shell input up to EOF or a line the same as "term".
680  * Unit 0 should have been closed before this call.
681  */
682 void
683 /*ARGSUSED*/
684 heredoc(term)
685     Char *term;
686 {
687     register int c;
688     Char   *Dv[2];
689     Char    obuf[BUFSIZ], lbuf[BUFSIZ], mbuf[BUFSIZ];
690     int     ocnt, lcnt, mcnt;
691     register Char *lbp, *obp, *mbp;
692     Char  **vp;
693     bool    quoted;
694     char   *tmp;
695 
696     if (creat(tmp = short2str(shtemp), 0600) < 0)
697 	stderror(ERR_SYSTEM, tmp, strerror(errno));
698     (void) close(0);
699     if (open(tmp, O_RDWR) < 0) {
700 	int     oerrno = errno;
701 
702 	(void) unlink(tmp);
703 	errno = oerrno;
704 	stderror(ERR_SYSTEM, tmp, strerror(errno));
705     }
706     (void) unlink(tmp);		/* 0 0 inode! */
707     Dv[0] = term;
708     Dv[1] = NULL;
709     gflag = 0;
710     trim(Dv);
711     rscan(Dv, Dtestq);
712     quoted = gflag;
713     ocnt = BUFSIZ;
714     obp = obuf;
715     for (;;) {
716 	/*
717 	 * Read up a line
718 	 */
719 	lbp = lbuf;
720 	lcnt = BUFSIZ - 4;
721 	for (;;) {
722 	    c = readc(1);	/* 1 -> Want EOF returns */
723 	    if (c < 0 || c == '\n')
724 		break;
725 	    if (c &= TRIM) {
726 		*lbp++ = c;
727 		if (--lcnt < 0) {
728 		    setname("<<");
729 		    stderror(ERR_NAME | ERR_OVERFLOW);
730 		}
731 	    }
732 	}
733 	*lbp = 0;
734 
735 	/*
736 	 * Check for EOF or compare to terminator -- before expansion
737 	 */
738 	if (c < 0 || eq(lbuf, term)) {
739 	    (void) write(0, short2str(obuf), (size_t) (BUFSIZ - ocnt));
740 	    (void) lseek(0, 0l, L_SET);
741 	    return;
742 	}
743 
744 	/*
745 	 * If term was quoted or -n just pass it on
746 	 */
747 	if (quoted || noexec) {
748 	    *lbp++ = '\n';
749 	    *lbp = 0;
750 	    for (lbp = lbuf; c = *lbp++;) {
751 		*obp++ = c;
752 		if (--ocnt == 0) {
753 		    (void) write(0, short2str(obuf), BUFSIZ);
754 		    obp = obuf;
755 		    ocnt = BUFSIZ;
756 		}
757 	    }
758 	    continue;
759 	}
760 
761 	/*
762 	 * Term wasn't quoted so variable and then command expand the input
763 	 * line
764 	 */
765 	Dcp = lbuf;
766 	Dvp = Dv + 1;
767 	mbp = mbuf;
768 	mcnt = BUFSIZ - 4;
769 	for (;;) {
770 	    c = DgetC(DODOL);
771 	    if (c == DEOF)
772 		break;
773 	    if ((c &= TRIM) == 0)
774 		continue;
775 	    /* \ quotes \ $ ` here */
776 	    if (c == '\\') {
777 		c = DgetC(0);
778 		if (!any("$\\`", c))
779 		    unDgetC(c | QUOTE), c = '\\';
780 		else
781 		    c |= QUOTE;
782 	    }
783 	    *mbp++ = c;
784 	    if (--mcnt == 0) {
785 		setname("<<");
786 		stderror(ERR_NAME | ERR_OVERFLOW);
787 	    }
788 	}
789 	*mbp++ = 0;
790 
791 	/*
792 	 * If any ` in line do command substitution
793 	 */
794 	mbp = mbuf;
795 	if (any(short2str(mbp), '`')) {
796 	    /*
797 	     * 1 arg to dobackp causes substitution to be literal. Words are
798 	     * broken only at newlines so that all blanks and tabs are
799 	     * preserved.  Blank lines (null words) are not discarded.
800 	     */
801 	    vp = dobackp(mbuf, 1);
802 	}
803 	else
804 	    /* Setup trivial vector similar to return of dobackp */
805 	    Dv[0] = mbp, Dv[1] = NULL, vp = Dv;
806 
807 	/*
808 	 * Resurrect the words from the command substitution each separated by
809 	 * a newline.  Note that the last newline of a command substitution
810 	 * will have been discarded, but we put a newline after the last word
811 	 * because this represents the newline after the last input line!
812 	 */
813 	for (; *vp; vp++) {
814 	    for (mbp = *vp; *mbp; mbp++) {
815 		*obp++ = *mbp & TRIM;
816 		if (--ocnt == 0) {
817 		    (void) write(0, short2str(obuf), BUFSIZ);
818 		    obp = obuf;
819 		    ocnt = BUFSIZ;
820 		}
821 	    }
822 	    *obp++ = '\n';
823 	    if (--ocnt == 0) {
824 		(void) write(0, short2str(obuf), BUFSIZ);
825 		obp = obuf;
826 		ocnt = BUFSIZ;
827 	    }
828 	}
829 	if (pargv)
830 	    blkfree(pargv), pargv = 0;
831     }
832 }
833