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

Examples

You can find these examples in the subdirectory examples/6-loops1 beneath our class data area on hills.

Example 1:

Write a program normalize to fix non-Unix filenames. Given a directory, normalize changes the name of any visible item in the directory by deleting spaces and the other shell special characters * [ ] ? and ; from the name.

If changing the name of an object would cause a name collision, an error message is output instead that includes the name of the object skipped. Before it exits, normalize outputs a count of the names changed.

Here are some things to consider about normalize:

the appropriate else parts of the if statements are used for error messages.

You could simplify this code by looking only for filenames that contained an illegal character, but the wildcard needed for this is kind of ugly.

Example run:

-bash$ ls tmp
a[*bc?]      abc          hello;again  my           my resume    resume?
-bash$ normalize tmp
normalize: ERROR - cannot change 'a[*bc?]', 'abc' exists
3 items changed
-bash$ ls tmp
a[*bc?]     abc         helloagain  my          myresume    resume

Example 2:

Write a function addints() to add a list of integers given as its argument. The function should output the sum on standard output. If any argument is not an integer, the function should output a warning message and skip that argument.

If we have addints in a file (like it is on the examples area), we can source it (using the . operator) to place the function in our login shell, then run the function as a command:

-bash$ . addints
-bash$ addints 1 2 abc -4
'abc' is not an integer - skipped.
-1

Make sure that the error message goes to standard error. The user may want to capture standard output (the sum) using command-substitution.

Sample problem for Example 2

The third field of the colon-delimited file Depts contains an integer. Add together each integer in this field and place the sum in the variable sum

$ cut -d: -f3 ../../samples/Data/Depts
489
14
1
78
4532
5
17
893
833
622
552

$ sum=$(addints $(cut -d: -f3 ../../samples/Data/Depts))
$ echo $sum
8036

Example 3

Write a function to ask the user a question and retrieve an alphabetic response. The function's name will be getalpha(). It will take a single argument - the question to ask. getalpha() will repeatedly ask the question and retrieve the user's response until the response consists of alphabetic characters and spaces only. If an invalid response is received, getalpha() will output a reasonable error message before reasking the question. 

Once a valid response is retrieved, getalpha() will output it to standard output. This must be the only output sent to standard output by getalpha, since the caller may want to use command-substitution to place the result in a variable or another command. The function must be simple and free of side-effects.

Again, place your function in a file and read it into your shell using the . operator for testing.

Here is a sample of getalpha running:

$ getalpha "How are you"
How are you:o.k.
Response may consist of alphabetic characters and spaces only.
How are you:ok
ok

This last line was the standard output of getalpha. If we had used command-substitution:

$ result=$(getalpha "How are you")
How are you:o.k.
Response may consist of alphabetic characters and spaces only.
How are you:ok
-bash$ echo $result
ok

Example 4

Write a shell script mycat which acts like an improved cat program. It has one option: -b, followed by any number of file paths. Here is what it does

If the -b option was given: 

Here are examples of mycat running on two text files, a directory, and an executable. Note that it quietly skips the directory and the executable. (Maybe the design should specify a message?)

$ mycat -b addints tmp a.out getalpha
************************* addints ***************************
addints() {
        #
        # this function adds a list of integers
        # given as arguments. It outputs the sum
        # to standard output
        #
        local sum=0 arg
        for  arg; do
                if ! echo "$arg" | grep -qE '^-?[0-9]+$'; then
                        echo "'$arg' is not an integer - skipped." >&2
                        continue
                fi
                ((sum = sum + arg))
        done
        echo "$sum"
}
************************* getalpha ***************************
# getalpha
getalpha() {
        # ask the user a question ($1) and get a response that
        # must contain only alphbetic characters and spaces
        # (an empty response is not allowed - this is your decision)
        # getalpha() repeats the question until a valid response is received
        local input
        # note that we do not check for an argument. This allows the user
        # to output their own question and just use our function (with an
        # empty question) to get the response.
        while : ; do
                read -p "$1:" input
                echo "$input" | grep -qE '^[[:alpha:] ]+$' && break
                echo \
            "Response may consist of alphabetic characters and spaces only.">&2
        done
        echo "$input"
        return 0
}
               

