[comp.sys.amiga] Shell Archive Programs for Amiga

fgd3@jc3b21.UUCP (Fabbian G. Dufoe) (07/22/87)

     Here are two programs to pack and unpack shell archives on the Amiga.
Shell archives are great on Unix systems.  They simplify handling
collections of files and everybody has the software to unpack them.  There
are several other protocols which simplify shipping collections of files by
phone.  The trouble with them is you need compatibile software at both ends
of the line.

     Shell archives are more forgiving.  If you don't have the software to
pack and unpack them you can treat them like ordinary text files.  You can
even unpack them by hand.  That's a real pain, though, so I wrote these
programs.

     Shar packs a shell archive and Sh unpacks it.  Any Unix system can
unpack a shell archive created by my Shar program.  In fact, my Shar program
will compile and run on Unix System/V on an AT&T 3B2.  My Sh program can
unpack shell archives created by many Unix Shar programs.

     This is how I handle shell archives which my Sh can't unpack.  My
Usenet access is via an AT&T 3B2 running Unix System/V.  I unpack the shell
archive on the 3B2, then repack it with my Shar.  Then I download it to my
Amiga and unpack it with Sh.  That's why I wrote Shar to be portable.

     The code was written for the Lattice AmigaDOS C Compiler, version 3.10.
If you have that compiler you can compile both programs by executing
Makefile once you have unpacked the shell archive.  If you have the Aztec C
compiler I can't help you.  The code is vanilla C, so I don't think you'll
have any trouble compiling it.  But I don't know the compiler commands you
need.

     I hope you'll find these programs useful.  Perhaps I should apologize
for posting them to comp.sys.amiga.  I never see anything in
comp.sources.amiga but I see a lot of articles in comp.sys.amiga lamenting 
the disappearance of things posted to one of those other groups.  From that 
I've learned not to trust them.  Feel free to pass the code on to 
comp.sources.amiga if you think that would help.

--Fabbian Dufoe
  350 Ling-A-Mor Terrace South
  St. Petersburg, Florida  33705
  813-823-2350

UUCP: ...seismo!akgua!usfvax2!jc3b21!fgd3 
-------------------------------cut here-------------------------------------
# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file"
# Created Wed Jul 22 10:37:36 1987
#
# This archive contains:
#      Sh.doc
#      Fatal.c
#      Fgetline.c
#      Gettoken.c
#      Makefile
#      Parse.c
#      Sh.c
#      Sh.lnk
#      Shar.c
#      Token.h
echo "Creating Sh.doc"
cat > Sh.doc <<"***EOF Sh.doc***"






                        Sh: Unpack Files from Shell Archive
                                        by
                               Fabbian G. Dufoe, III



          INTRODUCTION

               Sh is a program designed to unpack shell archives created
          with my Shar program.  It works by recognizing the "cat" and
          "echo" commands and by correctly processing quoted strings, >
          (standard output redirection), and << ("here document"
          redirection).  It does not rely on the existence of any external
          programs.

               The shell archive is a technique which makes it easier to
          manipulate collections of related files.  To create a shell
          archive file two or more files are concatenated into a single
          file.  Shell commands are interspersed with the original files.
          That is called packing.  When a command processor reads the shell
          archive file the shell commands cause it to create the original
          files.  That is called unpacking.

               The simplest program to pack a shell archive might work by
          prefixing the file with "cat > file <<string" where "file" is the
          name of the file and "string" is a character string which won't
          occur in the file itself.  Next the program copies the text of the
          file.  Finally, it writes a line beginning with "string".

               A nicer version would write "echo Creating file" before the
          "cat" command.  Then the shell will report on its progress as it
          unpacks the files.  that is how my Shar program works.  A lot of
          programs pack shell archives essentially the same way but include
          other commands to provide additional enhancements.  Because Sh
          ignores any commands except "echo" and "cat" it will handle those
          files correctly, although it does not support the added features.
          it will not handle files which rely on commands like "sed" for
          unpacking.


          EXECUTION

               To run Sh type "Sh file" where "file" is the name of the
          shell archive file to be unpacked.  If the shell archive file is
          not in the current directory it can be located by either a
          relative or absolute path name.  The unpacked files will be
          located relative to the current directory unless they have
          absolute path names.  There are no command line options.


          SUMMARY OF EXECUTION

               Sh reads the shell archive file until it encounters a line
          beginning with "echo" or "cat".  If the line begins with "echo" Sh












          writes the rest of the line to standard output.

               If the line begins with "cat" the processing is more
          complicated.  The program checks for ">" or "<<".  If it finds ">"
          it uses the following word as a file name to open for writing.  If
          it finds "<<" it remembers the following string as a terminator.

               Once the "cat" command line has been parsed Sh goes into
          "copy" mode.  It reads a line from the file.  If the line doesn't
          match the terminator string Sh writes it to the output file.  When
          the terminator string is encountered it closes the output file and
          leave "copy" mode.

               When Sh encounters end of file it closes its input file and
          terminates.
