1. Looping with for
-
With
forwe can loop a list of words:for i in A B C D; do echo $i; donefor i in {A..D}; do echo $i; donefor i in *.sh; do echo "$i"; doneIn 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.shActually it can take one or more files as parameters and process each of them. This is implemented by the
whileloop:while [[ -n "$1" ]]; do
if [[ -r "$1" ]]; then
# . . . . .
# . . . . .
fi
shift
doneThe
ifchecks 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 theforstatement:for i in $(strings "$1"); doNotice 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.shThe modification consists on replacing the
whileloop with aforloop like this:for i; do
if [[ -r "$i" ]]; then
# . . . . .
# . . . . .
fi
doneAnd since we are using the variable
ifor the outer loop, in the inner loop we have replacediwithj.When we omit the list of words,
forwill use by default the positional parameters.echo *./longest-word2.sh * -
forhas 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; doneAs 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.shthat 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.shOnly
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.htmllynx report.html