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***