***EOF Sh.doc***
echo "Creating Fatal.c"
cat > Fatal.c <<"***EOF Fatal.c***"
/* Fatal.c

   Copyright (c) 1987 by F. G. Dufoe, III
   All rights reserved.

   Permission is granted to redistribute this program provided the source
   code is included in the distribution and this copyright notice is
   unchanged.

*/


#include <stdio.h>
#ifdef AMIGA
#include <error.h>
#include <exec/types.h>
#else
#include <errno.h>
#include <ctype.h>
extern int sys_nerr;
extern char *sys_errlist[];
#endif


void
fatal(string)
   /* This function prints an error message passed by the calling program
      and exits with an error code. */
char *string;
{
   extern int errno;
      /* This global variable is used to communicate error codes. */

   fprintf(stderr, "%s\n", string);
      /* Print the error message passed by the calling program on standard
         error. */
   if (errno > 0 && errno < sys_nerr)
      fprintf(stderr, "\t%d: %s\n", errno, sys_errlist[errno]);
         /* If the error number is in the system error list print it and its
            explanation on standard error. */
   exit(errno);
      /* Terminate the program and pass an error code back to the parent
         program. */
}
***EOF Fatal.c***
echo "Creating Fgetline.c"
cat > Fgetline.c <<"***EOF Fgetline.c***"
/* Fgetline.c

   Copyright (c) by Fabbian G. Dufoe, III
   All rights reserved.

   Permission is granted to redistribute this program provided the source
   code is included in the distribution and this copyright notice is
   unchanged.

*/


#include <stdio.h>
#ifdef AMIGA
#include <error.h>
#include <exec/types.h>
#else
#include <errno.h>
#include <ctype.h>
extern int sys_nerr;
extern char *sys_errlist[];
#endif


#define AVGLINE 512
   /* This is the amount of space which will be allocated initially for
      the buffer into which lines read from the file will be placed. */
#define MARGIN 10
   /* When we get within MARGIN characters of the end of the buffer we
      will try to get more memory before proceeding. */


char *
fgetline(ifile)
   /* This function gets the next line of text from a file.  The calling
      program passes it a FILE pointer to identify the file.  Fgetline()
      returns a pointer to a null-terminated character string containing the
      line from that file if it successfully read any characters.  It
      returns a NULL pointer if an error occurs or if it encountered end of
      file.  The calling program must examine the external variable errno
      to determine which is the case.  If fgetline() encountered end of file
      errno will be zero. */
