THE UNIVERSITY OF BRITISH COLUMBIA
 
Physics 210 Assignment # 3:
 
SHELL SCRIPTS & PHP: SOLUTIONS
 
Tue. 21 Sep. 2010 - finish by Tue. 28 Sep.

In Assignment 2 you created a directory /home2/phys210/<you>/a02 in which to store the files you submitted for the second assignment.1 You then used "chmod -R o-r" to make it and all its contents (including any possible subdirectories and their contents) inaccessible to o (for others). The second step will no longer be necessary, as we have set up a utility which periodically restores the correct ownership, group and permissions for the /home2/phys210/ directory tree.

This week, create /home2/phys210/<you>/a03 for your submissions on this assignment; and so on for the rest of the course. We won't remind you any more.

In the previous Assignment you learned how to customize your bash environment using the bash resource file .bashrc in your $HOME directory. By now you should have edited the template .bashrc file and the .aliases file it sources to suit your taste, including lots of personalized, idiosyncratic aliases that you find easier to remember (or more æsthetically pleasing) than the "raw" bash commands - which, while æsthetically offensive, are at least reasonably universal and thus worth remembering. This may impede your familiarization with bash, but for now, go ahead and indulge yourself with alias.

The goal of this Assignment is to make you reasonably familiar and comfortable with shell scripts and a few other important tools.

  1. GET STUCK IN tar:  In your /home2/phys210/<you>/a03/ directory, write a short bash script called "bck.sh" that uses tar2 to make a compressed "backup" of your entire /home2/phys210/<you> directory tree in a single file ~/HW.tar.gz (note that this file should be in your $HOME directory). Make a symbolic link to ~/HW.tar.gz in your /home2/phys210/<you>/a03/ directory. Here's what bck.sh should do: First, if there is already a ~/HW.tar.gz file, gunzip (decompress) it to ~/HW.tar - then use tar to append to ~/HW.tar any files in ~/HW/ (and its subdirectories) that have changed since the last time bck.sh was invoked - then gzip ~/HW.tar back to ~/HW.tar.gz and you're done. Use "man tar" to find out how to do the middle part.3 This is a valuable thing to know how to do.4

    Here's a "minimalist" version of bck.sh:

    #! /bin/bash
    
    cd /home2/phys210/$USER
    gunzip HW.tar.gz
    tar -uf HW.tar .
    gzip HW.tar
    

    Note that none of these commands require parameter parsing; so the whole thing can be stuck together in one line and aliased to a "bck" command in .aliases:

    alias bck='cd /home2/phys210/$USER; gunzip HW.tar; tar -uf HW.tar .; gzip HW.tar'

    Now, there are several "ringers" in this version:

    1. The environment variable $USER contains your login ID on hyper. Since you already know it, this is rarely of much use to you; but I can use it to write a "generic" script that will work on everyone's account.
    2. tar -uf HW.tar will create the archive if it doesn't already exist, so the usual "-f" test to see if the file exists isn't actually necessary.
    3. tar -uf HW.tar may in some cases make multiple copies of the same files; I don't know why this happens. It doesn't do it on my computer.
    4. Even though tar is archiving the directory where it is stored itself, it is clever enough to not attempt to archive itself, as this might lead to an infinite recursion. Smart!

  2. PLAY WITH ImageMagick:  Everyone likes graphics, so here's a treat for you: in your Terminal window, type "man ImageMagick" and learn about some of the things this free graphical manipulation library provides. You should also use your Web browser to consult the documentation in our Manuals link. Then, in your browser, right-click on the PHYS 210 logo at the top of our class homepage and use the "Save Image As . . . " menu item to save the file P210logo.gif - pay attention to where it gets saved, and then move it to your /home2/phys210/<you>/a03/ directory. In that directory, use the convert command supplied by ImageMagick to make files P210logo.jpg, P210logo.png, P210logo.tif and P210logo.pdf (plus any other graphics formats you are especially fond of). Which file is the smallest? Is it the one you expected? Record (in /home2/phys210/<you>/a03/readme.txt) your answer and any comments. If you want to make your own graphics (maybe a personal logo?), try "gimp". But don't get too distracted by it!

    I don't think many had any trouble with this. I found the file sizes were

     5023 P210logo.png
     5456 P210logo.gif
     7128 P210logo.tif
     8091 P210logo.jpg
     8316 P210logo.eps.gz
    16840 P210logo.pdf
    58514 P210logo.eps
    76815 P210logo.pnm
    
    Note that Encapsulated PostScript (.eps) files are huge, but when gzipped they are often (not always!) smaller than .pdf files. Now, "results may vary" inasmuch as some types of files are much better suited to specific formats. For instance, .jpg is an efficient format for photographs, and you would usually get huge files and poor results storing them in other formats.

    Open Source programs often prefer .png, but proprietary software seems to avoid it for some reason.

  3. WHAT'S IN THE SCRIPT?   Copy the shell script ~phys210/bin/fib.sh to your own /home2/phys210/<you>/a03/ directory and (using your favourite editor) add comment lines (any line starting with "#") to explain what is happening at each step. Be ridiculously thorough; it will pay off in the end. Make sure that your copy of the file is executable ("chmod +x fib.sh") and check that it still works like the original despite all the comments you've added.5

    See fib-annotated.sh (Your browser won't [or shouldn't] display it, for security reasons.) It looks like this:

    #! /bin/bash
    
    ## Every bash shell script must start with the above 2 lines.
    ## Check whether the number of arguments ($#) is 2.  
    ## If not, then print on the screen everything down to EOHELP...
    if [ $# -ne 2 ] ; then
    
            cat <<EOHELP
    	
    	"Fibonacci numbers:"
    
    USAGE:   fib.sh  fstart  nn
    
    Generates & prints out the Fibonacci number fstart 
    and the nn following Fibonacci numbers.  If fstart 
    is not a Fibonacci number, prints an error message.  
    
    EOHELP
    ## ... and if $# IS = 2, AND the first argument ($1) is > 0, then...
    elif [ $1 -gt 0 ] ; then
    
    ## Create a "counter" called $jj and set it = 1:
      let "jj = 1"
    ## Create a variable called $flast and set it = 1:
      let "flast = 1"
    ## Create $ff (the current candidate F-number) and set it = 1:
      let "ff = 1"
    ## Create $fref and initialize it to the first argument ($1):
      let "fref = $1"
    ## Create $nn and set it = the second argument ($2) + 1:
      let "nn = $2 + 1"
    ## Print an empty line on the screen (for aesthetics): 
      echo " "
    ## Print other stuff on the screen -- note that the value of 
    ## a parameter can be printed, but must be outside quotation 
    ## marks, which must delimit all explicit text: 
      echo $nn" Fibonacci numbers starting with "$fref":"
      echo " "
    ## Do the following stuff as long as $nn > 0, then stop:
      while test $nn -gt 0
      do
    ## If the current candidate F-number ($ff) is > the first argument, ...
        if [ $ff -gt $fref ] ; then
    ## ... then the first argument must NOT be an F-number.  Say so, 
    ## then set $nn = 0 so that we'll exit: 
          echo " "
          echo "Ahem!  "$fref"  is not a Fibonacci number!"
          echo " "
          let "nn = 0"
    ## If $ff is equal to $fref, print it on screen and decrement $nn by 1: 
        elif [ $ff -eq $fref ] ; then
          echo "  "$jj":	"$ff
          let "fref = $ff + $flast"
          let "nn = $nn - 1" 
    ## Every "if" structure is terminated by "fi":
        fi
    ## Every time, increment $jj by 1, store $ff in $ftmp, add $flast to $ff 
    ## and store $ftmp (the previous value of $ff) in $flast.  
    ## Then jump back to the while statement and (if $nn > 0) go around again:  
        let "jj = jj + 1"
        let "ftmp = $ff"
        let "ff = $ff + $flast"
        let "flast = $ftmp"
    ## Every "do" loop is terminated by "done":
      done
      echo " "
    fi
    

  4. BE THE BARD!6   WRITE A fact.7   Make your own bash script to generate a sequence of factorials: $F_n = n! \equiv n \times (n-1) \times
(n-2) \times (n-3) \times \cdots \times 3 \times 2 \times 1$ with the exception of F0 = 0! = 1. Thus the first six factorials (F0 through F5) are 1, 1, 2, 6, 24, 120 and so on. Of course, they go on forever, but they get very big very fast, so you don't want to try to generate all the factorials. Better if you specify the one to start on and how many more to print out.

    So your script, which you should call fact.sh  and which should be in your /home2/phys210/<you>/a03/ directory, should look for two arguments, $F_{\rm start}$ and N, so that (for example) if you invoke it with "fact.sh 2 3" it should print out on your screen "2 6 24 120" (not necessarily all on the same line). This should be simple, eh?8 To keep it that way, let's forbid using either of the first two factorials as $F_{\rm start}$. Thus if you enter 1 as your first argument you should get no response.

    If you enter the wrong number of arguments, the script should print out a "USAGE: . . . " message analogous to the one in fib.sh explaining how it is supposed to be used.

    Oh, and one final feature: if you enter a first argument that is not a factorial, your script should print out a polite but perhaps slightly sarcastic message informing the user that they have entered an invalid starting point.9

    You may want to start with a simpler version that just prints out the first 10 or so factorials, to make sure you have that part right; then add the bells and whistles involving arguments.

    The idea here was to take the fib.sh script you just finished annotating (and which you therefore presumably understand, at least a little) and make the fewest possible changes to adapt it into fact.sh -- ideally, making one small change at a time and running the script to see the effect. This is why I went on about "copycat programming" in class, but many people insisted on "doing it properly" -- first learning all about bash shell programming and then writing their own fact.sh "from scratch". This is admirable in principle, but overwhelmingly difficult in practice.

    Here is my fact.sh:

    #! /bin/bash
    
    if [ $# -ne 2 ] ; then
    
            cat <<EOHELP
    
            "Factorials:"
    
    USAGE:   fact  fstart  nn
    
    Generates & prints out the factorial fstart
    and the nn following factorials.  If fstart
    is not a factorial, prints an error message.
    
    EOHELP
    
    elif [ $1 -gt 0 ] ; then
    
      let "jj = 1"
      let "ff = 1"
      let "fref = $1"
      let "nn = $2 + 1"
      echo " "
      echo $nn" factorials starting with "$fref":"
      echo " "
      while test $nn -gt 0
      do
        if [ $ff -gt $fref ] ; then
          echo " "
          echo "Ahem!  "$fref"  is not a factorial!"
          echo " "
          let "nn = 0"
        elif [ $ff -eq $fref ] ; then
          echo "  "$jj":    "$ff
          let "fref = $ff*($jj+1)"
          let "nn = $nn - 1"
        fi
        let "jj = jj + 1"
        let "ff = $ff*$jj"
        if [ $jj -gt 21 ] ; then
          let "nn = 0"
        fi
      done
      echo " "
    fi
    

  5. PUT IT ONLINE:  In my humble opinion10 shell scripts are not very elegant. There are much cleaner ways to do the same things, and then some! For instance, your personal homepage that you created last week need not be just a passive HTML file; it can actually do stuff, and show off the results to visitors, using scripting languages like PHP or JavaScript. I prefer PHP. The PHP script in ~phys210/public_html/fib.php does the same thing as the bash shell script fib.sh, but it does it online for anyone. Try it out at http://www.physics.ubc.ca/~phys210/fib.php and then convert it to make factorials in a file called fact.php in your own ~/public_html/p210/ directory.11

    See comments above about "copycat programming". PHP is a huge and versatile language that you couldn't possibly learn in a few days, much less a few hours. I hope you were motivated to read up on PHP, as you might find it a useful way to implement simple tasks that are not very compute-intensive; it has the advantage of elaborate formatting for output (limited only by your knowledge of HTML and the not-necessarily-an-advantage of being open to use by anyone on the Internet. If you do much PHP programming, you will soon want to read up on authentication.

    For this part I didn't explicitly suggest that you annotate the fib.php script, but I did mention in a lecture that it is a very good idea to sketch a flowchart of the basic workings of a program, especially if you are about to modify it to do something else.

    It is also a good idea to change as few things as possible between tests. This is covered by the 11th Commandment (for experimental scientists): Thou shalt not change more than one independent variable at a time.

    On hyper, PHP warnings and error messages are suppressed for security reasons (they can give away the secret workings of the site's PHP scripts, thus giving hackers a golden opportunity to enter ingenious inputs that cause bad things to happen). Unfortunately, this means that if you have any error in your PHP script, it yields only a blank page. The dedicated PHP enthusiast with Linux installed at home can freely install the Apache web server with PHP, turn on verbose warning/error messages and learn exactly what is going wrong; but this is a substantial commitment of time and effort, since Apache is a complex application.

    Anyway, here is a link to my fact.php -- but you won't be able to see the actual PHP code by using View Page Source. Do you see why? This would be a huge security risk! All you can see is the HTML generated by the PHP script. Just for you, here is the actual code:

    <?php                                                                           
    
    // -- The following 2 lines retrieve values (if any) 
    //    POSTed to fact.php:                            
    
    $fref = $_POST["fstart"];
    $nn = $_POST["nn"];      
    
    // -- Now we start up the HTML that will be sent to 
    //    the user's Web browser:                       
    
    echo "
     <!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">
     <HTML>                                          
     <HEAD>                                          
      <TITLE>Factorials</TITLE>                      
      <LINK REL=\"STYLESHEET\" HREF=\"http://musr.physics.ubc.ca/css/p210.css\">
     </HEAD>                                                                    
     <BODY bgcolor=\"FFFFFF\">                                                  
    <CENTER>                                                                    
    <H2 CLASS=\"fancy\"> Factorials: </H2>                                      
    ";                                                                          
    
    // -- (Note that any embedded quotation marks must be 'escaped' as \"
    //     because echo used double quotes to delimit its output.)       
    
    // -- Now check to see if meaningful arguments are provided: 
    
    if ( $fref <= 0 || $nn < 1 ) {  // No.  Ask for some!
    
      // Note that the user's answers are resubmitted to this script 
      // (i.e. it calls itself!):                                    
    
      echo "
    Generate & print out   <tt>fstart</tt>   
    and the following   <tt>nn</tt>   Factorials,  
    <BR>                                                     
    unless   <tt>fstart</tt>   is <i>not</i> a Factorial, 
    in which case print an error message.                           
    <P>                                                             
    <FORM METHOD=\"POST\" ACTION=\"fact.php\">                      
    Start with <tt>fstart</tt> =                                    
    <INPUT TYPE=\"text\" NAME=\"fstart\" SIZE=\"14\">;              
    print it                                                        
    <BR>                                                            
    and the next <tt>nn</tt> =                                      
    <INPUT TYPE=\"text\" NAME=\"nn\" SIZE=\"3\"> Factorials.
    <P>
    <INPUT TYPE=\"submit\" value=\"GO!\">
    </CENTER>
    </BODY>
    </html>
      ";
      exit;  // Once the HTML is sent, exit and wait for user to hit GO!
    }
    
    // -- OK, we have some plausible arguments.  Run the numbers:
    //    Anything starting with "$" is a variable name.
    
    $jj = 1;  // $jj is the current index.  Start with 1.
    $ff = 1;  // $ff is the current Factorial.  Start with 1*1.
    $nn++;    // Add 1 to # of factorials to print.
    
    echo "$nn Factorials starting with $fref:<P>";
    // -- Note that variable values can be embedded in HTML by echo.
    // -- Let's make it pretty, too:
    echo "<TABLE>";
    
    while ( $nn > 0 ) {
      // For debugging:  echo "jj=$jj; ff=$ff; nn=$nn<BR>";
      if ( $ff > $fref ) {  // Have we passed the starting point?
        echo "<TR><TD COLSPAN=2 ALIGN=center>";
        echo "Ahem!  $fref  is not a Factorial! </TD></TR>";
        echo "</TD></TR>";
        $nn = 0;
      } else if ( $ff == $fref ) {
        echo "<TR><TD ALIGN=right> #$jj: </TD><TD>   $ff </TD></TR>";
        $fref = $ff*($jj+1); // Set $fref = next Factorial.  (Crude, but it works.)
        $nn--;
      }
      $jj++;  // Increment index.
      $ff = $ff*$jj;  // Next factorial.
    }
    
    // -- All done.  Wrap it up and close the HTML output:
    
    echo "
    </TABLE>
    <P>
    <A HREF=fact.php?fstart=0&nn=0>Try again</A> or
    <A HREF=.>exit</A>?
    </CENTER>
    </BODY>
    </HTML>
    ";
    
    // -- Note: see "Page Source" in your browser's
    //    "View" menu to see the HTML this generates.
    
    ?>
    



Jess H. Brewer 2010-09-18