bcsh.sh   [plain text]


# 1-Feb-86 09:37:35-MST,30567;000000000001
# Return-Path: <unix-sources-request@BRL.ARPA>
# Received: from BRL-TGR.ARPA by SIMTEL20.ARPA with TCP; Sat 1 Feb 86 09:36:16-MST
# Received: from usenet by TGR.BRL.ARPA id a002623; 1 Feb 86 9:33 EST
# From: chris <chris@globetek.uucp>
# Newsgroups: net.sources
# Subject: Improved Bcsh (Bourne Shell Cshell-Emulator)
# Message-ID: <219@globetek.UUCP>
# Date: 30 Jan 86 17:34:26 GMT
# To:       unix-sources@BRL-TGR.ARPA
#
# This is a new, improved version of my Bourne shell cshell-emulator.
# The code has been cleaned up quite a bit, and a couple of new features
# added (now supports 'noclobber' and 'iclobber' variables).  A bug with
# 'eval' that caused "illegal I/O" error messages on vanilla V7 shells has
# also been fixed.

# I have posted the program in its entirety because a context diff of the
# old and new versions was longer than the new version...

# --Chris
#	Bcsh -- A Simple Cshell-Like Command Pre-Processor For The Bourne Shell
#
#	"Copyright (c) Chris Robertson, December 1985"
#
#	This software may be used for any purpose provided the original
#	copyright notice and this notice are affixed thereto.  No warranties of
#	any kind whatsoever are provided with this software, and it is hereby
#	understood that the author is not liable for any damagages arising
#	from the use of this software.
#
#	Features Which the Cshell Does Not Have:
#	----------------------------------------
#
#	+  command history persists across bcsh sessions
# 	+  global last-command editing via 'g^string1^string2^' syntax
#	+  edit any command via $EDITOR or $VISUAL editors
#	+  history file name, .bcshrc file name, alias file name, and number
#	   of commands saved on termination can be set by environment variables
#	+  prompt may evaluate commands, such as `pwd`, `date`, etc.
#	+  the whole text of interactive 'for' and 'while' loops and 'if'
#	   statements goes into the history list and may be re-run or edited
#	+  multiple copies of commands and requests to see command history
#	   are not added to the history list
#	+  the history mechanism actually stores all commands entered in a
#	   current session, not just $history of them.  This means that you
#	   can increase $history on the fly and at once have a larger history.
#
#
#	Synonyms:
#	---------
#
#	logout, exit, bye	write out history file and exit
#	h, history		show current history list
#	
#	
#	Aliases:
#	--------
#
#	alias NAME CMND		create an alias called NAME to run CMND
#	unalias NAME		remove the alias NAME
#
#	There are no 'current-session only' aliases -- all alias and unalias
#	commands are permanent, and stored in the $aliasfile.
#
#	If an alias contains positional variables -- $1, $2, $*, etc. -- any
#	arguments following the alias name are considered to be values for
#	those variables, and the alias is turned into a command of the form
#	'set - arguments;alias'.  Otherwise, a simple substitution is performed
#	for the alias and the rest of the command preserved.  The cshell
#	convention of using '\!:n' in an alias to get bits of the current
#	command is mercifully abandoned.
#
#	Quotes are not necessary around the commands comprising an alias;
#	in fact, any enclosing quotes are stripped when the alias is added
#	to the file.
#
#	A couple of typical aliases might be:
#
#		goto	cd $1;pwd
#		l	ls -F
#
#	Note that aliasing something to "commands;logout" will not work -- if
#	you want something to happen routinely on logout put it in the file
#	specified by $logoutfile, default = $HOME/.blogout.
#
#
#	Command Substitutions:
#	----------------------
#
#	!!			substitute last command from history list
#	!!:N			substitute Nth element of last command from
#				history list -- 0 = command name, 1 = 1st arg
# 	!!:$			substitute last element of last command from
#				history list
# 	!!:*			substitute all arguments to last command
#				from history list
#	!NUMBER			substitute command NUMBER from the history list
#	!NUMBER:N		as above, but substitute Nth element, where
#				0 = command name, 1 = 1st arg, etc.
# 	!NUMBER:$		as above, but substitute last element
# 	!NUMBER:*		as above, but substitute all arguments
#	!-NUMBER		substitute the command NUMBER lines from the
#				end of the history list; 1 = last command
#	!-NUMBER:N		as above, but substitute Nth element, where
#				0 = command name, 1 = 1st arg, etc.
# 	!-NUMBER:$		as above, but substitute last element
# 	!-NUMBER:*		as above, but substitute all arguments
#	!?STRING		substitute most-recent command from history list
#				containing STRING -- STRING must be enclosed in
#				braces if followed by any other characters
#	!?STRING:N		as above, but substitute Nth element, where
#				0 = command name, 1 = 1st arg, etc.
# 	!?STRING:$		as above, but substitute last element	
# 	!?STRING:*		as above, but substitute all arguments
#
#
#	Command Editing:
#	----------------
#
#	CMND~e			edit CMND using $EDITOR, where CMND may be found
#				using a history substitution
#	CMND~v			edit CMND using $VISUAL, where CMND may be found
#				using a history substitution
# "	^string1^string2^	substitute string2 for string1 in last command"
#				command and run it
# "	g^string1^string2^	globally substitute string2 for string1 in  "
#				last command and run it
# 	!NUMBER:s/string1/string2/
#				substitute string2 for string1 in
#				command NUMBER and run it
# 	!NUMBER:gs/string1/string2/
#				globally substitute string2 for string1 in
#				command NUMBER and run it
# 	!?STRING:s/string1/string2/
#				substitute string2 for string1 in last command
#				containing STRING and run it
# 	!?STRING:gs/string1/string2/
#				globally substitute string2 for string1 in last
#				command containing STRING and run it
#	
#	Any command which ends in the string ":p" is treated as a normal
#	command until all substitutions have been completed.  The trailing
#	":p" is then stripped, and the command is simply echoed and added to
#	the history list instead of being executed.
#
#	None of the other colon extensions of the cshell are supported.
#
#
#	Shell Environment Variables:
#	----------------------------
#
#	EDITOR		editor used by ~e command, default = "ed"
#	VISUAL		editor used by ~v command, default = "vi"
#	MAIL		your system mailbox
#	PAGER		paging program used by history command, default = "more"
#	PS1		primary prompt
#	PS2		secondary prompt
#	history		number of commands in history list, default = 22
#	histfile	file history list is saved in, default = $HOME/.bhistory
#	savehist	number of commands remembered from last bcsh session
#	aliasfile	file of aliased commands, default = $HOME/.baliases
#	logoutfile	file of commands to be executed before termination
#	inc_cmdno	yes/no -- keep track of command numbers or not
#	noclobber	if set, existing files are not overwritten by '>'
#	iclobber	if both noclobber and iclobber are set, the user is
#			prompted for confirmation before existing files are
#			overwritten by '>'
#
#	Note:	if you are setting either noclobber or iclobber mid-session,
#		set them to 'yes'
#
#
#	Regular Shell Variables:
#	------------------------
#
#	Shell variables may be set via Bourne or cshell syntax, e.g., both
#	"set foo=bar" and "foo=bar" set a variable called "foo" with the value
#	"bar".  However, all variables are automatically set as environment
#	variables, so there is no need to export them.  Conversely, there
#	are NO local variables.  Sorry, folks.
#
#	A cshell-style "setenv" command is turned into a regular "set" command.
#
#
#	The Prompt:
#	----------
#
#	You may, if you wish, have a command executed in your prompt.  If
#	the variable PS1 contains a dollar sign or a backquote, it is
#	evaluated and the result used as the prompt, provided the evaluation
#	did not produce a "not found" error message.  The two special cases
#	of PS1 consisting solely of "$" or "$ " are handled correctly.  For
#	example, to have the prompt contain the current directory followed
#	by a space, enter:
#
#		PS1=\'echo "`pwd` "\'
#
#	You need the backslashed single quotes to prevent the command being
#	evaluated by the variable-setting mechanism and the shell before it
#	is assigned to PS1.
#
#	To include the command number in your prompt, enter the command:
#
#		PS1=\'echo "$cmdno "\'
#
#
#	Shell Control-Flow Syntax:
#	--------------------------
#
#	'While', 'for', 'case', and 'if' commands entered in Bourne shell
#	syntax are executed as normal.
#
#	A valiant attempt is made to convert 'foreach' loops into 'for' loops,
#	cshell-syntax 'while' loops into Bourne shell syntax, and 'switch'
#	statements into 'case' statements.  I cannot guarantee to always get it
#	right.  If you forget the 'do' in a 'while' or 'for' loop, or finish
#	them with 'end' instead of 'done', this will be corrected.
#
#	Note that cshell-to-Bourne control flow conversions do not take place
#	if control is nested -- e.g., a 'foreach' inside a 'while' will fail.
#
#	The simple-case cshell "if (condition) command" is turned into Bourne
#	syntax.  Other 'if' statements are left alone apart from making the
#	'then' a separate statement, because constructing a valid interactive
#	cshell 'if' statement is essentially an exercise in frustration anyway.
#	The cshell and Bourne shell have sufficiently different ideas about
#	conditions that if is probably best to resign yourself to learning
#	the Bourne shell conventions.
#
#	Note that since most of the testing built-ins of the cshell are
#	not available in the Bourne shell, a complex condition in a 'while'
#	loop or an 'if' statement will probably fail.
#	
#
#	Bugs, Caveats, etc.:
#	--------------------
#
#	This is not a super-speedy program.  Be patient, especially on startup.
#
#	To the best of my knowledge this program should work on ANY Bourne
#	shell -- note that if your shell does not understand 'echo -n' you
#	will have to re-set the values of '$n' and '$c'.
#
#	This program may run out of stack space on a 16-bit machine where
#	/bin/sh is not split-space.
#
#	Mail checking is done every 10 commands if $MAIL is set in your
#	environment.  For anything fancier, you will have to hack the code.
#
#	Because commands are stuffed in a file before sh is invoked on them,
#	error messages from failed commands are ugly.
#
#	Failed history substitutions either give nothing at all, or a
#	"not found" style of error message.
#
#	A command history is kept whether you want it or not.  This may be
#	perceived as a bug or a feature, depending on which side of bed you
#	got out on.
#
#	If you want a real backslash in a command, you will have to type two
# 	of them  because the shell swallows the first backslash in the initial
# 	command pickup.  This means that to include a non-history '!' in a
#	command you need '\\!' -- a real wart, especially for net mail,
#	but unavoidable.
#
#	Commands containing an '@' will break all sorts of things.
#
#	Very complex history substitutions may fail.
#
#	File names containing numbers may break numeric history sustitutions.
#
#	Commands containing bizzare sequences of characters may conflict
#	with internal kludges.
#
#	Aliasing something to "commands;logout" will not work -- if you
#	want something to happen routinely on logout, put it in the file
#	specified by $logoutfile, default = $HOME/.blogout.
#
#	Please send all bug reports to ihnp4!utzoo!globetek!chris.
#	Flames will be posted to net.general with 'Reply-to' set to your
# '	path...  :-)							'
#
#
#
#		************* VERY IMPORTANT NOTICE *************
#
# If your shell supports # comments, then REPLACE all the colon 'comments'
# with # comments.  If it does not, then REMOVE all the 'comment' lines from the
# working copy of the file, as it will run MUCH faster -- the shell evaluates
# lines starting with a colon but does not actually execute them, so you will
# save the read-and-evaluate time by removing them.