FILE *ifile;
   /* This is a file pointer for the file to be read. */
{
   int c;
      /* This is the character read from the file. */
   extern int errno;
      /* This global variable is used to communicate error codes. */
   int i;
      /* This is a counter that is incremented for each character placed
         in the buffer. */
   static int length = AVGLINE;
      /* This is the length of the character buffer that has been
         allocated. */
   static char *line = NULL;
      /* This is the address of the character string containing the line
         read from the file. */
   char *newline;
      /* This is a temporary pointer used when more memory has to be
         allocated.  Realloc() returns a NULL pointer if it fails and we
         don't want to lose track of the characters we have already
         collected. */


   if (line == NULL)
      /* If the line buffer hasn't been allocated yet do it now. */
      if ((line = (char *)malloc(length)) == NULL)
         /* If malloc() returns a NULL pointer the memory couldn't be
            allocated.  Return a NULL pointer.  The calling program
            can look at errno for the reason. */
         return(NULL);

   for (i = 0; (c = fgetc(ifile)) != EOF && c != '\n'; i++)
      /* As long as we don't hit EOF or a newline keep collecting
         characters. */
   {
      *(line + i) = c;
      if (i > length - MARGIN)
         /* If we are within MARGIN characters of the end of the
            buffer try to allocate more memory. */
      {
         newline = (char *)realloc(line, length + AVGLINE);
         if (newline == NULL)
            /* If realloc() failed we try to salvage what we have
               already collected. */
         {
            *(line + ++i) = '\0';
               /* Tack on a null-terminator. */
            return(line);
               /* Return the pointer to the string we have
                  collected so far. */
         }
         length += AVGLINE;
            /* The buffer is longer now, so we'll update our record
               of its length. */
         line = newline;
            /* We got the additional space we asked for so we change
               the pointer to refer to the new buffer. */
      }
   }

   if (c == '\n')
      /* If we came to a newline character we are at the end of the
         line.  Add the newline to the end of the buffer. */
      *(line + i++) = c;

   if (c == EOF && i == 0)
      /* If we are at end of file and there are no characters in the
         buffer we can free the buffer's memory, set the pointer to
         NULL, and return. */
   {
      free(line);
      line = NULL;
      errno = 0;
      return(NULL);
   }

   *(line + i) = '\0';
      /* Terminate the string with a null character. */

   return(line);
      /* Return the pointer to the calling program. */
}
***EOF Fgetline.c***
echo "Creating Gettoken.c"
cat > Gettoken.c <<"***EOF Gettoken.c***"
/* Gettoken.c

   Copyright (c) 1987 by F. G. Dufoe, III
   All rights reserved.

   Permission is granted to redistribute this program provided the source
   code is included in the distribution and this copyright notice is
   unchanged.

*/

#include <string.h>
#include "Token.h"


TOKEN
gettoken(line, word)
   /* This function gets the next token from a line of text.  The calling
      program passes it two character pointers.  The first points to the
      buffer containing the line of text to be parsed.  The second points to
      a buffer in which the function is to place the text of the token.  The
      function returns the token type (as defined in Token.h). */
char *line;
   /* This points to the line of text to be parsed. */
char *word;
   /* This is where the function can store the text of the token. */
{
   int c;
      /* This is the current character read from the line. */

   static int il = 0;
      /* This is the index for line[]. */

   int iw = 0;
      /* This is the index for word[]. */

   enum
   {
      LT,      /* One "<" found, look for another. */
      NEUTRAL, /* Look for first character of token. */
      INQUOTE, /* Open quote found, accumulate until close quote found. */
      INWORD   /* Unrecognized character, accumulate until word ends. */
   } state = NEUTRAL;

   while ((c = line[il++]) != '\0')
      /* We are prepared to read the entire line.  If we identify a token
         before we reach the end of the line we'll return early. */
   {
      switch (state)
      {
         case NEUTRAL:
            switch (c)
            {
               case '>':
                  word[iw++] = c;
                  word[iw] = '\0';
                  return(T_GT);
                     /* We recognize the greater than sign immediately, so
                        we put it in the buffer and return. */
               case '<':
                  state = LT;
                  word[iw++] = c;
                  continue;
                     /* We can't identify this token until we know whether
                        the next character is another "<", so we just save
                        it and look at the next character.  The state change
                        "remembers" that we saw a "<". */
               case ' ':
               case '\n':
               case '\t':
                  continue;
                     /* We just ignore any whitespace characters. */
               case '"':
                  state = INQUOTE;
                  continue;
                     /* Inside a quoted string we will accumulate all
                        characters, including whitespace characters.  But we
                        don't consider the quotation marks as part of the
                        string.  We change state to accumulate characters
                        differently. */
               default:
                  state = INWORD;
                  word[iw++] = c;
                  continue;
                     /* Any character not listed above is the beginning of a
                        word.  We will want to accumulate it and those that
                        follow until the word ends. */
            }
         case LT:
            if (c == '<')
            {
               word[iw++] = c;
               word[iw] = '\0';
               return(T_LTLT);
                  /* We found the second "<", so we put it in the buffer and
                     return. */
            }
            word[iw] = '\0';
            il--;
            return(T_WORD);
               /* The second character wasn't "<", so we will return the
                  single "<" as a word.  We will want to look at the second
                  character again, so we decrement il. */
         case INQUOTE:
            switch (c)
            {
               case '\\':
                  word[iw++] = line[il++];
                  continue;
                     /* If we find a "\" we want to include the next
                        character in our string instead of acting on it. */
               case '"':
                  word[iw] = '\0';
                  return(T_WORD);
                     /* We are at the end of the quoted string.  We
                        terminate the string with a null and return.  We
                        don't include the quotation mark in the string. */
               default:
                  word[iw++] = c;
                  continue;
                     /* This isn't one of the special characters dealt with
                        above, so we just accumulate it into our string. */
            }
         case INWORD:
            switch (c)
            {
               case ' ':
               case '\t':
               case '\n':
               case '<':
               case '>':
               case '"':
                  il--;
                     /* Decrement the pointer so we will look at this
                        character again. */
                  word[iw] = '\0';
                     /* Add a null terminator to the token's text string. */
                  if (strcmp(word, "cat") == 0)
                     return(T_CAT);
                        /* There are two special cases of word tokens, the
                           "cat" and "echo" commands. */
                  if (strcmp(word, "echo") == 0)
                     return(T_ECHO);
                  return(T_WORD);
               default:
                  word[iw++] = c;
                     /* Add the character to the string. */
                  continue;
                     /* Get the next character and repeat the cycle. */
            }
      }
   }
   il = 0;
      /* Since we have finished with the current line we reset the index so
         we will start examining the next line at the beginning. */
   return(T_NL);
      /* This is how we tell the calling program we have reached the end of
         the line. */
}
***EOF Gettoken.c***
echo "Creating Makefile"
cat > Makefile <<"***EOF Makefile***"
lc -L Shar
lc -M Sh Fatal Fgetline Gettoken Parse
blink with Sh.lnk
***EOF Makefile***
echo "Creating Parse.c"
cat > Parse.c <<"***EOF Parse.c***"
/* Parse.c

   Copyright (c) 1987 by F. G. Dufoe, III
   All rights reserved.

   Permission is granted to redistribute this program provided the source
   code is included in the distribution and this copyright notice is
   unchanged.

*/

