Using the Zoo
The best place for information about the Zoo is at http://zoo.cs.yale.edu/. Below are some points that are of particular relevance for CS223 students.
Getting an account
To get an account in the Zoo, follow the instructions at http://zoo.cs.yale.edu/cgi-bin/accounts.pl. You will need your NetID and password to sign up for an account.
Even if you already have an account, you still need to use this form to register as a CS 223 student, or you will not be able to submit assignments.
Getting into the room
The Zoo is located on the third floor of Arthur K Watson Hall, toward the front of the building. You will need to get your ID validated (go to the basement of AKW) to get in after hours.
Remote use
Using Unix
The Zoo runs a Unix-like operating system called Linux. Most people run Unix with a command-line interface provided by a shell. Each line typed to the shell tells it what program to run (the first word in the line) and what arguments to give it (remaining words). The interpretation of the arguments is up to the program.
Getting a shell prompt in the Zoo
When you log in to a Zoo node directly, you may not automatically get a shell window. If you use the default login environment (which puts you into the KDE window manager), you need to click on the picture of the display with a shell in from of it in the toolbar at the bottom of the screen. If you run Gnome instead (you can change your startup environment using the popup menu in the login box), you can click on the foot in the middle of the toolbar. Either approach will pop up a terminal emulator from which you can run emacs, gcc, and so forth.
Unix command-line programs
Most of what one does with Unix programs is manipulate the filesystem. Unix files are unstructured blobs of data whose names are given by paths consisting of a sequence of directory names separated by slashes: for example /home/accts/some-user/cs223/hw1.c. At any time you are in a current working directory (type pwd to find out what it is and cd new-directory to change it). When working on files in the current working directory you only have to give the part of the pathname after the last slash.
Here are some handy Unix commands:
- man
man program will show you the on-line documentation for a program (e.g., try man man or man ls). Handy if you want to know what a program does. On Linux machines like the ones in the Zoo you can also get information using info program, which has an Emacs-like interface.
- ls
ls lists all the files in the current directory. Some useful variants:
ls /some/other/dir; list files in that directory instead.
ls -l; long output format showing modification dates and owners.
- mkdir
mkdir dir will create a new directory in the current directory named dir.
- rmdir
rmdir dir deletes a directory. It only works on directories that contain no files.
- cd
cd dir changes the current working directory. With no arguments, cd changes back to your home directory.
- pwd
pwd ("print working directory") shows what your current directory is.
- mv
mv old-name new-name changes the name of a file. You can also use this to move files between directories.
- cp
cp old-name new-name makes a copy of a file.
- rm
rm file deletes a file. Deleted files cannot be recovered. Use this command carefully.
- chmod
chmod changes the permissions on a file or directory. See the man page for the full details of how this works. Here are some common chmod's:
chmod 644 file; owner can read or write the file, others can only read it.
chmod 600 file; owner can read or write the file, others can't do anything with it.
chmod 755 file; owner can read, write, or execute the file, others can read or execute it. This is typically used for programs or for directories (where the execute bit has the special meaning of letting somebody find files in the directory).
chmod 700 file; owner can read, write, or execute the file, others can't do anything with it.
- emacs, gcc, make, gdb, cvs
- See next section.
Running your own programs
If you compile your own program, you will need to prefix it with ./ on the command line to tell the shell that you want to run a program in the current directory (called '.') instead of one of the standard system directories. So for example, if I've just built a program called count, I can run it by typing
$ ./count
Here the "$ " is standing in for whatever your prompt looks like; you should not type it.
Any words after the program name (separated by whitespace---spaces and/or tabs) are passed in as arguments to the program. Sometimes you may wish to pass more than one word as a single argument. You can do so by wrapping the argument in single quotes, as in
$ ./count 'this is the first argument' 'this is the second argument'
Some programs take input from standard input (typically the terminal). If you are doing a lot of testing, you will quickly become tired of typing test input at your program. You can tell the shell to redirect standard input from a file by putting the file name after a < symbol, like this:
$ ./count < huge-input-file
A '>' symbol is used to redirect standard output, in case you don't want to read it as it flies by on your screen:
$ ./count < huge-input-file > huger-output-file
A useful file for both input and output is the special file /dev/null. As input, it looks like an empty file. As output, it eats any characters sent to it:
$ ./sensory-deprivation-experiment < /dev/null > /dev/null
You can also pipe programs together, connecting the output of one to the input of the next. Good programs to put at the end of a pipe are head (eats all but the first ten lines), tail (eats all but the last ten lines), more (lets you page through the output by hitting the space bar, and tee (shows you the output but also saves a copy to a file). A typical command might be something like ./spew | more or ./slow-but-boring | tee boring-output. Pipes can consist of a long train of programs, each of which processes the output of the previous one and supplies the input to the next. A typical case might be:
$ ./do-many-experiments | sort | uniq -c | sort -nr
which, if ./do-many-experiments gives the output of one experiment on each line, produces a list of distinct experimental outputs sorted by decreasing frequency. Pipes like this can often substitute for hours of real programming.
Editing C programs
Writing C programs with Emacs
In order to write your programs you will need to use some sort of text editor. There are two reasonable text editors on Linux: Vi and Emacs. My personal preference is for Vi, but almost everybody likes Emacs better.
To start Emacs, type emacs at the command line. If you are actually sitting at a Zoo node it should put up a new window. If not, Emacs will take over the current window. If you have never used Emacs before, you should immediately type C-h t (this means hold down the Control key, type h, then type t without holding down the Control key). This will pop you into the Emacs built-in tutorial.
My favorite Emacs commands
General note: C-x means hold down Control and press x; M-x means hold down Alt (Emacs calls it "Meta") and press x. For M-x you can also hit Esc and then x.
- C-h
Get help. Everything you could possibly want to know about Emacs is available through this command. Some common versions: C-h t puts up the tutorial, C-h b lists every command available in the current mode, C-h k tells you what a particular sequence of keystrokes does, and C-h l tells you what the last 50 or so characters you typed were (handy if Emacs just garbled your file and you want to know what command to avoid in the future).
- C-x u
- Undo. Undoes the last change you made to the current buffer. Type it again to undo more things. A lifesaver. Note that it can only undo back to the time you first loaded the file into Emacs--- if you want to be able to back out of bigger changes, use CVS (described below).
- C-x C-s
- Save. Saves changes to the current buffer out to its file on disk.
- C-x C-f
- Edit a different file.
- C-x C-c
Quit out of Emacs. This will ask you if you want to save any buffers that have been modified. You probably want to answer yes (y) for each one, but you can answer no (n) if you changed some file inside Emacs but do not want the changes to appear in the file on the disk.
- C-f
- Go forward one character.
- C-b
- Go back one character.
- C-n
- Go to the next line.
- C-p
- Go to the previous line.
- C-a
- Go to the beginning of the line.
- C-k
Kill the rest of the line starting with the current position. Useful Emacs idiom: C-a C-k.
- C-y
- "Yank." Get back what you just killed.
- TAB
- Re-indent the current line. In C mode this will indent the line according to Emacs's notion of how C should be indented.
- M-x compile
Compile a program. This will ask you if you want to save out any unsaved buffers and then run a compile command of your choice (see the section on compiling programs below). The exciting thing about M-x compile is that if your program has errors in it, you can type C-x ` to jump to the next error, or at least where gcc thinks the next error is.
Using Vi instead of Emacs
If you don't find yourself liking Emacs very much, you might want to try Vim instead. Vim is a vastly enhanced reimplementation of the classic vi editor, which I personally find easier to use than Emacs. Type vimtutor to run the tutorial. You can always get out by hitting the Escape key a few times and then typing :qa!.
Compiling programs
Using gcc
A C program will typically consist of one or more files whose names end with .c. To compile foo.c, you can type gcc foo.c. Assuming foo.c contains no errors egregious enough to be detected by the extremely forgiving C compiler, this will produce a file named a.out that you can then execute by typing ./a.out.
If you want to debug your program using gdb or give it a different name, you will need to use a longer command line. Here's one that compiles foo.c to foo (run it using ./foo) and includes the information that gdb needs: gcc -g3 -o foo foo.c
By default, gcc doesn't check everything that might be wrong with your program. But if you give it a few extra arguments, it will warn you about many (but not all) potential problems: gcc -g3 -Wall -ansi -pedantic -o foo foo.c
Using make
For complicated programs involving multiple source files, you are probably better off using make than calling gcc directly. Make is a "rule-based expert system" that figures out how to compile programs given a little bit of information about their components.
For example, if you have a file called foo.c, try typing make foo and see what happens.
In general you will probably want to write a Makefile, which is named Makefile or makefile and tells make how to compile programs in the same directory. Here's a typical Makefile:
# Any line that starts with a sharp is a comment and is ignored
# by Make.
# These lines set variables that control make's default rules.
# We STRONGLY recommend putting "-Wall -ansi -pedantic" in your CFLAGS.
CC=gcc
CFLAGS=-g3 -Wall -ansi -pedantic
# The next line is a dependency line.
# It says that if somebody types "make all"
# make must first make "hello-world".
# By default the left-hand-side of the first dependency is what you
# get if you just type "make" with no arguments.
all: hello-world
# How do we make hello-world?
# The dependency line says you need to first make hello-world.o
# and hello-library.o
hello-world: hello-world.o hello-library.o
# Subsequent lines starting with a TAB character give
# commands to execute. Note the use of the CC and CFLAGS
# variables.
$(CC) $(CFLAGS) -o hello-world hello-world.o hello-library.o
echo "I just built hello-world! Hooray!"
# We can also declare that several things depend on one thing.
# Here we are saying that hello-world.o and hello-library.o
# should be rebuilt whenever hello-library.h changes.
# There are no commands attached to this dependency line, so
# make will have to figure out how to do that somewhere else
# (probably from the builtin .c -> .o rule).
hello-world.o hello-library.o: hello-library.h
# Command lines can do more than just build things. For example,
# "make test" will rebuild hello-world (if necessary) and then run it.
test: hello-world
./hello-world
# This lets you type "make clean" and get rid of anything you can
# rebuild. The -f tells rm not to complain about files that aren't
# there.
clean:
rm -f hello-world *.oGiven a Makefile, make looks at each dependency line and asks: (a) does the target on the left hand side exist, and (b) is it older than the files it depends on. If so, it looks for a set of commands for rebuilding the target, after first rebuilding any of the files it depends on; the commands it runs will be underneath some dependency line where the target appears on the left-hand side. It has built-in rules for doing common tasks like building .o files (which contain machine code) from .c files (which contain C source code). If you have a fake target like all above, it will try to rebuild everything all depends on because there is no file named all (one hopes).
Make gotchas
Make really really cares that the command lines start with a TAB character. TAB looks like eight spaces in Emacs and other editors, but it isn't the same thing. If you put eight spaces in (or a space and a TAB), Make will get horribly confused and give you an incomprehensible error message about a "missing separator". This misfeature is so scary that I avoided using make for years because I didn't understand what was going on. Don't fall into that trap--- make really is good for you, especially if you ever need to recompile a huge program when only a few source files have changed.
If you use GNU Make (on a zoo node), note that beginning with version 3.78, GNU Make prints a message that hints at a possible SPACEs-vs-TAB problem, like this:
$ make Makefile:23:*** missing separator (did you mean TAB instead of 8 spaces?). Stop.
If you need to repair a Makefile that uses spaces, one way of converting leading spaces into TABs is to use the unexpand program:
$ mv Makefile Makefile.old $ unexpand Makefile.old > Makefile
Debugging
The standard debugger on the Zoo is gdb. See C/Debugging.
Version control
When you are programming, you will make mistakes. If you program long enough, these will eventually include true acts of boneheadedness like accidentally deleting all of your source files. You are also likely to spend some of your time trying out things that don't work, at the end of which you'd like to go back to the last version of your program that did work. All these problems can be solved by using a version control system.
We recommend using CVS, the Concurrent Versions System. CVS has a number of very complicated features designed to allow large teams of programmers to work together on a project without clobbering each other's code, but the main thing that it does is allow you to keep around a history of every significant change that you have made to your files.
Setting up CVS
CVS stores a master copy of each of your files in a repository, a special directory somewhere that you should probably never touch directly. You can tell CVS where to find the repository by setting the CVSROOT environment variable; if you are using bash as your shell, the command for doing this would be something like this: export CVSROOT=/c/cs223/class/your-name/cvsroot
This will tell CVS where you want to put the repository; it's probably best if the directory you specify does not already exist. To avoid having to set this variable all the time, you should put the command in your ~/.bashrc file, which contains commands that are executed every time bash starts.
To initialize CVS's internal data, run cvs init
This should create your CVSROOT directory and populate it with various bookkeeping files that CVS uses for its own nefarious purposes (which will in fact be in a subdirectory that is also, confusingly, called CVSROOT). Assuming everything went right, you now have an empty CVS repository.
Creating new modules
In order to use your new CVS repository, you need to create at least one module, which is what CVS calls a subdirectory of its root directory. There is a right way and a wrong way to do this:
Right way: make a directory containing only the files you want your new module to contain (e.g., create a directory called hw1 and put hw1.c in it). Make that directory the current working directory using cd, and then run cvs import hw1 your-name some-arbitrary-release-tag. This should pop up your favorite editor (specifically, whatever is set in the EDITOR environment variable) to enter an initial log message for all the files in the directory. Put anything you like in the log message. When you exit the editor, CVS will create a new subdirectory of your CVSROOT and populate it with files corresponding to the files you just imported. Don't touch these new files yourself.
Wrong way: Type mkdir $CVSROOT/hw1. This will make a new module named hw1 with no files in it, and it's a lot quicker than doing it the right way. However: While the wrong way works with the current version of CVS at the time this document was written, it might not work in the future--- such is the penalty for violating abstraction boundaries.
Checking out a module
To edit the files in a module, you must first check it out. This is true even if you just imported it from somewhere (in fact, once you do an import, the directory you were in is useless to CVS). Change to some convenient directory and type: cvs checkout hw1
This should create a new directory hw1 with whatever files you imported when you created the module, along with a subdirectory called CVS. Don't change anything in the CVS subdirectory.
You now have up to three copies of your directory. There's the original directory you did the cvs import in, there's the directory in the repository, and there's the directory you just checked out. You can safely throw away the original directory--- you don't need it any more. You can also safely throw away the checked-out directory--- you can get it back at any time by running cvs checkout again. If you never damage your repository, your module is now immortal. But it's probably nearly empty, so let's talk about how to add and edit files.
Editing files
You can edit files in the checked-out directory normally. These changes do not affect the repository until you check them back in, using cvs commit. During the commit you will be asked for a log message that describes the changes you made.
Adding and removing files
A special case of editing is adding or removing files. To add a file to the module, create the file (say, foo.c), and run cvs add foo.c. This tells CVS you would like to add the file to the repository, but doesn't actually do it yet. To remove a file, use cvs remove -f foo.c; this will delete the file from the checked-out directory, but has no immediate effect on the repository. In both cases, the repository is only updated when you run cvs commit.
You can also type cvs add subdirectory to add an existing subdirectory (which you should create first using mkdir). This takes effect immediately.
Seeing old versions
The command cvs log filename (or just cvs log to get all files) will show you a list of all the different versions you have checked in to the repository, along with their log messages. If you want to know when particular lines in the current file showed up, try cvs annotate filename. As you can see, CVS keeps every version of every file you ever check in, forever. This is very useful, since it means that you can always go back and see what you did.
Recovering old versions
If you want to get an old version back, do cvs update -r[version-number] filename
where [version-number] will be something like 1.3. Warning: this will throw away any changes that you haven't checked in yet. You can also use dates:
cvs update -Dyesterday somefile cvs update -D"01 March 1993"
After you do the update, your checked-out files will be in a special "sticky" state that will prevent you from doing new commits (after all, you can't change the past). To get all your files back to the present, use cvs update -A.
If you want to undo a bunch of checked-in changes, the easiest way to do it is probably to use cvs update -D to go back to the previous version you liked, copy the files somewhere else, do cvs update -A, and then copy the files back and do a commit. It's also possible to do this more directly using the -j flag to cvs update, but that approach can often go wrong in confusing ways.
If you want to undo some changes that you haven't checked in yet, just delete the offending files and run cvs update. I often use this trick when I realize that I didn't want to do what I just did, to go back to the last (hopefully stable) commit.
Seeing what you've changed
cvs diff
Change a few files around and then try it! Some people like the output from cvs diff -c better.
Getting rid of the checked-out directory
Change to the parent directory and type cvs release -d directory-name. This is safer than just deleting it, because it will check to see if anything still needs to be checked in to the repository. (You can always tell it to go ahead and delete the directory anyway.)
More information about CVS
For more information about CVS, type info cvs, or, better yet, see the book Open Source Development With CVS.
Submitting assignments
The submit command is is found in /c/cs223/bin on the Zoo. Here is the documentation (adapted from comments in the script):
submit assignment-number file(s)
unsubmit assignment-number file(s)
check assignment-number
makeit assignment-number [file]
protect assignment-number file(s)
unprotect assignment-number file(s)
retrieve assignment-number file[s]
testit assignment-number test
The submit program can be invoked in eight different ways:
/c/cs223/bin/submit 1 Makefile tokenize.c unique.c time.log
submits the named source files as your solution to Homework #1;
/c/cs223/bin/check 2
lists the files that you have submitted for Homework #2;
/c/cs223/bin/unsubmit 3 error.submit bogus.solution
deletes the named files that you had submitted previously for Homework #3
(i.e., withdraws them from submission, which is useful if you accidentally
submit the wrong file);
/c/cs223/bin/makeit 4 tokenize unique
runs "make" on the files that you submitted previously for Homework #4;
/c/cs223/bin/protect 5 tokenize.c time.log
protects the named files that you submitted previously for Homework #5 (so
they cannot be deleted accidentally); and
/c/cs223/bin/unprotect 6 unique.c time.log
unprotects the named files that you submitted previously for Homework #6
(so they can be deleted); and
/c/cs223/bin/retrieve 7 Csquash.c
retrieves copies of the named files that you submitted previously for
/c/cs223/bin/testit 7 BigTest
runs the test script /c/cs223/Hwk7/test.BigTest.The submit program will only work if there is a directory with your name and login on it under /c/cs223/class. If there is no such directory, you need to make sure that you have correctly signed up for CS223 using the web form. Note that it may take up to an hour for this directory to appear after you sign up.
Seeing your grades
You can find out your officially entered assignment and test grades using Grade-o-Matic, our half-baked WWW-accessible grade database. You should be sent a login name and password by email when you have completed the first homework assignment.
PineWiki