case "`echo -n foo`" in
	-n*)
		n=
		c="\c"
		;;
	foo)
		n=-n
		c=
		;;
	*)
		echo "Your 'echo' command is broken."
		exit 1
		;;
esac
history=${history-22}
savehist=${savehist-22}
histfile=${histfile-$HOME/.bhistory}
logoutfile=${logoutfile-$HOME/.blogout}
EDITOR=${EDITOR-ed}
VISUAL=${VISUAL-vi}
PAGER=${PAGER-more}

aliasfile=${aliasfile-$HOME/.baliases}

# the alias file may contain 1 blank line, so a test -s will not work

case "`cat $aliasfile 2> /dev/null`" in
	"")
		doalias=no
		;;
	*)
		doalias=yes
		;;
esac

if test -s "${sourcefile-$HOME/.bcshrc}"
	then
	. ${sourcefile-$HOME/.bcshrc}
fi

if test -s "$histfile"
	then
	cmdno="`set - \`wc -l $histfile\`;echo $1`"
	cmdno="`expr \"$cmdno\" + 1`"
	lastcmd="`sed -n '$p' $histfile`"
	copy=false
	ohist=$histfile
	while test ! -w "$histfile"
		do
		echo "Cannot write to history file '$histfile'."
		echo $n "Please enter a new history filename: $c"
		read histfile
		copy=true
	done
	if $copy
		then
		cp $ohist $histfile
	fi
