In the real world, when you write code, you either maintain it forever or someone takes ownership of it later and makes changes into it. It is very important that you write a good quality shell script so that it's easier to maintain it further. It is also important that the shell script is bug-free in order to get the work done as expected. Scripts running on production systems are very critical because any error or wrong behavior of the script may cause minor or major damage. To solve such critical issues, it is important to get it fixed as soon as possible.
In this chapter, we will see how we can write modular and reusable code so that maintaining and updating our shell script application can be done quickly and without any hassle. We will also see how easily and quickly bugs in shell scripts can be solved using different debugging techniques. We will see how we can provide our users different choices for different tasks by providing support for command line options in a script. The knowledge of how to provide command line completion in a script will even increase the ease of using the script.
This chapter will cover the following topics in detail:
While writing a shell script, there is one stage when we feel that a shell script file has become too big to read and manage. To avoid such a scenario in our shell script, it is very important to keep the script modular.
In order to keep the script modular and maintainable, you can do the following:
We have already seen how to define and use a function in Chapter 3, Effective Script Writing. Here, we will see how to divide a bigger script into smaller shell script modules and then use them by sourcing. In other words, we can say creating libraries in bash
.
Source is a shell built in command that reads and executes a script file in the current shell environment. If a script calls a source on another script file, all functions and variables available in that file will be loaded for use in calling script.
The syntax of using the source is as follows:
source <script filename> [arguments]
OR:
. <script filename> [arguments]
The script filename
can be with or without a path name. If the absolute or relative path is provided, it will look only into that path. Otherwise, a filename will be searched in the directories specified in the PATH
variable.
The arguments
are treated as positional parameters to the script filename.
The exit status of the source
command will be the exit code of the last command executed in the script filename. If the script filename doesn't exist or there is no permission, then the exit status will be 1
.
A library provides a collection of features that can be reused by another application without rewriting from scratch. We can create a library in shell by putting our functions and variables to be reused in a shell script file.
The following shell_library.sh
script is an example of a shell library:
#!/bin/bash # Filename: shell_library.sh # Description: Demonstrating creation of library in shell # Declare global variables declare is_regular_file declare is_directory_file # Function to check file type function file_type() { is_regular_file=0 is_directory_file=0 if [ -f $1 ] then is_regular_file=1 elif [ -d $1 ] then is_directory_file=1 fi } # Printing regular file detail function print_file_details() { echo "Filename - $1" echo "Line count - `cat $1 | wc -l`" echo "Size - `du -h $1 | cut -f1`" echo "Owner - `ls -l $1 | tr -s ' '|cut -d ' ' -f3`" echo "Last modified date - `ls -l $1 | tr -s ' '|cut -d ' ' -f6,7`" } # Printing directory details function print_directory_details() { echo "Directory Name - $1" echo "File Count in directory - `ls $1|wc -l`" echo "Owner - `ls -ld $1 | tr -s ' '|cut -d ' ' -f3`" echo "Last modified date - `ls -ld $1 | tr -s ' '|cut -d ' ' -f6,7`" }
The preceding shell_library.sh
shell script contains the is_regular_file
and is_directory_file
global variables that can be used to know whether a given file is a regular file or directory after invoking the file_type()
function. Furthermore, depending upon the type of the file, useful detailed information can be printed.
Creating shell libraries are of no use unless it is used in another shell script. We can either use a shell script library directly in shell or within another script file. To load a shell script library, we will use the source command or. (period character) followed by shell script library.
To use the shell_library.sh
script file in shell, we can do the following:
$ source shell_library.sh
OR:
$ . shell_library.sh
Calling any of them will make functions and variables available for use in the current shell:
$ file_type /usr/bin $ echo $is_directory_file 1 $ echo $is_regular_file 0 $ if [ $is_directory_file -eq 1 ]; then print_directory_details /usr/bin; fi Directory Name - /usr/bin File Count in directory - 2336 Owner - root Last modified date - Jul 12
When the file_type /usr/bin
command is executed, the file_type()
function with the /usr/bin
parameter will be called. As a result, the global variable is_directory_file
or is_regular_file
will get set to 1
(true
), depending upon the type of the /usr/bin
path. Using the shell if
condition, we test whether the is_directory_file
variable is set to 1
or not. If set to 1
, then call the print_directory_details()
function with /usr/bin
as a parameter to print its details.
The following example explains the usage of the shell library in a shell script file:
#!/bin/bash # Filename: shell_library_usage.sh # Description: Demonstrating shell library usage in shell script # Print details of all files/directories in a directory echo "Enter path of directory" read dir # Loading shell_library.sh module . $PWD/shell_library.sh # Check if entered pathname is a directory # If directory, then print files/directories details inside it file_type $dir if [ $is_directory_file -eq 1 ] then pushd $dir > /dev/null # Save current directory and cd to $dir for file in `ls` do file_type $file if [ $is_directory_file -eq 1 ] then print_directory_details $file echo elif [ $is_regular_file -eq 1 ] then print_file_details $file echo fi done fi
The output after running the shell_library_usage.sh
script is as follows:
$ sh shell_library_usage.sh # Few outputs from /usr directory Enter path of directory /usr Directory Name - bin File Count in directory - 2336 Owner - root Last modified date - Jul 12 Directory Name - games File Count in directory - 0 Owner - root Last modified date - Aug 16 Directory Name - include File Count in directory - 172 Owner - root Last modified date - Jul 12 Directory Name - lib File Count in directory - 603 Owner - root Last modified date - Jul 12 Directory Name - lib64 File Count in directory - 3380 Owner - root Last modified date - Jul 12 Directory Name - libexec File Count in directory - 170 Owner - root Last modified date - Jul 7
To load a shell script library, use source
or .
followed by script_filename
.
Both source and .
(period character) execute a script in the current shell. ./script
is not the same as . script
because ./script
executes the script in a subshell, while . script
executes in a shell from where it was invoked.
3.17.110.113