*****************************************************************
2 files cat-ed

Here are some design considerations:

There are several problems that are open questions

Answers

The code for each of these examples is in the examples/6-examples directory on our class public data area on hills.

Example 1:

This example has some tricky code for certain parts. Let's discuss it:

this wildcard would be "$1"/*
Note that  you should put double-quotes around $1 but not around *
if file is the iteration variable in your for loop:
orig=$(basename "$file")
try this:
name="abc?[* ;d"
echo "$name" | tr -d ' *?;]['
command-substitute the changed name (the output of tr -d above) above into a variable and compare it to the original name
use mv and see if the mv succeeds. If it fails, report that the name cannot be changed.

To test normalize, create a directory with some strange names and run normalize on the directory, as in the example above.

Example 2:

Here's the solution from the examples directory:

addints() {
    #
    # this function adds a list of integers
    # given as arguments. It outputs the sum
    # to standard output
    #
    local sum=0 arg
    for  arg; do
        if ! echo "$arg" | grep -qE '^-?[0-9]+$'; then
            echo "'$arg' is not an integer - skipped." >&2
            continue
        fi
        ((sum = sum + arg))
    done
    echo "$sum"
}

Example 3

The getalpha program has a couple of unspecified issues. Did you think about them?

The sample solution is below and is also on hills.

getalpha() {
        # ask the user a question ($1) and get a response that
        # must contain only alphbetic characters and spaces
        # (an empty response is not allowed - this is your decision)
        # getalpha() repeats the question until a valid response is received
        local input
        # note that we do not check for an argument. This allows the user
        # to output their own question and just use our function (with an
        # empty question) to get the response.
        while : ; do
                read -p "$1:" input
                echo "$input" | grep -qE '^[[:alpha:] ]+$' && break
                echo \
            "Response may consist of alphabetic characters and spaces only.">&2
        done
        echo "$input"
        return 0
}

Remember, the : (null command) can replace the command true to control a while loop!

Example 4

Here is the code for mycat. It looks like a lot, but there is really only 16 lines of new code here! The rest is comments, and from our existing error() and fatal() functions

#!/bin/bash
#
# mycat [-b] files...
#
# mycat cats each file given on the commandline that is a
#  readable text file (as identified by file)
#
# the -b option separates files with a banner and outputs
# a count of the files cat-ed
#
# i have added the error and fatal functions from normalize.
# (merging code is very easy with vi)
#
error() {
    echo "$(basename "$0"): ERROR - $*" >&2
    errors=true
}
fatal() {
    error "$*"
    echo "syntax: $(basename "$0") [-b] files..." >&2
    exit 1
}

bflag=false
[ "$1" = -b ] && bflag=true && shift
[ $# -eq 0 ] && fatal "no files given"
filecount=0
while [ $# -gt 0 ]; do
    # get the current file and prepare for the next
    # (so that we can use continue later)
    thisfile=$1
    shift
    # only try for readable items
    [ ! -r "$thisfile" ] && continue
    # check to see if the description (not the filename)
    # output by file contains the word text
    if file "$thisfile" | grep -q ':.*text' ; then
        [ "$bflag" = true ] && \
            echo "************************* $thisfile ***************************"
        # at this point, cat shouldn't give an error
        # but if it does, we'll just let it output the message
        cat "$thisfile" && ((filecount=filecount+1))
    fi
done
if [ "$bflag" = true ] ; then
    echo "*****************************************************************"
    echo "$filecount files cat-ed"
fi

Prev This page was made entirely with free software on linux:  
the Mozilla Project
and Openoffice.org    
Next

Copyright 2012 Greg Boyd - All Rights Reserved.