Overview
In this entry we present a template file for a shell script that can be tailored to your needs. For other languages, the logic is the same.
Templates are handy starting points that provide a basic framework for code files. A well-designed template is modular, reusable and simplifies coding.
Benefits
Done correctly, a template helps you:
- Focus on functionality.
- Streamline workflow.
- Structure code and improve overall quality.
- Improve code maintenance.
Generic code: Common re-usable elements such as predefined variables and functions. This is code that is common to all scripts and can be adapted for different purposes. For example, a variable for the script name.
Readability / Maintainability / Uniformity: Templates promote a consistent coding style and structure across your scripts, making them easier to understand and maintain for yourself and others.
Automation / DevOps: Templates can be used in conjunction with configuration management tools like Ansible or Puppet. These tools can dynamically generate shell scripts based on pre-defined templates and configuration data. This allows for automated script creation and deployment based on specific needs.
Portability
For portability, follow these guidelines:
- It’s best to use
/bin/sh
(Bourne shell). It exists on all Unix/Unix-like systems. While Bash is portable, it is not installed on all platforms. - Write minimally safe scripts with a preference for shell built-ins over system commands.
- If you do use system command (like
sed
orawk
), be sure to test across all platforms – as there are implemenation differences. - Be sure to test against all vendor
/bin/sh
implementations. - Use this handy online tool: ShellCheck. Note, you can also pass
--shell=sh
to ShellCheck.
Template
This boilerplate helps you quickly get started writing robust quality scripts. Adapt it to your needs, but only include what’s necessary – having too much boilerplate code may be unproductive.
Interpreter
Specify the interpreter in the first line of your script. This is called the shebang (or hasbang) line.
The #!
is a directive that indicates the the file should be considered an executable and that it should be executed using the specified interpreter that follows:
#!/path/to/shell
Or:
#!/usr/bin/env bash
Always add a shebang line, especially if you’re using a shell other than Bourne (sh) (e.g., [ ash, bash, dash, ksh, mksh, pdksh, zsh ] / [ csh, tcsh ] / [ fish ]
All Unix and Unix-like systems will have Bourne /bin/sh
shell installed, so there’s no need to use the #!/usr/bin/env
shebang approach if you’re using sh
.
Note, Red Hat Linux sh
is a sym link to Bash:
[root@devlinux:/root]# which sh
/usr/bin/sh
[root@devlinux:/root]# ls -l /usr/bin/sh
lrwxrwxrwx 1 root root 4 May 7 2022 /usr/bin/sh -> bash
If the account’s default shell is Bash, you can use #!/usr/bin/env bash
instead of hard coding the path. This is important if you manage other platforms (AIX, *BSD, HP-UX, Solaris) that have Bash installed in a different path than Linux.
[root@devlinux:/root]# which bash
/usr/bin/bash
[root@devbsd:/root]# which bash
/usr/local/bin/bash
You can list which shells are registered/known by the system with:
[root@devbsd ~]# cat /etc/shells
[...]
/bin/sh
/bin/csh
/bin/tcsh
/usr/local/bin/ksh
/usr/local/bin/bash
/usr/local/bin/rbash
/usr/local/libexec/git-core/git-shell
[...]
Bash installs in a different path on different on FreeBSD:
[root@devbsd ~]# ls -l /usr/bin/bash
ls: /usr/bin/bash: No such file or directory
Column width: A widely accepted practice is to use an 80 character column width.
But the specific reasoning behind this standard is more historical than technical. The following 80 character lines separators have been added for readability.
#-----------------------------------------------------------------------------
$script_name: dynamically assigned from $0
.
$0
is a shell built-in. It is a positional parameters that’s assigned from argv[]
. argv[]
(argument vector), is a character array that holds argument passed to the main function in C and C++ programs that stores the actual arguments passed to the program. argv[0]
always contains the name of the program itself. basename
is a utility that strips away the path prefix.
Commenting:
Comment as necessary, let code speak for itselef.
- It’s unecessary to comment code that’s self-explanatory, For example this adds no inforamtion:
# while loop:
while true
do
: Do something
done
- Do comment code that’s not obvious or unnatural.
- Do comment code that affects other parts of the program or affects program execution flow.
Code written early on in my career has an excessive amount of comments. As I became more proficient, learnig best practices and language-specficic rules – my commenting became more concise over time.
Commenting can serve as a valuable aid, calrifying the thought process and code. It forces you to think things out (architect) as you write (coding with the end of in mind). This can sometimes lead to improving / refactoring code. It can also help you navigating complex logic. If an idea for another function or feature emerges in your mind, you can simply jot it down as a mental note elsewhere in the code and come back to it later.
Description: A short description about what the script does.
Args: Arguments the script accepts.
Output: Script output.
NOTEs: Any notes concerning the script in general that’s worth mentioning.
Version: Semantic versioning x. major version, .y minor version.
CHANGELOG:
A version control section to track script version and changes. Or you can just add this to version control using an SCM like git.
ISO 8601 is a widely-adopted standard for representing time/date stamps in code. Time units are specified in descending order (left-to-right): YYYY-MM-DD (optionally: hour, minute, second).
TODO: A code roadmap.
CONFIG: Script configuration for things like global vars and default settings.
Sub Routines: Our template includes 2 functions that are common to all scripts: Usage and command-line argument handling (via the shell-builtin getopts, not the command getopt). You can add additional functions like basic error handling and logging.
getopts
built-in which closely resembles the C library function getopt(3)
– not the getopt
command.
MAIN: The main body of the script, where execution begins (not mandatory).
EOF: end-of-file, signifies the end of the script (not mandatory).
Following the guidelines above:
#!/usr/bin/env bash
#-----------------------------------------------------------------------------
script_name=`basename $0`
#
# Version: x. major version, .y minor version.
version="2.0"
#
# Description:
# License: BSD-3-Clause - https://opensource.org/license/bsd-3-clause
# Author: XOMedia Support
# https://xomedia.io/unix-script-template/
# Deps: ---
# Args: ---
# Output: ---
# BUGs: ---
# NOTEs: ---
#
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# License:
#------------------------------------------------------------------------------
# Copyright (c) 2024, XOMedia, LLC All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this # list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation and
# /or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors
# maybe used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS”
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# CHANGELOG:
#
# [1.0]
# - 2010-11-14: Initial script.
# [2.0]
# - 2024-05-29: Rewrite.
#
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# TODO:
#
#------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Initializations:
# ------------------------------------------------------------------------------
# Safe and sane environment with POSIX-y behavior:
#set -o posix # Bash
umask 022
#PATH=${PATH}:/sbin:/usr/sbin:/bin:/usr/bin:/usr/contrib/bin:/usr/local/bin
#UNIX95=TRUE
#LANG=C
#export PATH UNIX95 LANG
#
# ------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# Globals / CONFIG:
#------------------------------------------------------------------------------
# DEBUG:
# set -x # Debug
# set -n # Shell syntax check (without execution)
# set -v # Verbose
#f
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# Sub Routines:
#------------------------------------------------------------------------------
usage() {
cat << EOF
USAGE
${script_name} [-hv] [-b <string>] [-g <string>]
<string> First and last name
DESCRIPTION
A script similiar to the traditional “Hello, World! program”.
In Computer Science 101, the first program many students create is a simple
one that outputs an iconic line of text: "Hello World!
OPTIONS
-b name Prints goodbye
-g name Prints hello
-h Help
-v Version info
EXAMPLES
${script_name} -g Jane
Print Hello Jane!
${script_name} -b John
Print Goodbye John!
EOF
}
# Arg handler: Using getopts shell built-in.
getargs() {
if (($# == 0))
then
usage
exit 1
fi
while getopts g:b:hv args
do
case ${args} in
b) GOODBYE=1
name="${OPTARG}"
;;
g) HELLO=1
name="${OPTARG}"
;;
h) usage ;;
v) echo "${script_name} ${version} by XOMedia Support " ;;
?) echo "Invalid option(s)" >&2
usage
exit 1
;;
esac
done
}
#------------------------------------------------------------------------------
# Main:
#------------------------------------------------------------------------------
# Quotes enable getopts to handle spaces.
getargs "${@}"
if ((${HELLO}))
then
echo "Hello ${name}!"
elif ((${GOODBYE}))
then
echo "Goodbye ${name}!"
fi
#EOF
Conclusion
In this post we covered crafting a template that can be reused for scripting.