else
	cat /dev/null > $histfile
	cmdno=1
	lastcmd=
fi

# keep track of command number as the default

inc_cmdno=${inc_cmdo-yes}

# default prompts -- PS1 and PS2 may be SET but EMPTY, so '${PS1-% }' syntax
# is not used here

case "$PS1" in
	"")					
		PS1="% "
		;;				
esac
case "$PS2" in
	"")					
		PS2="> "
		;;				
esac

export histfile savehist history aliasfile EDITOR VISUAL PAGER cmdno PS1 PS2

case "$MAIL" in
	"")
		;;
	*)
		if [ -f $MAIL ]; then
			mailsize=`set - \`wc -c $MAIL\`;echo $1`
		else
			mailsize=0
		fi
		;;
esac

trap ':' 2
trap exit 3
trap "tail -n $savehist $histfile>/tmp/hist$$;uniq /tmp/hist$$ > $histfile;\
rm -f /tmp/*$$;exit 0" 15

getcmd=yes
mailcheck=
exclaim=
echoit=
mailprompt=

while :
do

	run=yes
	case "$mailprompt" in
		"")
			;;
		*)
			echo "$mailprompt"
			;;
	esac
	case "$getcmd" in
	yes)
		: guess if the prompt should be evaluated or not
		case "$PS1" in
		\$|\$\ )
			echo $n "$PS1$c"
				;;
		*\`*|*\$*)
			tmp="`(eval $PS1) 2>&1`"
			case "$tmp" in
			*not\ found)			
				echo $n "$PS1$c"
				;;			
			*)				
				echo $n "$tmp$c"
				;;			
			esac
			;;
		*)
			echo $n "$PS1$c"
			;;
		esac

		read cmd || cmd="exit"
		;;
	*)	;;
	esac

	case "$MAIL" in
	"")
		;;
	*)
		: check for mail every 10 commands
		case "$mailcheck" in
		1111111111)
			mailcheck=
			if [ -f $MAIL ]; then
				newsize="`set - \`wc -c $MAIL\`;echo $1`"
			else
				newsize=0
			fi
			if test "$newsize" -gt "$mailsize"; then
				mailprompt="You have new mail"
			else
				mailprompt=
			fi
			mailsize=$newsize
			;;
		*)
			mailcheck=1$mailcheck
			;;
		esac
		;;
	esac
	hist=no

	case "$cmd" in
	"")
		continue
		;;
	sh)
		sh
		run=no
		;;
	!!)
		cmd=$lastcmd
		echoit=yes
		getcmd=no
		continue
		;;
	*:p)
		cmd="`expr \"$cmd\" : '\(.*\):p'` +~+p"
		getcmd=no
		continue
		;;
	foreach[\ \	]*)
		while test "$line" != "end"; do
			echo $n "$PS2$c"
			read line
			cmd="${cmd};$line"
		done
		echo "$cmd" > /tmp/bcsh$$
		ed - /tmp/bcsh$$ << ++++
		s/end/done/
		s/foreach[ 	]\(.*\)(/for \1 in /
		s/)//
		s/;/;do /
		w
