date and time created 91/03/07 20:27:34 by bostic
[unix-history] / usr / src / bin / sh / eval.c
CommitLineData
b3d7414a
KB
1/*-
2 * Copyright (c) 1991 The Regents of the University of California.
3 * All rights reserved.
4 *
5 * This code is derived from software contributed to Berkeley by
6 * Kenneth Almquist.
7 *
8 * %sccs.include.redist.c%
9 */
10
11#ifndef lint
12static char sccsid[] = "@(#)eval.c 5.1 (Berkeley) %G%";
13#endif /* not lint */
14
15/*
16 * Evaluate a command.
17 */
18
19#include "shell.h"
20#include "nodes.h"
21#include "syntax.h"
22#include "expand.h"
23#include "parser.h"
24#include "jobs.h"
25#include "eval.h"
26#include "builtins.h"
27#include "options.h"
28#include "exec.h"
29#include "redir.h"
30#include "input.h"
31#include "output.h"
32#include "trap.h"
33#include "var.h"
34#include "memalloc.h"
35#include "error.h"
36#include "mystring.h"
37#include <signal.h>
38
39
40/* flags in argument to evaltree */
41#define EV_EXIT 01 /* exit after evaluating tree */
42#define EV_TESTED 02 /* exit status is checked; ignore -e flag */
43#define EV_BACKCMD 04 /* command executing within back quotes */
44
45
46/* reasons for skipping commands (see comment on breakcmd routine) */
47#define SKIPBREAK 1
48#define SKIPCONT 2
49#define SKIPFUNC 3
50
51MKINIT int evalskip; /* set if we are skipping commands */
52STATIC int skipcount; /* number of levels to skip */
53MKINIT int loopnest; /* current loop nesting level */
54int funcnest; /* depth of function calls */
55
56
57char *commandname;
58struct strlist *cmdenviron;
59int exitstatus; /* exit status of last command */
60
61
62#ifdef __STDC__
63STATIC void evalloop(union node *);
64STATIC void evalfor(union node *);
65STATIC void evalcase(union node *, int);
66STATIC void evalsubshell(union node *, int);
67STATIC void expredir(union node *);
68STATIC void evalpipe(union node *);
69STATIC void evalcommand(union node *, int, struct backcmd *);
70STATIC void prehash(union node *);
71#else
72STATIC void evalloop();
73STATIC void evalfor();
74STATIC void evalcase();
75STATIC void evalsubshell();
76STATIC void expredir();
77STATIC void evalpipe();
78STATIC void evalcommand();
79STATIC void prehash();
80#endif
81
82
83
84/*
85 * Called to reset things after an exception.
86 */
87
88#ifdef mkinit
89INCLUDE "eval.h"
90
91RESET {
92 evalskip = 0;
93 loopnest = 0;
94 funcnest = 0;
95}
96
97SHELLPROC {
98 exitstatus = 0;
99}
100#endif
101
102
103
104/*
105 * The eval builtin. Do you want clean, straight-forward semantics for
106 * your eval command? If so, read on....
107 */
108
109#ifdef ELIGANT
110evalcmd(argc, argv) char **argv; {
111 char **ap;
112
113 for (ap = argv + 1 ; *ap ; ap++) {
114 evalstring(*ap);
115 }
116 return exitstatus;
117}
118#else
119
120/*
121 * If, on the other hand, you prefer downright bogus semantics in the
122 * name of compatibility, here it is...
123 */
124
125evalcmd(argc, argv) char **argv; {
126 char **ap;
127 char *p, *q;
128
129 if (argc <= 1) {
130 p = nullstr;
131 } else if (argc == 2) {
132 p = argv[1];
133 } else {
134 STARTSTACKSTR(p);
135 ap = argv + 1;
136 for (;;) {
137 q = *ap++;
138 while (*q) {
139 STPUTC(*q++, p);
140 }
141 if (*ap == NULL)
142 break;
143 STPUTC(' ', p);
144 }
145 STPUTC('\0', p);
146 p = grabstackstr(p);
147 }
148 evalstring(p);
149 return exitstatus;
150}
151#endif
152
153
154
155/*
156 * Execute a command or commands contained in a string.
157 */
158
159void
160evalstring(s)
161 char *s;
162 {
163 union node *n;
164 struct stackmark smark;
165
166 setstackmark(&smark);
167 setinputstring(s, 1);
168 while ((n = parsecmd(0)) != NEOF) {
169 evaltree(n, 0);
170 popstackmark(&smark);
171 }
172 popfile();
173 popstackmark(&smark);
174}
175
176
177
178/*
179 * Evaluate a parse tree. The value is left in the global variable
180 * exitstatus.
181 */
182
183void
184evaltree(n, flags)
185 union node *n;
186 {
187 if (n == NULL) {
188 TRACE(("evaltree(NULL) called\n"));
189 return;
190 }
191 TRACE(("evaltree(0x%x: %d) called\n", (int)n, n->type));
192 switch (n->type) {
193 case NSEMI:
194 evaltree(n->nbinary.ch1, 0);
195 if (evalskip)
196 goto out;
197 evaltree(n->nbinary.ch2, flags);
198 break;
199 case NAND:
200 evaltree(n->nbinary.ch1, EV_TESTED);
201 if (evalskip || exitstatus != 0)
202 goto out;
203 evaltree(n->nbinary.ch2, flags);
204 break;
205 case NOR:
206 evaltree(n->nbinary.ch1, EV_TESTED);
207 if (evalskip || exitstatus == 0)
208 goto out;
209 evaltree(n->nbinary.ch2, flags);
210 break;
211 case NREDIR:
212 expredir(n->nredir.redirect);
213 redirect(n->nredir.redirect, REDIR_PUSH);
214 evaltree(n->nredir.n, flags);
215 popredir();
216 break;
217 case NSUBSHELL:
218 evalsubshell(n, flags);
219 break;
220 case NBACKGND:
221 evalsubshell(n, flags);
222 break;
223 case NIF:
224 evaltree(n->nif.test, EV_TESTED);
225 if (evalskip)
226 goto out;
227 if (exitstatus == 0) {
228 evaltree(n->nif.ifpart, flags);
229 } else if (n->nif.elsepart) {
230 evaltree(n->nif.elsepart, flags);
231 }
232 break;
233 case NWHILE:
234 case NUNTIL:
235 evalloop(n);
236 break;
237 case NFOR:
238 evalfor(n);
239 break;
240 case NCASE:
241 evalcase(n, flags);
242 break;
243 case NDEFUN:
244 defun(n->narg.text, n->narg.next);
245 exitstatus = 0;
246 break;
247 case NPIPE:
248 evalpipe(n);
249 break;
250 case NCMD:
251 evalcommand(n, flags, (struct backcmd *)NULL);
252 break;
253 default:
254 out1fmt("Node type = %d\n", n->type);
255 flushout(&output);
256 break;
257 }
258out:
259 if (pendingsigs)
260 dotrap();
261 if ((flags & EV_EXIT) || (eflag && exitstatus && !(flags & EV_TESTED)))
262 exitshell(exitstatus);
263}
264
265
266STATIC void
267evalloop(n)
268 union node *n;
269 {
270 int status;
271
272 loopnest++;
273 status = 0;
274 for (;;) {
275 evaltree(n->nbinary.ch1, EV_TESTED);
276 if (evalskip) {
277skipping: if (evalskip == SKIPCONT && --skipcount <= 0) {
278 evalskip = 0;
279 continue;
280 }
281 if (evalskip == SKIPBREAK && --skipcount <= 0)
282 evalskip = 0;
283 break;
284 }
285 if (n->type == NWHILE) {
286 if (exitstatus != 0)
287 break;
288 } else {
289 if (exitstatus == 0)
290 break;
291 }
292 evaltree(n->nbinary.ch2, 0);
293 status = exitstatus;
294 if (evalskip)
295 goto skipping;
296 }
297 loopnest--;
298 exitstatus = status;
299}
300
301
302
303STATIC void
304evalfor(n)
305 union node *n;
306 {
307 struct arglist arglist;
308 union node *argp;
309 struct strlist *sp;
310 struct stackmark smark;
311
312 setstackmark(&smark);
313 arglist.lastp = &arglist.list;
314 for (argp = n->nfor.args ; argp ; argp = argp->narg.next) {
315 expandarg(argp, &arglist, 1);
316 if (evalskip)
317 goto out;
318 }
319 *arglist.lastp = NULL;
320
321 exitstatus = 0;
322 loopnest++;
323 for (sp = arglist.list ; sp ; sp = sp->next) {
324 setvar(n->nfor.var, sp->text, 0);
325 evaltree(n->nfor.body, 0);
326 if (evalskip) {
327 if (evalskip == SKIPCONT && --skipcount <= 0) {
328 evalskip = 0;
329 continue;
330 }
331 if (evalskip == SKIPBREAK && --skipcount <= 0)
332 evalskip = 0;
333 break;
334 }
335 }
336 loopnest--;
337out:
338 popstackmark(&smark);
339}
340
341
342
343STATIC void
344evalcase(n, flags)
345 union node *n;
346 {
347 union node *cp;
348 union node *patp;
349 struct arglist arglist;
350 struct stackmark smark;
351
352 setstackmark(&smark);
353 arglist.lastp = &arglist.list;
354 expandarg(n->ncase.expr, &arglist, 0);
355 for (cp = n->ncase.cases ; cp && evalskip == 0 ; cp = cp->nclist.next) {
356 for (patp = cp->nclist.pattern ; patp ; patp = patp->narg.next) {
357 if (casematch(patp, arglist.list->text)) {
358 if (evalskip == 0) {
359 evaltree(cp->nclist.body, flags);
360 }
361 goto out;
362 }
363 }
364 }
365out:
366 popstackmark(&smark);
367}
368
369
370
371/*
372 * Kick off a subshell to evaluate a tree.
373 */
374
375STATIC void
376evalsubshell(n, flags)
377 union node *n;
378 {
379 struct job *jp;
380 int backgnd = (n->type == NBACKGND);
381
382 expredir(n->nredir.redirect);
383 jp = makejob(n, 1);
384 if (forkshell(jp, n, backgnd) == 0) {
385 if (backgnd)
386 flags &=~ EV_TESTED;
387 redirect(n->nredir.redirect, 0);
388 evaltree(n->nredir.n, flags | EV_EXIT); /* never returns */
389 }
390 if (! backgnd) {
391 INTOFF;
392 exitstatus = waitforjob(jp);
393 INTON;
394 }
395}
396
397
398
399/*
400 * Compute the names of the files in a redirection list.
401 */
402
403STATIC void
404expredir(n)
405 union node *n;
406 {
407 register union node *redir;
408
409 for (redir = n ; redir ; redir = redir->nfile.next) {
410 if (redir->type == NFROM
411 || redir->type == NTO
412 || redir->type == NAPPEND) {
413 struct arglist fn;
414 fn.lastp = &fn.list;
415 expandarg(redir->nfile.fname, &fn, 0);
416 redir->nfile.expfname = fn.list->text;
417 }
418 }
419}
420
421
422
423/*
424 * Evaluate a pipeline. All the processes in the pipeline are children
425 * of the process creating the pipeline. (This differs from some versions
426 * of the shell, which make the last process in a pipeline the parent
427 * of all the rest.)
428 */
429
430STATIC void
431evalpipe(n)
432 union node *n;
433 {
434 struct job *jp;
435 struct nodelist *lp;
436 int pipelen;
437 int prevfd;
438 int pip[2];
439
440 TRACE(("evalpipe(0x%x) called\n", (int)n));
441 pipelen = 0;
442 for (lp = n->npipe.cmdlist ; lp ; lp = lp->next)
443 pipelen++;
444 INTOFF;
445 jp = makejob(n, pipelen);
446 prevfd = -1;
447 for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) {
448 prehash(lp->n);
449 pip[1] = -1;
450 if (lp->next) {
451 if (pipe(pip) < 0) {
452 close(prevfd);
453 error("Pipe call failed");
454 }
455 }
456 if (forkshell(jp, lp->n, n->npipe.backgnd) == 0) {
457 INTON;
458 if (prevfd > 0) {
459 close(0);
460 copyfd(prevfd, 0);
461 close(prevfd);
462 }
463 if (pip[1] >= 0) {
464 close(pip[0]);
465 if (pip[1] != 1) {
466 close(1);
467 copyfd(pip[1], 1);
468 close(pip[1]);
469 }
470 }
471 evaltree(lp->n, EV_EXIT);
472 }
473 if (prevfd >= 0)
474 close(prevfd);
475 prevfd = pip[0];
476 close(pip[1]);
477 }
478 INTON;
479 if (n->npipe.backgnd == 0) {
480 INTOFF;
481 exitstatus = waitforjob(jp);
482 TRACE(("evalpipe: job done exit status %d\n", exitstatus));
483 INTON;
484 }
485}
486
487
488
489/*
490 * Execute a command inside back quotes. If it's a builtin command, we
491 * want to save its output in a block obtained from malloc. Otherwise
492 * we fork off a subprocess and get the output of the command via a pipe.
493 * Should be called with interrupts off.
494 */
495
496void
497evalbackcmd(n, result)
498 union node *n;
499 struct backcmd *result;
500 {
501 int pip[2];
502 struct job *jp;
503 struct stackmark smark; /* unnecessary */
504
505 setstackmark(&smark);
506 result->fd = -1;
507 result->buf = NULL;
508 result->nleft = 0;
509 result->jp = NULL;
510 if (n->type == NCMD) {
511 evalcommand(n, EV_BACKCMD, result);
512 } else {
513 if (pipe(pip) < 0)
514 error("Pipe call failed");
515 jp = makejob(n, 1);
516 if (forkshell(jp, n, FORK_NOJOB) == 0) {
517 FORCEINTON;
518 close(pip[0]);
519 if (pip[1] != 1) {
520 close(1);
521 copyfd(pip[1], 1);
522 close(pip[1]);
523 }
524 evaltree(n, EV_EXIT);
525 }
526 close(pip[1]);
527 result->fd = pip[0];
528 result->jp = jp;
529 }
530 popstackmark(&smark);
531 TRACE(("evalbackcmd done: fd=%d buf=0x%x nleft=%d jp=0x%x\n",
532 result->fd, result->buf, result->nleft, result->jp));
533}
534
535
536
537/*
538 * Execute a simple command.
539 */
540
541STATIC void
542evalcommand(cmd, flags, backcmd)
543 union node *cmd;
544 struct backcmd *backcmd;
545 {
546 struct stackmark smark;
547 union node *argp;
548 struct arglist arglist;
549 struct arglist varlist;
550 char **argv;
551 int argc;
552 char **envp;
553 int varflag;
554 struct strlist *sp;
555 register char *p;
556 int mode;
557 int pip[2];
558 struct cmdentry cmdentry;
559 struct job *jp;
560 struct jmploc jmploc;
561 struct jmploc *volatile savehandler;
562 char *volatile savecmdname;
563 volatile struct shparam saveparam;
564 struct localvar *volatile savelocalvars;
565 volatile int e;
566 char *lastarg;
567
568 /* First expand the arguments. */
569 TRACE(("evalcommand(0x%x, %d) called\n", (int)cmd, flags));
570 setstackmark(&smark);
571 arglist.lastp = &arglist.list;
572 varlist.lastp = &varlist.list;
573 varflag = 1;
574 for (argp = cmd->ncmd.args ; argp ; argp = argp->narg.next) {
575 p = argp->narg.text;
576 if (varflag && is_name(*p)) {
577 do {
578 p++;
579 } while (is_in_name(*p));
580 if (*p == '=') {
581 expandarg(argp, &varlist, 0);
582 continue;
583 }
584 }
585 expandarg(argp, &arglist, 1);
586 varflag = 0;
587 }
588 *arglist.lastp = NULL;
589 *varlist.lastp = NULL;
590 expredir(cmd->ncmd.redirect);
591 argc = 0;
592 for (sp = arglist.list ; sp ; sp = sp->next)
593 argc++;
594 argv = stalloc(sizeof (char *) * (argc + 1));
595 for (sp = arglist.list ; sp ; sp = sp->next)
596 *argv++ = sp->text;
597 *argv = NULL;
598 lastarg = NULL;
599 if (iflag && funcnest == 0 && argc > 0)
600 lastarg = argv[-1];
601 argv -= argc;
602
603 /* Print the command if xflag is set. */
604 if (xflag) {
605 outc('+', &errout);
606 for (sp = varlist.list ; sp ; sp = sp->next) {
607 outc(' ', &errout);
608 out2str(sp->text);
609 }
610 for (sp = arglist.list ; sp ; sp = sp->next) {
611 outc(' ', &errout);
612 out2str(sp->text);
613 }
614 outc('\n', &errout);
615 flushout(&errout);
616 }
617
618 /* Now locate the command. */
619 if (argc == 0) {
620 cmdentry.cmdtype = CMDBUILTIN;
621 cmdentry.u.index = BLTINCMD;
622 } else {
623 find_command(argv[0], &cmdentry, 1);
624 if (cmdentry.cmdtype == CMDUNKNOWN) { /* command not found */
625 exitstatus = 2;
626 flushout(&errout);
627 return;
628 }
629 /* implement the bltin builtin here */
630 if (cmdentry.cmdtype == CMDBUILTIN && cmdentry.u.index == BLTINCMD) {
631 for (;;) {
632 argv++;
633 if (--argc == 0)
634 break;
635 if ((cmdentry.u.index = find_builtin(*argv)) < 0) {
636 outfmt(&errout, "%s: not found\n", *argv);
637 exitstatus = 2;
638 flushout(&errout);
639 return;
640 }
641 if (cmdentry.u.index != BLTINCMD)
642 break;
643 }
644 }
645 }
646
647 /* Fork off a child process if necessary. */
648 if (cmd->ncmd.backgnd
649 || cmdentry.cmdtype == CMDNORMAL && (flags & EV_EXIT) == 0
650 || (flags & EV_BACKCMD) != 0
651 && (cmdentry.cmdtype != CMDBUILTIN
652 || cmdentry.u.index == DOTCMD
653 || cmdentry.u.index == EVALCMD)) {
654 jp = makejob(cmd, 1);
655 mode = cmd->ncmd.backgnd;
656 if (flags & EV_BACKCMD) {
657 mode = FORK_NOJOB;
658 if (pipe(pip) < 0)
659 error("Pipe call failed");
660 }
661 if (forkshell(jp, cmd, mode) != 0)
662 goto parent; /* at end of routine */
663 if (flags & EV_BACKCMD) {
664 FORCEINTON;
665 close(pip[0]);
666 if (pip[1] != 1) {
667 close(1);
668 copyfd(pip[1], 1);
669 close(pip[1]);
670 }
671 }
672 flags |= EV_EXIT;
673 }
674
675 /* This is the child process if a fork occurred. */
676 /* Execute the command. */
677 if (cmdentry.cmdtype == CMDFUNCTION) {
678 trputs("Shell function: "); trargs(argv);
679 redirect(cmd->ncmd.redirect, REDIR_PUSH);
680 saveparam = shellparam;
681 shellparam.malloc = 0;
682 shellparam.nparam = argc - 1;
683 shellparam.p = argv + 1;
684 shellparam.optnext = NULL;
685 INTOFF;
686 savelocalvars = localvars;
687 localvars = NULL;
688 INTON;
689 if (setjmp(jmploc.loc)) {
690 if (exception == EXSHELLPROC)
691 freeparam((struct shparam *)&saveparam);
692 else {
693 freeparam(&shellparam);
694 shellparam = saveparam;
695 }
696 poplocalvars();
697 localvars = savelocalvars;
698 handler = savehandler;
699 longjmp(handler->loc, 1);
700 }
701 savehandler = handler;
702 handler = &jmploc;
703 for (sp = varlist.list ; sp ; sp = sp->next)
704 mklocal(sp->text);
705 funcnest++;
706 evaltree(cmdentry.u.func, 0);
707 funcnest--;
708 INTOFF;
709 poplocalvars();
710 localvars = savelocalvars;
711 freeparam(&shellparam);
712 shellparam = saveparam;
713 handler = savehandler;
714 popredir();
715 INTON;
716 if (evalskip == SKIPFUNC) {
717 evalskip = 0;
718 skipcount = 0;
719 }
720 if (flags & EV_EXIT)
721 exitshell(exitstatus);
722 } else if (cmdentry.cmdtype == CMDBUILTIN) {
723 trputs("builtin command: "); trargs(argv);
724 mode = (cmdentry.u.index == EXECCMD)? 0 : REDIR_PUSH;
725 if (flags == EV_BACKCMD) {
726 memout.nleft = 0;
727 memout.nextc = memout.buf;
728 memout.bufsize = 64;
729 mode |= REDIR_BACKQ;
730 }
731 redirect(cmd->ncmd.redirect, mode);
732 savecmdname = commandname;
733 cmdenviron = varlist.list;
734 e = -1;
735 if (setjmp(jmploc.loc)) {
736 e = exception;
737 exitstatus = (e == EXINT)? SIGINT+128 : 2;
738 goto cmddone;
739 }
740 savehandler = handler;
741 handler = &jmploc;
742 commandname = argv[0];
743 argptr = argv + 1;
744 optptr = NULL; /* initialize nextopt */
745 exitstatus = (*builtinfunc[cmdentry.u.index])(argc, argv);
746 flushall();
747cmddone:
748 out1 = &output;
749 out2 = &errout;
750 freestdout();
751 if (e != EXSHELLPROC) {
752 commandname = savecmdname;
753 if (flags & EV_EXIT) {
754 exitshell(exitstatus);
755 }
756 }
757 handler = savehandler;
758 if (e != -1) {
759 if (e != EXERROR || cmdentry.u.index == BLTINCMD
760 || cmdentry.u.index == DOTCMD
761 || cmdentry.u.index == EVALCMD
762 || cmdentry.u.index == EXECCMD)
763 exraise(e);
764 FORCEINTON;
765 }
766 if (cmdentry.u.index != EXECCMD)
767 popredir();
768 if (flags == EV_BACKCMD) {
769 backcmd->buf = memout.buf;
770 backcmd->nleft = memout.nextc - memout.buf;
771 memout.buf = NULL;
772 }
773 } else {
774 trputs("normal command: "); trargs(argv);
775 clearredir();
776 redirect(cmd->ncmd.redirect, 0);
777 if (varlist.list) {
778 p = stalloc(strlen(pathval()) + 1);
779 scopy(pathval(), p);
780 } else {
781 p = pathval();
782 }
783 for (sp = varlist.list ; sp ; sp = sp->next)
784 setvareq(sp->text, VEXPORT|VSTACK);
785 envp = environment();
786 shellexec(argv, envp, p, cmdentry.u.index);
787 /*NOTREACHED*/
788 }
789 goto out;
790
791parent: /* parent process gets here (if we forked) */
792 if (mode == 0) { /* argument to fork */
793 INTOFF;
794 exitstatus = waitforjob(jp);
795 INTON;
796 } else if (mode == 2) {
797 backcmd->fd = pip[0];
798 close(pip[1]);
799 backcmd->jp = jp;
800 }
801
802out:
803 if (lastarg)
804 setvar("_", lastarg, 0);
805 popstackmark(&smark);
806}
807
808
809
810/*
811 * Search for a command. This is called before we fork so that the
812 * location of the command will be available in the parent as well as
813 * the child. The check for "goodname" is an overly conservative
814 * check that the name will not be subject to expansion.
815 */
816
817STATIC void
818prehash(n)
819 union node *n;
820 {
821 struct cmdentry entry;
822
823 if (n->type == NCMD && goodname(n->ncmd.args->narg.text))
824 find_command(n->ncmd.args->narg.text, &entry, 0);
825}
826
827
828
829/*
830 * Builtin commands. Builtin commands whose functions are closely
831 * tied to evaluation are implemented here.
832 */
833
834/*
835 * No command given, or a bltin command with no arguments. Set the
836 * specified variables.
837 */
838
839bltincmd(argc, argv) char **argv; {
840 listsetvar(cmdenviron);
841 return exitstatus;
842}
843
844
845/*
846 * Handle break and continue commands. Break, continue, and return are
847 * all handled by setting the evalskip flag. The evaluation routines
848 * above all check this flag, and if it is set they start skipping
849 * commands rather than executing them. The variable skipcount is
850 * the number of loops to break/continue, or the number of function
851 * levels to return. (The latter is always 1.) It should probably
852 * be an error to break out of more loops than exist, but it isn't
853 * in the standard shell so we don't make it one here.
854 */
855
856breakcmd(argc, argv) char **argv; {
857 int n;
858
859 n = 1;
860 if (argc > 1)
861 n = number(argv[1]);
862 if (n > loopnest)
863 n = loopnest;
864 if (n > 0) {
865 evalskip = (**argv == 'c')? SKIPCONT : SKIPBREAK;
866 skipcount = n;
867 }
868 return 0;
869}
870
871
872/*
873 * The return command.
874 */
875
876returncmd(argc, argv) char **argv; {
877 int ret;
878
879 ret = exitstatus;
880 if (argc > 1)
881 ret = number(argv[1]);
882 if (funcnest) {
883 evalskip = SKIPFUNC;
884 skipcount = 1;
885 }
886 return ret;
887}
888
889
890truecmd(argc, argv) char **argv; {
891 return 0;
892}
893
894
895execcmd(argc, argv) char **argv; {
896 if (argc > 1) {
897 iflag = 0; /* exit on error */
898 setinteractive(0);
899#if JOBS
900 jflag = 0;
901 setjobctl(0);
902#endif
903 shellexec(argv + 1, environment(), pathval(), 0);
904
905 }
906 return 0;
907}