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

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:

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: this is pretty easy - just check the directory. If it's not writable, abort. 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:

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:

  1. 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.

  2. 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:
#!/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.

Document made with Kompozer