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 fileor--file file.
- Tell it to ask interactively for a filename for saving the
output. This option should be specified by -ior--interactive.
- Use the options -hor--helpto 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.tgzcd sys_info/ls -algit log --onelinegit tag
- 
Let's get first the initial version of the script (that was developed in a previous lesson): git checkout -q 1.initialgit statusvim sys_info.shsys_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_pagegit diff 1.initialgit diff 1.initialdiff --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.usagegit diff 2.write_html_pagegit diff 2.write_html_pagediff --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_optionsgit diff 3.usagegit diff 3.usagediff --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 whileloop andshiftto process all the options. Inside the loop we usecaseto 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 variableinteractiveto1(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 -ior (--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.interactivegit diff 4.process_optionsgit diff 4.process_optionsdiff --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 interactiveis not empty. There is an infinitewhileloop 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_pagegit diff 5.interactivegit diff 5.interactivediff --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 filenameis 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 mastervim sys_info.shsys_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=8Notice 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 "$@"