4. An example
Let's try to improve the program sys_info.sh
, that we started to
build in a previous lesson, by adding some parameters and option to
it. We want to be able to:
- Tell it to save the output to a specific file (instead of sending it
to stdout), by using the options
-f file
or--file file
. - Tell it to ask interactively for a filename for saving the
output. This option should be specified by
-i
or--interactive
. - Use the options
-h
or--help
to make the program output information about its usage.
-
There is a small git repo on the archive
sys_info.tgz
, let's open it:tar xfz sys_info.tgz
cd sys_info/
ls -al
git log --oneline
git tag
-
Let's get first the initial version of the script (that was developed in a previous lesson):
git checkout -q 1.initial
git status
vim sys_info.sh
sys_info.initial.sh
#!/bin/bash
# Program to output a system information page
TITLE="System Information Report For $HOSTNAME"
CURRENT_TIME=$(date +"%x %r %Z")
TIMESTAMP="Generated on $CURRENT_TIME, by $USER"
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() {
if [[ "$(id -u)" -eq 0 ]]; then
cat <<- _EOF_
<h2>Home Space Utilization (All Users)</h2>
<pre>$(du -hs /home/*)</pre>
_EOF_
else
cat <<- _EOF_
<h2>Home Space Utilization ($PWD)</h2>
<pre>$(du -hs "$PWD")</pre>
_EOF_
fi
return
}
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_./sys_info.sh
-
Let's see some modifications and improvements to it:
Enclose in a function the last part (that generates the HTML page):
git checkout -q 2.write_html_page
git diff 1.initial
git diff 1.initial
diff --git a/sys_info.sh b/sys_info.sh
index 4f261db..6291299 100755
--- a/sys_info.sh
+++ b/sys_info.sh
@@ -37,18 +37,23 @@ report_home_space() {
return
}
+write_html_page () {
+ 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_
+}
+
+# output html page
+write_html_page
-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_Add a function that displays the usage of the program:
git checkout -q 3.usage
git diff 2.write_html_page
git diff 2.write_html_page
diff --git a/sys_info.sh b/sys_info.sh
index 6291299..b58cf06 100755
--- a/sys_info.sh
+++ b/sys_info.sh
@@ -1,7 +1,23 @@
#!/bin/bash
-
# Program to output a system information page
+usage () {
+ cat <<- _EOF_
+ $PROGNAME: usage:
+
+ $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
+}
+
+PROGNAME="$(basename "$0")"
TITLE="System Information Report For $HOSTNAME"
CURRENT_TIME=$(date +"%x %r %Z")
TIMESTAMP="Generated on $CURRENT_TIME, by $USER" -
Add some code that reads the command line options:
git checkout -q 4.process_options
git diff 3.usage
git diff 3.usage
diff --git a/sys_info.sh b/sys_info.sh
index b58cf06..37ddc1b 100755
--- a/sys_info.sh
+++ b/sys_info.sh
@@ -17,6 +17,30 @@ usage () {
return
}
+# process command line options
+interactive=''
+filename=''
+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
+
PROGNAME="$(basename "$0")"
TITLE="System Information Report For $HOSTNAME"
CURRENT_TIME=$(date +"%x %r %Z")We use a
while
loop andshift
to process all the options. Inside the loop we usecase
to match the option with one of those that are expected. If the option is-f
(or--file
), we interpret the next parameter as a filename and set it to the variablefilename
. If the option is-i
(or--interactive
), we set the variableinteractive
to1
(otherwise it will remain empty).Notice that the actions corresponding to
-h | --help)
and*)
are very similar, they display the usage and exit the program. However the later case is considered an error, because there is an unknown/unsupported option, so the usage is sent to stderr (>&2
) and the program exits with code1
(error). -
If the option
-i
or (--interactive
) is supplied, the program should get a file name interactively (from the keyboard). Let's see the code that does that:git checkout -q 5.interactive
git diff 4.process_options
git diff 4.process_options
diff --git a/sys_info.sh b/sys_info.sh
index 37ddc1b..f8a2fba 100755
--- a/sys_info.sh
+++ b/sys_info.sh
@@ -41,6 +41,32 @@ while [[ -n "$1" ]]; do
shift
done
+# 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
+
PROGNAME="$(basename "$0")"
TITLE="System Information Report For $HOSTNAME"
CURRENT_TIME=$(date +"%x %r %Z")This code is executed only if the global variable
interactive
is not empty. There is an infinitewhile
loop that tries to read the name of the file into to global variablefilename
. We check that the given value is not empty and that such a file does not exist already. If there is already such a file, we ask again whether we can overwrite the file or not.We use the loop so that we can ask again for another file name if the given one is not suitable, and we repeat until we have a suitable file name (stored in the variable
filename
). -
Now let's see the code that outputs the HTML page:
git checkout -q 6.output_html_page
git diff 5.interactive
git diff 5.interactive
diff --git a/sys_info.sh b/sys_info.sh
index f8a2fba..8c3e576 100755
--- a/sys_info.sh
+++ b/sys_info.sh
@@ -121,5 +121,13 @@ write_html_page () {
}
# output html page
-write_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
+fiIf the variable
filename
is empty, then the HTML page will be sent to the stdout, same as before. Otherwise the program will try to send it to the given file (using redirection). The program also makes sure that we can write to the file, by trying to create an empty file first. -
Finally, let's study the latest version of the program.
git checkout master
vim sys_info.sh
sys_info.final.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() {
if [[ "$(id -u)" -eq 0 ]]; then
cat <<- _EOF_
<h2>Home Space Utilization (All Users)</h2>
<pre>$(du -hs /home/*)</pre>
_EOF_
else
cat <<- _EOF_
<h2>Home Space Utilization ($PWD)</h2>
<pre>$(du -hs "$PWD")</pre>
_EOF_
fi
return
}
# call the main function
main "$@":set tabstop=8
Notice that we have placed almost all the code inside a function. There is a function
main()
that calls some other functions, then these functions call some other ones, and so on.The
main()
function is called at the very end of the program, like this:main "$@"
This makes sure that all the parameters given to the program are passed to the main function. The main function in turn passes all of them to the function
process_options
, like this:process_options "$@"