[net.sources] Connoisseur's shar

ajs@hpfcla.UUCP (ajs) (10/02/84)

Here is perhaps the best shar(1) around.  We call it the  "connoisseur's
shar".  This shell  script  and manual  entry are the  result of lots of
iteration between various  engineers using some HP internal  notesfiles.
We're  posting  it  because we hope you will like it and use it for your
postings  to the  Net  (among  other  things).  Thanks  to Bob  Desinger
(hpda!bd) for doing most of the work.

Alan Silverstein, Hewlett-Packard Fort Collins Systems Division, Colorado
{ihnp4 | hplabs}!hpfcla!ajs, 303-226-3800 x3053, N 40 31'31" W 105 00'43"

The rest of this article was created with:  :r!shar -c -t shar*

# This is a shell archive.  Remove anything before this line, then
# unpack it by saving it in a file and typing "sh file".  (Files
# unpacked will be owned by you and have default permissions.)
#
# This archive contains:
# shar.1 shar.sh

echo x - shar.1
cat > "shar.1" << '//E*O*F shar.1//'
.TH SHAR 1 HEWLETT-PACKARD
.ad b
.SH NAME
shar \- make a shell archive package
.SH SYNOPSIS
\fBshar\fR [\fB-b\fR] [\fB-c\fR] [\fB-t\fR] [\fB-v\fR] \fIfiles...\fR
.SH DESCRIPTION
.I Shar
bundles the named
.IR file ( s )
into a single distribution package suitable for mailing or carrying around.
The files should be themselves mailable, e.g. not object code.
.IR Shar 's
resulting package, written to standard output, is an editable ASCII file.
It is actually a shell script which uses \fIsh\fR\^(1) "here" documents to
extract its contents into appropriate places.
.PP
The package is unwrapped by running \fIsh\fR\^(1) with the package name as an
argument.
Its files are written to the pathnames recorded in the archive.
.PP
Available options are:
.TP
.B \-b
Archive files under their basenames, regardless of the original pathnames
specified.
The contents are thus unpacked into the current directory instead of to the
originally-specified pathnames.
This allows you to archive files from many directories but unpack them into a
single directory.
It also allows you to unpack, say,
.I /etc/termcap
into
.I ./termcap
instead of overwriting the original one in
.IR /etc .
.TP
.B \-c
Cause
.I shar
to append to the package a simple data-integrity check using
.I wc
to insure that the contents were not damaged in transit.
This check is performed automatically after unpacking.
.TP
.B \-t
Write diagnostics and messages directly to
.IR /dev/tty ,
your terminal, instead of to standard error.
This is useful when invoking
.I shar
from programs such as
.I vi
which normally combine standard error with standard output.
Specifying 
.B \-t
also turns on the
.B \-v
(verbose) option.
.TP
.B \-v
Announce archived file names as they are packed.
The
.B \-t
option affects where these announcements go.
.SH FILES
/dev/tty    	if specified by the \fB-t\fR option
.br
/tmp/shar*  	used for the insurance check after unpacking
.br
cat, echo   	as subprocesses
.br
basename, wc	as optional subprocesses
.SH DIAGNOSTICS
.I Shar
refuses to archive nonexistent files and directories.
It terminates and does no archiving if it encounters them.
.PP
Exit status 1 is returned upon interrupt or trouble with arguments.
.SH "SEE ALSO"
ar(1),
basename(1),
cat(1),
cpio(1),
echo(1),
sh(1),
tar(1),
wc(1).
.SH BUGS
Ownerships and permissions for archived files are not retained.
.PP
The integrity check is very simple-minded.
In particular, it only notices if the number of characters, words, or lines is
altered.
It fails to catch bits flipped during transmission.
.PP
There should be a way to archive directories by name instead of using arguments
such as
.IR dir/* .
//E*O*F shar.1//

echo x - shar.sh
cat > "shar.sh" << '//E*O*F shar.sh//'

# UNISRC_ID: @(#)shar.sh	26.2	84/09/06  
: Make a shell archive package

# Usage: $0 [-b] [-c] [-t] [-v] files... > package
# See the manual entry for details.


# Initialize:

	diagnostic='eval echo >&2'	# diagnostics to stderr by default.
	trap '$diagnostic "$0: quitting early"; exit 1' 1 2 3 15

	base_option=FALSE		# use pathnames, not basenames.
	check_option=FALSE		# don't generate integrity check.
	usage='Usage: $0 \[-b] \[-c] \[-t] \[-v] files... \> package'


# Extract and digest options, if any:
#
# Un-comment the "-)" line below to treat single dashes as a no-op.
# Commented means single dashes elicit a usage diagnostic.

    while [ -n "$1" ]	# while there are more arguments,
    do			# digest them; stop when you find a non-option.
	case "$1" in
	-[bB])	base_option=TRUE;				shift ;;
	-[cC])	check_option=TRUE;				shift ;;
	-[tT])	verbose=TRUE; diagnostic='eval echo >/dev/tty';	shift ;;
	-[vV])	verbose=TRUE;					shift ;;
    ##	-)	shift ;;			# eat single dashes.
	-*)	$diagnostic $usage; exit 1 ;;	# die at illegal options.
	*)	break ;;
	esac
    done


# Check remaining arguments, which should be just a list of files:

	if [ $# = 0 ]
	then			# no arguments left!
	    $diagnostic $usage
	    exit 1
	fi


# Emit the prologue, then check and list ingredients:
# (The leading newline is for those who type csh instead of sh.)

	cat <<\!!!

# This is a shell archive.  Remove anything before this line, then
# unpack it by saving it in a file and typing "sh file".  (Files
# unpacked will be owned by you and have default permissions.)
#
# This archive contains:
!!!

	contents=''				# no files so far.

	for arg					# for all files specified,
	do					# establish archive name.
	    if [ -f "$arg" ]			# non-directory and exists.
	    then
		case $base_option in
		TRUE)	unpack_name=`basename "$arg"`	;;
		FALSE)	unpack_name="$arg"		;;
		esac

		contents="$contents $unpack_name"

	    else
		$diagnostic "$0: cannot archive $arg"
		exit 1
	    fi
	done

	echo "#$contents"
	echo


# Emit the files and their separators:

	for arg		# for all arguments left (file names).
	do
	    case $base_option in

	    TRUE)   unpack_name=`basename "$arg"`
		    test $verbose \
		      && $diagnostic "a - $unpack_name [from $arg]" ;;

	    FALSE)  unpack_name="$arg"
		    test $verbose && $diagnostic "a - $arg" ;;
	    esac

	    separator="//E*O*F $unpack_name//"
	    echo "echo x - $unpack_name"
	    echo "cat > \"$unpack_name\" << '$separator'"
	    cat "$arg"
	    echo $separator
	    echo
	done


# If the -c option was given, emit the checking epilogue:
# (The sed script converts files to basenames so it works regardless of -b.)

	if [ $check_option = TRUE ]
	then
	    echo "echo Possible errors detected by \'wc\' [hopefully none]:"
	    echo 'temp=/tmp/shar$$'
	    echo 'trap "rm -f $temp; exit" 0 1 2 3 15'	# will clean up.
	    echo 'cat > $temp <<\!!!'
	    wc $@ | sed 's=[^ ]*/=='
	    echo '!!!'
	    echo "wc $contents | sed 's=[^ ]*/==' | "'diff -b $temp -'
	fi


# Finish up:

	echo 'exit 0'		# sharchives unpack even if junk follows.
	exit 0
//E*O*F shar.sh//

echo Possible errors detected by \'wc\' [hopefully none]:
temp=/tmp/$0$$
trap "rm -f $temp; exit" 0 1 2 3 15
cat > $temp <<\!!!
     99    425   2644 shar.1
    123    480   3083 shar.sh
    222    905   5727 total
!!!
wc  shar.1 shar.sh | sed 's=[^ ]*/==' | diff -b $temp -
exit 0