Yet Another Shell (part 5 of 11)

Dave Clemans dclemans.falcon at mntgfx.mentor.com
Thu Mar 16 08:22:37 AEST 1989


With all the talk about shells that has been going on recently...

Here's an early pre-release of a "Korn"-like shell for anyone
who might want to experiment.  It is definitely NOT complete,
but it starting to be usable.  It does use some GNU code (for
expression evaluation), so it presumably comes under their
"copyleft".

It basically runs on BSD/USG Unix systems, and on the Atari ST.
I'm currently working on a port to the Amiga.

dgc

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 5 (of 11)."
# Contents:  cmd1.c util.c
# Wrapped by dclemans at dclemans on Wed Mar 15 14:03:57 1989
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'cmd1.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'cmd1.c'\"
else
echo shar: Extracting \"'cmd1.c'\" \(21733 characters\)
sed "s/^X//" >'cmd1.c' <<'END_OF_FILE'
X/*
X * Command Input Shell
X * Dave Clemans
X * 12/88-1/89
X *
X * "spiritually" based on Bourne, Korn shells
X *
X * Built-in Commands (part 1)
X *
X * $Id: cmd1.c,v 1.8 89/02/25 17:39:38 dclemans Exp $
X *
X * $Log:	cmd1.c,v $
X * Revision 1.8  89/02/25  17:39:38  dclemans
X * miscellaneous bug fixes/speedups
X * 
X * Revision 1.7  89/02/22  21:31:37  dclemans
X * Implement simple background job monitoring facility
X * 
X * Revision 1.6  89/02/22  16:27:05  dclemans
X * Implement [[, ]] brackets
X * 
X * Revision 1.5  89/02/20  22:29:08  dclemans
X * Add new builtin commands "trap" and "kill" to command switch table.
X * 
X * Revision 1.4  89/02/20  20:14:15  dclemans
X * Add RCS identifiers
X * 
X */
X#include <stdio.h>
X#include "shell.h"
X#ifdef  LINED
X#include "reader\history.h"
X#endif  /* LINED */
X
Xint cmd_lastrc = 0;
Xint cmd_forceexit = 0;
Xint cmd_returnexit = 0;
Xint cmd_count = 0;
X
Xextern  int cmd_for();      /* from part 2 */
Xextern  int cmd_select();
Xextern  int cmd_while();
Xextern  int cmd_function();
Xextern  int cmd_if();
Xextern  int cmd_case();
Xextern  int cmd_break();
Xextern  int cmd_continue();
Xextern  int cmd_return();
Xextern  int cmd_fc();
Xextern  int cmd_eval();
Xextern  int cmd_exec();
Xextern  int cmd_time();
X
Xextern  int cmd_test();     /* from part 3 */
Xextern  int cmd_version();
X#ifdef  MYMALLOC
Xextern  int cmd_memory();
X#endif  /* MYMALLOC */
Xextern  int cmd_typeset();
Xextern  int cmd_let();
Xextern  int cmd_dparen();
Xextern  int cmd_whence();
X
Xextern  int cmd_trap();     /* from trap */
X#ifndef GEMDOS
Xextern  int cmd_kill();
Xextern  int cmd_jobs();
X#endif  /* GEMDOS */
X
Xint cmd_null()
X{
X    return 0;
X}   /* end of cmd_null */
X
Xint cmd_print(pp)
Xstruct phrase *pp;
X{
X    int flag,noesc;
X    register struct token *tp;
X    register char *p,*b;
X    char buffer[BUFSIZ];
X
X    flag = 1;
X    noesc = 0;
X    b = buffer;
X    tp = pp->body->next;
X    while (tp != (struct token *)NULL && tp->name[0] == '-')
X    {   /* check for special flags */
X        if (tp->name[1] == '\0')
X        {   /* forced end of args? */
X            tp = tp->next;
X            break;
X        }
X        for (p = &tp->name[1]; *p; p++)
X        {   /* for each possibility */
X            switch (*p)
X            {   /* select what it does */
X                case 'n':
X                    flag = 0;
X                    break;
X                case 'R':
X                case 'r':
X                    noesc = 1;
X                    break;
X                default:
X                    errmsg(0,LOC("cmd_print"),"unknown print arg in: %s",tp->name);
X                    return 1;
X            }
X        }
X        tp = tp->next;
X    }
X    for (; tp != (struct token *)NULL; tp = tp->next)
X    {   /* for each argument, echo its characters */
X        for (p = tp->name; *p; p++)
X        {   /* for each char */
X            switch (*p)
X            {   /* what char? */
X                case '\'':
X                    p++;
X                    while (*p && *p != '\'')
X                    {   /* dump the string */
X                        *b++ = *p;
X                        p++;
X                    }
X                    break;
X                case '"':
X                    p++;
X                    while (*p && *p != '"')
X                    {   /* dump the string */
X                        if (!noesc && *p == ESCAPE_CHAR)
X                        {   /* process the escape char */
X                            p++;
X                            switch (*p)
X                            {   /* what was escaped */
X                                case 'a':
X                                    *b++ = '\007';
X                                    break;
X                                case 'b':
X                                    *b++ = '\b';
X                                    break;
X                                case 'c':
X                                    flag = 0;
X                                    break;
X                                case 'f':
X                                    *b++ = '\f';
X                                    break;
X                                case 'n':
X                                    *b++ = '\n';
X                                    break;
X                                case 'r':
X                                    *b++ = '\r';
X                                    break;
X                                case 't':
X                                    *b++ = '\t';
X                                    break;
X                                default:
X                                    *b++ = *p;
X                                    break;
X                            }
X                        }
X                        else    *b++ = *p;
X                        p++;
X                    }
X                    break;
X                case ESCAPE_CHAR:
X                    if (!noesc)
X                    {   /* if just ordinary chars... */
X                        p++;
X                        switch (*p)
X                        {   /* what was escaped */
X                            case 'a':
X                                *b++ = '\007';
X                                break;
X                            case 'b':
X                                *b++ = '\b';
X                                break;
X                            case 'c':
X                                flag = 0;
X                                break;
X                            case 'f':
X                                *b++ = '\f';
X                                break;
X                            case 'n':
X                                *b++ = '\n';
X                                break;
X                            case 'r':
X                                *b++ = '\r';
X                                break;
X                            case 't':
X                                *b++ = '\t';
X                                break;
X                            default:
X                                *b++ = *p;
X                                break;
X                        }
X                    } else *b++ = *p;
X                    break;
X                default:
X                    *b++ = *p;
X                    break;
X            }
X        }
X        if (tp->next != (struct token *)NULL)
X            *b++ = ' ';
X    }
X    if (flag)
X    {   /* add in an eol */
X        *b++ = '\n';
X    }
X    *b = '\0';
X    io_writestring(0,buffer);
X    return 0;
X}   /* end of cmd_print */
X
Xint cmd_alias(pp)
Xstruct phrase *pp;
X{
X    register struct token *tp,*args;
X    register char *p;
X    int type;
X
X    type = 0;
X    args = pp->body->next;
X    while (args != (struct token *)NULL && args->name[0] == '-')
X    {   /* if there are some arguments */
X        for (p = &args->name[1]; *p; p++)
X        {   /* what kind of alias? */
X            switch (*p)
X            {   /* what switch? */
X                case 't':
X                    type = TYPE_TRACKED;
X                    break;
X                default:
X                    errmsg(0,LOC("cmd_alias"),"bad switch: %s",args->name);
X                    break;
X            }
X        }
X        args = args->next;
X    }
X
X    if (args == (struct token *)NULL)
X        alias_dump(base_env.alias_table,type);
X    else
X    {   /* should be a definition */
X        tp = args;
X        p = strchr(tp->name,'=');
X        if (p == (char *)NULL && tp->next == (struct token *)NULL && type == 0)
X        {   /* print definition of alias */
X            alias_print(tp->name);
X            return 0;
X        }
X        if (p != (char *)NULL && tp->next == (struct token *)NULL)
X        {   /* ksh style alias */
X            *p++ = '\0';
X            alias_sdefine(tp->name,p,type);
X            *--p = '=';
X        }
X        else
X        {   /* csh style alias */
X            alias_define(tp->name,tp->next,type);
X        }
X    }
X    return 0;
X}   /* end of cmd_alias */
X
Xint cmd_unalias(pp)
Xstruct phrase *pp;
X{
X    register struct token *tp;
X
X    for (tp = pp->body->next; tp != (struct token *)NULL; tp = tp->next)
X        alias_define(tp->name,(struct token *)NULL,0);
X    return 0;
X}   /* end of cmd_unalias */
X
Xstatic struct token *proc_opts(tp,value)
Xregister struct token *tp;
Xint value;
X{
X    char buffer[BUFSIZ];
X
X    buffer[0] = '\0';
X    if (tp->next == (struct token *)NULL)
X    {   /* just print current options state */
X        if (flag_allexport)
X            strcat(buffer,"allexport ");
X        if (flag_noglob)
X            strcat(buffer,"noglob ");
X        if (flag_cmdhash)
X            strcat(buffer,"trackall ");
X        if (flag_interactive)
X            strcat(buffer,"interactive ");
X        if (flag_keywords)
X            strcat(buffer,"keyword ");
X        if (flag_monitor)
X            strcat(buffer,"monitor ");
X        if (flag_noexec)
X            strcat(buffer,"noexec ");
X        if (flag_varerr)
X            strcat(buffer,"nounset ");
X        if (flag_echoinput)
X            strcat(buffer,"verbose ");
X        if (flag_echoexec)
X            strcat(buffer,"xtrace ");
X#ifdef  LINED
X        if (_savedState.isEmacs)
X            strcat(buffer,"emacs ");
X        if (_savedState.isVi)
X            strcat(buffer,"vi ");
X#endif  /* LINED */
X        strcat(buffer,"\n");
X        io_writestring(0,buffer);
X        return tp;
X    }
X    if (strcmp(tp->next->name,"allexport") == 0)
X    {   /* export all? */
X        flag_allexport = value;
X        return tp->next;
X    }
X    if (strcmp(tp->next->name,"keyword") == 0)
X    {   /* flag keywords? */
X        flag_keywords = value;
X        return tp->next;
X    }
X    if (strcmp(tp->next->name,"monitor") == 0)
X    {   /* monitor background jobs */
X        flag_monitor = value;
X        return tp->next;
X    }
X    if (strcmp(tp->next->name,"noexec") == 0)
X    {   /* no execution? */
X        flag_noexec = value;
X        return tp->next;
X    }
X    if (strcmp(tp->next->name,"noglob") == 0)
X    {   /* file expansion? */
X        flag_noglob = value;
X        return tp->next;
X    }
X    if (strcmp(tp->next->name,"nounset") == 0)
X    {   /* unknown variables? */
X        flag_varerr = value;
X        return tp->next;
X    }
X    if (strcmp(tp->next->name,"trackall") == 0)
X    {   /* command hashing? */
X        flag_cmdhash = value;
X        return tp->next;
X    }
X    if (strcmp(tp->next->name,"verbose") == 0)
X    {   /* input echoing */
X        flag_echoinput = value;
X        return tp->next;
X    }
X    if (strcmp(tp->next->name,"xtrace") == 0)
X    {   /* exec echoing */
X        flag_echoexec = value;
X        return tp->next;
X    }
X#ifdef  LINED
X    if (strcmp(tp->next->name,"emacs") == 0)
X    {   /* emacs mode? */
X        _savedState.isEmacs = -1;
X        _savedState.isVi = 0;
X        return tp->next;
X    }
X    if (strcmp(tp->next->name,"vi") == 0)
X    {   /* vi mode? */
X        _savedState.isEmacs = 0;
X        _savedState.isVi = -1;
X    }
X#endif  /* LINED */
X    errmsg(0,LOC("proc_opts"),"unknown 'set -o' option: %s",tp->next->name);
X    return tp;
X}   /* end of proc_opts */
X
Xint cmd_set(pp)
Xstruct phrase *pp;
X{
X    register struct token *tp;
X    register char *p;
X    int rc,value;
X
X    rc = 0;
X    if (pp->body == pp->body_end)
X    {   /* print out defined variables */
X        var_dump(0,1);
X    }
X    else
X    {   /* set flags and/or positional args */
X        for (tp = pp->body->next; tp != (struct token *)NULL; tp = tp->next)
X        {   /* look for flags */
X            if (tp->name[0] != '-' && tp->name[0] != '+')
X                break;
X            else if (strcmp(tp->name,"-") == 0)
X            {   /* forced end of flags */
X                flag_echoinput = 0;
X                flag_echoexec = 0;
X                tp = tp->next;
X                break;
X            }
X            else if (strcmp(tp->name,"--") == 0)
X            {   /* forced end of flags */
X                tp = tp->next;
X                break;
X            }
X            if (tp->name[0] == '-')
X                value = 1;
X            else value = 0;
X            for (p = &tp->name[1]; *p; p++)
X            {   /* set switches as appropriate */
X                switch (*p)
X                {   /* what switch? */
X                    case 'o':   /* options? */
X                        tp = proc_opts(tp,value);
X                        break;
X                    case 'a':   /* export all? */
X                        flag_allexport = value;
X                        break;
X                    case 'f':   /* no file expansion? */
X                        flag_noglob = value;
X                        break;
X                    case 'm':   /* monitor jobs? */
X                        flag_monitor = value;
X                        break;
X                    case 'n':   /* no execution? */
X                        flag_noexec = value;
X                        break;
X                    case 'u':   /* flag var errors */
X                        flag_varerr = value;
X                        break;
X                    case 'v':   /* echo input? */
X                        flag_echoinput = value;
X                        break;
X                    case 'x':   /* echo execution? */
X                        flag_echoexec = value;
X                        break;
X                    case 'k':   /* all keywords */
X                        flag_keywords = value;
X                        break;
X                    case 'h':   /* command hashing? */
X                        flag_cmdhash = value;
X                        break;
X                    case 'i':   /* interactive? */
X                        flag_interactive = value;
X                        break;
X                    default:
X                        errmsg(0,LOC("cmd_set"),"bad switch: %s",tp->name);
X                        rc = 1;
X                        break;
X                }
X            }
X            if (tp == (struct token *)NULL)
X                break;
X        }
X        if (tp != (struct token *)NULL)
X        {   /* any positional args to reset? */
X            var_resetargs(tp);
X        }
X    }
X    return rc;
X}   /* end of cmd_set */
X
Xint cmd_unset(pp)
Xstruct phrase *pp;
X{
X    register struct token *tp;
X
X    for (tp = pp->body->next; tp != (struct token *)NULL; tp = tp->next)
X        var_settype(tp->name,TYPE_DELETED,~TYPE_DELETED);
X    return 0;
X}   /* end of cmd_unset */
X
Xint cmd_export(pp)
Xstruct phrase *pp;
X{
X    register struct token *tp;
X
X    if (pp->body->next == (struct token *)NULL)
X    {   /* just list currently exported things? */
X        var_dump(TYPE_EXPORTED,1);
X    }
X    else
X    {   /* actually export some things */
X        for (tp = pp->body->next; tp != (struct token *)NULL; tp = tp->next)
X            var_settype(tp->name,TYPE_EXPORTED,~TYPE_EXPORTED);
X    }
X    return 0;
X}   /* end of cmd_export */
X
Xint cmd_unexport(pp)
Xstruct phrase *pp;
X{
X    register struct token *tp;
X
X    for (tp = pp->body->next; tp != (struct token *)NULL; tp = tp->next)
X        var_settype(tp->name,0,~TYPE_EXPORTED);
X    return 0;
X}   /* end of cmd_unexport */
X
Xint cmd_readonly(pp)
Xstruct phrase *pp;
X{
X    register struct token *tp;
X
X    if (pp->body->next == (struct token *)NULL)
X    {   /* just list currently readonly things? */
X        var_dump(TYPE_READONLY,1);
X    }
X    else
X    {   /* actually make some things readonly */
X        for (tp = pp->body->next; tp != (struct token *)NULL; tp = tp->next)
X            var_settype(tp->name,TYPE_READONLY,~TYPE_READONLY);
X    }
X    return 0;
X}   /* end of cmd_readonly */
X
Xint cmd_shift(pp)
Xstruct phrase *pp;
X{
X    if (pp->body->next == (struct token *)NULL)
X        var_shiftargs(1);
X    else if (pp->body->next->type == SYM_NUMBER)
X        var_shiftargs(atoi(pp->body->next->name));
X    else    errmsg(0,LOC("cmd_shift"),"bad argument: %s",pp->body->next->name);
X    return 0;
X}   /* end of cmd_shift */
X
Xint cmd_exit(pp)
Xstruct phrase *pp;
X{
X    cmd_forceexit = 1;
X    if (pp->body->next == (struct token *)NULL)
X        return cmd_lastrc;
X    else    return atoi(pp->body->next->name);
X}   /* end of cmd_exit */
X
Xint cmd_source(pp)
Xstruct phrase *pp;
X{
X    if (pp->body->next == (struct token *)NULL)
X    {   /* any file to source? */
X        errmsg(0,LOC("cmd_source"),"no file given");
X        return 1;
X    }
X    if (io_pushfile(pp->body->next->name,0,0,0))
X        return 1;
X    return 0;
X}   /* end of cmd_source */
X
Xint cmd_chdir(pp)
Xstruct phrase *pp;
X{
X    struct token *tp;
X    char *dir,*newdir;
X    int rc;
X
X    tp = pp->body->next;
X    if (tp == (struct token *)NULL)
X    {   /* goto home directory */
X        dir = base_env.homedir;
X    }
X    else
X    {   /* goto specified directory */
X        dir = new_string(strlen(tp->name)+1);
X        if (dir == (char *)NULL)
X        {   /* enough memory */
X            errmsg(SHERR_NOMEM,LOC("cmd_chdir"));
X            return 1;
X        }
X        strcpy(dir,tp->name);
X        stripquotes(dir);
X        while (dir[0] == '\'' || dir[0] == '"')
X            stripquotes(dir);
X        if (strlen(dir) == 0)
X        {   /* if nothing left */
X            free(dir);
X            dir = base_env.homedir;
X            tp = (struct token *)NULL;
X        }
X    }
X    newdir = (char *)NULL;
X    rc = lchdir(dir,&newdir);
X    if (rc != 0)
X    {   /* if we didn't get there */
X        errmsg(0,LOC("cmd_chdir"),"couldn't change to %s",dir);
X        if (tp != (struct token *)NULL)
X            free(dir);
X        return rc;
X    }
X    if (tp != (struct token *)NULL)
X        free(dir);
X    var_define0("OLDPWD",base_env.dir->current,0);
X    var_define0("PWD",newdir,0);
X    free(newdir);
X    return 0;
X}   /* end of cmd_chdir */
X
Xint cmd_read(pp)
Xstruct phrase *pp;
X{
X    char buffer[BUFSIZ+1];
X    register char *first,*last;
X    register struct token *tp;
X    int rc,ctr;
X
X    tp = pp->body->next;
X    buffer[0] = '\0';
X    for (ctr = 0; ; ctr++)
X    {   /* get a line */
X        rc = read(base_env.io->input,&buffer[ctr],1);
X        if (rc <= 0)
X            return 1;
X        if (buffer[ctr] == '\n')
X            break;
X    }
X    buffer[++ctr] = '\0';
X    for (first = buffer; *first; first++)
X        if (*first == '\n')
X            *first = '\0';
X    for (last = buffer; *last; )
X    {   /* break up into words, assign to variables */
X        while (*last && strchr(base_env.separators,*last) != (char *)NULL)
X            last++;
X        first = last;
X        if (tp == (struct token *)NULL)
X        {   /* just assign everything to REPLY? */
X            var_define0("REPLY",first,0);
X            break;
X        }
X        else if (tp->next == (struct token *)NULL)
X        {   /* just assign everything to one variable */
X            var_define0(tp->name,first,0);
X            break;
X        }
X        /* else break up into words, keep going */
X        while (*last && strchr(base_env.separators,*last) == (char *)NULL)
X            last++;
X        *last++ = '\0';
X        var_define0(tp->name,first,0);
X        tp = tp->next;
X    }
X    return 0;
X}   /* end of cmd_read */
X
Xint cmd_pwd()
X{
X    char buffer[BUFSIZ];
X
X    sprintf(buffer,"%s\n",base_env.dir->current);
X    io_writestring(0,buffer);
X    return 0;
X}   /* end of cmd_pwd */
X
Xint cmd_sleep(pp)
Xstruct phrase *pp;
X{
X#ifdef  GEMDOS
X    long tstart,tend;
X#endif  /* GEMDOS */
X    int tdiff;
X
X    if (pp->body->next == (struct token *)NULL)
X        return 0;
X    if (pp->body->next->type != SYM_NUMBER)
X    {   /* an amount of time to sleep? */
X        errmsg(0,LOC("cmd_sleep"),"bad argument to sleep: %s",pp->body->next->name);
X        return 1;
X    }
X    tdiff = atoi(pp->body->next->name);
X    if (tdiff < 0)
X        return 0;
X#ifdef  GEMDOS
X    tstart = time(0L);
X    do
X    {   /* while the waiting period is in progress */
X        tend = time(0L);
X    } while ((tstart + (long)tdiff) > tend);
X#else
X    sleep(tdiff);
X#endif  /* GEMDOS */
X    return 0;
X}   /* end of cmd_sleep */
X
Xstruct commands cmd_builtin[] =
X{   /* keep this list sorted... */
X    { "((",         cmd_dparen },
X    { ".",          cmd_source },
X    { ":",          cmd_null },
X    { "[",          cmd_test },
X    { "[[",         cmd_test },
X    { "alias",      cmd_alias },
X    { "break",      cmd_break },
X    { "case",       cmd_case },
X    { "cd",         cmd_chdir },
X    { "chdir",      cmd_chdir },
X    { "continue",   cmd_continue },
X    { "do",         cmd_null },
X    { "done",       cmd_null },
X    { "elif",       cmd_null },
X    { "else",       cmd_null },
X    { "esac",       cmd_null },
X    { "eval",       cmd_eval },
X    { "exec",       cmd_exec },
X    { "exit",       cmd_exit },
X    { "export",     cmd_export },
X    { "fc",         cmd_fc },
X    { "fi",         cmd_null },
X    { "for",        cmd_for },
X    { "function",   cmd_function },
X    { "if",         cmd_if },
X#ifndef GEMDOS
X    { "jobs",       cmd_jobs },
X    { "kill",       cmd_kill },
X#endif  /* GEMDOS */
X    { "let",        cmd_let },
X#ifdef  MYMALLOC
X    { "memory",     cmd_memory },
X#endif  /* MYMALLOC */
X    { "print",      cmd_print },
X    { "pwd",        cmd_pwd },
X    { "read",       cmd_read },
X    { "readonly",   cmd_readonly },
X    { "return",     cmd_return },
X    { "select",     cmd_select },
X    { "set",        cmd_set },
X    { "shift",      cmd_shift },
X    { "sleep",      cmd_sleep },
X    { "test",       cmd_test },
X    { "then",       cmd_null },
X    { "time",       cmd_time },
X    { "trap",       cmd_trap },
X    { "typeset",    cmd_typeset },
X    { "unalias",    cmd_unalias },
X    { "unexport",   cmd_unexport },
X    { "unset",      cmd_unset },
X    { "until",      cmd_while },
X    { "version",    cmd_version },
X    { "whence",     cmd_whence },
X    { "while",      cmd_while },
X    { "{",          cmd_null },
X    { "}",          cmd_null },
X    { (char *)NULL }
X};
X
Xint builtin_word(word)
Xchar *word;
X{
X    register int i;
X
X    for (i = 0; cmd_builtin[i].name != (char *)NULL; i++)
X    {   /* is this a builtin command? */
X        if (strcmp(cmd_builtin[i].name,word) == 0)
X            return 1;
X    }
X    return 0;
X}   /* end of builtin_word */
END_OF_FILE
if test 21733 -ne `wc -c <'cmd1.c'`; then
    echo shar: \"'cmd1.c'\" unpacked with wrong size!
