1 # 1-Feb-86 09:37:35-MST,30567;000000000001
2 # Return-Path: <unix-sources-request@BRL.ARPA>
3 # Received: from BRL-TGR.ARPA by SIMTEL20.ARPA with TCP; Sat 1 Feb 86 09:36:16-MST
4 # Received: from usenet by TGR.BRL.ARPA id a002623; 1 Feb 86 9:33 EST
5 # From: chris <chris@globetek.uucp>
6 # Newsgroups: net.sources
7 # Subject: Improved Bcsh (Bourne Shell Cshell-Emulator)
8 # Message-ID: <219@globetek.UUCP>
9 # Date: 30 Jan 86 17:34:26 GMT
10 # To: unix-sources@BRL-TGR.ARPA
12 # This is a new, improved version of my Bourne shell cshell-emulator.
13 # The code has been cleaned up quite a bit, and a couple of new features
14 # added (now supports 'noclobber' and 'iclobber' variables). A bug with
15 # 'eval' that caused "illegal I/O" error messages on vanilla V7 shells has
18 # I have posted the program in its entirety because a context diff of the
19 # old and new versions was longer than the new version...
22 # Bcsh -- A Simple Cshell-Like Command Pre-Processor For The Bourne Shell
24 # "Copyright (c) Chris Robertson, December 1985"
26 # This software may be used for any purpose provided the original
27 # copyright notice and this notice are affixed thereto. No warranties of
28 # any kind whatsoever are provided with this software, and it is hereby
29 # understood that the author is not liable for any damagages arising
30 # from the use of this software.
32 # Features Which the Cshell Does Not Have:
33 # ----------------------------------------
35 # + command history persists across bcsh sessions
36 # + global last-command editing via 'g^string1^string2^' syntax
37 # + edit any command via $EDITOR or $VISUAL editors
38 # + history file name, .bcshrc file name, alias file name, and number
39 # of commands saved on termination can be set by environment variables
40 # + prompt may evaluate commands, such as `pwd`, `date`, etc.
41 # + the whole text of interactive 'for' and 'while' loops and 'if'
42 # statements goes into the history list and may be re-run or edited
43 # + multiple copies of commands and requests to see command history
44 # are not added to the history list
45 # + the history mechanism actually stores all commands entered in a
46 # current session, not just $history of them. This means that you
47 # can increase $history on the fly and at once have a larger history.
53 # logout, exit, bye write out history file and exit
54 # h, history show current history list
60 # alias NAME CMND create an alias called NAME to run CMND
61 # unalias NAME remove the alias NAME
63 # There are no 'current-session only' aliases -- all alias and unalias
64 # commands are permanent, and stored in the $aliasfile.
66 # If an alias contains positional variables -- $1, $2, $*, etc. -- any
67 # arguments following the alias name are considered to be values for
68 # those variables, and the alias is turned into a command of the form
69 # 'set - arguments;alias'. Otherwise, a simple substitution is performed
70 # for the alias and the rest of the command preserved. The cshell
71 # convention of using '\!:n' in an alias to get bits of the current
72 # command is mercifully abandoned.
74 # Quotes are not necessary around the commands comprising an alias;
75 # in fact, any enclosing quotes are stripped when the alias is added
78 # A couple of typical aliases might be:
83 # Note that aliasing something to "commands;logout" will not work -- if
84 # you want something to happen routinely on logout put it in the file
85 # specified by $logoutfile, default = $HOME/.blogout.
88 # Command Substitutions:
89 # ----------------------
91 # !! substitute last command from history list
92 # !!:N substitute Nth element of last command from
93 # history list -- 0 = command name, 1 = 1st arg
94 # !!:$ substitute last element of last command from
96 # !!:* substitute all arguments to last command
98 # !NUMBER substitute command NUMBER from the history list
99 # !NUMBER:N as above, but substitute Nth element, where
100 # 0 = command name, 1 = 1st arg, etc.
101 # !NUMBER:$ as above, but substitute last element
102 # !NUMBER:* as above, but substitute all arguments
103 # !-NUMBER substitute the command NUMBER lines from the
104 # end of the history list; 1 = last command
105 # !-NUMBER:N as above, but substitute Nth element, where
106 # 0 = command name, 1 = 1st arg, etc.
107 # !-NUMBER:$ as above, but substitute last element
108 # !-NUMBER:* as above, but substitute all arguments
109 # !?STRING substitute most-recent command from history list
110 # containing STRING -- STRING must be enclosed in
111 # braces if followed by any other characters
112 # !?STRING:N as above, but substitute Nth element, where
113 # 0 = command name, 1 = 1st arg, etc.
114 # !?STRING:$ as above, but substitute last element
115 # !?STRING:* as above, but substitute all arguments
121 # CMND~e edit CMND using $EDITOR, where CMND may be found
122 # using a history substitution
123 # CMND~v edit CMND using $VISUAL, where CMND may be found
124 # using a history substitution
125 # " ^string1^string2^ substitute string2 for string1 in last command"
127 # " g^string1^string2^ globally substitute string2 for string1 in "
128 # last command and run it
129 # !NUMBER:s/string1/string2/
130 # substitute string2 for string1 in
131 # command NUMBER and run it
132 # !NUMBER:gs/string1/string2/
133 # globally substitute string2 for string1 in
134 # command NUMBER and run it
135 # !?STRING:s/string1/string2/
136 # substitute string2 for string1 in last command
137 # containing STRING and run it
138 # !?STRING:gs/string1/string2/
139 # globally substitute string2 for string1 in last
140 # command containing STRING and run it
142 # Any command which ends in the string ":p" is treated as a normal
143 # command until all substitutions have been completed. The trailing
144 # ":p" is then stripped, and the command is simply echoed and added to
145 # the history list instead of being executed.
147 # None of the other colon extensions of the cshell are supported.
150 # Shell Environment Variables:
151 # ----------------------------
153 # EDITOR editor used by ~e command, default = "ed"
154 # VISUAL editor used by ~v command, default = "vi"
155 # MAIL your system mailbox
156 # PAGER paging program used by history command, default = "more"
158 # PS2 secondary prompt
159 # history number of commands in history list, default = 22
160 # histfile file history list is saved in, default = $HOME/.bhistory
161 # savehist number of commands remembered from last bcsh session
162 # aliasfile file of aliased commands, default = $HOME/.baliases
163 # logoutfile file of commands to be executed before termination
164 # inc_cmdno yes/no -- keep track of command numbers or not
165 # noclobber if set, existing files are not overwritten by '>'
166 # iclobber if both noclobber and iclobber are set, the user is
167 # prompted for confirmation before existing files are
170 # Note: if you are setting either noclobber or iclobber mid-session,
174 # Regular Shell Variables:
175 # ------------------------
177 # Shell variables may be set via Bourne or cshell syntax, e.g., both
178 # "set foo=bar" and "foo=bar" set a variable called "foo" with the value
179 # "bar". However, all variables are automatically set as environment
180 # variables, so there is no need to export them. Conversely, there
181 # are NO local variables. Sorry, folks.
183 # A cshell-style "setenv" command is turned into a regular "set" command.
189 # You may, if you wish, have a command executed in your prompt. If
190 # the variable PS1 contains a dollar sign or a backquote, it is
191 # evaluated and the result used as the prompt, provided the evaluation
192 # did not produce a "not found" error message. The two special cases
193 # of PS1 consisting solely of "$" or "$ " are handled correctly. For
194 # example, to have the prompt contain the current directory followed
197 # PS1=\'echo "`pwd` "\'
199 # You need the backslashed single quotes to prevent the command being
200 # evaluated by the variable-setting mechanism and the shell before it
201 # is assigned to PS1.
203 # To include the command number in your prompt, enter the command:
205 # PS1=\'echo "$cmdno "\'
208 # Shell Control-Flow Syntax:
209 # --------------------------
211 # 'While', 'for', 'case', and 'if' commands entered in Bourne shell
212 # syntax are executed as normal.
214 # A valiant attempt is made to convert 'foreach' loops into 'for' loops,
215 # cshell-syntax 'while' loops into Bourne shell syntax, and 'switch'
216 # statements into 'case' statements. I cannot guarantee to always get it
217 # right. If you forget the 'do' in a 'while' or 'for' loop, or finish
218 # them with 'end' instead of 'done', this will be corrected.
220 # Note that cshell-to-Bourne control flow conversions do not take place
221 # if control is nested -- e.g., a 'foreach' inside a 'while' will fail.
223 # The simple-case cshell "if (condition) command" is turned into Bourne
224 # syntax. Other 'if' statements are left alone apart from making the
225 # 'then' a separate statement, because constructing a valid interactive
226 # cshell 'if' statement is essentially an exercise in frustration anyway.
227 # The cshell and Bourne shell have sufficiently different ideas about
228 # conditions that if is probably best to resign yourself to learning
229 # the Bourne shell conventions.
231 # Note that since most of the testing built-ins of the cshell are
232 # not available in the Bourne shell, a complex condition in a 'while'
233 # loop or an 'if' statement will probably fail.
236 # Bugs, Caveats, etc.:
237 # --------------------
239 # This is not a super-speedy program. Be patient, especially on startup.
241 # To the best of my knowledge this program should work on ANY Bourne
242 # shell -- note that if your shell does not understand 'echo -n' you
243 # will have to re-set the values of '$n' and '$c'.
245 # This program may run out of stack space on a 16-bit machine where
246 # /bin/sh is not split-space.
248 # Mail checking is done every 10 commands if $MAIL is set in your
249 # environment. For anything fancier, you will have to hack the code.
251 # Because commands are stuffed in a file before sh is invoked on them,
252 # error messages from failed commands are ugly.
254 # Failed history substitutions either give nothing at all, or a
255 # "not found" style of error message.
257 # A command history is kept whether you want it or not. This may be
258 # perceived as a bug or a feature, depending on which side of bed you
261 # If you want a real backslash in a command, you will have to type two
262 # of them because the shell swallows the first backslash in the initial
263 # command pickup. This means that to include a non-history '!' in a
264 # command you need '\\!' -- a real wart, especially for net mail,
267 # Commands containing an '@' will break all sorts of things.
269 # Very complex history substitutions may fail.
271 # File names containing numbers may break numeric history sustitutions.
273 # Commands containing bizzare sequences of characters may conflict
274 # with internal kludges.
276 # Aliasing something to "commands;logout" will not work -- if you
277 # want something to happen routinely on logout, put it in the file
278 # specified by $logoutfile, default = $HOME/.blogout.
280 # Please send all bug reports to ihnp4!utzoo!globetek!chris.
281 # Flames will be posted to net.general with 'Reply-to' set to your
286 # ************* VERY IMPORTANT NOTICE *************
288 # If your shell supports # comments, then REPLACE all the colon 'comments'
289 # with # comments. If it does not, then REMOVE all the 'comment' lines from the
290 # working copy of the file, as it will run MUCH faster -- the shell evaluates
291 # lines starting with a colon but does not actually execute them, so you will
292 # save the read-and-evaluate time by removing them.
294 case "`echo -n foo`" in
304 echo "Your 'echo' command is broken."
308 history=${history-22}
309 savehist
=${savehist-22}
310 histfile
=${histfile-$HOME/.bhistory}
311 logoutfile
=${logoutfile-$HOME/.blogout}
316 aliasfile
=${aliasfile-$HOME/.baliases}
318 # the alias file may contain 1 blank line, so a test -s will not work
320 case "`cat $aliasfile 2> /dev/null`" in
329 if test -s "${sourcefile-$HOME/.bcshrc}"
331 .
${sourcefile-$HOME/.bcshrc}
334 if test -s "$histfile"
336 cmdno
="`set - \`wc -l $histfile\`;echo $1`"
337 cmdno
="`expr \"$cmdno\" + 1`"
338 lastcmd
="`sed -n '$p' $histfile`"
341 while test ! -w "$histfile"
343 echo "Cannot write to history file '$histfile'."
344 echo $n "Please enter a new history filename: $c"
353 cat /dev
/null
> $histfile
358 # keep track of command number as the default
360 inc_cmdno
=${inc_cmdo-yes}
362 # default prompts -- PS1 and PS2 may be SET but EMPTY, so '${PS1-% }' syntax
376 export histfile savehist
history aliasfile EDITOR VISUAL PAGER cmdno PS1 PS2
382 if [ -f $MAIL ]; then
383 mailsize
=`set - \`wc -c $MAIL\
`;echo $1`
392 trap "tail -n $savehist $histfile>/tmp/hist$$;uniq /tmp/hist$$ > $histfile;\
393 rm -f /tmp/*$$;exit 0" 15
405 case "$mailprompt" in
414 : guess
if the prompt should be evaluated or not
420 tmp="`(eval $PS1) 2>&1`"
435 read cmd || cmd="exit"
444 : check for mail every 10 commands
448 if [ -f $MAIL ]; then
449 newsize="`set - \
`wc -c $MAIL\`;echo $1`"
453 if test "$newsize" -gt "$mailsize"; then
454 mailprompt="You have new mail"
461 mailcheck=1$mailcheck
483 cmd="`expr \"$cmd\" : '\(.*\):p'` +~+p"
488 while test "$line" != "end"; do
493 echo "$cmd" > /tmp/bcsh$$
494 ed - /tmp/bcsh$$ << ++++
496 s/foreach[ ]\(.*\)(/for \1 in /
502 for[\ \ ]*|while[\ \ ]*)
503 # try to catch the most common cshell-to-Bourne-shell
520 while test "$line" != "done" && test "$line" != "end"
531 echo "$cmd" > /tmp/bcsh$$
534 while test "$line" != "fi" && test "$line" != "endif"
540 line="`expr \"$line\" : '\(.*\)then'`;then"
548 echo "$cmd" > /tmp/bcsh$$
549 case "`grep then /tmp
/bcsh$$
`" in
551 # fix 'if foo bar' cases
553 ed - /tmp/bcsh$$ << ++++
562 while test "$line" != "esac"
568 cmd="`echo \"$cmd\" |
tr '@' ' '`"
569 echo "$cmd" > /tmp/bcsh$$
572 while test "$line" != "endsw"
578 echo "$cmd" > /tmp/bcsh$$
579 ed - /tmp/bcsh$$ << '++++'
582 g/switch.*(/s//case "/
584 1,$s/case[ ]\(.*\):$/;;\
594 cmd="`cat /tmp
/bcsh$$
`"
603 # deal with genuine exclamation marks, go back and parse again
607 cmd="`echo \"$cmd\" |
sed -e 's@\\!@REALEXCLAMATIONMARK@g'`"
614 # break command into elements, parse each one
619 # find element with !, peel off stuff up to !
623 # most likely a typo for !!, so fix it
629 i="`expr \"$i\" : '.*\(!!.*\)'`"
632 front="`expr \"$i\" : '\(.*\)!!.*'`"
633 i="`expr \"$i\" : '.*\(!!.*\)'`"
637 i="`expr \"$i\" : '.*!\(.*\)'`"
648 rest="`expr \"$i\" : '!!\(.*\)'`"
652 # we want to search back through the history list
656 rest="`expr \"$i\" : '-\(.*\)'`"
660 wanted="`expr \"$i\" : '-\([0-9][0-9]*\).*'`"
661 rest="`expr \"$i\" : '-[0-9][0-9]*\(.*\)'`"
662 i="`tail -n $wanted $histfile |
sed -e "1q"`"
667 # find which number command is wanted
669 wanted="`expr \"$i\" : '\([0-9][0-9]*\).*'`"
670 rest="`expr \"$i\" : '[0-9][0-9]*\(.*\)'`"
671 i="`grep -n .
$histfile |
grep \"^
$wanted\"`"
672 i="`expr \"$i\" : \"${wanted}.\
(.
*\
)\"`"
676 # find which 'command-contains' match is wanted
680 wanted="`expr \"$i\" : '?{\(.*\)}.*'`"
681 rest="`expr \"$i\" : '?.*}\(.*\)'`"
684 wanted="`expr \"$i\" : '?\(.*\):.*'`"
685 rest="`expr \"$i\" : '?.*\(:.*\)'`"
688 wanted="`expr \"$i\" : '?\(.*\)'`"
692 i="`grep \"$wanted\" $histfile |
sed -n '$p'`"
695 # find which 'start-of-command' match is wanted
699 wanted="`expr \"$i\" : '{\(.*\)}.*'`"
700 rest="`expr \"$i\" : '.*}\(.*\)'`"
703 wanted="`expr \"$i\" : '\(.*\):.*'`"
704 rest="`expr \"$i\" : '.*\(:.*\)'`"
711 i="`grep \"^
$wanted\" $histfile |
sed -n '$p'`"
715 # see if we actually found anything to substitute
719 badsub="Event not found"
733 # find which element of $i is wanted
735 number="`expr \"$rest\" : ':\([0-9][0-9]*\).*'`"
736 rest="`expr \"$rest\" : ':[0-9][0-9]*\(.*\)'`"
738 # count through $i till we get to the
749 counter="`expr \"$counter\" + 1`"
750 # counter=$[ $counter + 1 ]
759 badsub="Bad command element"
763 tmp="$tmp$front$element$rest "
767 # spin through $i till we hit the last element
769 rest="`expr \"$rest\" : ':\$\(.*\)'`"
774 tmp="$tmp$front$element$rest "
778 # we want all elements except the command itself
780 rest="`expr \"$rest\" : ':\*\(.*\)'`"
786 badsub="No arguments to command '$save'"
793 tmp="$tmp$front$*$rest "
797 # we are doing a substitution
798 # put / on end if needed
808 # find what substitution is wanted
810 first="`expr \"$rest\" : ':*s\/\(.*\)\/.*\/.*'`"
811 second="`expr \"$i\" : ':*s/.*/\(.*\)/.*'`"
813 # see if it is a global substitution
823 rest="`expr \"$rest\" : '.*/.*/.*/\(.*\)'`"
824 i="`echo \"$i\" |
sed -e \"s@
$first@
$second@
$global\"`"
826 # see if subsitution worked
830 badsub="Substiution failed"
837 tmp="$tmp$front$i$rest "
841 tmp="$tmp$front$i$rest "
866 # see if the substitution is global
876 # put a '^' on the end if necessary
885 # find what substitution is wanted
887 first="`expr \"$cmd\" : '*\^\(.*\)\^.*\^.*'`"
888 second="`expr \"$cmd\" : '*\^.*\^\(.*\)\^.*'`"
889 rest="`expr \"$cmd\" : '*\^.*\^.*\^\(.*\)'`"
890 cmd="`echo \"$lastcmd\" |
sed -e \"s@
$first@
$second@
$global\"`$rest"
892 # see if the substitution worked
896 echo "Substitution failed"
905 echo "$cmd" | sed -e "s@~e@@" > /tmp/bcsh$$
907 cmd="`cat /tmp
/bcsh$$
`"
912 echo "$cmd" | sed -e "s@~v@@" > /tmp/bcsh$$
913 echo "$lastcmd" > /tmp/bcsh$$
915 cmd="`cat /tmp
/bcsh$$
`"
920 tail -n $savehist $histfile>/tmp/hist$$
921 uniq /tmp/hist$$ > $histfile
923 echo $cmd > /tmp/cmd$$
926 login[\ \ ]*|newgrp[\ \ ]*)
927 tail -n $savehist $histfile>/tmp/hist$$
928 uniq /tmp/hist$$ > $histfile
930 echo $cmd > /tmp/cmd$$
934 if test -s "$logoutfile"
939 tail -n $savehist $histfile > /tmp/hist$$
940 uniq /tmp/hist$$ > $histfile
945 grep -n . $histfile | tail -n $history | sed -e 's@:@ @' | $PAGER
948 h[\ \ ]\|*|h[\ \ ]\>*|h\|*|h\>*)
949 cmd="`echo \"$cmd\" |
sed -e \"s@h@
grep -n .
$histfile |
tail -n $history |
sed -e 's@:@ @'@
\"`"
953 history[\ \ ]*\|*|history[\ \ ]*\>*)
954 cmd="`echo \"$cmd\" |
sed -e \"s@
history@
grep -n .
$histfile |
tail -n $history |
sed -e 's@:@ @'@
\"`"
961 echo . $* > /tmp/cmd$$
970 echo $cmd > /tmp/cmd$$
975 # check if it will work first, or else this shell will terminate
976 # if the cd dies. If you have a built-in test, you might want
977 # to replace the try-it-and-see below with a couple of tests,
978 # but it is probably just as fast like this.
980 echo $cmd > /tmp/cmd$$
981 if ($SHELL /tmp/cmd$$) ; then
986 awk[\ \ ]*|dd[\ \ ]*|cc[\ \ ]*|make[\ \ ]*)
987 # these are the only commands I can think of whose syntax
988 # includes an equals sign. Add others as you find them.
990 echo "$cmd" > /tmp/bcsh$$
993 # handle setting shell variables, turning cshell syntax to Bourne
994 # syntax -- note all variables must be exported or they will not
995 # be usable in other commands
997 echo "$cmd" > /tmp/cmd$$
998 ed - /tmp/cmd$$ << ++++
999 g/^setenv[ ]/s/[ ]/@/
1012 unset[\ \ ]*|umask[\ \ ]*|export[\ \ ]*|set[\ \ ]*)
1013 # handle commands which twiddle current environment
1019 if [ -f $aliasfile ]; then
1028 alias[\ \ ]\|*|alias[\ \ ]\>*)
1029 cmd="`echo \"$cmd\" |
sed -e \"s@
alias@
cat $aliasfile@
\"`"
1033 alias[\ \ ]*[\ \ ]*)
1036 echo "Syntax: alias name command"
1045 # make sure there is always 1 blank line in file so
1046 # unaliasing will always work -- ed normally refuses
1047 # to write an empty file
1048 echo "" >> $aliasfile
1049 cat << ++++ >> $aliasfile
1053 # ed - $aliasfile << '++++'
1055 # g/^['"]\(.*\)['"]$/s//\1/
1060 sort -u -o $aliasfile $aliasfile
1072 echo "Syntax: unalias alias_name"
1076 ed - $aliasfile << ++++
1080 case "`set - \
`wc -l $aliasfile\`;echo $1`" in
1082 # just removed last alias
1092 tmp="`grep \"^
$1 \" $aliasfile`"
1102 # uses positional variables
1104 cmd="set - $cmd ; $tmp"
1116 echo "$cmd" > /tmp/bcsh$$
1121 echo "$cmd" > /tmp/bcsh$$
1129 cmd="`expr \"$cmd\" : '\(.*\)+~+p'`"
1142 cmd="`echo \"$cmd\" |
sed -e 's@REALEXCLAMATIONMARK@!@g'`"
1143 echo "$cmd" > /tmp/bcsh$$
1153 case "${noclobber+yes}" in
1157 ed - /tmp/bcsh$$ << ++++
1165 outfile="`expr \"$cmd\" : '.*>\(.*\)'`"
1172 if test -s "$outfile"
1174 case "${iclobber+yes}" in
1176 echo $n "Overwrite ${outfile}? $c"
1182 echo ':' > /tmp/bcsh$$
1187 echo "${outfile}: file exists"
1188 echo ':' > /tmp/bcsh$$
1200 ed - /tmp/bcsh$$ << ++++
1208 (trap 'exit 1' 2 3; $BASH /tmp/bcsh$$)
1217 cmd="`echo \"$cmd\" |
sed -e 's@!@\\\\!@g'`"
1221 cat << ++++ >> $histfile
1226 case "$inc_cmdno" in
1228 cmdno="`expr \"$cmdno\" + 1`"
1229 # cmdno=$[$cmdno + 1]
1237 # The next commented-out line sets the prompt to include the command
1238 # number -- you should only un-comment this if it is the ONLY thing
1239 # you ever want as your prompt, because it will override attempts
1240 # to set PS1 from the command level. If you want the command number
1241 # in your prompt without sacrificing the ability to change the prompt
1242 # later, replace the default setting for PS1 before the beginning of
1243 # the main loop with the following: PS1='echo -n "${cmdno}% "'
1244 # Doing it this way is, however, slower than the simple version below.
1254 # Christine Robertson {linus, ihnp4, decvax}!utzoo!globetek!chris