Programming tips and some time saving features of the bash shell

Lab 4
CSCI 112


Goals:
Learn some programming tips that will make implementing the programs easier.

Learn some simple features of the bash shell that will save you lots of time typing


Program 2 clarification:

In lecture on Monday I used the Movie class as an example of creating a class.

Unfortunately, because the course has not covered dynamic memory enough to use in this assignment, you have to do something different then what I presented.

Once you know about dynamic memory (the next assignment) you can call the constructor for a Movie class after you read in the field values (title, year, rating).  However, in this assignment you need to construct the Movie objects before you read in the field values.

The Movie_db class contains an array of 100 Movie objects.  This array has to be initialized before the ratings are read.  The bottom line:  Movie should have a constructor that takes no arguments.  Instead of initializing the fields in a constructor for class Movie, write a non-constructor function that initializes the fields:

from Movie_db.h
        Movie m_movies[MAX];  // declare an array of MAX (100) Movie objects that are not initialized.

When a movie is read, it must be inserted into this array.  But since this is already an array of Movie objects, you have to update one of the non-used objects.  Here is the pseudo-code:

bool Movie_db::insert_rating(string title, int year, int rating)
{

look through the non-empty movies in the movie array
if you find one that has the same title and year as passed into insert_rating() update its rating

    m_movies[match_index].update_rating(rating);

if you don't find it, and there is an unused Movie in the array, initialize that movie to the title, year, and rating passed in

    m_movies[num_movies].initialize(title, year, rating);
    update num_movies
}



Preventing files being included multiple times

Consider the following files:

main.cpp              
#include "bar.h"
#include "foo.h"


bar.h
#include "foo.h"

foo.h
class foo {...};

The preprocessor replaces each #include with the contents of the file being included.  In this example, when the file main.cpp is preprocessed, the contents of foo.h will replace the #include "foo.h" in both main.cpp and bar.h.  And the #include "bar.h" in main.cpp will be replaced by the contents of foo.h.  The files generated by the preprocessor will look like this:

bar.h
class foo {...}; // this came from replacing the #include "foo.h" with the contents of foo.h

main.cpp
class foo {...};  // this came from expanding the #include "bar.h"
class foo{...};  // this came from expanding the #include "foo.h"

Now when main.cpp is compiled, there will be a compilation error because class foo is multiply defined.  The problem is that foo.h was included multiple times.

You can prevent this problem by putting the following in EVERY .h file you write (substitute the filename of the current file for FOO):

#ifndef FOO_H   // this must go on the first line of your file
#define FOO_H

contents of file

#endif  // this must go on the last line of your file

How this works.

Many students have a hard time understanding how the above mechanism works, and since it can be used without understanding how it works, I'm not going to spend time lecturing on how it works.  I provide a brief description here for those of you who are sill curious, and I will be happy to discuss how it works anyone who is interested.

The directive ifndef stands for "if not defined."  We can read the above directives like this:

if the variable FOO_H is not defined                                      this is the #ifndef FOO_H
{
define the variable FOO_h                                            this is the #define FOO_H

rest of file goes here


}                                                                                            this is the #endif


When the preprocessor starts to process a new .cpp file, there are no variables defined (its table of defined variables is empty).  Each time it encounters a #define directive, it put that variable in its table.

The first time a .h file is read, the variable (by convention we use FILENAME_H, for the file filename.h) associated with that file is not in the table of defined variables, and thus the preprocessor enters the body of the if-not-defined statement and reads everything in the file.