fi
# end of 'cmd1.c'
fi
if test -f 'util.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'util.c'\"
else
echo shar: Extracting \"'util.c'\" \(20385 characters\)
sed "s/^X//" >'util.c' <<'END_OF_FILE'
X/*
X * Command Input Shell
X * Dave Clemans
X * 12/88-1/89
X *
X * "spiritually" based on Bourne, Korn shells
X *
X * Utility Routines
X *
X * $Id: util.c,v 1.6 89/02/25 17:40:21 dclemans Exp $
X *
X * $Log:	util.c,v $
X * Revision 1.6  89/02/25  17:40:21  dclemans
X * miscellaneous bug fixes/speedups
X * 
X * Revision 1.5  89/02/20  20:07:09  dclemans
X * Add RCS identifiers
X * 
X */
X#include <stdio.h>
X#include "shell.h"
X
X#ifdef  GEMDOS
X#include <types.h>
X#include <stat.h>
X#else
X#ifndef USG
X#include <sys/param.h>
X#endif  /* USG */
X#include <sys/types.h>
X#include <sys/stat.h>
X#ifndef USG
X#include <sys/file.h>
X#else
X#include <fcntl.h>
X#define	F_OK		0	/* does file exist */
X#define	X_OK		1	/* is it executable by caller */
X#define	W_OK		2	/* writable by caller */
X#define	R_OK		4	/* readable by caller */
X#include <dirent.h>
X#define MAXPATHLEN  DIRBUF
X#endif  /* USG */
Xextern char *getwd();
X#endif  /* GEMDOS */
X
Xstruct to_del
X{
X    struct  to_del *next;
X    char    file[1];            /* over-indexed; must be at end */
X};
Xstatic struct to_del *deletes = (struct to_del *)NULL;
X
Xvoid delete_later(file)
Xchar *file;
X{
X    register struct to_del *td;
X
X    td = (struct to_del *)malloc(sizeof(*td)+strlen(file));
X    if (td == (struct to_del *)NULL)
X    {   /* enough memory? */
X        errmsg(SHERR_NOMEM,LOC("delete_later"));
X        errmsg(0,LOC("delete_later"),"can't delete %s",file);
X        return;
X    }
X    strcpy(td->file,file);
X    td->next = deletes;
X    deletes = td;
X}   /* end of delete_later */
X
Xvoid do_deletes()
X{
X    register struct to_del *td,*otd;
X
X    for (td = deletes; td != (struct to_del *)NULL; )
X    {   /* delete files in list; free list */
X        otd = td;
X        td = td->next;
X        unlink(otd->file);
X        free(otd);
X    }
X    deletes = (struct to_del *)NULL;
X}   /* end of do_deletes */
X
Xchar *strcopy(s)
Xregister char *s;
X{
X    register char *ns;
X
X    if (s == (char *)NULL)
X        return (char *)NULL;
X    ns = new_string(strlen(s)+1);
X    if (ns == (char *)NULL)
X    {   /* enough memory? */
X        errmsg(SHERR_NOMEM,LOC("strcopy"));
X        return (char *)NULL;
X    }
X    strcpy(ns,s);
X    return ns;
X}   /* end of strcopy */
X
Xvoid stripquotes(s)
Xregister char *s;
X{
X    register char *p;
X
X    for (p = s; *p != '\0'; p++)
X    {   /* check for special chars */
X        switch (*p)
X        {   /* a special char? */
X            case '\'':
X                strcpy(p,p+1);
X                while (*p && *p != '\'')
X                    p++;
X                if (*p == '\'')
X                    strcpy(p,p+1);
X                break;
X            case '"':
X                strcpy(p,p+1);
X                while (*p && *p != '"')
X                {   /* scan the string */
X                    if (*p == ESCAPE_CHAR)
X                        strcpy(p,p+1);
X                    p++;
X                }
X                if (*p == '"')
X                    strcpy(p,p+1);
X                break;
X            case ESCAPE_CHAR:
X                strcpy(p,p+1);
X                break;
X            default:
X                break;
X        }
X    }
X}   /* end of stripquotes */
X
Xint isexec(path)
Xregister char *path;
X{
X#ifdef  GEMDOS
X    struct stat statb;
X
X    if (stat(path,&statb) < 0)
X        return 0;
X    else
X    {   /* check the modes */
X        return 1;
X    }
X#else
X    return (access(path,X_OK) == 0);
X#endif  /* GEMDOS */
X}   /* end of isexec */
X
Xint isread(path)
Xregister char *path;
X{
X#ifdef  GEMDOS
X    struct stat statb;
X
X    if (stat(path,&statb) < 0)
X        return 0;
X    else
X    {   /* check the modes */
X        return 1;
X    }
X#else
X    return (access(path,R_OK) == 0);
X#endif  /* GEMDOS */
X}   /* end of isread */
X
Xint isfullpath(path)
Xregister char *path;
X{
X    char drive;
X
X    if (path[0] == '.' && path[1] == DIR_SEPARATOR)
X        return 1;
X    if (path[0] == '.' && path[1] == '.' && path[2] == DIR_SEPARATOR)
X        return 1;
X    if (path[0] == DIR_SEPARATOR)
X        return 1;
X#ifdef	GEMDOS
X    drive = path[0];
X    if (islower(drive))
X        drive = _toupper(drive);
X    if (drive >= 'A' && drive <= 'P' && path[1] == ':')
X        return 1;
X#endif	/* GEMDOS */
X    return 0;
X}   /* end of isfullpath */
X
X#ifdef  GEMDOS
Xchar *xchdir(path)
Xregister char *path;
X{
X    char drive,currdrive;
X    register char *ldir;
X    int mask,rc;
X    char newpath[258];
X
X    drive = path[0];
X    if (islower(drive))
X        drive = _toupper(drive);
X    if (drive >= 'A' && drive <= 'P' && path[1] == ':')
X    {   /* dir includes a drive spec */
X        ldir = &path[2];
X    }
X    else
X    {   /* local to current drive */
X        drive = Dgetdrv() + 'A';
X        ldir = path;
X    }
X    currdrive = Dgetdrv() + 'A';
X    if (drive != currdrive)
X    {   /* need to switch to another drive? */
X        mask = Drvmap();
X        if (!(mask && (1 << (drive-'A'))))
X            return (char *)NULL;
X        Dsetdrv(drive-'A');
X    }
X    rc = Dsetpath(ldir);
X    if (rc != 0)
X        return (char *)NULL;
X    rc = Dgetpath(&newpath[2],drive-'A'+1);
X    if (rc != 0)
X        return (char *)NULL;
X    newpath[0] = drive;
X    newpath[1] = ':';
X    return strcopy(newpath);
X}   /* end of xchdir */
X#else
Xchar *xchdir(path)
Xregister char *path;
X{
X    char buffer[MAXPATHLEN];
X
X    if (chdir(path) < 0)
X        return (char *)NULL;
X#ifndef USG
X    if (getwd(buffer) == (char *)NULL)
X#else
X    if (getcwd(buffer,sizeof buffer) == (char *)NULL)
X#endif  /* USG */
X    {   /* if couldn't find new path */
X        errmsg(0,LOC("xchdir"),buffer);
X        return (char *)NULL;
X    }
X    return strcopy(buffer);
X}   /* end of xchdir */
X#endif  /* GEMDOS */
X
Xint lchdir(dir,path)
Xchar *dir;
Xchar **path;
X{
X    char *ldir;
X    register char *first,*last,*fullpath;
X
X    if (strcmp(dir,"-") == 0)
X    {   /* change to OLDPWD? */
X        ldir = var_normal("$OLDPWD");
X	if (ldir == (char *)NULL)
X        {   /* something to go to */
X            *path = (char *)NULL;
X            return 1;
X        }
X        *path = xchdir(ldir);
X        free(ldir);
X        if (*path == (char *)NULL)
X            return 1;
X        io_writestring(0,*path);
X        io_writestring(0,"\n");
X        return 0;
X    }
X    if (base_env.cd_path == (char *)NULL || isfullpath(dir))
X    {   /* if no path supplied */
X        *path = xchdir(dir);
X        if (*path == (char *)NULL)
X            return 1;
X#ifdef  GEMDOS
X        for (ldir = *path; *ldir; ldir++)
X            if (isupper(*ldir))
X                *ldir = _tolower(*ldir);
X#endif  /* GEMDOS */
X        return 0;
X    }
X    for (first = last = base_env.cd_path; *last; last++)
X    {   /* for each element of path */
X        while (*last && *last != ',')
X            last++;
X        fullpath = new_string(strlen(dir)+(int)(last-first)+2);
X        if (fullpath == (char *)NULL)
X        {   /* enough memory? */
X            errmsg(SHERR_NOMEM,LOC("lchdir"));
X            return 1;
X        }
X        if ((int)(last-first) > 0)
X        {   /* if a prefix present */
X            strncpy(fullpath,first,(int)(last-first));
X            if (fullpath[(int)(last-first)-1] != DIR_SEPARATOR)
X                fullpath[(int)(last-first)] = DIR_SEPARATOR;
X            fullpath[(int)(last-first)+1] = '\0';
X        }
X        else    fullpath[0] = '\0';
X        strcat(fullpath,dir);
X        *path = xchdir(fullpath);
X        free(fullpath);
X        if (*path != (char *)NULL)
X        {   /* if directory finally found */
X#ifdef  GEMDOS
X            for (ldir = *path; *ldir; ldir++)
X                if (isupper(*ldir))
X                    *ldir = _tolower(*ldir);
X#endif  /* GEMDOS */
X            io_writestring(0,*path);
X            io_writestring(0,"\n");
X            return 0;
X        }
X        first = last+1;
X    }
X    return 1;
X}   /* end of lchdir */
X
Xint errmsg(code,file,routine,line,fmt,a0,a1,a2,a3,a4,a5,a6,a7,a8,a9)
Xint code;
Xchar *file;
Xchar *routine;
Xint line;
Xchar *fmt;
X{
X    extern int errno;
X    char buffer[BUFSIZ];
X    register char *p;
X
X    if (var_arg0 == (char *)NULL)
X        var_arg0 = "";
X    sprintf(buffer,"%s: %s(%s,%d): ",var_arg0,file,routine,line);
X    for (p = buffer; *p; p++)
X        /* do nothing */;
X    switch (code)
X    {   /* check for common err messages; else do normal one */
X        case SHERR_NOMEM:
X            strcpy(p,"out of memory");
X            break;
X        case SHERR_PERROR:
X            /* perror(a0,a1); */
X            /* fall through... */
X        default:
X            sprintf(p,fmt,a0,a1,a2,a3,a4,a5,a6,a7,a8,a9);
X            break;
X    }
X    io_writestring(1,buffer);
X    io_writestring(1,"\n");
X    return 0;
X}   /* end of errmsg */
X
Xint copy_file(old,new)
Xchar *old;
Xchar *new;
X{
X    register int ifd,ofd,rc;
X    char buffer[BUFSIZ];
X
X    ifd = open(old,0);
X    if (ifd < 0)
X        return -1;
X    ofd = creat(new,0666);
X    if (ofd < 0)
X    {   /* did the file get created? */
X        close(ifd);
X        return -1;
X    }
X    while ((rc = read(ifd,buffer,sizeof buffer)) > 0)
X        write(ofd,buffer,rc);
X    close(ifd);
X    close(ofd);
X    return 0;
X}   /* end of copy_file */
X
Xstatic struct token *copy_tokens(ftp,strip)
Xstruct token *ftp;
Xint strip;
X{
X    register struct token *stp,*etp,*tp;
X
X    stp = etp = (struct token *)NULL;
X    while (ftp != (struct token *)NULL)
X    {   /* copy the list */
X        tp = new_token(strlen(ftp->name));
X        if (tp == (struct token *)NULL)
X        {   /* enough memory? */
X            errmsg(SHERR_NOMEM,LOC("copy_tokens"));
X            tokens_free(stp);
X            return (struct token *)NULL;
X        }
X        strcpy(tp->name,ftp->name);
X        if (strip)
X            stripquotes(tp->name);
X        tp->type = ftp->type;
X        if (stp == (struct token *)NULL)
X            stp = etp = tp;
X        else
X        {   /* tack onto end */
X            etp->next = tp;
X            etp = tp;
X        }
X        ftp = ftp->next;
X    }
X    return stp;
X}   /* end of copy_tokens */
X
Xstruct phrase *copy_phrase(fpp,expand,strip,copybody)
Xstruct phrase *fpp;
Xint expand;
Xint strip;
Xint copybody;
X{
X    register struct phrase *npp;
X    struct phrase *pp,*newpp;
X    register struct token *newtp;
X    struct iotoken *io,*newio;
X    char *ptr;
X    int len;
X
X    npp = new_phrase();
X    if (npp == (struct phrase *)NULL)
X    {   /* enough memory? */
X        errmsg(SHERR_NOMEM,LOC("copy_phrase"));
X        return (struct phrase *)NULL;
X    }
X
X    if (fpp->type != (struct token *)NULL)
X        len = strlen(fpp->type->name);
X    else len = 0;
X    newtp = new_token(len);
X    if (newtp == (struct token *)NULL)
X    {   /* enough memory? */
X        errmsg(SHERR_NOMEM,LOC("copy_phrase"));
X        phrase_free(npp);
X        return (struct phrase *)NULL;
X    }
X    if (fpp->type != (struct token *)NULL)
X    {   /* if a type token to copy */
X        newtp->type = fpp->type->type;
X        strcpy(newtp->name,fpp->type->name);
X        if (strip)
X            stripquotes(newtp->name);
X    }
X    else
X    {   /* stick in a dummy type */
X        newtp->type = SYM_SEMI;
X        newtp->name[0] = '\0';
X    }
X    npp->type = newtp;
X
X    if (expand)
X        npp->var = lex_reparse_tokens(fpp->var,strip);
X    else npp->var = copy_tokens(fpp->var,strip);
X    for (newtp = npp->var; newtp != (struct token *)NULL; newtp = newtp->next)
X        if (newtp->next == (struct token *)NULL)
X            break;
X    if (newtp != (struct token *)NULL)
X        npp->var_end = newtp;
X
X    if (expand)
X        npp->body = lex_reparse_tokens(fpp->body,strip);
X    else npp->body = copy_tokens(fpp->body,strip);
X    for (newtp = npp->body; newtp != (struct token *)NULL; newtp = newtp->next)
X        if (newtp->next == (struct token *)NULL)
X            break;
X    if (newtp != (struct token *)NULL)
X        npp->body_end = newtp;
X
X    for (io = fpp->io; io != (struct iotoken *)NULL; io = io->next)
X    {   /* for each iotoken in phrase */
X        newtp = (struct token *)NULL;
X        if (expand && io_pushtoken(io->file,0) == 0)
X        {   /* set up for var expansion */
X            newtp = lex_token(1);
X            ptr = newtp->name;
X        }
X        else    ptr = io->file;
X        newio = new_iotoken(strlen(ptr));
X        if (newio == (struct iotoken *)NULL)
X        {   /* enough memory? */
X            errmsg(SHERR_NOMEM,LOC("copy_phrase"));
X            phrase_free(npp);
X            if (newtp != (struct token *)NULL)
X                free(newtp);
X            return (struct phrase *)NULL;
X        }
X        if (io->tempfile != (char *)NULL)
X        {   /* if temp file here, need to copy it */
X            len = 16;
X#ifdef  GEMDOS
X            len += strlen(base_env.tmpdir);
X#endif  /* GEMDOS */
X            newio->tempfile = new_string(len+1);
X            if (newio->tempfile == (char *)NULL)
X                errmsg(SHERR_NOMEM,LOC("copy_phrase"));
X            else
X            {   /* copy the file */
X#ifdef  GEMDOS
X                sprintf(newio->tempfile,"%stemp%d.tmp",base_env.tmpdir,
X                    base_env.temp_count++);
X#else
X                sprintf(newio->tempfile,"/tmp/sh%d.temp",base_env.temp_count++);
X#endif  /* GEMDOS */
X                len = copy_file(io->tempfile,newio->tempfile);
X                if (len < 0)
X                {   /* if copy didn't take */
X                    errmsg(0,LOC("copy_phrase"),"error copying %s to %s",
X                        io->tempfile,newio->tempfile);
X                    free(newio->tempfile);
X                    newio->tempfile = (char *)NULL;
X                }
X            }
X        }
X        newio->fd = io->fd;
X        newio->type = io->type;
X        strcpy(newio->file,ptr);
X        if (newtp != (struct token *)NULL)
X            free(newtp);
X        if (npp->io == (struct iotoken *)NULL)
X            npp->io = npp->io_end = newio;
X        else
X        {   /* tack onto end */
X            npp->io_end->next = newio;
X            npp->io_end = newio;
X        }
X    }
X
X    for (pp = fpp->group; copybody && pp != (struct phrase *)NULL; pp = pp->next)
X    {   /* for each phrase in sentence */
X        newpp = copy_phrase(pp,0,0,copybody);
X        if (newpp == (struct phrase *)NULL)
X        {   /* enough memory? */
X            /* message already printed */
X            phrase_free(npp);
X            return (struct phrase *)NULL;
X        }
X        if (npp->group == (struct phrase *)NULL)
X            npp->group = npp->group_end = newpp;
X        else
X        {   /* tack onto end */
X            npp->group_end->next = newpp;
X            npp->group_end = newpp;
X        }
X    }
X
X    return npp;
X}   /* end of copy_phrase */
X
Xstruct phrase *copy_group(fpp,expand,strip,copybody)
Xstruct phrase *fpp;
Xint expand;
Xint strip;
Xint copybody;
X{
X    register struct phrase *ppf,*ppl,*cpp;
X
X    ppf = ppl = (struct phrase *)NULL;
X    while (fpp != (struct phrase *)NULL)
X    {   /* copy list of phrases */
X        cpp = copy_phrase(fpp,expand,strip,copybody);
X        if (cpp == (struct phrase *)NULL)
X        {   /* enough memory? */
X            /* message already printed */
X            if (ppf != (struct phrase *)NULL)
X                sentence_free(ppf);
X            return (struct phrase *)NULL;
X        }
X        if (ppf == (struct phrase *)NULL)
X            ppf = ppl = cpp;
X        else
X        {   /* tack onto end */
X            ppl->next = cpp;
X            ppl = cpp;
X        }
X        fpp = fpp->next;
X    }
X    return ppf;
X}   /* end of copy_group */
X
Xstruct phrase *new_phrase()
X{
X    register struct phrase *pp;
X
X    pp = (struct phrase *)malloc(sizeof(*pp));
X    if (pp == (struct phrase *)NULL)
X        return (struct phrase *)NULL;
X    pp->type = pp->body = pp->body_end = (struct token *)NULL;
X    pp->var = pp->var_end = (struct token *)NULL;
X    pp->io = pp->io_end = (struct iotoken *)NULL;
X    pp->group = pp->group_end = (struct phrase *)NULL;
X    pp->next = (struct phrase *)NULL;
X
X    return pp;
X}   /* end of new_phrase */
X
Xstruct token *new_token(len)
Xint len;
X{
X    register struct token *tp;
X
X    tp = (struct token *)malloc(sizeof(*tp)+len);
X    if (tp == (struct token *)NULL)
X        return (struct token *)NULL;
X    tp->next = (struct token *)NULL;
X    tp->type = -1;
X    tp->name[0] = '\0';
X
X    return tp;
X}   /* end of new_token */
X
Xstruct iotoken *new_iotoken(len)
Xint len;
X{
X    register struct iotoken *ip;
X
X    ip = (struct iotoken *)malloc(sizeof(*ip)+len);
X    if (ip == (struct iotoken *)NULL)
X        return (struct iotoken *)NULL;
X    ip->fd = -1;
X    ip->type = -1;
X    ip->tempfile = (char *)NULL;
X    ip->next = (struct iotoken *)NULL;
X    ip->file[0] = '\0';
X
X    return ip;
X}   /* end of new_iotoken */
X
Xstruct variable *new_variable()
X{
X    register struct variable *vp;
X
X    vp = (struct variable *)malloc(sizeof(*vp));
X    if (vp == (struct variable *)NULL)
X        return (struct variable *)NULL;
X    vp->name = vp->value = (char *)NULL;
X    vp->cmd = vp->type = vp->misc = 0;
X    vp->left = vp->right = (struct variable *)NULL;
X
X    return vp;
X}   /* end of new_variable */
X
Xstruct aliases *new_alias()
X{
X    register struct aliases *ap;
X
X    ap = (struct aliases *)malloc(sizeof(*ap));
X    if (ap == (struct aliases *)NULL)
X        return (struct aliases *)NULL;
X    ap->name = (char *)NULL;
X    ap->tp = (struct token *)NULL;
X    ap->type = 0;
X    ap->left = ap->right = (struct aliases *)NULL;
X
X    return ap;
X}   /* end of new_alias */
X
Xstruct function *new_function()
X{
X    register struct function *fp;
X
X    fp = (struct function *)malloc(sizeof(*fp));
X    if (fp == (struct function *)NULL)
X        return (struct function *)NULL;
X    fp->name = (char *)NULL;
X    fp->code = (struct phrase *)NULL;
X    fp->left = fp->right = (struct function *)NULL;
X
X    return fp;
X}   /* end of new_function */
X
Xchar *new_string(len)
Xint len;
X{
X    register char *cp;
X
X    cp = (char *)malloc(len);
X    if (cp == (char *)NULL)
X        return (char *)NULL;
X    *cp = '\0';
X
X    return cp;
X}   /* end of new_string */
X
Xstruct infile *new_infile()
X{
X    register struct infile *ip;
X
X    ip = (struct infile *)malloc(sizeof(*ip));
X    if (ip == (struct infile *)NULL)
X        return (struct infile *)NULL;
X    ip->name = ip->start = ip->end = (char *)NULL;
X#ifdef  GEMDOS
X    ip->fp = -1;
X#else
X    ip->fp = -1;
X#endif  /* GEMDOS */
X    ip->delete = ip->nonl = ip->markeof = 0;
X    ip->next = (struct infile *)NULL;
X
X    return ip;
X}   /* end of new_infile */
X
Xchar **new_argv(count)
Xint count;
X{
X    register char **av;
X    register int i;
X
X    av = (char **)malloc(sizeof(char *) * (count+1));
X    if (av == (char **)NULL)
X        return (char **)NULL;
X    for (i = 0; i <= count; i++)
X        av[i] = (char *)NULL;
X
X    return av;
X}   /* end of new_argv */
X
Xstruct strsave *new_strsave(s)
Xchar *s;
X{
X    register char *news;
X    register struct strsave *sp;
X
X    news = strcopy(s);
X    if (news == (char *)NULL)
X        return (struct strsave *)NULL;
X    sp = (struct strsave *)malloc(sizeof(*sp));
X    if (sp == (struct strsave *)NULL)
X    {   /* enough memory? */
X        free(news);
X        return (struct strsave *)NULL;
X    }
X    sp->next = (struct strsave *)NULL;
X    sp->string = sp->ptr = news;
X
X    return sp;
X}   /* end of new_strsave */
X
Xvoid phrase_free(pp)
Xstruct phrase *pp;
X{
X    register struct phrase *ppp,*opp;
X    register struct iotoken *iop;
X    struct iotoken *oiop;
X
X    if (pp->type != (struct token *)NULL)
X        free(pp->type);
X    for (ppp = pp->group; ppp != (struct phrase *)NULL; )
X    {
X        opp = ppp;
X        ppp = ppp->next;
X        phrase_free(opp);
X    }
X    tokens_free(pp->body);
X    tokens_free(pp->var);
X    for (iop = pp->io; iop != (struct iotoken *)NULL; )
X    {
X        oiop = iop;
X        iop = iop->next;
X        if (oiop->tempfile != (char *)NULL)
X        {   /* a tempfile (probably a heredoc) */
X            unlink(oiop->tempfile);
X            free(oiop->tempfile);
X        }
X        free(oiop);
X    }
X    free(pp);
X}   /* end of phrase_free */
X
Xvoid sentence_free(fsp)
Xstruct phrase *fsp;
X{
X/*
X    struct phrase *pp,*opp;
X*/
X    struct phrase *osp,*sp;
X
X    for (sp = fsp; sp != (struct phrase *)NULL; )
X    {   /* delete whole paragraph */
X/*
X        for (pp = sp->group; pp != (struct phrase *)NULL; )
X        {
X            opp = pp;
X            pp = pp->next;
X            phrase_free(opp);
X        }
X*/
X        osp = sp;
X        sp = sp->next;
X        phrase_free(osp);
X    }
X}   /* end of sentence_free */
X
Xvoid tokens_free(ftp)
Xstruct token *ftp;
X{
X    register struct token *tp,*otp;
X
X    for (tp = ftp; tp != (struct token *)NULL ; )
X    {
X        otp = tp;
X        tp = tp->next;
X        free(otp);
X    }
X}   /* end of tokens_free */
END_OF_FILE
if test 20385 -ne `wc -c <'util.c'`; then
    echo shar: \"'util.c'\" unpacked with wrong size!
fi
# end of 'util.c'
fi
echo shar: End of archive 5 \(of 11\).
cp /dev/null ark5isdone
MISSING=""
for I in 1 2 3 4 5 6 7 8 9 10 11 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 11 archives.
    rm -f ark[1-9]isdone ark[1-9][0-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0



More information about the Alt.sources mailing list