sections in this module City College of San Francisco - CS160B
Unix/Linux Shell Scripting
Module: Conditionals
module list


Example 1:

You are writing a shell script that has this synopsis:

myss { -a | -b } file

This means that myss requires two arguments. The first is an option, which must be either -a or -b. The second is a file. We will make the further restriction that the file argument must be a readable file.

Write code for this shell script to check its arguments. Your code should output an error if the arguments are invalid.

Example 2:

What is wrong with the following command:

if [ grep "$user" file1 ]; then
echo "'$user' found"

How about this one:

if $(grep "$user" file1); then
echo "'$user' found"

Can you rewrite the command so that it works correctly and is as simple as possible?

Example 3:

Rewrite the following command using an if statement

test -f "$file" -a -r "$file" && cat "$file" || \
echo "$file is not a readable file"

Rewrite the following if statement using && and/or ||

if !grep "Greg" $file; then
echo "Greg was not in $file"

Example 4:

The date command outputs

Mon Jan 25 12:23:11 PST 2010

where the month is expressed using its first three characters. Write a sequence of code including a case statement that translates today's date into the format MM/DD/YYYY. For example, the date above would be output as 01/25/2010 For extra practice, you should allow the name of the month to be expressed either as it appears in the date (with the first character capitalized) or with the first character lowercase, e.g., as Jan or jan

Example 5:

What is the problem with the following command?

if [-f "$file" -a -r$file ]; then
cat $file

There are four bugs in this sequence: two will cause syntax errors immediately, and two are data-dependent. Can you find them? (Hint: they are all due to spacing or quoting.)


Example 1:

Note: the code for myss can be found in the examples-shotts/conditionals directory beneath our class data area on hills.

First, let's discuss an algorithm for our argument checking. This is very important to do before you write the code. Here is an outline for processing arguments in general. We will ask a series of questions. At each step if it doesn't make sense to continue we will exit with an error message.

  1. are the number of arguments correct? 

  2. is each option valid? do they make sense together? are required options present?

  1. after the options are processed, are the path arguments acceptible? 

If your program passed all these tests, you are ready to start the real work. 

For our simple shell script, here is how our tests would proceed:

  1. there must be exactly two arguments
  2. the first argument must be either -a or -b
  3. the second argument must be a readable file