The next time that .h file is read, the variable associated with that file IS in the table of defined variables and thus the preprocessor goes to the end of the if-not-defined statement (that is, the #endif) without reading everything in between.

The result is that the preprocessor will only ever read each .h file once and you won't get multiply defined errors.



Incremental Programming

When learning to program in a new language, the compiler can provide the most help if you write your programs incrementally.  In other words, write a few lines of code and then compile, and make sure your program works.  If you have only written a few lines of code, it will be much easier to figure out the errors the compiler generated, and it will much easier to track down logic errors.

Here is a game plan for writing program 3 incrementally:

  1. Create a makefile as describe in lab 3 or copy my Makefile.
  2. Create the Movie and Movie_db classes with nothing in them, just empty classes.
  3. Add the code described above to prevent multiple includes.
  4. Put in all the #include directives (movie_db.h needs to include movie.h, main.cpp needs to include movie_db.h)
  5. Write a simple dummy main() function (I usually just have my dummy main print out "hello world")
  6. Compile all 5 files.  Fix all errors until your program prints out "hello world."
  7. Write the code in main.cpp to read in the data.  Write temporary code to write out the data so you can be sure your program reads the data correctly.
  8. Compile and test your program with several different data sets, fix all problems now.
  9. Write, compile, and test class Movie (implement all the functions and call them from main()).
  10. Write, compile, and test class Movie_db
  11. You should be done at this point.

Programming incrementally will save you lots and lots of time even though you have to write some code you eventually will delete (e.g. printing hello words, and printing out the input from main



Making bash your default shell

On the department's computers, bash is not the default shell.

The choice of shells is a matter of personal preference.  bash is currently one of the most popular shells, and is available on many platforms including Windows (most varieties), Linux (all varieties), UNIX (all varieties), OSX.

If you make bash your default shell, you can use the features described below.  Chances are that bash will work just like your current shell, and you won't notice any difference, except that bash allows you to use the features described below.

To make bash your default shell:

Log on to cougar using a secure-shell (ssh)  (note: ssh is similar to telnet but encrypts your password so no one can steal it)

    $ ssh cougar.ecst.csuchico.edu

Now execute the command chsh (change shell)

    $ chsh /bin/bash

It will ask you for your password, and then change your shell.  After about an hour, every time you log onto a department machine, your default shell will be bash.


In the mean time, you can start the bash shell manually by typing:

$ /usr/bin/bash




bash profile

When bash starts, it executes the file .bash_profile in your home directory (unless something is screwed up with your preferences, see below).

You can customize bash so it behaves the way you like it by putting commands in .bash_profile.  There is an example of such a command below in the section of command line editing.

Another thing you can add to .bash_profile is called an alias.  An alias is another name for a command.  For example, if your 112 directory is:

    ~/classes/f05/112

to go to this directory you would have to type:

    cd ~/classes/f05/112

if you do this a lot, you can create an alias in .bash_profile that looks like this

    alias 112="cd ~/classes/f05/112"

now when you type 112 at the command prompt you will go to your 112 directory.  I set up dozens of alias to make it easy to navigate my file structure.

Another very helpful alias is:

    alias ls="ls -F"

The -F causes ls to show all directories with a "/" at the end and all executables with a "*" at the end.   Now when you type "ls" you will get "ls -F"

I usually add the following line to the top of my .bash_profile

    echo "hello from ~tyson/.bash_profile"

This will be executed every time the bash_profile is read.  It simple writes the screen to the terminal.  This way I can make sure that my .bash_profile is read when I expect it to be read.

Turns out the above is a bad idea.  While it is handy in that it makes it clear when your .bash_profile is read, it causes sftp and scp to fail.

If your .bash_profile is not being executed automatically, the problem is probably with your windowing system settings.  You can manually run your .bash_profile by typing

    $ source .bash_profile



Command history

bash keeps track of all the commands you type in.  You can see a list of your recent commands using the history command

    $ history

each command has a number:

...
401  cd lab6_files
402 ls -l
403 pico hello.cpp
...

    you can execute one of the commands again by typing ! followed by the number.  For example,

$ !402

    would be the same as typing "ls -l"


Command line editing

Commands in the command history can also be edited

Use the up/down arrows to move up and down through the list, use the left/right arrows to edit a command

For example, assume that the last command was "pico hello.cpp" if you press up-arrow, "pico hello.cpp" will appear on the command line.  You can edit this to be "pico hello.h" by deleting the cpp and inserting an h

If you are a vi or vim user, you can set the mode of command line editing to use all the vi edit keys (e.g. i for insert, k for up).  Put the following in your .bash_profile (ONLY DO THIS IF YOU ARE A VI USER)

    set -o vi

There is also an emacs command line edit mode (ONLY DO THIS IF YOU ARE AN EMACS USER)

   set -o emacs


File completion

bash has a feature to save typing filenames.  When you press the <tab> key, bash matches what you have typed to all the files in the currently directory.  For example:

    $ pico m<tab>

will automatically fill in

    $ pico material_list.cpp

IF material_list.cpp is the only file in that directory that starts with m.  Assume that is another, material_list.h.  then we would get

    $ pico material_list.

and you would have to fill in the "h" or type "c<tab" to get the .cpp.

This is a real time savor, it is worth learning how to use.


More about shells

The bash shell (and other shells) can do a whole lot more.  There is an entire programming language at your finger tips.  I recommend you buy the O'Reilly book, Learning the bash shell to learn more about bash.


Exercise 1:
Try the above bash features.


Exercise 2:
Work on program 2