#include <stdlib.h>
#include <string.h>
#include "Token.h"


struct Token *
parse(line, text)
   /* This function parses a line of text.  The calling program passes it
      two character pointers.  The first points to the null-terminated
      string containing the line of text to be parsed.  The second points to
      a buffer the calling program has defined for storing the text of the
      tokens parse() gets.  Parse() returns a pointer to a linked list of
      Token structures (Token structures are defined in Token.h). */
char *line;
   /* This points to the line of text to be parsed.  If it is NULL the
      calling program is through with the list of Token structures and their
      memory can be freed. */
char *text;
   /* This points to the area where token text will be stored.  Text from
      all the tokens will be stored here, so it must be as long as the line
      to be parsed. */
{
   struct Token *current;
      /* This points to the current Token structure. */

   static struct Token *first = NULL;
      /* This points to the beginning of the list of Token structures. */

   void freetokens(struct Token *);
      /* This function walks the list of Token structures and frees their
         memory. */

   TOKEN gettoken(char *, char *);
      /* This function gets the next token from a character string. */

   TOKEN type;
      /* This is the token type returned by gettoken(). */

   char word[100];
      /* This array will be used by gettoken() to store the text of the
         tokens it gets.  Only one token at a time will be stored here. */


   if (line == NULL)
      /* If the calling program passes a NULL pointer we'll free the memory
         allocated for Token structures and return a NULL pointer. */
   {
      freetokens(first);
      return(NULL);
   }

   if (first == NULL)
      /* No memory for Token structures has been allocated yet.  Allocate
         the first one. */
   {
      if ((first = (struct Token *)malloc(sizeof(struct Token))) == NULL)
         fatal("Parse: Could not allocate Token structure.");
         /* If we can't allocate memory for a Token structure we can't
            continue.  Notify the user and terminate. */
      first->text = text;
         /* We'll use the text buffer supplied by the calling program to
            store the token text. */
      first->next = NULL;
         /* Until we have allocated another Token structure this one is the
            end of the list. */
   }

   current = first;
      /* We'll start by using the first Token structure we allocated. */

   while ((type = gettoken(line, word)) != T_NL)
      /* Until we get to the end of the line, keep asking for the next
         token. */
   {
      current->type = type;
         /* Save the type which gettoken() returned. */
      strcpy (current->text, word);
         /* Save the text which gettoken() returned. */
      if (current->next == NULL)
         /* If we don't already have another Token structure allocated let's
            allocate one now to use for the next token. */
      {
         if ((current->next = (struct Token *)
                              malloc(sizeof(struct Token))) == NULL)
            fatal("Parse: Could not allocate Token structure.");
               /* If we couldn't allocate the next Token structure we can't
                  continue.  Notify the user and terminate. */
         current->next->next = NULL;
            /* Make the new structure the one at the end of the list by
               setting its next pointer to NULL. */
      }
      current->next->text = current->text + strlen(word) + 1;
         /* Advance the text pointer to the next available address in the
            calling program's text buffer. */
      current = current->next;
         /* Make the next structure the current one. */
   }
   current->type = type;
   *(current->text) = 0;
   if (current->next != NULL)
      /* If we have some unused Token structures in our list let's free them
         now.  Since we use the same Token structure list over for
         subsequent lines this can happen if a short line follows a long
         one. */
   {
      freetokens(current->next);
      current->next = NULL;
   }
   return(first);
}


