sections in this module | City College of San Francisco - CS160B Unix/Linux Shell Scripting Module: Functions |
module list |
Some of the files containing examples are in the examples directory beneath the class work area on hills.
Example 1:
You maintain files of contacts in your home directory. A contacts file consists of one line per contact with three fields separated by tabs: the contact's name, their phone number and their email address.
Write a function to add a contact to a contacts file. It should have this synopsis:
newcontact file
where file is the path to the contacts file. Your function should ask the user for the contact information, then append the contact to the file
Example 2:
Given the following file, named addresses
and the following commands
what is the value of each of the variables?
Can you rewrite this example (both the file addresses and the code that reads them) so that the variables are set correctly? (This is tricky.)
Example 3:
Consider the following shell script getcontact
getcontact has a bug. Can you find it? Explain the problem and how to fix it.
Example 4:
You have a command xxx that outputs both to standard output and to standard error. Write a command to
send standard output of xxx to the file output and the standard error to the file errors.
send both standard output and standard error of xxx to the file output
send standard output of xxx to the file output and throw away the error messages.
Write a pipelined command with these attributes:
Example 5:
As you know, the command
mv source target
functions very differently depending on whether target exists. Write a "safe rename" command (shell script) to rename a file or directory (source) only if the target does not exist:
safemv source target
so long as target does not exist, safemv will rename anything (file, directory, etc). If savemv encounters an error or invalid arguments, it must output an appropriate error message to stderr and exit with an error.
Example 6:Write a command diff to output the difference of two integers expressed as a positive number. Thus
diff 4 5
and
diff 5 4
both output 1
Your command must check its arguments to ensure they "look like" integers. If either is invalid, diff must output an error message to stderr and exit with an error. (Note: diff is a standard command. If you write this command, you will have to execute it like this: ./diff Otherwise, you will be running the Unix command diff, which is used to compare two text files.)
Answers
Example 1:
Your function will take one argument, which must be present and be a writable file. It will then interact with the user to get the contact information and append the result to the file. We will just accept whatever information the user types in: there will be no checking the format of the fields:
Example 2:
The value of name1 is the same as the value of name2; the value of address1 is the same as that of address2, etc. This is because the redirection operator opens the file each time it is executed, so each read command reads the first line of addresses. Let's fix that problem first. This means the commands can only open addresses once.
We could place the commands in a shell script, then redirect to the shell script instead. We'll create our shell script readaddresses with the commands
we could then execute readaddresses like this:
readaddresses < addresses
The first line of addresses would then be read by the first read command, the second by the second read command. We could also do this using a function
and then redirect to the function like this
readaddresses < addresses
But we can get trickier still by using an anonymous function, which is also called a block:
Any of these techniques would work. However, the addresses file still has some problems. Let's see what the result is if we execute our read commands as a block on the current file whose contents is below:
Here are the values of the resulting variables:
Let's modify our addresses file so that it works correctly:
Example 3:
Referring back to our shell script getcontact
Here's what happens when we run getcontact:
The problem has to do with the variable line. Remember, by default all variables in a function are global. This means that the variable line in the function is the same variable as the variable line in the surrounding shell program. The alteration of the global variable line by the function is called a side-effect. To avoid it you should declare all variables used for temporary storage in a function local. When we add the following line as the first line of the function addcontact():
local line name phone email
the program executes correctly.
Example 4:
Let's go through these one at a time:
1. send standard output of xxx to the file output and the standard error to the file errors. This one is easy:
xxx > output 2> errors
2. send both standard output and standard error of xxx to the file output
xxx > output 2>&1 OR
xxx 2> output >&2
3. send standard output of xxx to the file output and throw away the error messages.
xxx > output 2> /dev/null
Write a pipelined command with these attributes:
xxx 2> xxx.errors | tee xxx.out | yyy > yyy.out 2>&1
Example 5:
We are supposed to write a "safe rename" command (shell script) to rename a file or directory (source) only if the target does not exist:safemv source target
As always, the meat of this problem is easy - it is just a mv command. The complexity is in the details. To satisfy our requirements, we should ask the following questions when we start. We want to answer each of these questions as "yes" in order to be successful.
There are also a lot of permissions issues to address. These can be very complicated and it is very easy to miss a requirement. To be safe, we would have to test that the first argument is readable, that its directory is writable, and that the directory of the second argument is writable. (I don't know about you, but I'm tired already.) For this reason, shell scripts (and Unix programs as well) often take a different approach:
Here's our code. (See the examples directory on hills).
Example 6:
Conceptually, the solution for
diff num1 num2
is simple. The problem comes with this restriction:
How do we go about doing this? The solution is that most-dreaded of words, regular expression.
Regular expressions are your friends. They provide an exact, compact, method to look for data, or, in our case, to validate input, using a pattern. For us, we will take a variable containing the [candidate] integer, echo its contents to grep and ask grep to tell is if it matches a pattern. Let's ask some questions to create our pattern:
Here's our answers:
Our regular expression checks to see if our variable is an integer, so we want to see if the grep command fails. This calls for a negation symbol. But we will be using a pipeline of commands to test the integer. This raises two interesting questions that may not yet be obvious. Suppose we have a pipelined command
cmd1 | cmd2
|
Now that we've covered those issues, here is a command to test $num1 to see if it's an integer:
(Remember, the -q option to grep has the same effect as redirecting grep's standard output to /dev/null.) And here is our code for diff:
Remember, diff is a standard Unix command, so you will have to run this command as
./diff 4 5
A different version of diff
We can make our code even more readable by moving our code that checks for an integer into a Boolean function. The idea is to create a function that succeeds or fails depending on whether its single argument "looks like" an integer. In our case, we could get rid of the ugly regular expressions from our main code:
by creating a function isint(). The result is much prettier:
if ! isint "$num1" ; then
fatal "'$num1' is not an integer"
fi
or, better,
Here is our code after creation of the function isint(). Notice that the function is 'safe', since it uses only its arguments and only sets its exit status:
#!/bin/bash
#
#
# diff num1 num2
#
# output the absolute value of (num1 - num2).
# num1 and num2 must be integers. They may be negative
#
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") num1 num2" >&2
exit 1
}
[ $# -ne 2 ] && fatal "need exactly two arguments"
num1=$1
num2=$2
isint "$num1" || fatal "'$num1' is not an integer"
isint "$num2" || fatal "'$num2' is not an integer"
((result=num1-num2))
if [ "$result" -lt 0 ]; then
((result=0-result))
fi
echo $result
This last version is named diff2. It can be found in the examples area.
Prev | This page was made entirely with free software on linux: Kompozer and Openoffice.org |
Next |