Improved Bcsh (Bourne Shell Cshell-Emulator)

chris chris at globetek.UUCP
Fri Jan 31 03:34:26 AEST 1986


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
--------------- cut here --------------------- cut here -------------------

:	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="`tail -1 $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
	"")
		;;
	*)
		mailsize=`set - \`wc -c $MAIL\`;echo $1`
		;;
esac

trap ':' 2
trap exit 3
trap "tail -$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
			;;
	esac
	case "$MAIL" in
		"")
			;;
		*)
			: check for mail every 10 commands

			case "$mailcheck" in
				1111111111)
					mailcheck=
					newsize="`set - \`wc -c $MAIL\`;echo $1`"
					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" -a "$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" -a "$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 at 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 -$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 | tail -1`"
						;;
					*)

						: 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 | tail -1`"
						;;
				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`"
									;;
							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 -$savehist $histfile>/tmp/hist$$
			uniq /tmp/hist$$ > $histfile
			rm -f /tmp/*$$
			echo $cmd > /tmp/cmd$$
			. /tmp/cmd$$
			;;
		login[\ \	]*|newgrp[\ \	]*)
			tail -$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
			fi
			tail -$savehist $histfile > /tmp/hist$$
			uniq /tmp/hist$$ > $histfile
			rm -f /tmp/*$$
			exit 0
			;;
		h|history)
			grep -n . $histfile | tail -$history | sed -e 's@:@	@' | $PAGER
			continue
			;;
		h[\ \	]\|*|h[\ \	]\>*|h\|*|h\>*)
			cmd="`echo \"$cmd\" | sed -e \"s at h@grep -n . $histfile | tail -$history | sed -e 's@:@	@'@\"`"
			getcmd=no
			continue
			;;
		history[\ \	]*\|*|history[\ \	]*\>*)
			cmd="`echo \"$cmd\" | sed -e \"s at history@grep -n . $histfile | tail -$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 (sh /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[\ \	])
			$PAGER $aliasfile
			lastcmd=$cmd
			run=no
			continue
			;;
		alias[\ \	]*)
			case "$cmd" in
				alias[\ \	]\|*|alias[\ \	]\>*)
					cmd="`echo \"$cmd\" | sed -e \"s at 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 at 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;sh /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`"
							;;
					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
--------------- cut here --------------------- cut here -------------------
-- 

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

Money may not buy happiness, but misery in luxury has its compensations...



More information about the Comp.sources.unix mailing list