++++
		;;
	for[\ \	]*|while[\ \	]*)
		# try to catch the most common cshell-to-Bourne-shell
		# mistakes

		echo $n "$PS2$c"
		read line
		case "$line" in
		*do)
			line="do :"
			;;
		*do*)
			;;
		*)
			line="do $line"
			;;
		esac

		cmd="${cmd};$line"
		while test "$line" != "done"  && test "$line" != "end"
		do
			echo $n "$PS2$c"
			read line
			case "$line" in
			end)
				line=done
				;;
			esac
			cmd="${cmd};$line"
		done
		echo "$cmd" > /tmp/bcsh$$
		;;
	if[\ \	]*)
		while test "$line" != "fi" && test "$line" != "endif"
		do
			echo $n "$PS2$c"
			read line
			case "$line" in
			*[a-z]*then)
				line="`expr \"$line\" : '\(.*\)then'`;then"
				;;
			endif)
				line=fi
				;;
			esac
			cmd="${cmd};$line"
		done
		echo "$cmd" > /tmp/bcsh$$
		case "`grep then /tmp/bcsh$$`" in
		"")
			# fix 'if foo bar' cases

			ed - /tmp/bcsh$$ << ++++
			s/)/);then/
			s/.*/;fi/
			w
++++
			;;
		esac
		;;
	case[\ \	]*)
		while test "$line" != "esac"
		do
			echo $n "$PS2$c"
			read line
			cmd="${cmd}@$line"
		done
		cmd="`echo \"$cmd\" | tr '@' ' '`"
		echo "$cmd" > /tmp/bcsh$$
		;;
	switch[\ \	]*)
		while test "$line" != "endsw"
		do
			echo $n "$PS2$c"
			read line
			cmd="${cmd}@$line"
		done
		echo "$cmd" > /tmp/bcsh$$
		ed - /tmp/bcsh$$ << '++++'
		1,$s/@/\