When we exit with an error, we will output a synopsis to help the user run our shell script next time. In Assignment Two you are given a function called fatal, which you can call to output an error message and exit. We will use a modified version of fatal in our code. (Note that this function outputs its error message to standard error. We will learn about this in the next module.

# i won't write the documentation for this code, since I don't know what it does!
fatal() {
# fatal: output an error message and a synopsis message to standard error.
# then exit with an error.
local prog=$(basename "$0")
echo "$prog: ERROR - $*" >&2
echo "usage: $(basename "$0") { -a | -b } inputfile" >&2
exit 1

# First, check the number of arguments
# I will use && for the first error message and if statements for the rest
[ $# -ne 2 ] && fatal "exactly two arguments required."

# There is only one option, and it must be one of two possibilities. 
When you have two possibilities for a test, you can write
# the test in one of two ways
#    1. if its not the first possibility and it's not the second
#          possibility, then there's an error.
#    2. if not (it's the first possibility or the second), then
#          there's an error.
# Here's how to write it the first way:
if [ "$1" != '-a' -a "$1" != '-b' ]; then
fatal "first argument ('$1') must be -a or -b"
# Here's how to write it the second way:
# if ! [ "$1" = '-a' -o "$1" = '-b' ]; then ...

# Last, the path argument must be readable and be a file.
# Again, the test has two required attributes: BOTH must be satisfied.
# there are two ways to write this:
#    - if it doesn't have the first attribute or it doesn't have
#      the second, there's an error.
#    - if not (it has both attributes), there's an error.
# Again, we'll write it the first way, then show the second:
if [ ! -f "$2" -o ! -r "$2" ]; then
fatal "'$2' is not a readable file"
# if ! [ -f "$2" -a -r "$2" ]; then ...

Example 2:

Let's look at the following command:

if [ grep "$user" file1 ]; then
echo "'$user' found"

This is a mess, but is a common mistake. Beginning shell programmers think that [ ] is punctuation that makes an if statement work (just like parenthesis would be in a C/C++/Java expression). [ ] is NOT punctuation. It is the test command in disguise. Let's rewrite the if statement replacing [ ] with the command test:

if test grep "$user" file1 ; then ...

Most people would agree this looks silly. Indeed, the command we want to use as the condition for the if statement is grep, not test. Just delete the test command (or the [ ] ):

if  grep "$user" file1 ; then
echo "'$user' found"

There is still a problem, however. The grep command, if it succeeds, will output the lines that match. You must tell grep to 'shut up'. (Many of us want to tell grep just this from time to time.) Just redirect its output somewhere so you don't see it. We will use a temporary file, then delete it. (In the next module we will use the null device instead):

if  grep "$user" file1 > $$.out ; then
echo "'$user' found"
rm -f $$.out

The next command 

if $(grep "$user" file1); then
echo "'$user' found"

is similarly over-complex. The command-substitution is not only silly, it causes a big problem. Remember, command-substitution substitutes the standard output of the substituted command back onto the commandline of the enclosing command. Suppose $user was Greg and our grep command output the following text:

Greg Boyd teacher cs160b

After the command-substitution was finished, the if statement would look like this:

if  Greg Boyd teacher cs160b ; then
echo "'$user' found"

Now we see how silly it is. The if statement will try to run the command Greg, giving it the arguments Boyd teacher cs160b ! (Although there should always be a command Greg, there probably isn't, so this if statement will fail.) Just remove the command substitution and redirect the output of grep as in our original solution.

Some cs160b students try to force the command-substitution version to work like this:

if [ "$(grep "$user" file1)" ]; then
echo "'$user' found"

Interestingly, this works! However, this is due to the default option in the test command, which is -n. The command is thus:

if [ -n "$(grep "$user" file1)" ]; then
echo "'$user' found"

which says, in effect, "if grep outputs any text", then ... 

Although it works, it is again unnecessarily complex. If you see this type of code in a shell script, either the programmer was doing something very tricky, or they just didn't know what they were doing!

Example 3:

The if statement that is normally thought to be equivalent to

test -f "$file" -a -r "$file" && cat "$file" || \
echo "$file is not a readable file"


if test -f "$file" -a -r "$file"; then
cat "$file"
echo "$file is not a readable file"

This is usually fine, but it is inexact. (See the closing paragraph about && / ||

The && and/or || statement that is equivalent to

if !grep "Greg" "$file"; then
echo "Greg was not in $file"


grep "Greg" "$file" || echo "Greg was not in $file"

Notice how the ! changed the && to ||

Example 4:

Of course, most of the output we want is already in the output of date:

Mon Jan 25 12:23:11 PST 2010

The month, however, must be translated. This is where a case statement will come in handy. Let's start by using our set -- trick to place the day in $3, the year in $6 and the month in $2. Then, the case statement. Last, output the result:

# distribute the output of date among positional parameters
#   by breaking the text on spaces
set -- $(date)   
case "$2" in
[Jj]an) mon=1;;
[Ff]eb) mon=2;;
[Mm]ar) mon=3;;
[Aa]pr) mon=4;;
[Mm]ay) mon=5;;
[Jj]un) mon=6;;
[Jj]ul) mon=7;;
[Aa]ug) mon=8;;
[Ss]ep) mon=9;;
[Oo]ct) mon=10;;
[Nn]ov) mon=11;;
[Dd]ec) mon=12;;
*) echo "something is wrong. Month = '$2'"; exit 1;;
echo "$mon/$3/$6"

Prev This page was made entirely with free software on Linux:  
and LibreOffice

Copyright 2016 Greg Boyd - All Rights Reserved.

Document made with Kompozer