void
freetokens(head)
     /* This function walks the list of Token structures and frees the
        memory allocated for the structures. */
struct Token *head;
{
     if (head->next != NULL)
          freetokens(head->next);
     free((char *)head);
}
***EOF Parse.c***
echo "Creating Sh.c"
cat > Sh.c <<"***EOF Sh.c***"
/* Sh.c

   Copyright (c) 1987 by F. G. Dufoe, III
   All rights reserved.

   Permission is granted to redistribute this program provided the source
   code is included in the distribution and this copyright notice is
   unchanged.

*/


#include <stdio.h>
#include <stdlib.h>
#include "Token.h"


main(argc, argv)
   /* This program unpacks a shell archive created by Shar.  If the user
      typed more than one file name or typed a question mark on the command
      line the program prints an error message and terminates.  Otherwise it
      tries to open the specified file for input.  If it cannot open the
      file it prints an error message and terminates. */
int argc;
   /* This is the number of arguments on the command line. */
char **argv;
   /* This points to a character array containing the argument values. */
{
   char ehd[512];
      /* When we find a token which identifies the end of the "here
         document" we'll store it here. */
   int ehdl;
      /* This is the length of the "end here document" string. */
   char errmsg[256];
      /* We'll use this character array to put together error messages which
         contain variables. */
   char *fgetline(FILE *);
      /* This function gets the next line of text from a file. */
   FILE *ifile;
      /* This FILE pointer identifies the input file we want to read with
         fgetline(). */
   char *line;
      /* This is the pointer to the line of text returned by fgetline(). */
   int linelen;
      /* This is the length of the line returned by fgetline(). */
   enum
   {
      NEUTRAL,
      COPY
   } mode = NEUTRAL;
      /* In NEUTRAL mode the program reads through the file looking for
         "echo" and "cat" commands and their arguments.  In COPY mode it
         writes each line to an output file until it encounters the text
         string marking the end of the "here document". */
   FILE *ofile;
      /* This file pointer identifies the current output file. */
   struct Token *parse(char *, char *);
      /* This function parses a line of text. */
   TOKEN state;
      /* State is the token type of the previous token. */
   char *text = NULL;
      /* This points to the text buffer where the token text will be
         stored. */
   int textlen = 0;
      /* This is the length of the buffer allocated for token text. */
   struct Token *token;
      /* This points to the list of Token structures returned by parse(). */

   if ((argc != 2) || (argv[1][0] == '?'))
      fatal("Usage: Sh file.");
      /* If the user requested one or provided the wrong number of arguments
         print a usage message and terminate. */

   if ((ifile = fopen(*(argv + 1), "r")) == NULL)
   {
      sprintf(errmsg, "Sh: Can't open %s for input.", *(argv + 1));
      fatal(errmsg);
   }
      /* If we can't open the input file print an error message and
         terminate. */

   while ((line = fgetline(ifile)) != NULL)
      /* Read the file, line by line, until we get to the end. */
   {
      switch (mode)
      {
      case NEUTRAL:
         if (textlen < (linelen = strlen(line)))
            /* If the line is longer than the text buffer we already have
               let's get a new one. */
         {
            if (text != NULL)
               free(text);
                  /* If we had a text buffer allocated we must free it
                     before we allocate another one. */
            if ((text = malloc(linelen)) == NULL)
               /* If we couldn't allocate memory for the token text print an
                  error message and terminate. */
               fatal("Sh: Couldn't allocate buffer for token text.");
            textlen = linelen;
               /* Remember the new text buffer length. */
         }
         token = parse(line, text);
            /* Break the line down into tokens. */
         switch (token->type)
         {
         case T_CAT:
            while (token->next != NULL)
            {
               token = token->next;
               switch (token->type)
               {
               case T_GT:
                  state = T_GT;
                  continue;
               case T_LTLT:
                  state = T_LTLT;
                  continue;
               case T_WORD:
                  switch (state)
                  {
                  case T_GT:
                     if ((ofile = fopen(token->text, "w")) == NULL)
                     {
                        sprintf(errmsg, "Sh: Can't open %s.", token->text);
                        fatal(errmsg);
                     }
                     state = T_WORD;
                     continue;
                  case T_LTLT:
                     strcpy(ehd, token->text);
                     ehdl = strlen(ehd);
                     mode = COPY;
                     state = T_WORD;
                     continue;
                  }
               }
            }
            continue;
         case T_ECHO:
            while (token->next != NULL)
            {
               token = token->next;
               printf("%s ", token->text);
            }
            printf("\n");
            continue;
         }
         continue;
      case COPY:
         if (strncmp(line, ehd, ehdl) == 0)
            /* This line marks the "here document" end. */
         {
            mode = NEUTRAL;
            fclose(ofile);
            continue;
         }
         fprintf(ofile, "%s", line);
         continue;
      }
   }
   parse(NULL, NULL);
   return(0);
}
***EOF Sh.c***
echo "Creating Sh.lnk"
cat > Sh.lnk <<"***EOF Sh.lnk***"
FROM LIB:c.o+Sh.o+Fatal.o+Fgetline.o+Gettoken.o+Parse.o
TO Sh
LIB LIB:lc.lib+LIB:amiga.lib
MAP ram:Sh.map
NODEBUG
VERBOSE
***EOF Sh.lnk***
echo "Creating Shar.c"
cat > Shar.c <<"***EOF Shar.c***"
/*   Shar.c

     Copyright (c) 1987 by Fabbian G. Dufoe, III
     All rights reserved.

     Permission is granted to redistribute this program provided the source
     code is included in the distribution and this copyright notice is
     unchanged.

     This program creates a Unix-compatible shell archive in the first file
     named on the command line.  It packs all the command-line files after
     the first into that archive.

     For each file to be included in the archive the program writes
          echo "Creating filename"
          cat > filename <<"***EOF filename***"
     Then it writes a copy of the file and terminates it with
          ***EOF filename***

*/