/g
		g/switch.*(/s//case "/
		s/)/" in/
		1,$s/case[	 ]\(.*\):$/;;\
	\1)/
		2d
		1,$s/endsw/;;\
esac/
		g/breaksw/s///
		1,$s/default.*/;;\
	*)/
		w
++++
		cmd="`cat /tmp/bcsh$$`"
		;;
	*!*)
		hist=yes
		;;
	esac

	case "$hist" in
	yes)
		# deal with genuine exclamation marks, go back and parse again

		case "$cmd" in
		*\>![\ \	]*|*\\!*)
			cmd="`echo \"$cmd\" | sed -e 's@\\!@REALEXCLAMATIONMARK@g'`"
			exclaim=yes
			getcmd=no
			continue
			;;
		esac

		# break command into elements, parse each one

		tmp=
		for i in $cmd
		do
			# find element with !, peel off stuff up to !

			case "$i" in
			!)
				# most likely a typo for !!, so fix it
				front=
				$i=!!
				;;
			!!*)
				front=
				i="`expr \"$i\" : '.*\(!!.*\)'`"
				;;
			*!!*)
				front="`expr \"$i\" : '\(.*\)!!.*'`"
				i="`expr \"$i\" : '.*\(!!.*\)'`"
				;;
			!*)
				front=
				i="`expr \"$i\" : '.*!\(.*\)'`"
				;;
			*)
				tmp="$tmp$i "
				continue
				;;
			esac
			case "$i" in
			!!*)
				# want last command

				rest="`expr \"$i\" : '!!\(.*\)'`"
				i=$lastcmd
				;;
			-*)
				# we want to search back through the history list

				case "$i" in
				-)
					rest="`expr \"$i\" : '-\(.*\)'`"
					i=$lastcmd
					;;
				-[0-9]*)
					wanted="`expr \"$i\" : '-\([0-9][0-9]*\).*'`"
					rest="`expr \"$i\" : '-[0-9][0-9]*\(.*\)'`"
					i="`tail -n $wanted $histfile | sed -e "1q"`"
					;;
				esac
				;;
			[0-9]*)
				# find which number command is wanted

				wanted="`expr \"$i\" : '\([0-9][0-9]*\).*'`"
				rest="`expr \"$i\" : '[0-9][0-9]*\(.*\)'`"
				i="`grep -n . $histfile | grep \"^$wanted\"`"
				i="`expr \"$i\" : \"${wanted}.\(.*\)\"`"
				;;
			\?*)

				# find which 'command-contains' match is wanted

				case "$i" in
				\?{*}*)
					wanted="`expr \"$i\" : '?{\(.*\)}.*'`"
					rest="`expr \"$i\" : '?.*}\(.*\)'`"
					;;
				\?*:*)
					wanted="`expr \"$i\" : '?\(.*\):.*'`"
					rest="`expr \"$i\" : '?.*\(:.*\)'`"
					;;
				\?*)
					wanted="`expr \"$i\" : '?\(.*\)'`"
					rest=
					;;
				esac
				i="`grep \"$wanted\" $histfile | sed -n '$p'`"
				;;
			*)
				# find which 'start-of-command' match is wanted

				case "$i" in
				{*}*)
					wanted="`expr \"$i\" : '{\(.*\)}.*'`"
					rest="`expr \"$i\" : '.*}\(.*\)'`"
					;;
				*:*)
					wanted="`expr \"$i\" : '\(.*\):.*'`"
					rest="`expr \"$i\" : '.*\(:.*\)'`"
					;;
				*)
					wanted="$i"
					rest=
					;;
				esac
				i="`grep \"^$wanted\" $histfile | sed -n '$p'`"
				;;
			esac

			# see if we actually found anything to substitute

			case "$i" in
			"")
				badsub="Event not found"
				break
				;;
			*)
				badsub=no
				;;
			esac

			case "$rest" in
			"")
				tmp="$front$tmp$i "
				continue
				;;
			:[0-9]*)
				# find which element of $i is wanted

				number="`expr \"$rest\" : ':\([0-9][0-9]*\).*'`"
				rest="`expr \"$rest\" : ':[0-9][0-9]*\(.*\)'`"

				# count through $i till we get to the
				# right element

				counter=0
				for element in $i
				do
					case "$counter" in
					$number)
						break
						;;
					*)
						counter="`expr \"$counter\" + 1`"
						# counter=$[ $counter + 1 ]
						;;
					esac
				done
				case "$counter" in
				$number)
					badsub=no
					;;
				*)
					badsub="Bad command element"
					break
					;;
				esac
				tmp="$tmp$front$element$rest "
				continue
				;;
			:\$*)
				# spin through $i till we hit the last element

				rest="`expr \"$rest\" : ':\$\(.*\)'`"
				for element in $i
				do
					:
				done
				tmp="$tmp$front$element$rest "
				continue
				;;
			:\**)
				# we want all elements except the command itself

				rest="`expr \"$rest\" : ':\*\(.*\)'`"
				save=$i
				set - $i
				shift
				case "$*" in
				"")
					badsub="No arguments to command '$save'"
					break
					;;
				*)
					badsub=no
					;;
				esac
				tmp="$tmp$front$*$rest "
				continue
				;;
			:s*|:gs*)
				# we are doing a substitution
				# put / on end if needed

				case "$rest" in
				:s/*/*/*|:gs/*/*/*)
					;;
				:s/*/*|:gs/*/*)
					rest="${rest}/"
					;;
				esac

				# find what substitution is wanted

				first="`expr \"$rest\" : ':*s\/\(.*\)\/.*\/.*'`"
				second="`expr \"$i\" : ':*s/.*/\(.*\)/.*'`"

				# see if it is a global substitution

				case "$rest" in
				:gs*)
					global=g
					;;
				:s*)
					global=
					;;
				esac
				rest="`expr \"$rest\" : '.*/.*/.*/\(.*\)'`"
				i="`echo \"$i\" | sed -e \"s@$first@$second@$global\"`"

				# see if subsitution worked

				case "$i" in
				"")
					badsub="Substiution failed"
					break
					;;
				*)
					badsub=no
					;;
				esac
				tmp="$tmp$front$i$rest "
				continue
				;;
			*)
				tmp="$tmp$front$i$rest "
				;;
			esac
		done
		case "$badsub" in
		no)
			;;
		*)
			echo "$badsub"
			badsub=no
			continue
			;;
		esac
		cmd="$tmp"
		echoit=yes
		getcmd=no
		continue
		;;
	*)
		run=yes
		;;
	esac

	case "$cmd" in
	*\^*\^*\^*)
		# see if the substitution is global
		case "$cmd" in
		g*)
			global=g
			;;
		*)
			global=
			;;
		esac

		# put a '^' on the end if necessary
		case "$cmd" in
		*\^)
			;;
		*)
			cmd="${cmd}^"
			;;
		esac

		# find what substitution is wanted

		first="`expr \"$cmd\" : '*\^\(.*\)\^.*\^.*'`"
		second="`expr \"$cmd\" : '*\^.*\^\(.*\)\^.*'`"
		rest="`expr \"$cmd\" : '*\^.*\^.*\^\(.*\)'`"
		cmd="`echo \"$lastcmd\" | sed -e \"s@$first@$second@$global\"`$rest"

		# see if the substitution worked

		case "$cmd" in
		"")
			echo "Substitution failed"
			continue
			;;
		esac
		echoit=yes
		getcmd=no
		continue
		;;
	*~e)
		echo "$cmd" | sed -e "s@~e@@" > /tmp/bcsh$$
		$EDITOR /tmp/bcsh$$
		cmd="`cat /tmp/bcsh$$`"
		getcmd=no
		continue
		;;
	*~v)
		echo "$cmd" | sed -e "s@~v@@" > /tmp/bcsh$$
		echo "$lastcmd" > /tmp/bcsh$$
		$VISUAL /tmp/bcsh$$
		cmd="`cat /tmp/bcsh$$`"
		getcmd=no
		continue
		;;
	exec[\ \	]*)
		tail -n $savehist $histfile>/tmp/hist$$
		uniq /tmp/hist$$ > $histfile
		rm -f /tmp/*$$
		echo $cmd > /tmp/cmd$$
		. /tmp/cmd$$
		;;
	login[\ \	]*|newgrp[\ \	]*)
		tail -n $savehist $histfile>/tmp/hist$$
		uniq /tmp/hist$$ > $histfile
		rm -f /tmp/*$$
		echo $cmd > /tmp/cmd$$
		. /tmp/cmd$$
		;;
	logout|exit|bye)
		if test -s "$logoutfile"
			then
			# sh $logoutfile
			$SHELL $logoutfile
		fi
		tail -n $savehist $histfile > /tmp/hist$$
		uniq /tmp/hist$$ > $histfile
		rm -f /tmp/*$$
		exit 0
		;;
	h|history)
		grep -n . $histfile | tail -n $history | sed -e 's@:@	@' | $PAGER
		continue
		;;
	h[\ \	]\|*|h[\ \	]\>*|h\|*|h\>*)
		cmd="`echo \"$cmd\" | sed -e \"s@h@grep -n . $histfile | tail -n $history | sed -e 's@:@	@'@\"`"
		getcmd=no
		continue
		;;
	history[\ \	]*\|*|history[\ \	]*\>*)
		cmd="`echo \"$cmd\" | sed -e \"s@history@grep -n . $histfile | tail -n $history | sed -e 's@:@ @'@\"`"
		getcmd=no
		continue
		;;
	source[\ \	]*)
		set - $cmd
		shift
		echo . $*  > /tmp/cmd$$
		. /tmp/cmd$$
		run=no
		;;
	wait)
		wait
		run=no
		;;
	.[\ \	]*)
		echo $cmd > /tmp/cmd$$
		. /tmp/cmd$$
		run=no
		;;
	cd|cd[\ \	]*)
		# check if it will work first, or else this shell will terminate
		# if the cd dies.  If you have a built-in test, you might want
		# to replace the try-it-and-see below with a couple of tests,
		# but it is probably just as fast like this.

		echo $cmd > /tmp/cmd$$
		if ($SHELL /tmp/cmd$$) ; then
			. /tmp/cmd$$
		fi
		run=no
		;;
	awk[\ \	]*|dd[\ \	]*|cc[\ \	]*|make[\ \	]*)
		# these are the only commands I can think of whose syntax
		# includes an equals sign.  Add others as you find them.

		echo "$cmd" > /tmp/bcsh$$
		;;
	setenv*|*=*)
		# handle setting shell variables, turning cshell syntax to Bourne
		# syntax -- note all variables must be exported or they will not
		# be usable in other commands

		echo "$cmd" > /tmp/cmd$$
		ed - /tmp/cmd$$ << ++++
		g/^setenv[ 	]/s/[ 	]/@/
		g/^setenv@/s/[ 	]/=/
		g/^setenv@/s///
		g/^set/s///
		.t.
		\$s/=.*//
		s/^/export /
		w
