chris@globetek.UUCP (chris) (01/06/86)
Okay, folks, I got enough requests for my Bourne shell script cshell-emulator that I decided to post it. It's grown a bit (well, actually, it's got more than twice as big) since I first described it, and has quite a lot more functionality. Do NOT expect it to be as fast as the cshell, though. After all, it *is* a shell script. The primary intent of this program was to provide cshell-like history for people familliar with the cshell who must use a Bourne-shell-only system. Hence, it also attempts to cope with the problem of incompatible control-flow syntax between the two shells (no more frustration from using "foreach i (...)" instead of "for i in ..." and vice-versa). I have not written a manual page for it 'cause I'm lazy -- er, I mean, because it's pretty well covered by the regular shell manual pages. I will actually do a man page and post it reasonably soon. The features, bugs, etc. are all described in the voluminous comments at the beginning of the script, which should really be peeled off as a README file. Enjoy it! I'm still amazed how much of the cshell's functionality can be done via shell. It should work on a vanilla Bourne shell (and hence has a lot of code duplication -- I'll do a cleaner version using shell functions soon). --chris This is not a shar file. Just cut on the line below and peel my signature off the end. ------------- 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 : !?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 : : : 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. : : : 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. : : 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 -- except that if your shell does not understand 'echo -n' you : will have to change the 10 or so places where this occurs. : : 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. : : This current version is excessively verbose because it was written to : run on a vanilla V7 Bourne shell if necessary. A much more pleasing : version using shell functions will appear soon, but it will of course : only work on V8 or System V.2 shells. : : Please send all bug reports to ihnp4!utzoo!globetek!chris. : Flames will be posted to net.general with 'Reply-to' set to your : ' mail 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. history=${history-22} savehist=${savehist-22} histfile=${histfile-$HOME/.bhistory} 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`" else cat /dev/null > $histfile cmdno=1 lastcmd= fi : 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 3 trap "tail -$savehist $histfile>/tmp/hist$$;uniq /tmp/hist$$ > $histfile;\ rm -f /tmp/*$$;exit 0" 15 getcmd=yes mailcheck= 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" ;; *\`*|*\$*) tmp="`eval $PS1 2>&1`" case "$tmp" in *not\ found) echo -n "$PS1" ;; *) echo -n "$tmp" ;; esac ;; *) echo -n "$PS1" ;; 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 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" read line cmd="${cmd};$line" done echo "$cmd" > /tmp/doit$$ ed - /tmp/doit$$ << ++++ 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" 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" read line case "$line" in end) line=done ;; esac cmd="${cmd};$line" done echo "$cmd" > /tmp/doit$$ ;; if[\ \ ]*) while test "$line" != "fi" -a "$line" != "endif" do echo -n "$PS2" read line case "$line" in *[a-z]*then) line="`expr \"$line\" : '\(.*\)then'`;then" ;; endif) line=fi ;; esac cmd="${cmd};$line" done echo "$cmd" > /tmp/doit$$ case "`grep then /tmp/doit$$`" in "") : fix 'if foo bar' cases ed - /tmp/doit$$ << ++++ s/)/);then/ s/.*/;fi/ w ++++ ;; esac ;; case[\ \ ]*) while test "$line" != "esac" do echo -n "$PS2" read line cmd="${cmd}@$line" done cmd="`echo \"$cmd\" | tr '@' ' '`" echo "$cmd" > /tmp/doit$$ ;; switch[\ \ ]*) while test "$line" != "endsw" do echo -n "$PS2" read line cmd="${cmd}@$line" done echo "$cmd" > /tmp/doit$$ ed - /tmp/doit$$ << '++++' 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/doit$$`" ;; *![0-9]*:s*|*![0-9]*:gs*) tmp= for i in $cmd do case "$i" in *![0-9]*:gs/*/*/*|*![0-9]*:s/*/*/*) ;; *![0-9]*:gs/*/*|*![0-9]*:s/*/*) i="${i}/" ;; esac case "$i" in *![0-9]*:gs/*) : get right command to parse frontstuff="`expr \"$i\" : '\(.*\)!.*:gs\/.*'`" wanted="`expr \"$i\" : '.*!\(.*\):gs\/.*'`" rest="`expr \"$i\" : '.*!.*:gs.*\/\(.*\)'`" cmd="`grep -n . $histfile | grep \"^$wanted\"`" cmd="`expr \"$cmd\" : \"${wanted}.\(.*\)\"`" : find what substitution is wanted tmp2="`expr \"$i\" : '.*:gs\/\(.*\)\/.*\/.*'`" wanted="`expr \"$i\" : '.*:gs/.*/\(.*\)/.*'`" tmp3="$frontstuff`echo \"$cmd\" | sed -e \"s@$tmp2@$wanted@g\"`$rest" ;; *![0-9]*:s/*) : get right command to parse frontstuff="`expr \"$i\" : '\(.*\)!.*:s\/.*'`" wanted="`expr \"$i\" : '.*!\(.*\):s\/.*'`" rest="`expr \"$i\" : '.*!.*:s.*\/\(.*\)'`" cmd="`grep -n . $histfile | grep \"^$wanted\"`" cmd="`expr \"$cmd\" : \"${wanted}.\(.*\)\"`" : find what substitution is wanted tmp2="`expr \"$i\" : '.*:s\/\(.*\)\/.*\/.*'`" wanted="`expr \"$i\" : '.*:s/.*/\(.*\)/.*'`" tmp3="$frontstuff`echo \"$cmd\" | sed -e \"s@$tmp2@$wanted@\"`$rest" ;; *) tmp3="$i" ;; esac tmp="$tmp $tmp3" done cmd="$tmp" echoit=yes getcmd=no continue ;; *![0-9]*:*) tmp= for i in $cmd do case "$i" in *![0-9]*:*) : get right command to parse frontstuff="`expr \"$i\" : '\(.*\)!.*:.*'`" wanted="`expr \"$i\" : '.*!\(.*\):.*'`" tmp3="`grep -n . $histfile | grep \"^$wanted\"`" tmp3="`expr \"$tmp3\" : \"${wanted}.\(.*\)\"`" : get right part of the command wanted="`expr \"$i\" : '.*!.*:\(.*\)'`" case "$wanted" in [0-9]*) rest="`expr \"$wanted\" : '.*[0-9]\(.*\)'`" case "$rest" in "") ;; *) wanted="`expr \"$wanted\" : '\(.*[0-9]\).*'`" ;; esac wanted="`expr \"$wanted\" + 1`" tmp2=1 for j in $tmp3 do case "$wanted" in $tmp2) tmp3="$frontstuff$j$rest" break ;; *) tmp2="`expr \"$tmp2\" + 1`" ;; esac done ;; \$*) rest="`expr \"$wanted\" : '\$\(.*\)'`" for j in $tmp3 do : done tmp3="$frontstuff$j$rest" ;; \**) rest="`expr \"$i\" : '.*:\*\(.*\)'`" set - $tmp3 shift tmp3="$frontstuff$*$rest" set - ;; esac ;; *) tmp3="$i" ;; esac tmp="$tmp $tmp3" done cmd="$tmp" echoit=yes getcmd=no continue ;; *![0-9]*) tmp= for i in $cmd do case "$i" in *![0-9]*) frontstuff="`expr \"$i\" : '\(.*\)!.*:.*'`" wanted="`expr \"$i\" : '.*!\([0-9].*\)'`" rest="`expr \"$wanted\" : '.*[0-9]\(.*\)'`" case "$rest" in "") ;; *) wanted="`expr \"$wanted\" : '\(.*[0-9]\).*'`" ;; esac tmp3="`grep -n . $histfile | grep \"^$wanted\"`" tmp3="$frontstuff`expr \"$tmp3\" : \"${wanted}.\(.*\)\"`$rest" ;; *) tmp3="$frontstuff$i" ;; esac tmp="$tmp $tmp3" done cmd="$tmp" echoit=yes getcmd=no continue ;; *!!:*) tmp= for i in $cmd do case "$i" in *!!:*) frontstuff="`expr \"$i\" : '\(.*\)!!:.*'`" wanted="`expr \"$i\" : '.*!!:\(.*\)'`" case "$wanted" in [0-9]*) rest="`expr \"$wanted\" : '.*[0-9]\(.*\)'`" case "$rest" in "") ;; *) wanted="`expr \"$wanted\" : '\(.*[0-9]\).*'`" ;; esac wanted="`expr \"$wanted\" + 1`" tmp2=1 for j in $lastcmd do case "$tmp2" in $wanted) tmp3="$frontstuff$j$rest" break ;; *) tmp2="`expr \"$tmp2\" + 1`" ;; esac done ;; \$*) rest="`expr \"$wanted\" : '\$\(.*\)'`" for j in $lastcmd do : done tmp3="$frontstuff$j$rest" ;; \**) rest="`expr \"$i\" : '.*:\*\(.*\)'`" set - $lastcmd shift tmp3="$frontstuff$*rest" set - ;; esac ;; *) tmp3="$i" ;; esac tmp="$tmp $tmp3" done cmd="$tmp" echoit=yes getcmd=no continue ;; *!!*) cmd="`echo \"$cmd\" | sed -e \"s@!!@$lastcmd@g\"`" echoit=yes getcmd=no continue ;; *!\?*:s/*|*!\?*:gs/*) tmp= for i in $cmd do case "$i" in *!\?*:gs/*/*/*|*!?*:s/*/*/*) ;; *!\?*:gs/*/*|*!?*:s/*/*) i="${i}/" ;; esac case "$i" in *!\?*:gs/*) : get right command to parse frontstuff="`expr \"$i\" : '\(.*\)!?.*:gs\/.*'`" wanted="`expr \"$i\" : '.*!?\(.*\):gs\/.*'`" rest="`expr \"$i\" : '.*!?.*:gs.*\/\(.*\)'`" cmd="`ed - $histfile << ++++ 1,\\$-${history}d a . ?$wanted? ++++ `" : find what substitution is wanted tmp2="`expr \"$i\" : '.*:gs\/\(.*\)\/.*\/.*'`" wanted="`expr \"$i\" : '.*:gs/.*/\(.*\)/.*'`" tmp3="$frontstuff`echo \"$cmd\" | sed -e \"s@$tmp2@$wanted@g\"`$rest" ;; *!\?*:s/*) : get right command to parse frontstuff="`expr \"$i\" : '\(.*\)!?.*:s\/.*'`" wanted="`expr \"$i\" : '.*!?\(.*\):s\/.*'`" rest="`expr \"$i\" : '.*!?.*:s.*\/\(.*\)'`" cmd="`ed - $histfile << ++++ 1,\\$-${history}d a . ?$wanted? ++++ `" : find what substitution is wanted tmp2="`expr \"$i\" : '.*:s\/\(.*\)\/.*\/.*'`" wanted="`expr \"$i\" : '.*:s/.*/\(.*\)/.*'`" tmp3="$frontstuff`echo \"$cmd\" | sed -e \"s@$tmp2@$wanted@\"`$rest" ;; *) tmp3="$i" ;; esac tmp="$tmp $tmp3" done; cmd="$tmp" echoit=yes getcmd=no continue ;; *!\?*:*) tmp= for i in $cmd do case "$i" in *!\?*:*) : get right command to parse frontstuff="`expr \"$i\" : '\(.*\)!?.*:.*'`" wanted="`expr \"$i\" : '.*!?\(.*\):.*'`" cmd="`ed - $histfile << ++++ 1,\\$-${history}d a . ?$wanted? ++++ `" : get right part of the command wanted="`expr \"$i\" : '.*!?.*:\(.*\)'`" case "$wanted" in [0-9]*) rest="`expr \"$wanted\" : '.*[0-9]\(.*\)'`" case "$rest" in "") ;; *) wanted="`expr \"$wanted\" : '\(.*[0-9]\).*'`" ;; esac wanted="`expr \"$wanted\" + 1`" tmp2=1 for j in $cmd do case "$tmp2" in $wanted) tmp3="$frontstuff$j$rest" break ;; *) tmp2="`expr \"$tmp2\" + 1`" ;; esac done ;; \$*) rest="`expr \"$wanted\" : '\$\(.*\)'`" for j in $cmd do : done tmp3="$frontstuff$j$rest" ;; \**) rest="`expr \"$i\" : '.*:\*\(.*\)'`" set - $cmd shift tmp3="$frontstuff$*$rest" set - ;; esac ;; *) tmp3="$i" ;; esac tmp="$tmp $tmp3" done cmd="$tmp" echoit=yes getcmd=no continue ;; *!\?*) tmp= for i in $cmd do case "$i" in *!\?{*}*) frontstuff="`expr \"$i\" : '\(.*\)!?.*:.*'`" wanted="`expr \"$i\" : '.*!?{\(.*\)}.*'`" rest="`expr \"$i\" : '.*!?{.*}\(.*\)'`" tmp3="`ed - $histfile << ++++ 1,\\$-${history}d a . ?$wanted? ++++ `" tmp3="$frontstuff$tmp3$rest" ;; *!\?*) wanted="`expr \"$i\" : '.*!?\(.*\)'`" tmp3="$frontstuff`ed - $histfile << ++++ 1,\\$-${history}d a . ?$wanted? ++++ `" ;; *) tmp3="$i" ;; esac tmp="$tmp $tmp3" done cmd="$tmp" echoit=yes getcmd=no continue ;; *!*:*) tmp= for i in $cmd do case "$i" in *!*:*) : get right command to parse frontstuff="`expr \"$i\" : '\(.*\)!.*:.*'`" wanted="`expr \"$i\" : '.*!\(.*\):.*'`" cmd="`grep \"^$wanted\" $histfile | tail -1`" : get right part of the command wanted="`expr \"$i\" : '.*!.*:\(.*\)'`" case "$wanted" in [0-9]*) rest="`expr \"$wanted\" : '.*[0-9]\(.*\)'`" case "$rest" in "") ;; *) wanted="`expr \"$wanted\" : '\(.*[0-9]\).*'`" ;; esac wanted="`expr \"$wanted\" + 1`" tmp2=1 for j in $cmd do case "$tmp2" in $wanted) tmp3="$frontstuff$j$rest" break ;; *) tmp2="`expr \"$tmp2\" + 1`" ;; esac done ;; \$*) rest="`expr \"$wanted\" : '\$\(.*\)'`" for j in $cmd do : done tmp3="$frontstuff$j$rest" ;; \**) rest="`expr \"$i\" : '.*:\*\(.*\)'`" set - $cmd shift tmp3="$frontstuff$*$rest" set - ;; esac ;; *) tmp3="$i" ;; esac tmp="$tmp $tmp3" done cmd="$tmp" echoit=yes getcmd=no continue ;; *\\!*) cmd="`echo \"$cmd\" | sed -e 's@\\!@REAL EXCLAMATION MARK@g'`" exclaim=yes getcmd=no continue ;; *!*) tmp= for i in $cmd do case "$i" in *!{*}*) frontstuff="`expr \"$i\" : '\(.*\)!.*:.*'`" wanted="`expr \"$i\" : '.*!{\(.*\)}.*'`" rest="`expr \"$i\" : '!{.*}\(.*\)'`" tmp3="$frontstuff`grep \"^$wanted\" $histfile | tail -1`$rest" ;; *!*) frontstuff="`expr \"$i\" : '\(.*\)!.*:.*'`" wanted="`expr \"$i\" : '.*!\(.*\)'`" tmp3="$frontstuff`grep \"^$wanted\" $histfile | tail -1`" ;; *) tmp3="$i" ;; esac tmp="$tmp $tmp3" done cmd="$tmp" echoit=yes getcmd=no continue ;; g\^*\^*\^*) tmp="`expr \"$cmd\" : 'g\^\(.*\)\^.*\^.*'`" wanted="`expr \"$cmd\" : 'g\^.*\^\(.*\)\^.*'`" rest="`expr \"$cmd\" : 'g\^.*\^.*\^\(.*\)'`" cmd="`echo \"$lastcmd\" | sed -e \"s@$tmp@$wanted@g\"`\"$rest\"" echoit=yes getcmd=no continue ;; \^*\^*\^*) tmp="`expr \"$cmd\" : '\^\(.*\)\^.*\^.*'`" wanted="`expr \"$cmd\" : '\^.*\^\(.*\)\^.*'`" rest="`expr \"$cmd\" : '\^.*\^.*\^\(.*\)'`" cmd="`echo \"$lastcmd\" | sed -e \"s@$tmp@$wanted@\"`$rest" echoit=yes getcmd=no continue ;; g\^*\^*) tmp="`expr \"$cmd\" : 'g\^\(.*\)\^.*'`" wanted="`expr \"$cmd\" : 'g\^.*\^\(.*\)'`" cmd="`echo \"$lastcmd\" | sed -e \"s@$tmp@$wanted@g\"`" echoit=yes getcmd=no continue ;; \^*\^*) tmp="`expr \"$cmd\" : '\^\(.*\)\^.*'`" wanted="`expr \"$cmd\" : '\^.*\^\(.*\)'`" cmd="`echo \"$lastcmd\" | sed -e \"s@$tmp@$wanted@\"`" echoit=yes getcmd=no continue ;; *~e) echo "$cmd" | sed -e "s@~e@@" > /tmp/doit$$ $EDITOR /tmp/doit$$ cmd="`cat /tmp/doit$$`" getcmd=no continue ;; *~v) echo "$cmd" | sed -e "s@~v@@" > /tmp/doit$$ echo "$lastcmd" > /tmp/doit$$ $VISUAL /tmp/doit$$ cmd="`cat /tmp/doit$$`" getcmd=no continue ;; wait) $cms ;; exec[\ \ ]*) tail -$savehist $histfile>/tmp/hist$$ uniq /tmp/hist$$ > $histfile rm -f /tmp/*$$ $cmd ;; umask[\ \ ]*|login[\ \ ]*|newgrp[\ \ ]*) tail -$savehist $histfile>/tmp/hist$$ uniq /tmp/hist$$ > $histfile rm -f /tmp/*$$ exec $cmd ;; logout|exit|bye) if test -s "${logoutfile-$HOME/.blogout}" 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@h@grep -n . $histfile | tail -$history | sed -e 's@:@ @'@\"`" getcmd=no continue ;; history*\|*|history*\>*) cmd="`echo \"$cmd\" | sed -e \"s@history@grep -n . $histfile | tail -$history | sed -e 's@:@ @'@\"`" getcmd=no continue ;; .[\ \ ]*) $cmd ;; 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. if ($cmd) then $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/doit$$ ;; 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 ;; export[\ \ ]*|set[\ \ ]*) : handle commands which twiddle current environment $cmd run=no ;; alias) $PAGER $aliasfile 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/doit$$ ;; esac ;; no) echo "$cmd" > /tmp/doit$$ ;; 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@REAL EXCLAMATION MARK@!@g'`" echo "$cmd" > /tmp/doit$$ ;; esac case "$echoit" in yes) echo $cmd ;; esac case "$run" in yes) echo "trap 'exit 1' 2 3" > /tmp/bcsh$$ cat /tmp/doit$$ >> /tmp/bcsh$$ 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 cmdno="`expr \"$cmdno\" + 1`" ;; 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 set - done exit 0 -- Christine Robertson {linus, ihnp4, decvax}!utzoo!globetek!chris Money may not buy happiness, but misery in luxury has its compensations...