summaryrefslogtreecommitdiff
path: root/bin/ksh/exec.c
diff options
context:
space:
mode:
authorJason Downs <downsj@cvs.openbsd.org>1996-08-19 20:09:10 +0000
committerJason Downs <downsj@cvs.openbsd.org>1996-08-19 20:09:10 +0000
commite7e852656e3beb5f62f8c146ba24004cfda2cf88 (patch)
treeb312bc3e985b3f1cf19691c779e7870219573b6e /bin/ksh/exec.c
parent1e9e82f84b5de8bcd110c41c780f476397c385d9 (diff)
update to pdksh-5.2.8
Diffstat (limited to 'bin/ksh/exec.c')
-rw-r--r--bin/ksh/exec.c312
1 files changed, 199 insertions, 113 deletions
diff --git a/bin/ksh/exec.c b/bin/ksh/exec.c
index 7563b2bb9d0..ce4ca7668c5 100644
--- a/bin/ksh/exec.c
+++ b/bin/ksh/exec.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: exec.c,v 1.1 1996/08/14 06:19:11 downsj Exp $ */
+/* $OpenBSD: exec.c,v 1.2 1996/08/19 20:08:50 downsj Exp $ */
/*
* execute command tree
@@ -9,14 +9,21 @@
#include <ctype.h>
#include "ksh_stat.h"
+/* Does ps4 get parameter substitutions done? */
+#ifdef KSH
+# define PS4_SUBSTITUTE(s) substitute((s), 0)
+#else
+# define PS4_SUBSTITUTE(s) (s)
+#endif /* KSH */
+
static int comexec ARGS((struct op *t, struct tbl *volatile tp, char **ap,
int volatile flags));
static void scriptexec ARGS((struct op *tp, char **ap));
static int call_builtin ARGS((struct tbl *tp, char **wp));
-static int iosetup ARGS((struct ioword *iop));
+static int iosetup ARGS((struct ioword *iop, struct tbl *tp));
static int herein ARGS((char *hname, int sub));
#ifdef KSH
-static char *do_selectargs ARGS((char **ap));
+static char *do_selectargs ARGS((char **ap, bool_t print_menu));
#endif /* KSH */
#ifdef KSH
static int dbteste_isa ARGS((Test_env *te, Test_meta meta));
@@ -80,6 +87,14 @@ execute(t, flags)
if (t == NULL)
return 0;
+ /* Is this the end of a pipeline? If so, we want to evaluate the
+ * command arguments
+ bool_t eval_done = FALSE;
+ if ((flags&XFORK) && !(flags&XEXEC) && (flags&XPCLOSE)) {
+ eval_done = TRUE;
+ tp = eval_execute_args(t, &ap);
+ }
+ */
if ((flags&XFORK) && !(flags&XEXEC) && t->type != TPIPE)
return exchild(t, flags, -1); /* run in sub-process */
@@ -96,10 +111,10 @@ execute(t, flags)
/* POSIX says expand command words first, then redirections,
* and assignments last..
*/
- ap = eval(t->args, t->evalflags | DOBLANK | DOGLOB | DOTILDE);
+ ap = eval(t->args, t->u.evalflags | DOBLANK | DOGLOB | DOTILDE);
if (Flag(FXTRACE) && ap[0]) {
shf_fprintf(shl_out, "%s",
- substitute(str_val(global("PS4")), 0));
+ PS4_SUBSTITUTE(str_val(global("PS4"))));
for (i = 0; ap[i]; i++)
shf_fprintf(shl_out, "%s%s", ap[i],
ap[i + 1] ? space : newline);
@@ -118,7 +133,7 @@ execute(t, flags)
/* do redirection, to be restored in quitenv() */
if (t->ioact != NULL)
for (iowp = t->ioact; *iowp != NULL; iowp++) {
- if (iosetup(*iowp) < 0) {
+ if (iosetup(*iowp, tp) < 0) {
exstat = rv = 1;
/* Redirection failures for special commands
* cause (non-interactive) shell to exit.
@@ -179,10 +194,31 @@ execute(t, flags)
#ifdef KSH
case TCOPROC:
{
+# ifdef JOB_SIGS
+ sigset_t omask;
+# endif /* JOB_SIGS */
+
+# ifdef JOB_SIGS
+ /* Block sigchild as we are using things changed in the
+ * signal handler
+ */
+ sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+ e->type = E_ERRH;
+ i = ksh_sigsetjmp(e->jbuf, 0);
+ if (i) {
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+ quitenv();
+ unwind(i);
+ /*NOTREACHED*/
+ }
+# endif /* JOB_SIGS */
+ /* Already have a (live) co-process? */
if (coproc.job && coproc.write >= 0)
errorf("coprocess already exists");
+
/* Can we re-use the existing co-process pipe? */
- cleanup_coproc(TRUE);
+ coproc_cleanup(TRUE);
+
/* do this before opening pipes, in case these fail */
e->savefd[0] = savefd(0, 0);
e->savefd[1] = savefd(1, 0);
@@ -191,6 +227,7 @@ execute(t, flags)
ksh_dup2(pv[0], 0, FALSE);
close(pv[0]);
coproc.write = pv[1];
+ coproc.job = (void *) 0;
if (coproc.readw >= 0)
ksh_dup2(coproc.readw, 1, FALSE);
@@ -199,9 +236,19 @@ execute(t, flags)
coproc.read = pv[0];
ksh_dup2(pv[1], 1, FALSE);
coproc.readw = pv[1]; /* closed before first read */
+ coproc.njobs = 0;
+ /* create new coprocess id */
+ ++coproc.id;
}
-
- /* exchild() closes coproc.* in child after fork */
+# ifdef JOB_SIGS
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+ e->type = E_EXEC; /* no more need for error handler */
+# endif /* JOB_SIGS */
+
+ /* exchild() closes coproc.* in child after fork,
+ * will also increment coproc.njobs when the
+ * job is actually created.
+ */
flags &= ~XEXEC;
exchild(t->left, flags|XBGND|XFORK|XCOPROC|XCCLOSE,
coproc.readw);
@@ -221,7 +268,7 @@ execute(t, flags)
case TAND:
rv = execute(t->left, XERROK);
if (t->right != NULL && (rv == 0) == (t->type == TAND))
- rv = execute(t->right, 0);
+ rv = execute(t->right, flags & XERROK);
else
flags |= XERROK;
break;
@@ -250,6 +297,8 @@ execute(t, flags)
case TFOR:
#ifdef KSH
case TSELECT:
+ {
+ volatile bool_t is_first = TRUE;
#endif /* KSH */
ap = (t->vars != NULL) ?
eval(t->vars, DOBLANK|DOGLOB|DOTILDE)
@@ -286,10 +335,11 @@ execute(t, flags)
struct tbl *vq;
for (;;) {
- if ((cp = do_selectargs(ap)) == (char *) 0) {
+ if (!(cp = do_selectargs(ap, is_first))) {
rv = 1;
break;
}
+ is_first = FALSE;
vq = global(t->str);
if (vq->flag & RDONLY)
errorf("%s is read only", t->str);
@@ -297,6 +347,7 @@ execute(t, flags)
rv = execute(t->left, flags & XERROK);
}
}
+ }
#endif /* KSH */
break;
@@ -348,7 +399,7 @@ execute(t, flags)
break;
case TFUNCT:
- rv = define(t->str, t->left);
+ rv = define(t->str, t);
break;
case TTIME:
@@ -483,15 +534,17 @@ comexec(t, tp, ap, flags)
break;
tp = findcom(ap[0], fcflags & (FC_BI|FC_FUNC));
}
- /* todo: POSIX says assignments preceding a function are kept, at&t
- * ksh does not do this
- */
- if (keepasn_ok && (!ap[0] || (tp && tp->flag & KEEPASN)))
+ if (keepasn_ok && (!ap[0] || (tp && (tp->flag & KEEPASN))))
type_flags = 0;
else {
/* create new variable/function block */
newblock();
- type_flags = LOCAL|LOCAL_COPY|EXPORT;
+ /* ksh functions don't keep assignments, POSIX functions do. */
+ if (keepasn_ok && tp && tp->type == CFUNC
+ && !(tp->flag & FKSH))
+ type_flags = 0;
+ else
+ type_flags = LOCAL|LOCAL_COPY|EXPORT;
}
if (Flag(FEXPORT))
type_flags |= EXPORT;
@@ -500,7 +553,7 @@ comexec(t, tp, ap, flags)
if (Flag(FXTRACE)) {
if (i == 0)
shf_fprintf(shl_out, "%s",
- substitute(str_val(global("PS4")), 0));
+ PS4_SUBSTITUTE(str_val(global("PS4"))));
shf_fprintf(shl_out, "%s%s", cp,
t->vars[i + 1] ? space : newline);
if (!t->vars[i + 1])
@@ -529,24 +582,30 @@ comexec(t, tp, ap, flags)
case CFUNC: /* function call */
{
volatile int old_xflag;
- volatile int old_inuse;
+ volatile Tflag old_inuse;
const char *volatile old_kshname;
if (!(tp->flag & ISSET)) {
struct tbl *ftp;
if (!tp->u.fpath) {
- /* XXX: exit code 126 vs 127 */
- warningf(TRUE,
+ if (tp->u2.errno_) {
+ warningf(TRUE,
+ "%s: can't find function definition file - %s",
+ cp, strerror(tp->u2.errno_));
+ rv = 126;
+ } else {
+ warningf(TRUE,
"%s: can't find function definition file", cp);
- rv = 127;
+ rv = 127;
+ }
break;
}
if (include(tp->u.fpath, 0, (char **) 0, 0) < 0) {
warningf(TRUE,
"%s: can't open function definition file %s - %s",
cp, tp->u.fpath, strerror(errno));
- rv = 126;
+ rv = 127;
break;
}
if (!(ftp = findfunc(cp, hash(cp), FALSE))
@@ -561,9 +620,11 @@ comexec(t, tp, ap, flags)
tp = ftp;
}
- /* posix says $0 remains unchanged, at&t ksh changes it */
+ /* ksh functions set $0 to function name, POSIX functions leave
+ * $0 unchanged.
+ */
old_kshname = kshname;
- if (!Flag(FPOSIX))
+ if (tp->flag & FKSH)
kshname = ap[0];
e->loc->argv = ap;
for (i = 0; *ap++ != NULL; i++)
@@ -588,7 +649,8 @@ comexec(t, tp, ap, flags)
Flag(FXTRACE) = old_xflag;
tp->flag = (tp->flag & ~FINUSE) | old_inuse;
/* Were we deleted while executing? If so, free the execution
- * tree. Unfortunately, the table entry is never re-used.
+ * tree. todo: Unfortunately, the table entry is never re-used
+ * until the lookup table is expanded.
*/
if ((tp->flag & (FDELETE|FINUSE)) == FDELETE) {
if (tp->flag & ALLOC) {
@@ -619,38 +681,26 @@ comexec(t, tp, ap, flags)
case CEXEC: /* executable command */
case CTALIAS: /* tracked alias */
if (!(tp->flag&ISSET)) {
- /*
- * mlj addition:
- *
- * If you specify a full path to a file
- * (or type the name of a file in .) which
- * doesn't have execute priv's, it used to
- * just say "not found". Kind of annoying,
- * particularly if you just wrote a script
- * but forgot to say chmod 755 script.
- *
- * This should probably be done in eaccess(),
- * but it works here (at least I haven't noticed
- * changing errno here breaking something
- * else).
- *
- * So, we assume that if the file exists, it
- * doesn't have execute privs; else, it really
- * is not found.
+ /* errno_ will be set if the named command was found
+ * but could not be executed (permissions, no execute
+ * bit, directory, etc). Print out a (hopefully)
+ * useful error message and set the exit status to 126.
*/
- if (access(cp, F_OK) < 0)
+ if (tp->u2.errno_) {
+ warningf(TRUE, "%s: cannot execute - %s", cp,
+ strerror(tp->u2.errno_));
+ rv = 126; /* POSIX */
+ } else {
warningf(TRUE, "%s: not found", cp);
- else
- warningf(TRUE, "%s: cannot execute", cp);
- /* XXX posix says 126 if in path and cannot execute */
- rv = 127;
+ rv = 127;
+ }
break;
}
/* set $_ to program's full path */
setstr(typeset("_", LOCAL|EXPORT, 0, 0, 0), tp->val.s);
- if ((flags&XEXEC)) {
+ if (flags&XEXEC) {
j_exit();
if (!(flags&XBGND) || Flag(FMONITOR)) {
setexecsig(&sigtraps[SIGINT], SS_RESTORE_ORIG);
@@ -683,7 +733,7 @@ scriptexec(tp, ap)
shell = str_val(global(EXECSHELL_STR));
if (shell && *shell)
- shell = search(shell, path, X_OK);
+ shell = search(shell, path, X_OK, (int *) 0);
if (!shell || !*shell)
shell = EXECSHELL;
@@ -746,7 +796,7 @@ scriptexec(tp, ap)
if (a1)
*tp->args-- = a1;
# ifdef OS2
- if (a0 != a2 && search_access(a0, X_OK))
+ if (a0 != a2 && search_access(a0, X_OK, (int *) 0))
a0 = a2;
# endif /* OS2 */
shell = a0;
@@ -762,7 +812,7 @@ scriptexec(tp, ap)
shell = str_val(global("EXECSHELL"));
if (shell && *shell)
- shell = search(shell, path, X_OK);
+ shell = search(shell, path, X_OK, (int *) 0);
if (!shell || !*shell) {
shell = p;
*tp->args-- = "/c";
@@ -809,7 +859,7 @@ findfunc(name, h, create)
for (l = e->loc; l; l = l->next) {
tp = tsearch(&l->funs, name, h);
- if (tp && (tp->flag & DEFINED))
+ if (tp)
break;
if (!l->next && create) {
tp = tenter(&l->funs, name, h);
@@ -823,25 +873,31 @@ findfunc(name, h, create)
}
/*
- * define function
+ * define function. Returns 1 if function is being undefined (t == 0) and
+ * function did not exist, returns 0 otherwise.
*/
int
define(name, t)
const char *name;
struct op *t;
{
- register struct tbl *tp;
+ struct tbl *tp;
+ int was_set = 0;
- tp = findfunc(name, hash(name), TRUE);
+ while (1) {
+ tp = findfunc(name, hash(name), TRUE);
- /* If this function is currently being executed, we zap this
- * table entry so findfunc() won't see it
- */
- if (tp->flag & FINUSE) {
- tp->name[0] = '\0';
- tp->flag &= ~DEFINED; /* ensure it won't be found */
- tp->flag |= FDELETE;
- return define(name, t);
+ if (tp->flag & ISSET)
+ was_set = 1;
+ /* If this function is currently being executed, we zap this
+ * table entry so findfunc() won't see it
+ */
+ if (tp->flag & FINUSE) {
+ tp->name[0] = '\0';
+ tp->flag &= ~DEFINED; /* ensure it won't be found */
+ tp->flag |= FDELETE;
+ } else
+ break;
}
if (tp->flag & ALLOC) {
@@ -851,11 +907,13 @@ define(name, t)
if (t == NULL) { /* undefine */
tdelete(tp);
- return 0;
+ return was_set ? 0 : 1;
}
- tp->val.t = tcopy(t, tp->areap);
+ tp->val.t = tcopy(t->left, tp->areap);
tp->flag |= (ISSET|ALLOC);
+ if (t->u.ksh_func)
+ tp->flag |= FKSH;
return 0;
}
@@ -869,7 +927,7 @@ builtin(name, func)
int (*func) ARGS((char **));
{
register struct tbl *tp;
- int flag;
+ Tflag flag;
/* see if any flags should be set for this builtin */
for (flag = 0; ; name++) {
@@ -920,10 +978,12 @@ findcom(name, flags)
if (!tp && (flags & FC_FUNC)) {
tp = findfunc(name, h, FALSE);
if (tp && !(tp->flag & ISSET)) {
- if ((fpath = str_val(global("FPATH"))) == null)
+ if ((fpath = str_val(global("FPATH"))) == null) {
tp->u.fpath = (char *) 0;
- else
- tp->u.fpath = search(name, fpath, R_OK);
+ tp->u2.errno_ = 0;
+ } else
+ tp->u.fpath = search(name, fpath, R_OK,
+ &tp->u2.errno_);
}
}
if (!tp && (flags & FC_REGBI) && tbi && (tbi->flag & REG_BI))
@@ -964,13 +1024,14 @@ findcom(name, flags)
tp->flag = DEFINED; /* make ~ISSET */
}
npath = search(name, flags & FC_DEFPATH ? def_path : path,
- X_OK);
+ X_OK, &tp->u2.errno_);
if (npath) {
tp->val.s = tp == &temp ? npath : str_save(npath, APERM);
tp->flag |= ISSET|ALLOC;
} else if ((flags & FC_FUNC)
&& (fpath = str_val(global("FPATH"))) != null
- && (npath = search(name, fpath, R_OK)) != (char *) 0)
+ && (npath = search(name, fpath, R_OK,
+ &tp->u2.errno_)) != (char *) 0)
{
/* An undocumented feature of at&t ksh is that it
* searches FPATH if a command is not found, even
@@ -1008,20 +1069,29 @@ flushcom(all)
/* Check if path is something we want to find. Returns -1 for failure. */
int
-search_access(path, mode)
+search_access(path, mode, errnop)
const char *path;
int mode;
+ int *errnop; /* set if candidate found, but not suitable */
{
#ifndef OS2
- int ret = eaccess(path, mode);
+ int ret, err = 0;
struct stat statb;
- /* if executable pipes come along, this will have to change */
- if (ret == 0 && (mode == X_OK)
- && (stat(path, &statb) < 0 || !S_ISREG(statb.st_mode)
- /* This 'cause access() says root can execute everything */
- || !(statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH))))
+ if (stat(path, &statb) < 0)
+ return -1;
+ ret = eaccess(path, mode);
+ if (ret < 0)
+ err = errno; /* File exists, but we can't access it */
+ else if (mode == X_OK && (!S_ISREG(statb.st_mode)
+ /* This 'cause access() says root can execute everything */
+ || !(statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH))))
+ {
ret = -1;
+ err = S_ISDIR(statb.st_mode) ? EISDIR : EACCES;
+ }
+ if (err && errnop)
+ *errnop = err;
return ret;
#else /* !OS2 */
/*
@@ -1050,7 +1120,7 @@ search_access(path, mode)
if ((p = strrchr((p = ksh_strrchr_dirsep(mpath)) ? p : mpath, '.'))) {
if (mode == X_OK)
mode = R_OK;
- return search_access1(mpath, mode);
+ return search_access1(mpath, mode, errnop);
}
/* Try appending the various suffixes. Different suffixes for
* read and execute 'cause we don't want to read an executable...
@@ -1058,7 +1128,7 @@ search_access(path, mode)
sfx = mode == R_OK ? rsuffixes : xsuffixes;
for (i = 0; sfx[i]; i++) {
strcpy(tp, p = sfx[i]);
- if (search_access1(mpath, R_OK) == 0)
+ if (search_access1(mpath, R_OK, errnop) == 0)
return 0;
*tp = '\0';
}
@@ -1068,17 +1138,25 @@ search_access(path, mode)
#ifdef OS2
static int
-search_access1(path, mode)
+search_access1(path, mode, errnop)
const char *path;
int mode;
+ int *errnop; /* set if candidate found, but not suitable */
{
- int ret = eaccess(path, mode);
+ int ret, err = 0;
struct stat statb;
- /* if executable pipes come along, this will have to change */
- if (ret == 0 && (mode == X_OK || mode == R_OK)
- && (stat(path, &statb) < 0 || !S_ISREG(statb.st_mode)))
+ if (stat(path, &statb) < 0)
+ return -1;
+ ret = eaccess(path, mode);
+ if (ret < 0)
+ err = errno; /* File exists, but we can't access it */
+ else if (!S_ISREG(statb.st_mode)) {
ret = -1;
+ err = S_ISDIR(statb.st_mode) ? EISDIR : EACCES;
+ }
+ if (err && errnop)
+ *errnop = err;
return ret;
}
#endif /* OS2 */
@@ -1087,16 +1165,19 @@ search_access1(path, mode)
* search for command with PATH
*/
char *
-search(name, path, mode)
+search(name, path, mode, errnop)
const char *name;
const char *path;
int mode; /* R_OK or X_OK */
+ int *errnop; /* set if candidate found, but not suitable */
{
const char *sp, *p;
char *xp;
XString xs;
int namelen;
+ if (errnop)
+ *errnop = 0;
#ifdef OS2
/* Xinit() allocates 8 additional bytes, so appended suffixes won't
* overflow the memory.
@@ -1106,18 +1187,18 @@ search(name, path, mode)
memcpy(Xstring(xs, xp), name, namelen);
if (ksh_strchr_dirsep(name)) {
- if (search_access(Xstring(xs, xp), mode) >= 0)
+ if (search_access(Xstring(xs, xp), mode, errnop) >= 0)
return Xstring(xs, xp); /* not Xclose() - see above */
Xfree(xs, xp);
return NULL;
}
/* Look in current context always. (os2 style) */
- if (search_access(Xstring(xs, xp), mode) == 0)
+ if (search_access(Xstring(xs, xp), mode, errnop) == 0)
return Xstring(xs, xp); /* not Xclose() - xp may be wrong */
#else /* OS2 */
if (ksh_strchr_dirsep(name)) {
- if (search_access(name, mode) == 0)
+ if (search_access(name, mode, errnop) == 0)
return (char *) name;
return NULL;
}
@@ -1140,7 +1221,7 @@ search(name, path, mode)
sp = p;
XcheckN(xs, xp, namelen);
memcpy(xp, name, namelen);
- if (search_access(Xstring(xs, xp), mode) == 0)
+ if (search_access(Xstring(xs, xp), mode, errnop) == 0)
#ifdef OS2
return Xstring(xs, xp); /* Not Xclose() - see above */
#else /* OS2 */
@@ -1177,8 +1258,9 @@ call_builtin(tp, wp)
* set up redirection, saving old fd's in e->savefd
*/
static int
-iosetup(iop)
+iosetup(iop, tp)
register struct ioword *iop;
+ struct tbl *tp;
{
register int u = -1;
char *cp = iop->name;
@@ -1197,7 +1279,7 @@ iosetup(iop)
if (Flag(FXTRACE))
shellf("%s%s\n",
- substitute(str_val(global("PS4")), 0),
+ PS4_SUBSTITUTE(str_val(global("PS4"))),
snptreef((char *) 0, 32, "%R", &iotmp));
switch (iotype) {
@@ -1287,23 +1369,19 @@ iosetup(iop)
close(u);
return -1;
}
- if (iotype == IODUP) {
+ if (iotype != IODUP)
+ close(u);
#ifdef KSH
- if (iop->flag & IORDUP) /* possible <&p */
- /* Ensure other side of read pipe is
- * closed so EOF can be read properly
- */
- coproc_readw_close(u);
- else /* possible >&p */
- /* If co-process input is duped,
- * close shell's copy
- */
+ /* Touching any co-process fd in an empty exec
+ * causes the shell to close its copies
+ */
+ else if (tp && tp->type == CSHELL && tp->val.f == c_exec) {
+ if (iop->flag & IORDUP) /* possible exec <&p */
+ coproc_read_close(u);
+ else /* possible exec >&p */
coproc_write_close(u);
-#else /* KSH */
- ;
+ }
#endif /* KSH */
- } else
- close(u);
}
if (u == 2) /* Clear any write errors */
shf_reopen(2, SHF_WR, shl_out);
@@ -1387,27 +1465,35 @@ herein(hname, sub)
* print the args in column form - assuming that we can
*/
static char *
-do_selectargs(ap)
+do_selectargs(ap, print_menu)
register char **ap;
+ bool_t print_menu;
{
static const char *const read_args[] = {
"read", "-r", "REPLY", (char *) 0
};
char *s;
- int i, UNINITIALIZED(argct);
+ int i, argct;
+ for (argct = 0; ap[argct]; argct++)
+ ;
while (1) {
- argct = pr_menu(ap);
+ /* Menu is printed if
+ * - this is the first time around the select loop
+ * - the user enters a blank line
+ * - the REPLY parameter is empty
+ */
+ if (print_menu || !*str_val(global("REPLY")))
+ pr_menu(ap);
shellf("%s", str_val(global("PS3")));
if (call_builtin(findcom("read", FC_BI), (char **) read_args))
return (char *) 0;
s = str_val(global("REPLY"));
- while (*s && isspace(*s))
- s++;
if (*s) {
i = atoi(s);
return (i >= 1 && i <= argct) ? ap[i - 1] : null;
}
+ print_menu = 1;
}
}