++++
		. /tmp/cmd$$
		rm -f /tmp/cmd$$
		run=no
		;;
	unset[\ \	]*|umask[\ \	]*|export[\ \	]*|set[\ \	]*)
		# handle commands which twiddle current environment

		$cmd
		run=no
		;;
	alias|alias[\ \	])
		if [ -f $aliasfile ]; then
			$PAGER $aliasfile
		fi
		lastcmd=$cmd
		run=no
		continue
		;;
	alias[\ \	]*)
		case "$cmd" in
		alias[\ \	]\|*|alias[\ \	]\>*)
			cmd="`echo \"$cmd\" | sed -e \"s@alias@cat $aliasfile@\"`"
			getcmd=no
			continue
			;;
		alias[\ \	]*[\ \	]*)
			;;
		*)
			echo "Syntax: alias name command"
			cmd=
			continue
			;;
		esac
		set - $cmd
		shift
		cmd="$*"

		# make sure there is always 1 blank line in file so
		# unaliasing will always work -- ed normally refuses
		# to write an empty file
		echo "" >> $aliasfile
		cat << ++++ >> $aliasfile
$cmd
++++

#		ed - $aliasfile << '++++'
#		g/alias[ 	]/s///
#		g/^['"]\(.*\)['"]$/s//\1/
#		g/^/s//alias	/
#		w
#++++

		sort -u -o $aliasfile $aliasfile
		doalias=yes
		cmd="alias $cmd"
		run=no
		;;
	unalias[\ \	]*)
		set - $cmd
		case "$#" in
		2)
			cmd=$2
			;;
		*)
			echo "Syntax: unalias alias_name"
			continue
			;;
		esac
		ed - $aliasfile << ++++
		/^$cmd[ 	]/d
		w
