The easiest way to automate things respectively formulate repetitive tasks is the use of a bash script - simple but powerful. But is it really that simple?
Sure, it isn't! Beside printing some numbers bash scripts may become destructive by deleting files and directories or modifying database contents.
1 2 3 4 | #!/bin/bash for i in {1..5}; do print "$i" done |
So, you would be well advised to be careful when writing your scripts. To ease your life I will provide you with some noteworth best practices for robust shell scripts.
Usage
Do you remember this situation? You needed to provide some updates for a software you delivered months ago. So, you checked out the project from your version control, made you changes and had to put your mighty bash script to work which performs some whatever-magic-is-required things. But what were that many options and arguments for?
Just calling the script should come up with some usage details at least. Even better: Provide a complete help with all options and examples for complex operations.
Pipefail and $PIPESTATUS
The return value of a pipeline is the return value of the last command usually.
1 | grep -lir foo /bar/baz|sort|xargs |
If sort
or grep
would fail in the above example you would
not notice it until xargs would fail.
By using set -o pipefail
at the beginning of your scripts
errors withing the pipe are no longer hidden from you. The
return value of the pipeline will be only 0 if any commands
exits with 0.
If you do not want to use the pipefail
option there is also
an alternative method, the $PIPESTATUS
array variable. It
contains the exit status of each element of the pipeline.
Exit on error
Use set -o errexit (a.k.a. set -e) to make your script exit when a command fails.
In combination with the pipefail
option the script will
also exit when a command within a pipeline fails.
Bash options afterwards
You should avoid settings bash options right in the shebang,
because the option would not effective when the script is called
with bash ./script.sh
.
Accidental accessing
Use set -o nounset
(a.k.a. set -u
) to exit when your
script tries to use undeclared variables.
1 | declare -r FOO="barbaz" |
This safes you from acessing non-existing variables because of spelling errors.
In combination with errexit
every access to a non-existing
variable would inevitably exit the script, as it triggers
an error.
Upshot
Afterwards your bash script should look like this somehow.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | #!/usr/bin/env bash # # Very sane bash script which helps you doing something useful. # # Author: johndoe@example.com # set -o errexit -o pipefail -o errexit declare -r LOGIN="johndoe" declare -r PASSWORD="secret" usage() { echo "usage: ${0##*/} <email>" exit 1 } do_sth_useful() { local -r EMAIL="$1" echo "Doing something: ${EMAIL}" } main() { [[ -z "$1" ]] && usage || do_sth_useful "$1" } main "$@" |