1. Looping with for
-
With
for
we can loop a list of words:for i in A B C D; do echo $i; done
for i in {A..D}; do echo $i; done
for i in *.sh; do echo "$i"; done
In these cases it is using shell expansions.
The last file expansion may fail if there are no files like that, in which case shell will return just
*.sh
(instead of a list of matching files). To protect against this, we can rewrite the last example like this:for i in *.sh; do [[ -e "$i" ]] && echo "$i"; done
-
Let's see an example that finds the longest word in a file:
longest-word.sh
#!/bin/bash
# longest-word: find longest string in a file
while [[ -n "$1" ]]; do
if [[ -r "$1" ]]; then
max_word=
max_len=0
for i in $(strings "$1"); do
len="$(echo -n "$i" | wc -c)"
if (( len > max_len )); then
max_len="$len"
max_word="$i"
fi
done
echo "$1: '$max_word' ($max_len characters)"
fi
shift
donevim longest-word.sh
Actually it can take one or more files as parameters and process each of them. This is implemented by the
while
loop:while [[ -n "$1" ]]; do
if [[ -r "$1" ]]; then
# . . . . .
# . . . . .
fi
shift
doneThe
if
checks that the file is readable (otherwise it is skipped).The list of words of the file is produced by the command
strings "$1"
. We use a command substitution to use this list in thefor
statement:for i in $(strings "$1"); do
Notice that we are not surrounding
$(strings "$1")
with double quotes, otherwise it will be treated as a single string, which is not what we want.Let's try it:
echo *
./longest-word.sh *
-
Let's see a slightly modified version of the previous example:
longest-word2.sh
#!/bin/bash
# longest-word2: find longest string in a file
for i; do
if [[ -r "$i" ]]; then
max_word=
max_len=0
for j in $(strings "$i"); do
len="$(echo -n "$j" | wc -c)"
if (( len > max_len )); then
max_len="$len"
max_word="$j"
fi
done
echo "$i: '$max_word' ($max_len characters)"
fi
donevim longest-word2.sh
The modification consists on replacing the
while
loop with afor
loop like this:for i; do
if [[ -r "$i" ]]; then
# . . . . .
# . . . . .
fi
doneAnd since we are using the variable
i
for the outer loop, in the inner loop we have replacedi
withj
.When we omit the list of words,
for
will use by default the positional parameters.echo *
./longest-word2.sh *
-
for
has also another form, which is similar to that of C (and many other languages):for ((i=0; i<5; i=i+1)); do echo $i; done
As you know, the construct
((...))
is used for arithmetic expressions, and inside it we don't use a$
in front of the variables. -
Let's also make a small modification to the program
sys_info.sh
that we saw in the last lesson:sys_info.sh
#!/bin/bash
# Program to output a system information page
PROGNAME="$(basename "$0")"
usage () {
cat <<- _EOF_
Usage:
$PROGNAME
Output the report to the stdout.
$PROGNAME (-f | --file) <file>
Output the report to the given file.
$PROGNAME (-i | --interactive)
Get the output file interactively from the keyboard.
$PROGNAME (-h | --help)
Display this help message.
_EOF_
return
}
main () {
# global aux vars
interactive=''
filename=''
process_options "$@"
interactive_mode
output_html_page
}
process_options () {
# process command line options
while [[ -n "$1" ]]; do
case "$1" in
-f | --file)
shift
filename="$1"
;;
-i | --interactive)
interactive=1
;;
-h | --help)
usage
exit
;;
*)
usage >&2
exit 1
;;
esac
shift
done
}
interactive_mode () {
# interactive mode
if [[ -n "$interactive" ]]; then
while true; do
read -p "Enter name of output file: " filename
if [[ -e "$filename" ]]; then
read -p "'$filename' exists. Overwrite? [y/n/q] > "
case "$REPLY" in
Y|y)
break
;;
Q|q)
echo "Program terminated."
exit
;;
*)
continue
;;
esac
elif [[ -z "$filename" ]]; then
continue
else
break
fi
done
fi
}
output_html_page () {
# output html page
if [[ -n "$filename" ]]; then
if touch "$filename" && [[ -f "$filename" ]]; then
write_html_page > "$filename"
else
echo "$PROGNAME: Cannot write file '$filename'" >&2
exit 1
fi
else
write_html_page
fi
}
write_html_page () {
local TITLE="System Information Report For $HOSTNAME"
local CURRENT_TIME=$(date +"%x %r %Z")
local TIMESTAMP="Generated on $CURRENT_TIME, by $USER"
cat <<- _EOF_
<html>
<head>
<title>$TITLE</title>
</head>
<body>
<h1>$TITLE</h1>
<p>$TIMESTAMP</p>
$(report_uptime)
$(report_disk_space)
$(report_home_space)
</body>
</html>
_EOF_
}
report_uptime() {
cat <<- _EOF_
<h2>System Uptime</h2>
<pre>$(uptime)</pre>
_EOF_
return
}
report_disk_space() {
cat <<- _EOF_
<h2>Disk Space Utilization</h2>
<pre>$(df -h .)</pre>
_EOF_
return
}
report_home_space () {
local format="%8s%10s%10s\n"
local i dir_list total_files total_dirs total_size user_name
if [[ "$(id -u)" -eq 0 ]]; then
dir_list=/home/*
user_name="All Users"
else
dir_list="$PWD"
user_name="$USER"
fi
echo "<h2>Home Space Utilization ($user_name)</h2>"
for i in $dir_list; do
total_files="$(find "$i" -type f | wc -l)"
total_dirs="$(find "$i" -type d | wc -l)"
total_size="$(du -sh "$i" | cut -f 1)"
echo "<H3>$i</H3>"
echo "<pre>"
printf "$format" "Dirs" "Files" "Size"
printf "$format" "----" "-----" "----"
printf "$format" "$total_dirs" "$total_files" "$total_size"
echo "</pre>"
done
return
}
# call the main function
main "$@"vim sys_info.sh
Only
report_home_space ()
(the last function) has been modified. It provides more detail for each user’s home directory and includes the total number of files and subdirectories in each. We also use some local variables and useprintf
(instead ofecho
) to format some of the output../sys_info.sh > report.html
lynx report.html