++++
		case "`set - \`wc -l $aliasfile\`;echo $1`" in
		1)
			# just removed last alias
			doalias=no
			;;
		esac
		run=no
		;;
	*)
		case "$doalias" in
		yes)
			set - $cmd
			tmp="`grep \"^$1 \" $aliasfile`"
			case "$tmp" in
			$1[\ \	]*)
				shift
				cmd=$*
				set - $tmp
				shift
				tmp=$*
				case "$tmp" in
				*\$*)
					# uses positional variables

					cmd="set - $cmd ; $tmp"
					getcmd=no
					continue
					;;
				*)
					cmd="$tmp $cmd"
					getcmd=no
					continue
					;;
				esac
				;;
			*)
				echo "$cmd" > /tmp/bcsh$$
				;;
			esac
			;;
		no)
			echo "$cmd" > /tmp/bcsh$$
			;;
		esac
		;;
	esac

	case "$cmd" in
	*+~+p)
		cmd="`expr \"$cmd\" : '\(.*\)+~+p'`"
		echoit=yes
		run=no
		;;
	esac

	case "$cmd" in
	"")
		continue
		;;
	*)
		case "$exclaim" in
		yes)
			cmd="`echo \"$cmd\" | sed -e 's@REALEXCLAMATIONMARK@!@g'`"
			echo "$cmd" > /tmp/bcsh$$
			;;
		esac
		case "$echoit" in
		yes)
			echo $cmd
			;;
		esac
		case "$run" in
		yes)
			case "${noclobber+yes}" in
			yes)
				case "$cmd" in
				*\>![\ \	]*)
					ed - /tmp/bcsh$$ << ++++
					g/>!/s//>/
					w
