Examples
Example files for this section can be found in this module's
examples directory on hills.
(the examples/7-loops2
directory beneath our public work area)
Example 1:
In the section on while
read, we had the following example:
# note that ls outputs names one-per-line if it is
writing to a file
ls "$dir" > $$tmp
while read file ; do
# remember, the filename does not have the
directory component!
# we must add it
if file "$dir/$file" |
grep ':.*text' > /dev/null; then
if ! echo "$file" | grep '\.txt$'; then
mv "$dir/$file" "$dir/$file.txt"
fi
fi
done < $$tmp
rm -f $$tmp
We indicated that there are two problems with this example:
- we should ensure that the directory is writable before
attempting the loop. If it isn't writable, we cannot rename any
files!
- we did not check for a name collision before performing the mv. Thus, if we had two
files, file1 and file1.txt when the loop
starts, we just deleted file1.txt
and renamed file1
to file1.txt!
Solve each of these problems.
Example 2:
Write a program ison
to output a list of members of a group (whose name is given on the
command line) who are currently logged in. You will use the /etc/group file for
this. Here is the line from the /etc/group file for group c38947. It has 5 members:
c38947::3082:cjones,mmarsha,jprondhei,lgunn,juarez1
Your program might output something like this:
$ ison c38947
mmarsha is logged on
lgunn is logged on
This program is actually useful for our classes on hills, as each CS class is in
its own group. Thus, you can use your program to see which of your
classmates are currently logged on.
Example 3:
You have a file sizes
of numbers measured in kB. Here are some example lines
453
1092
49783
8529
982645
Convert
this file so that each number has a size suffix, either kB or MB, so that
no number is larger than 10000.
(i.e., 10 would
become 10kB, while 100000 would become 97MB, since 1MB=1024kB.)
You can assume the largest number in
the file is 10000000(kb),
and you can drop any fractional MB for those
numbers you convert. The example lines above, after conversion,
would be
453kB
1092kB
48MB
8529kB
959MB
A sample file named sizes
can be found in our module's directory on hills.
Example 4:
You
want to find the largest regular file beneath a particular
directory.
Write a shell program to do just that: given a single directory
argument, output the name and size of the largest file beneath the
directory (in any subdirectory, recursively). We will call our
program largest.
Example 5:
Each
of the previous examples used a single loop to process one large
piece
of data: one directory, one file, one group, etc. How can you
generalize these programs to work on a set of those objects?
As an example, we will rewrite ison to output a list of all of the members
in any of a list of groups who are currently logged in. Call your
new program groupies.
The list of users who are logged on should be sorted and each user
should appear at most once, even if they are in multiple groups.
Example run: groupies
c38947 c78322 c32889 sys
If the corresponding lines in /etc/group are:
c38947::3082:cjones,mmarsha,jprondhei,lgunn,juarez1
c78322::3104:cjones,gboyd,lgunn,grogers,mfreud
c32889::2945:gboyd,hlaw,jtung1,hmoose,sjobs
sys::456:sadmin,grogers,mboss
groupies might
output
cjones is logged on
gboyd is logged on
jtung1 is logged on
sadmin is logged on
Answers
Example 1:
Let's discuss the issues we have to solve:
- we should ensure that the directory is writable before
attempting the loop. If it isn't writable, we cannot rename any
files!
this is pretty easy - just check the directory. If it's not
writable, abort.
- we did not check for a name collision before performing the mv. Thus, if we had two
files, file1 and file1.txt when the loop
starts, we just deleted file1.txt
and renamed file1
to file1.txt!
This
is a bit of a problem: what should we do about file1 when the name we
want to change it to already exists? The easiest thing to do is to
just
skip file1, after we
output a message. We have also added a check to ensure the mv succeeded. Below is the
code. It is also in the file addtxt.
Create a directory, place some text files in it, set dir to point to it, and
then source addtxt.
(i.e., use
. addtxt
make sure the $dir directory just
has junk files.
if [ ! -w "$dir" ]; then
echo "directory '$dir' is not writeable"
>&2
exit 1
fi
# note that ls outputs names one-per-line if it is writing to a
file
ls "$dir" > $$tmp
while read file ; do
# remember, the filename does not have the
directory component!
# we must add it
if file "$dir/$file" |
grep ':.*text' > /dev/null; then
if ! echo "$file" | grep -q '\.txt$'; then
if [ -e "$dir/$file.txt" ]; then
echo "'$dir/file.txt' already exists -
skipped." >&2
continue
else
if ! mv "$dir/$file" "$dir/$file.txt"
2>/dev/null; then
echo "cannot rename '$dir/$file' to
'$dir/file.txt'" >&2
fi
fi
fi
fi
done < $$tmp
rm -f $$tmp
Example 2:
The program simply extracts the line from /etc/group, converts the
members list to space separated, then uses it as a list in a for loop, grepping for
each user in the output of who.
For efficiency, and to provide a constant set of data, we save
the output of who in
a file, then grep
the file.
#!/bin/bash
#
# ison - output list of
members of a group who are currently logged in
# the group name is
the single command-line argument
#
progname=$(basename "$0")
fatal(){
echo
"$progname: ERROR: $*" >&2
echo
"syntax: $progname groupname" >&2
exit 1
}
[ $# -ne 1 ] &&
fatal "single argument required"
if ! group=$(grep "^$1:"
/etc/group); then
fatal "no
such group '$1'"
fi
members=$(echo "$group" |
cut -d: -f4 | tr ',' ' ')
# save a copy of the output
of who
# so we dont have to run it
multiple times
who > $$who.out
for user in $members; do
# see if
$user is logged in
if grep
"^$user " $$who.out >/dev/null/; then
echo "'$user' is logged on"
fi
done
rm -f $$who.out
Example 3:
We just need to read each line in from the file sizes and decide what to
do with it:
- add the kB
suffix if the number is <= 10000
- convert it to MB and add the MB suffix if the number was > 10000
Here's the code. Our version reads from the file sizes (in the current
directory) and outputs to the file sizes.kB. The code is in the file addkB: Create your own file
sizes, copy addkB to your directory, and try it.
# read from the file sizes in the current directory
# and write to the file
sizes.kB in the current directory
if [ ! -f sizes -o ! -r
sizes ]; then
echo "no
file 'sizes' found in the current directory" >&2
exit 1
fi
while read number ; do
if [
"$number" -gt 10000 ]; then
((number=number/1024))
echo "${number}MB"
else
echo "${number}kB"
fi
done < sizes >
sizes.kB
Example 4:
We are certainly going to use find to generate a list of paths to regular
files beneath the directory in question. We will then process the
output of find (one
path per line) using a while
read loop. Each readable file we encounter is possibly
the largest one. Its size can be determined by wc -c. How do we decide
which is the largest? There are two ways we could solve this
problem:
-
Keep track of which file is the largest so far, comparing it to
the size of the current file. When the loop ends, we have the
largest file and its size.
-
save
the name and size of each file in a temporary file. After the
loop is
finished, sort the file, then extract the largest one.
The loop below uses the first approach. Note that we cannot use a
pipe between find
and the while loop
since we must use variables in the loop and want them to be
available after the loop exits.
#!/bin/bash
#
# largest - output the path
and size of the largest readable
# regular file beneath the
directory given as its single argument
#
progname=$(basename "$0")
fatal(){
echo
"$progname: ERROR: $*" >&2
echo
"syntax: $progname directory" >&2
exit 1
}
[ $# -ne 1 ] &&
fatal "single directory argument required"
[ ! -d "$1" ] &&
fatal "single directory argument required"
#
# start with largestfile
empty and largestfilesize 0 bytes
#
largestfile=
largestfilesize=0
#
# save the output of find
(files only) in a temp file
# we are using the $$
variable to name our temporary file to make it
# particular to our process
id and keep its name unusual.
#
find "$1" -type f > $$tmp
#
# now we are ready to
process the files in our loop
#
while read path; do
if [ -r
"$path" ]; then
size=$(wc -c < "$path")
if [ "$size" -gt "$largestfilesize" ]; then
largestfilesize=$size
largestfile=$path
fi
fi
done < $$tmp
rm -f $$tmp
echo "the largest file
beneath '$1' is"
echo "'$largestfile'
($largestfilesize bytes)"
Question: why do we bother with ensuring "$path" is a readable
file? Answer: because we want to avoid generating error messages
due to the file not being readable.
The
other way to write this (the second approach above), where we keep
track of each file and its size in a temporary file, is a bit
harder to
write, but is more general. If we wanted to change our program to
output the largest N files, for example, it would require a small
change to the alternate program, whereas the program above would
be
useless.
Can you write a version of this program using the second
approach?
Example 5
We must run the code in ison
for each argument, with the following changes:
- instead
of outputting each user that is logged on directly to standard
output
we will append the list to a file. After all the groups have
been
processed, we will sort the file and remove duplicates. Then we
will
output it.
- the fatal
function will be accompanied with an error function that prints an error but
does not exit. Instead, we will set a flag in errors() to indicate
that an error occurred, and set our exit status appropriately at
the end of the program.
- otherwise, we have to rearrange a bit of our code, as below.
#!/bin/bash
#
# groupies - output list of
members of the groups listed on the command line
# who are currently logged in
# the commandline
consists of group names
# each member of any of these groups who is currently logged on
is identified.
#
# groupies gname [ gname ... ]
#
progname=$(basename "$0")
errors=false
rm -f $$on
error(){
echo
"$progname: ERROR: $*" >&2
errors=true
}
fatal(){
error
"$*"
echo
"syntax: $progname groupname [ groupname ... ]" >&2
exit 1
}
[ $# -eq 0 ] &&
fatal "no groups specified"
# save a copy of the output of who
# so we dont have to run it
multiple times
who > $$who.out
for thisgroup; do # same as for thisgroup in "$@"; do
if ! group=$(grep "^$thisgroup:" /etc/group); then
error
"no such group '$thisgroup'"
continue
fi
members=$(echo "$group" |
cut -d: -f4 | tr ',' ' ')
for user in $members; do
# see
if $user is logged in
if grep
"^$user " $$who.out >/dev/null/; then
echo "'$user' is logged on"
fi
done >> $$on
done
sort -u $$on
rm -f $$who.out $$on
[ $errors = true ] && exit 1
exit 0
Prev |
This page was made entirely
with free software on linux:
Kompozer and Openoffice.org
|
Next |
Copyright 2010 Greg Boyd - All Rights Reserved.