Tips and tricks for high-quality bash scripting
Bash is a very popular shell, especially in Linux environments. Having a good knowledge of bash can get you far in terms of getting things done. Especially if you are a System Admin or a DevOps engineer or anyone who likes working with command line/terminal, knowledge of shell scripting is a must. The official documentation for bash is available at https://www.gnu.org/software/bash/manual/html_node/index.html
Almost all the best practices when it comes to a procedural programming language like modularity, logging, documentation, error-handling etc, apply to bash too. However, here I will outline a few techniques that are very specific to bash scripting.
Using set
command effectively
set
is a command in bash that lets us change the shell options' values. Full documentation for the set
command is available at https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html
The most commonly used options in set
command are:
The
set -e
command in Bash instructs the shell to exit immediately if any command within the script returns a non-zero exit status. This is useful to ensure that the script stops executing if an error occurs. Conversely,set +e
disables this behavior, allowing the script to continue even if a command fails. This is helpful in implementing try/catch-like logic within your script. Try/catch is not available in bash out of the box. Here is a great example of the try/catch-like functions.The
set -x
the command in bash is very helpful while debugging your script. This enables the trace mode where it prints out all the commands along with the values any variable that is used in the command might have. Conversely,set +x
command suppresses printing out the commands.
Use variable quotes
It is always advisable to use quotes when using variables especially if your variable value involves space or any special characters. When variables are not enclosed in quotes, Bash performs word splitting and interprets certain characters as a command or shell syntax. Here is an example
#!/bin/bash
# Variable without quotes
name=John Smith
# Without quotes, Bash performs word splitting
echo Hello, $name
# Output: Hello,
# Variable with double quotes
name="John Smith"
# With quotes, the variable value is preserved
echo "Hello, $name"
# Output: Hello, John Smith
Sourcing other scripts within your script
If you need to access a function or content from another script, you use source
command. This is a common practice. However, many times we make the mistake of sourcing the other script using a relative path. This causes problems when your script is invoked from a different path. For example, Let's consider these two scripts. sourced.sh
is a script we source in a script called example_source.sh
Here is the content of sourced.sh
#! /bin/bash
function hello_world(){
echo "Hello $(whoami)"
}
And here is example_source.sh
#! /bin/bash
source ./sourced.sh
hello_world
Let us assume we store this script in the same directory called as src
If we run the bash script example_source
from within src
directory then the script will run fine. But if we run the script from any other path this script is bound to fail with the following error.
bash example_source.sh
bash: example_source.sh: No such file or directory
This common pitfall can be avoided with a simple trick. Now let's modify example_source.sh
#! /bin/bash
cur_dir=$(dirname $0)
source $cur_dir/sourced.sh
hello_world
This will now allow you to run the script example_source.sh
from anywhere within the machine.
Handle secrets within a script
When it comes to secrets the same principles that apply to any programming language apply here too. Particularly
Avoid hardcoding secrets within your scripts. Use environment variables, configuration files, or even secret managers like Hashicorp's Vault, AWS Secret Manager, etc.
Make sure the secret values are never printed in the terminal. If required use
set +x
command wherever secrets are accessed and referred to.Keep track of actions involving secrets and implement logging and monitoring to detect any unauthorized access attempts or unusual activity.
Error Handling
We have seen some techniques of error handling through set -/+ e
commands. In this section, we will see some more techniques for error handling
- Using return code through special variable
$?
- This can tell us if the command was executed successfully or not. One can combine this withif
statements.
#! /bin/bash
set +e
<some command/s that errors out>
exit_status=$?
if [ $exit_status -eq 0 ]; then
echo "Command succeeded"
else
echo "Command failed with exit status: $exit_status"
fi
- Using
$$
and||
for conditional executions.
command1 && command2 # Run command2 only if command1 succeeds
command1 || command2 # Run command2 only if command1 fails
- Use of
trap
command. This can be used to define your error-handling routines. Here is an example:
handle_error() {
echo "An error occurred. Exiting..."
exit 1
}
trap 'handle_error' ERR
- Effectively using
echo
for standard output andstderr
for standard error output streams
This blog would be incomplete without the mention of code linting for the bash. There are tools like shellcheck. This is incredibly helpful when you want to lint or check your shell script for any syntax errors. It also checks your script for formatting and adherence to best practices.
These are some of the commonly used techniques for creating well-structured Bash scripts. If there are any additional techniques that you frequently use that I might have missed here, please feel free to add them in the comments section.