++++
					;;
				*\>\>*)
					;;
				*\>*)
					outfile="`expr \"$cmd\" : '.*>\(.*\)'`"
					case "$outfile" in
					\&*)
						;;
					*)
						set - $outfile
						outfile="$1"
						if test -s "$outfile"
						then
							case "${iclobber+yes}" in
							yes)
								echo $n "Overwrite ${outfile}? $c"
								read answer
								case "$answer" in
								y*)
									;;
								*)
									echo ':' > /tmp/bcsh$$
									;;
								esac
								;;
							*)
								echo "${outfile}: file exists"
								echo ':' > /tmp/bcsh$$
								;;
							esac
						fi
						;;
					esac
					;;
				esac
				;;
			*)
				case "$cmd" in
				*\>![\ \	]*)
					ed - /tmp/bcsh$$ << ++++
					g/>!/s//>/g
					w
++++
					;;
				esac
				;;
			esac
			(trap 'exit 1' 2 3; $BASH /tmp/bcsh$$)
			;;
		esac
		case "$cmd" in
		$lastcmd)
			;;
		*)
			case "$exclaim" in
			yes)
				cmd="`echo \"$cmd\" | sed -e 's@!@\\\\!@g'`"
				;;
			esac

			cat << ++++ >> $histfile
$cmd
++++
			lastcmd=$cmd

			case "$inc_cmdno" in
			yes)
				cmdno="`expr \"$cmdno\" + 1`"
				# cmdno=$[$cmdno + 1]
				;;
			esac
			;;
		esac
		;;
	esac

	# The next commented-out line sets the prompt to include the command
	# number -- you should only un-comment this if it is the ONLY thing
	# you ever want as your prompt, because it will override attempts
	# to set PS1 from the command level.  If you want the command number
	# in your prompt without sacrificing the ability to change the prompt
	# later, replace the default setting for PS1 before the beginning of
	# the main loop with the following:  PS1='echo -n "${cmdno}% "'
	# Doing it this way is, however, slower than the simple version below.
	 
	PS1="${cmdno}% "

	getcmd=yes
	echoit=no
	exclaim=no
done
exit 0

# Christine Robertson  {linus, ihnp4, decvax}!utzoo!globetek!chris