Flow Control - Part 3
Now that we have learned about positional parameters, it's time to cover the
remaining flow control statement, for. Like while and until, for is used to construct loops. for works like this:
for variable in words; do
commands
doneIn essence, for assigns a word from the list of
words to the specified variable, executes the commands, and repeats this over
and over until all the words have been used up. Here is an example:
for i in word1 word2 word3; do
echo "$i"
doneIn this example, the variable i is assigned the string
"word1", then the statement echo "$i" is executed,
then the variable i is assigned the string "word2",
and the statement echo "$i" is executed, and so on, until all the
words in the list of words have been assigned.
The interesting thing about for is the many ways
we can construct the list of words. All kinds of expansions can be used. In the
next example, we will construct the list of words using command
substitution:
for i in $(cat ~/.bash_profile); do
count=$((count + 1))
echo "Word $count ($i) contains $(echo -n $i | wc -c) characters"
doneHere we take the file .bash_profile and count the number of
words in the file and the number of characters in each word.
So what's this got to do with positional parameters? Well, one of the
features of for is that it can use the positional
parameters as the list of words:
for i in "$@"; do
echo $i
doneThe shell variable "$@" contains the list of command line
arguments. This technique is often used to process a list of files on the
command line. Here is a another example:
for filename in "$@"; do
result=
if [ -f "$filename" ]; then
result="$filename is a regular file"
else
if [ -d "$filename" ]; then
result="$filename is a directory"
fi
fi
if [ -w "$filename" ]; then
result="$result and it is writable"
else
result="$result and it is not writable"
fi
echo "$result"
doneTry this script. Give it a list of files or a
wildcard like "*" to see it work.
The use of in "$@" is so common that it is assumed if the
in words clause is ommited.
Here is another example script. This one compares the files in two directories and lists which files in the first directory are missing from the second.
if [ $# -ne 2 ]; then
echo "usage: $0 directory_1 directory_2" 1>&2
exit 1
fi
# Make sure both arguments are directories
if [ ! -d "$1" ]; then
echo "$1 is not a directory!" 1>&2
exit 1
fi
if [ ! -d "$2" ]; then
echo "$2 is not a directory!" 1>&2
exit 1
fi
# Process each file in directory_1, comparing it to directory_2
missing=0
for filename in "$1"/*; do
fn=$(basename "$filename")
if [ -f "$filename" ]; then
if [ ! -f "$2/$fn" ]; then
echo "$fn is missing from $2"
missing=$((missing + 1))
fi
fi
done
echo "$missing files missing"Now on to the real work. We are going to improve the home_space
function to output more information. Recall that our previous version looked
like this:
if [ "$(id -u)" = "0" ]; then
echo "<h2>Home directory space by user</h2>"
echo "<pre>"
echo "Bytes Directory"
du -s /home/* | sort -nr
echo "</pre>"
fi
} # end of home_spaceHere is the new version:
echo "<h2>Home directory space by user</h2>"
echo "<pre>"
format="%8s%10s%10s %-s\n"
printf "$format" "Dirs" "Files" "Blocks" "Directory"
printf "$format" "----" "-----" "------" "---------"
if [ $(id -u) = "0" ]; then
dir_list="/home/*"
else
dir_list=$HOME
fi
for home_dir in $dir_list; do
total_dirs=$(find $home_dir -type d | wc -l)
total_files=$(find $home_dir -type f | wc -l)
total_blocks=$(du -s $home_dir)
printf "$format" "$total_dirs" "$total_files" "$total_blocks"
done
echo "</pre>"
} # end of home_spaceThis improved version introduces a new command printf, which is used to produce
formatted output according to the contents of a format string. printf comes from the C programming language and has been
implemented in many other programming languages including C++, Perl, awk, java,
PHP, and of course, bash. More information about printf format strings can be found at:
We also introduce the find command. find
is used to search for files or directories that meet specific criteria. In the
home_space function, we use find to
list the directories and regular files in each home directory. Using the wc command, we count the number of files and directories
found.
The really interesting thing about home_space is how we deal
with the problem of superuser access. Notice that we test for the superuser
with id and, according to the outcome of the test, we
assign different strings to the variable dir_list, which becomes
the list of words for the for loop that follows. This
way, if an ordinary user runs the script, only his/her home directory will be
listed.
Another function that can use a for loop is our
unfinished system_info function. We can build it like this:
if ls /etc/*release 1>/dev/null 2>&1; then
echo "<h2>System release info</h2>"
echo "<pre>"
for i in /etc/*release; do
# Since we can't be sure of the
# length of the file, only
# display the first line.
head -n 1 "$i"
done
uname -orp
echo "</pre>"
fi
} # end of system_infoIn this function, we first determine if there are any release files to
process. The release files contain the name of the vendor and the version of
the distribution. They are located in the /etc directory. To
detect them, we perform an ls command and throw away
all of its output. We are only interested in the exit status. It will be true
if any files are found.
Next, we output the HTML for this section of the page, since we now know
that there are release files to process. To process the files, we start a for loop to act on each one. Inside the loop, we use the
head command
to return the first line of each file.
Finally, we use the uname command with the "o", "r", and "p"
options to obtain some additional information from the system.