[comp.os.minix] Unshar

wkt@csadfa.cs.adfa.oz.au (Warren Toomey) (12/19/89)

A small implementation of unshar follows. Pity it's in shar form ;-)

----------------------------- cut here -----------------------------

echo x - README
sed '/^X/s///' > README << '/'
XHere is a version of unshar that I have `knocked up' in the past few weeks.
XIt isn't as flash as the standard unshar, but I've made it modular so it
Xcan be enhanced easily.
X
XHowever, it has some advantages:
X
X	- it does not call any other program, especially /bin/sh
X	- it can extract specific files from the shar
X	- can give a table of contents without extracting files
X	- takes several input files, or the standard input
X	- ignores leading `junk' e.g news headers
X	- emulates sed, gres and cat to do the unsharing
X	- avoids the Minix `cat /dev/null > file' problem :-)
X	- it works `as is' under MS-DOS (needs getopt)
X
XI've used it to unshar lots of different files; there are bound to be
Xsome that it can't extract, but fixing the program should be easy.
X
X	Warren Toomey  -  wtoomey@csadfa.oz.au@munnari.oz[@uunet]
/
echo x - Makefile
sed '/^X/s///' > Makefile << '/'
XO= s
X
Xunshar: Makefile unshar.$(O)
X	cc -o unshar unshar.$(O) -lgetopt
X	strip unshar
X	chmem =10000 unshar
X
Xunshar.$(O): Makefile unshar.c unshar.h
X	cc -c -O unshar.c
X
Xshar: README Makefile unshar.h unshar.c unshar.1
X	shar README Makefile unshar.h unshar.c unshar.1 > unshar.shar
/
echo x - unshar.h
sed '/^X/s///' > unshar.h << '/'
X/*
X * Unshar - extract files from shell archive
X *
X * Written by Warren Toomey. Nov, 1989.
X * You may freely copy or give away this source as
X * long as this notice remains intact.
X *
X * Definitions used by unshar
X */
X
X	/* Methods of unsharing */
X
X#define UNKNOWN	0
X#define BRUTAL	1
X
X
X	/* Whitespace indicators */
X
X#define WHITE	0
X#define NOWHITE 1
X
X
X	/* Leading character indicators */
X
X#define NOX	0
X#define YESX	1
X
X
X	/* Emulation types available */
X
X#define NUMTOKS    4		/* Must change NUMTOKS to equal the */
X/* define UNKNOWN  0 */		/* number of emulation types */
X#define SED	   1
X#define GRES 	   2
X#define CAT	   3
X
Xstatic char *token[NUMTOKS] =	/* The list of emulation types! */
X	{ "",
X	  "sed",
X	  "gres",
X	  "cat"
X	};
X
X
X
X	/* Misc. constants */
X
X#define BUFSIZE	256		/* Size of line buffer */
/
echo x - unshar.c
sed '/^X/s///' > unshar.c << '/'
X#include <stdio.h>
X#include "unshar.h"
X
X/*
X * Unshar - extract files from shell archive
X *
X * Written by Warren Toomey. Nov, 1989.
X * You may freely copy or give away this source as
X * long as this notice remains intact.
X *
X */
X
X/* Global variables */
X
Xint table;			/* Generate a table, or extract */
Xint verbose;			/* Unshar verbosely - debugging */
Xint numext;			/* Number of files to extract */
Xchar *exfile[100];		/* Files to extract */
X
X
X#define getline(x,y)	fgetline(stdin,x,y)
X
Xint fgetline(zin,how,buf)	/* Get a line from a file */
X FILE *zin;
X int how;			/* Ignore leading whitespace if */
X char *buf;			/* how == NOWHITE */
X {
X  int ch=NULL;
X
X  *buf=NULL;			/* Null the buffer */
X  if (how==NOWHITE)		/* If skip any whitespace */
X    {
X     while (((ch=fgetc(zin))==' ') || (ch=='\t'));
X     if (ch==EOF)  return(EOF);	/* Returning EOF or NULL */
X     if (ch=='\n') return(NULL);
X     *buf++ =ch;		/* Put char in buffer */
X    }
X
X  while ((ch=fgetc(zin))!='\n')	/* Now get the line */
X   {
X    if (ch==EOF) { *buf=NULL; return(EOF); }
X    *buf++ = ch;
X   }
X  
X  *buf=NULL;			/* Finally null-terminate the buffer */
X  return(NULL);			/* and return */
X }
X
X
X
Xchar *getstring(buf)		/* Get the next string from the buffer */
X char *buf;			/* ignoring any quotes */
X {
X  char out[BUFSIZE];
X  char *temp=out;
X  while ((*buf==' ') || (*buf=='\t')) buf++;	/* Skip whitespace */
X
X  switch(*buf)			/* Now check first char */
X   {
X    case '\'' : buf++;
X		while (*buf!='\'') *temp++ = *buf++;
X		*temp=NULL;
X		return(out);
X    case '\"' : buf++;
X		while (*buf!='\"') *temp++ = *buf++;
X		*temp=NULL;
X		return(out);
X    case NULL : return(NULL);
X    default   : while ((*buf!=' ') && (*buf!='\t'))
X		   if (*buf!='\\') *temp++ = *buf++;
X		   else buf++;
X		*temp=NULL;
X		return(out);
X   }
X }
X
X
Xint firstword(buf)		/* Return token value of first word */
X char *buf;			/* in the buffer. Assume no leading */
X {				/* whitespace in the buffer */
X  int i;
X
X  for (i=1;i<NUMTOKS;i++)
X     if (strncmp(buf,token[i],strlen(token[i]))==0)
X	return(i);
X
X  return(UNKNOWN);
X }
X
X
Xint mustget(s1)			/* Return 1 if s1 is in the list of  */
X char *s1;			/* files to extract. Return 0 if not */
X {				
X  int i;
X
X  if (numext==0) return(0);
X  for (i=0;i<numext;i++)
X   if (!strcmp(s1,exfile[i])) return(1);
X  return(0);
X }
X
X
Xvoid extract(how,file,end,lead)	/* Extract file, up until end word */
X int how;			/* If how==YESX, then ignore lead   */
X char *file;			/* character on every line */
X char *end;
X int lead;
X {
X  FILE *zout;
X  char line[BUFSIZE];
X  char *temp;
X  int ch;
X
X  zout=fopen(file,"w");		/* Open output file */
X  if (zout==NULL)
X    { perror("unshar");
X      return;
X    }
X
X  while(1)
X   {
X    ch=getline(WHITE,line);	/* Get a line of file */
X    temp=line;
X    if (ch==EOF)
X      { fprintf(zout,"%s\n",line);
X	fclose(zout);
X	return;
X      }
X
X    if (strncmp(line,end,strlen(end))==0)	/* If end word */
X      { fclose(zout);				/* close the file */
X	return;
X      }
X
X     if ((how==YESX) && (*temp==lead)) temp++;	/* Skip any lead */
X     fprintf(zout,"%s\n",temp);
X    }
X }
X
X
Xvoid getnames(buf,file,word)	/* Get the file & end word */
X char *buf, *file, *word;	/* from the buffer */
X {
X  char *temp;
X
X  temp=buf;
X  if (verbose) printf("Getnames: buf is %s\n",buf);
X
X  while (*temp!=NULL)		/* Scan along buffer */
X   {
X    switch(*temp)		/* Get file or end word */
X     {
X      case '>' : strcpy(file,getstring(++temp)); /* Get the file name */
X	 	 break;
X      case '<' : if (*(++temp)=='<') ++temp;	/* Skip 2nd < */
X		 strcpy(word,getstring(temp));	/* Get next word */
X		 break;
X      default  : temp++;
X     }
X   }
X }
X
X
X
Xvoid disembowel()		/* Unshar brutally! */
X {
X  char buf[BUFSIZE];		/* Line buffer */
X  char file[BUFSIZE];		/* File name */
X  char word[BUFSIZE];		/* Word buffer */
X  int ch,x;
X
X  if (verbose) printf("Entering disembowel\n");
X  x='X';			/* Leading X character */
X  while(1)
X   {
X    ch=getline(NOWHITE,buf);	/* Get a line from file */
X    if (ch==EOF) 
X	return;
X
X    switch(firstword(buf))	/* Extract, depending on first word */
X     {
X      case CAT:
X      case GRES:
X      case SED:  if (verbose) printf("About to do getnames\n");
X		 getnames(buf,file,word);
X		 if (table==0)
X		  {
X		   if ((numext==0) || (mustget(file)))
X		    {
X		     printf("unshar: Extracting  %s\n",file);
X		     if (verbose) 
X			printf("        stopping at %s\n",word);
X		     extract(YESX,file,word,x);
X		    }
X		  }
X		 else printf("%s\n",file);
X		 break;
X      default:   break;
X     }
X   }
X }
X  
X
X
Xusage()
X {
X  fprintf(stderr,"Usage: unshar [-t] [-b] [-v] [-xfile] [file(s)]\n");
X  exit(0);
X }
X
X
Xmain(argc,argv)
X int argc;
X char *argv[];
X {
X  extern int optind;
X  extern char *optarg;
X  int i,c,first;
X
X  FILE *zin;			/* Dummy file descriptor */
X  int method;			/* Method of unsharing */
X
X  method= BRUTAL;		/* Only BRUTAL currently available */
X  table=  0;			/* Don't generate a table */
X  verbose=0;			/* Nor be very verbose */
X  numext= 0;			/* Initially no files to extract */
X
X
X  while ((c=getopt(argc,argv,"x:tbv"))!=EOF)
X    switch(c)
X     {
X      case 't' : table=1;	/* Get the various options */
X		 break;
X      case 'b' : method= BRUTAL;
X		 break;
X      case 'v' : verbose=1;
X		 break;
X      case 'x' : exfile[numext]= (char *)malloc(strlen(optarg)+1);
X		 strcpy(exfile[numext++],optarg);
X		 break;
X      default  : usage();
X     }
X
X  if (argc==1) first=argc;		/* Find first file argument */
X  else for (first=1;first<argc;first++)
X	if (argv[first][0]!='-') break;
X
X  if (first==argc)			/* If no file argument */
X   {					/* use stdin only */
X    switch(method)
X     {
X      case BRUTAL: disembowel();	/* Unshar brutally! */
X		   break;
X      default:	   fprintf(stderr,"unshar: Unknown method of unsharing\n");
X		   exit(1);
X      }
X   }
X  else
X   for (i=first;i<argc;i++)	/* open stdio with every file */
X   {
X    fclose(stdin);
X    if ((zin=fopen(argv[i],"r"))==NULL)
X      { perror("unshar");
X        exit(1);
X      }
X
X    switch(method)
X     {
X      case BRUTAL: disembowel();	/* Unshar brutally! */
X		   break;
X      default:	   fprintf(stderr,"unshar: Unknown method of unsharing\n");
X		   exit(1);
X      }
X   }
X  exit(0);
X }
/
echo x - unshar.1
sed '/^X/s///' > unshar.1 << '/'
X.T1 unshar 1L
X.SH NAME
Xunshar \- extract files from shell archive(s)
X.SH SYNTAX
Xunshar [\-t] [\-b] [-v] [-xfile] [file(s)]
X.SH OPTIONS
XOptions are processed by
X.B getopts(3).
X.TP 8
X.B \-t   
XDo not extract files, just list the files in the archive(s).
X.TP
X.B \-b
XExtract files from the archive(s) brutally, with no regard at all to things
Xsuch as testing if the file exists, or chmoding the file etc. Currently
Xthis is the only method supported, but other methods would be easy to add.
X.TP
X.B \-v
XBe verbose. Used only for debugging purposes.
X.TP
X.B \-xfile
XExtract the named file from the shell archive. If the \-x flag is used,
Xonly those files specified will be extracted.
X.SH DESCRIPTION
X.I Unshar
Xextracts files from one or more shell archives, and places them in the current
Xdirectory. It does no forking to achieve this, and is relatively fast.
XIf no file name is given as an agrumemt,
X.I unshar
Xwill use standard input.
XAt the moment
X.I unshar
Xis very simple-minded, and does its extraction rather brutally. The code
Xhas been written to allow more intelligent unarchiving methods to be
Xadded.
X.SH SEE ALSO
Xshar(1), sh(1).
/
  Warren Toomey VK2XWT, electric guitar licker.
  Deep in the bowels of ADFA Comp Science.
  Canberra. ACT. 2600.  Email: wkt@csadfa.oz.au 
  `Worth the money but not the risk -CC'

wkt@csadfa.cs.adfa.oz.au (Warren Toomey) (01/09/90)

In article <7680@nigel.udel.EDU>, Peter_Van_Epp@cc.sfu.ca writes:

>Here are some times to unshar posting #33 with sh and unshar:
>  sh   = 25 minites and 20 odd seconds,
>unshar = 58 seconds (yep no minites just 58 seconds!).
>A very worthwhile program to grab and a vote of thanks to Warren for
>writing/ posting it!

Thanks! Yes, I forgot to point out how FAST it is ;-). After posting it I
noticed a small bug unsharing `cat' shars, due to the fact that I
had altered the program without retesting it. So here it is again....
---------------------- use unshar(1L) to unshar! --------------------
echo x - README
sed '/^X/s///' > README << '/'
XHere is a version of unshar that I have `knocked up' in the past few weeks.
XIt isn't as flash as the standard unshar, but I've made it modular so it
Xcan be enhanced easily.
X
XHowever, it has some advantages:
X
X	- it does not call any other program, especially /bin/sh
X	- it can extract specific files from the shar
X	- can give a table of contents without extracting files
X	- takes several input files, or the standard input
X	- ignores leading `junk' e.g news headers
X	- emulates sed, gres and cat to do the unsharing
X	- avoids the Minix `cat /dev/null > file' problem :-)
X	- it works `as is' under MS-DOS (needs getopt)
X
XI've used it to unshar lots of different files; there are bound to be
Xsome that it can't extract, but fixing the program should be easy.
X
X	Warren Toomey  -  wtoomey@csadfa.oz.au@munnari.oz[@uunet]
/
echo x - Makefile
sed '/^X/s///' > Makefile << '/'
XO= s
X
Xunshar: Makefile unshar.$(O)
X	cc -o unshar unshar.$(O) -lgetopt
X	strip unshar
X	chmem =10000 unshar
X
Xunshar.$(O): Makefile unshar.c unshar.h
X	cc -c -O unshar.c
X
Xshar: README Makefile unshar.h unshar.c unshar.1
X	shar README Makefile unshar.h unshar.c unshar.1 > unshar.shar
/
echo x - unshar.h
sed '/^X/s///' > unshar.h << '/'
X/*
X * Unshar - extract files from shell archive
X *
X * Written by Warren Toomey. Nov, 1989.
X * You may freely copy or give away this source as
X * long as this notice remains intact.
X *
X * Definitions used by unshar
X */
X
X	/* Methods of unsharing */
X
X#define UNKNOWN	0
X#define BRUTAL	1
X
X
X	/* Whitespace indicators */
X
X#define WHITE	0
X#define NOWHITE 1
X
X
X	/* Leading character indicators */
X
X#define NOX	0
X#define YESX	1
X
X
X	/* Emulation types available */
X
X#define NUMTOKS    4		/* Must change NUMTOKS to equal the */
X/* define UNKNOWN  0 */		/* number of emulation types */
X#define SED	   1
X#define GRES 	   2
X#define CAT	   3
X
Xstatic char *token[NUMTOKS] =	/* The list of emulation types! */
X	{ "",
X	  "sed",
X	  "gres",
X	  "cat"
X	};
X
X
X
X	/* Misc. constants */
X
X#define BUFSIZE	256		/* Size of line buffer */
/
echo x - unshar.c
sed '/^X/s///' > unshar.c << '/'
X#include <stdio.h>
X#include "unshar.h"
X
X/*
X * Unshar - extract files from shell archive
X *
X * Written by Warren Toomey. Nov, 1989.
X * You may freely copy or give away this source as
X * long as this notice remains intact.
X *
X */
X
X/* Global variables */
X
Xint table;			/* Generate a table, or extract */
Xint verbose;			/* Unshar verbosely - debugging */
Xint numext;			/* Number of files to extract */
Xchar *exfile[100];		/* Files to extract */
X
X
X#define getline(x,y)	fgetline(stdin,x,y)
X
Xint fgetline(zin,how,buf)	/* Get a line from a file */
X FILE *zin;
X int how;			/* Ignore leading whitespace if */
X char *buf;			/* how == NOWHITE */
X {
X  int ch=NULL;
X
X  *buf=NULL;			/* Null the buffer */
X  if (how==NOWHITE)		/* If skip any whitespace */
X    {
X     while (((ch=fgetc(zin))==' ') || (ch=='\t'));
X     if (ch==EOF)  return(EOF);	/* Returning EOF or NULL */
X     if (ch=='\n') return(NULL);
X     *buf++ =ch;		/* Put char in buffer */
X    }
X
X  while ((ch=fgetc(zin))!='\n')	/* Now get the line */
X   {
X    if (ch==EOF) { *buf=NULL; return(EOF); }
X    *buf++ = ch;
X   }
X  
X  *buf=NULL;			/* Finally null-terminate the buffer */
X  return(NULL);			/* and return */
X }
X
X
X
Xchar *getstring(buf)		/* Get the next string from the buffer */
X char *buf;			/* ignoring any quotes */
X {
X  char out[BUFSIZE];
X  char *temp=out;
X  while ((*buf==' ') || (*buf=='\t')) buf++;	/* Skip whitespace */
X
X  switch(*buf)			/* Now check first char */
X   {
X    case '\'' : buf++;
X		while (*buf!='\'') *temp++ = *buf++;
X		*temp=NULL;
X		return(out);
X    case '\"' : buf++;
X		while (*buf!='\"') *temp++ = *buf++;
X		*temp=NULL;
X		return(out);
X    case NULL : return(NULL);
X    default   : while ((*buf!=' ') && (*buf!='\t'))
X		   if (*buf!='\\') *temp++ = *buf++;
X		   else buf++;
X		*temp=NULL;
X		return(out);
X   }
X }
X
X
Xint firstword(buf)		/* Return token value of first word */
X char *buf;			/* in the buffer. Assume no leading */
X {				/* whitespace in the buffer */
X  int i;
X
X  for (i=1;i<NUMTOKS;i++)
X     if (strncmp(buf,token[i],strlen(token[i]))==0)
X	return(i);
X
X  return(UNKNOWN);
X }
X
X
Xint mustget(s1)			/* Return 1 if s1 is in the list of  */
X char *s1;			/* files to extract. Return 0 if not */
X {				
X  int i;
X
X  if (numext==0) return(0);
X  for (i=0;i<numext;i++)
X   if (!strcmp(s1,exfile[i])) return(1);
X  return(0);
X }
X
X
Xvoid extract(how,file,end,lead)	/* Extract file, up until end word */
X int how;			/* If how==YESX, then ignore lead   */
X char *file;			/* character on every line */
X char *end;
X int lead;
X {
X  FILE *zout;
X  char line[BUFSIZE];
X  char *temp;
X  int ch;
X
X  zout=fopen(file,"w");		/* Open output file */
X  if (zout==NULL)
X    { perror("unshar");
X      return;
X    }
X
X  while(1)
X   {
X    ch=getline(WHITE,line);	/* Get a line of file */
X    temp=line;
X    if (ch==EOF)
X      { fprintf(zout,"%s\n",line);
X	fclose(zout);
X	return;
X      }
X
X    if (strncmp(line,end,strlen(end))==0)	/* If end word */
X      { fclose(zout);				/* close the file */
X	return;
X      }
X
X     if ((how==YESX) && (*temp==lead)) temp++;	/* Skip any lead */
X     fprintf(zout,"%s\n",temp);
X    }
X }
X
X
Xvoid getnames(buf,file,word)	/* Get the file & end word */
X char *buf, *file, *word;	/* from the buffer */
X {
X  char *temp;
X
X  temp=buf;
X  if (verbose) printf("Getnames: buf is %s\n",buf);
X
X  while (*temp!=NULL)		/* Scan along buffer */
X   {
X    switch(*temp)		/* Get file or end word */
X     {
X      case '>' : strcpy(file,getstring(++temp)); /* Get the file name */
X	 	 break;
X      case '<' : if (*(++temp)=='<') ++temp;	/* Skip 2nd < */
X		 strcpy(word,getstring(temp));	/* Get next word */
X		 break;
X      default  : temp++;
X     }
X   }
X }
X
X
X
Xvoid disembowel()		/* Unshar brutally! */
X {
X  char buf[BUFSIZE];		/* Line buffer */
X  char file[BUFSIZE];		/* File name */
X  char word[BUFSIZE];		/* Word buffer */
X  int ch,x;
X
X  if (verbose) printf("Entering disembowel\n");
X  x='X';			/* Leading X character */
X  while(1)
X   {
X    ch=getline(NOWHITE,buf);	/* Get a line from file */
X    if (ch==EOF) 
X	return;
X
X    switch(firstword(buf))	/* Extract, depending on first word */
X     {
X      case GRES:
X      case SED:  if (verbose) printf("About to do getnames\n");
X		 getnames(buf,file,word);
X		 if (table==0)
X		  {
X		   if ((numext==0) || (mustget(file)))
X		    {
X		     printf("unshar: Extracting  %s\n",file);
X		     if (verbose) 
X			printf("        stopping at %s\n",word);
X		     extract(YESX,file,word,x);
X		    }
X		  }
X		 else printf("%s\n",file);
X		 break;
X      case CAT:  if (verbose) printf("About to do getnames\n");
X		 getnames(buf,file,word);
X		 if (table==0)
X		  {
X		   if ((numext==0) || (mustget(file)))
X		    {
X		     printf("unshar: Extracting  %s\n",file);
X		     if (verbose) 
X			printf("        stopping at %s\n",word);
X		     extract(NOX,file,word,x);
X		    }
X		  }
X		 else printf("%s\n",file);
X		 break;
X      default:   break;
X     }
X   }
X }
X  
X
X
Xusage()
X {
X  fprintf(stderr,"Usage: unshar [-t] [-b] [-v] [-xfile] [file(s)]\n");
X  exit(0);
X }
X
X
Xmain(argc,argv)
X int argc;
X char *argv[];
X {
X  extern int optind;
X  extern char *optarg;
X  int i,c,first;
X
X  FILE *zin;			/* Dummy file descriptor */
X  int method;			/* Method of unsharing */
X
X  method= BRUTAL;		/* Only BRUTAL currently available */
X  table=  0;			/* Don't generate a table */
X  verbose=0;			/* Nor be very verbose */
X  numext= 0;			/* Initially no files to extract */
X
X
X  while ((c=getopt(argc,argv,"x:tbv"))!=EOF)
X    switch(c)
X     {
X      case 't' : table=1;	/* Get the various options */
X		 break;
X      case 'b' : method= BRUTAL;
X		 break;
X      case 'v' : verbose=1;
X		 break;
X      case 'x' : exfile[numext]= (char *)malloc(strlen(optarg)+1);
X		 strcpy(exfile[numext++],optarg);
X		 break;
X      default  : usage();
X     }
X
X  if (argc==1) first=argc;		/* Find first file argument */
X  else for (first=1;first<argc;first++)
X	if (argv[first][0]!='-') break;
X
X  if (first==argc)			/* If no file argument */
X   {					/* use stdin only */
X    switch(method)
X     {
X      case BRUTAL: disembowel();	/* Unshar brutally! */
X		   break;
X      default:	   fprintf(stderr,"unshar: Unknown method of unsharing\n");
X		   exit(1);
X      }
X   }
X  else
X   for (i=first;i<argc;i++)	/* open stdio with every file */
X   {
X    fclose(stdin);
X    if ((zin=fopen(argv[i],"r"))==NULL)
X      { perror("unshar");
X        exit(1);
X      }
X
X    switch(method)
X     {
X      case BRUTAL: disembowel();	/* Unshar brutally! */
X		   break;
X      default:	   fprintf(stderr,"unshar: Unknown method of unsharing\n");
X		   exit(1);
X      }
X   }
X  exit(0);
X }
/
echo x - unshar.1
sed '/^X/s///' > unshar.1 << '/'
X.T1 unshar 1L
X.SH NAME
Xunshar \- extract files from shell archive(s)
X.SH SYNTAX
Xunshar [\-t] [\-b] [-v] [-xfile] [file(s)]
X.SH OPTIONS
XOptions are processed by
X.B getopts(3).
X.TP 8
X.B \-t   
XDo not extract files, just list the files in the archive(s).
X.TP
X.B \-b
XExtract files from the archive(s) brutally, with no regard at all to things
Xsuch as testing if the file exists, or chmoding the file etc. Currently
Xthis is the only method supported, but other methods would be easy to add.
X.TP
X.B \-v
XBe verbose. Used only for debugging purposes.
X.TP
X.B \-xfile
XExtract the named file from the shell archive. If the \-x flag is used,
Xonly those files specified will be extracted.
X.SH DESCRIPTION
X.I Unshar
Xextracts files from one or more shell archives, and places them in the current
Xdirectory. It does no forking to achieve this, and is relatively fast.
XIf no file name is given as an agrumemt,
X.I unshar
Xwill use standard input.
XAt the moment
X.I unshar
Xis very simple-minded, and does its extraction rather brutally. The code
Xhas been written to allow more intelligent unarchiving methods to be
Xadded.
X.SH SEE ALSO
Xshar(1), sh(1).
/
  Warren Toomey VK2XWT, heavily steaming Frenchman
     Deep in the bowels of ADFA Comp Science.
  Canberra. ACT. 2600.  Email: wkt@csadfa.oz.au 
    `Sex isn't everything. Love is. - WT'