#include <stdio.h>
#include <time.h>
#ifdef AMIGA
#include <error.h>
#include <exec/types.h>
#else
#include <errno.h>
#include <ctype.h>
extern int sys_nerr;
extern char *sys_errlist[];
#endif

main(argc, argv)
int argc;
char **argv;
{
     int c;
          /* The character or code returned by getc will be stored here. */

     int i;
          /* This will be used as a loop counter to point to the command
             line argument the program is processing. */

     FILE *in;
          /* This is the file pointer which will be used to refer to the
             current input file in the fgetc() and fclose() functions. */

     FILE *out;
          /* This is the file pointer which will be used to refer to the
             output file in the fputc() and fclose() functions. */

     int r;
          /* The code returned by putc will be stored here. */

     long t;
          /* The current time in seconds returned by the time() function
             will be stored here.  The ctime() function will convert it to
             an ASCII string for inclusion in the output file. */

     if (argc < 3)
          /* There must be at least two files named for the program to work.
             The first argument is always the program name.  The second is
             the output file name.  The third is the first input file name.
             Without at least one input file there is no point continuing.
             The program will write an error message to standard error and
             terminate. */
     {
          fprintf(stderr, "Usage: shar outputfile file[s]\n");
          return(-1);
     }

     if ((out = fopen(argv[1], "r")) == NULL)
          /* We try to open the file for reading to see if it is there.  If
             the open fails we check the error number to see why. */
     {
          if (errno == ENOENT)
               /* An error number of ENOENT means the file doesn't exist.
                  That's what we want, so we'll just open it. */

          {
               if ((out = fopen(argv[1], "w")) == NULL)
                    /* If we couldn't open the file for writing print an
                       error message and terminate. */
               {
                    fprintf(stderr, "Shar: Couldn't open %s for output.\n",
                            argv[1]);
                    if (errno > 0 && errno < sys_nerr)
                         /* If there is an entry in the system error list
                            for this error, print the reason for the
                            error. */
                         fprintf(stderr, "\t%d: %s\n", errno,
                                 sys_errlist[errno]);
                    return(-1);
                         /* Terminate with a code to indicate the program
                            did not complete successfully. */
               }
          }
          else
               /* If the file open failed for any other reason we know it
                  exists so we want to display the error message and
                  terminate. */
          {
               fprintf(stderr, "Shar: %s already exists.\n", argv[1]);
               return(-1);
          }
     }
     else
          /* If we were able to open the file we want to close it before we
             print our error message and terminate. */
     {
          (void)fclose(out);
               /* We don't care if fclose fails--we're going to terminate
                  the program anyway--so we ignore the value it returns by
                  casting it to a void. */
          fprintf(stderr, "Shar: %s already exists.\n", argv[1]);
          return(-1);
     }

     /* If we got this far we succeeded in opening the output file for
        writing. */

     time(&t);
          /* We want to include the current time in the opening comments.
             This function gets the number of seconds since the system's
             base date. */

     /* Write some identifying comments to the beginning of the output
        file. */
     fprintf(out,
        "# This is a shell archive.  Remove anything before this line,\n");
     fprintf(out,
        "# then unpack it by saving it in a file and typing \"sh file\"\n");
     fprintf(out, "# Created %s#\n", ctime(&t));
     fprintf(out, "# This archive contains:\n");

     for (i = 2; i < argc; i++)
          /* Now we are going to list each of the remaining file names in a
             comment line. */
          fprintf(out, "#\t\t%s\n", argv[i]);

     for (i = 2; i < argc; i++)
          /* Now we are going to copy each of the remaining file names
             from the command line. */
     {
          if ((in = fopen(argv[i], "r")) == NULL)
               /* Try to open the file for reading.  If the open fails write
                  an error message. */
          {
               fprintf(stderr, "Shar: couldn't open %s for input.\n",
                       argv[i]);
               if (errno > 0 && errno < sys_nerr)
                    /* If there is an entry in the system error list
                       for this error, print the reason for the
                       error. */
                    fprintf(stderr, "\t%d: %s\n", errno,
                            sys_errlist[errno]);
          }
          else
               /* If the file was opened successfully add it to the output
                  file. */
          {
               fprintf(out, "echo \"Creating %s\"\n", argv[i]);
               fprintf(out, "cat > %s <<\"***EOF %s***\"\n", argv[i],
                       argv[i]);
               while ((c = getc(in)) != EOF)
                    /* Read the entire input file and copy it to the output
                       file. */
                    if ((r = putc(c, out)) != c)
                         /* If we couldn't write the character successfully
                            print an error message and terminate. */
                    {
                         fprintf(stderr,
                                 "Shar: couldn't write from %s to %s.\n",
                                 argv[i], argv[1]);
                         if (errno > 0 && errno < sys_nerr)
                              /* If there is an entry in the system error
                                 list for this error, print the reason for
                                 the error. */
                              fprintf(stderr, "\t%d: %s\n", errno,
                                      sys_errlist[errno]);
                         return(-1);
                    }
               fprintf(out, "***EOF %s***\n", argv[i]);
               fclose(in);
          }
     }
     return(0);
}
***EOF Shar.c***
echo "Creating Token.h"
cat > Token.h <<"***EOF Token.h***"
typedef enum
{
     T_CAT,    /* The token is the "cat" command.  To be a command the token
                  must be the first one on the line. */
     T_ECHO,   /* The token is the "echo" command.  It must be the first
                  token on the line. */
     T_GT,     /* The token is ">" indicating redirection of standard
                  output.  It must be preceded by a T_CAT or T_ECHO
                  token. */
     T_LTLT,   /* The token is "<<" indicating "here document" redirection.
                  It must be preceded by a T_CAT or T_ECHO token.  The next
                  word defines the "here document" terminator.  Subsequent
                  lines from the input file are treated as input for the
                  command on this line. */
     T_NL,     /* The token is a newline character.  It means we have
                  encountered the end of the text line.  There are no more
                  tokens to identify. */
     T_WORD    /* The token is any word not of the above type.  A word is a
                  string of characters surrounded by white space or enclosed
                  in double quotation marks. */
} TOKEN;
     /* This is a list of recognized token types. */

struct Token
     /* This is a linked list of tokens giving type, text, and pointer to
        the next token. */
{
     TOKEN type;
          /* This is the token type.  It must have one of the values listed
             under the data type TOKEN. */
     char *text;
          /* This is a pointer to a character string containing the actual
             text of the token. */
     struct Token *next;
          /* This is a pointer to the next token structure in the list.
             When it is NULL there are no more tokens. */
};
***EOF Token.h***