Mastering the World-Wide Web

Spring `96 Assignment #1
Perl Shell

Perl programming often involves performing quick tests of features of the language to see how they behave in certain contexts and constructs. This is often quicker, and almost always more reliable, than only consulting documentation such as a Perl reference book or the Perl man page. And, it helps your debugging, because you can have more confidence about some aspects of the program's behavior and reduce the number of sources of bugs you need to test.

These quick tests can often be done with a perl -e command on the command line. However, these can be tedious and the quoting needed around the Perl code for the shell command line can interfere with quoting within the Perl code itself. You can also type test code directly into the input of a simple perl command and terminate it with a control-D to signal the end of the input and to run the code. This, however, forces you to enter all the test code in a raw context before you can test it. One typo, and you have to type it all in again. The final option is to create a little test script in an editor to run to do your testing. If the test is complex enough, this may be the best option, but there is a significant class of things you may want to test for which a test script is overkill.

So, it could be handy if Perl had an interactive mode where you could enter and run Perl code a line at a time in a consistent program environment. This would be similar to the interactive mode of some other interpreted programming languages, like Lisp or Scheme, you get by simply invoking the language interpreter. Perl's debugger serves this purpose to a fair extent (try it by running perl -de 0) and could be used for this sort of quick testing.

However, we might notice that this interactive mode resembles a regular Unix command shell, and call it a Perl shell. Also, it could be useful to be able to run regular Unix commands from within our Perl shell as well as Perl commands. Both types could be intermixed as we desire and we might find it convenient to use our Perl shell throughout our Perl programming session. We could even write scripts for our Perl shell, in our own simple scripting language, mixing Unix commands and Perl commands.

So, with all this heady ambition, let's develop such a Perl shell and call it psh. Despite all the functionality we have planned, we might be surprised how little code it'll take, easily less than a printed page. Here's an outline of a sequence of steps you might take to build your program, so you can test and develop your Perl shell in stages similar to that demonstrated for the haiku program in class.

Goal 1: Write a read/print loop
Write a simple loop so that through each iteration of the loop, you read a line of input and print it out. This'll form the skeleton of the Perl shell's read/evaluate command loop. The program should exit if you type control-D at the beginning of a line.
Goal 2: Add prompts
Print the string 'psh> ' before reading each command to prompt the user to enter a line of text.
Goal 3: Make the prompt configurable
Define a configuration for your program so that the prompt it uses is taken from the environment variable PSH_PROMPT. If that's not defined or is null, use the default prompt 'psh> '.
Goal 4: Differentiate Unix commands vs Perl commands
Now to do something with the commands you're reading ... If the first non-whitespace character of the command is a semicolon, strip it off and print the command tagged with the string 'UNIX'. If the first non-whitespace character is a colon, strip it off and print the command tagged with the string 'PERL'. Do this latter, too, if the command begins with neither a semicolon or colon.
Goal 5: Add a -s option
The previous stage made the assumption that commands without special marking (semicolon or colon) were Perl commands. Add a -s option to your psh to change the assumption so that such unmarked commands are taken as Unix commands instead.
Goal 6: Add a usage message and -h option
Add a usage message to your program, like in the haiku program, that's printed if psh is invoked with a -h option. The message should document the features and behavior you've implemented so far and should be updated appropriately at each further stage of development.
Goal 7: Run the commands
Instead of printing the Unix commands, run them. Instead of printing the Perl commands, evaluate them. Test extensively here. Make sure you get a shell or command error message if you type a Unix command that doesn't exist or otherwise give a bad Unix command line. After evaluating a Perl command, you shouldn't see anything unless the command itself printed something. You should be able to change your Perl shell's prompt interactively now with the command :$prompt = 'What is thy command? '.
Goal 8: Catch Perl evaluation errors
If a Perl command is invalid, print out its evaluation error message to standard error.
Goal 9: Allow alternate Unix shells
Add a configuration which will use the Unix shell defined in the environment variable SHELL, instead of the one you chose in a previous step, to run the Unix commands. If that's undefined or null, use csh.
Goal 10: Add a -p option
Add a -p option to psh which will indicate that the resulting value of any Perl command it evaluates should be printed.
Goal 11: Suppress the prompt with non-interactive input
Create a file containing a series of psh commands. Run psh with the input coming from this file. It should run the commands, but still print prompts. Change the program to not print the prompts if the input to the program is not from a user terminal.
Goal 12: Support a command line script and arguments
If there are any arguments to psh, besides the hyphen options of course, take the first one to be a script file that psh should use as its input and any following ones to be arguments to the script to be used inside the script. These arguments should be stored in an @argv array for this purpose. Make sure you don't print prompts if you're running a script (like you don't when the input is not from a terminal). Adapt the file you created in the previous step to be a psh script beginning with a #! line, make it executable, and see if it runs directly as a script like any other script would. If it doesn't entirely work, don't worry -- it seems many operating systems can't handle scripts as script interpreters.
Goal 13 (optional): That's it!
Feel free to add any additional features to your Perl shell that you think might be useful, as long as they're compatible with the specifications above. Make special note of them in the comments. A couple things you might consider are a source (csh) or . (sh) command and a ~/.pshrc file to run upon startup (unless a -f option is given). You might also implement a -c command option like regular shells have.
Work on this assignment must be independent. Implement as many goals as you can, all non-optional ones for full credit. Make note of any goals that are not implemented. Grading will also consider readability, efficiency, style, conciseness and adequacy and appropriateness of program documentation (ie, comments and program messages).

Note: Since this is an independent assignment, you must ensure that your program files are not readable by other users on the system. I recommend working on this (and other independent assignments) in a restricted subdirectory of your home directory, created and entered like so:

% cd; mkdir a348
% chmod go-rwx a348
% cd a348
This program will be due Wed 31 Jan before 11:59pm for full credit. Programs will be accepted up until Thu 1 Feb before 4:00pm at a maximum of 90% full credit. No programs will be accepted after this time. Please submit it via plain text e-mail to <> with Assignment 1 in the subject line.

<> 24 January 1996