Bash Scripting

  • Create the file with a .sh extension.
  • Chmod with some user +x to enable execution


  • Can run directly from the command line e.g.
  • Can run using bash command. This is useful for passing flags e.g. bash -x my_script

Set Options

  • set -x - this displays each line that is executed
  • set -u - this does not allow unbound variables
  • set -e - aborts the script upon encountering an error
  • set -o pipefail - raises an error if any part of a pipeline command fails
  • Reference - Bash's unofficial strict mode


ShellCheck is a linter for bash scripts. This is a Haskell program and can be installed through the package manager.

If this is not available on your OS e.g. early versions of CentOS then they distribute it via Docker images.

docker pull koalaman/shellcheck
docker run -v $(pwd):/scripts koalaman/shellcheck /scripts/

Need to invoke this with the -x argument to make it follow scripts that were sourced by the current script.

To enable this option in Vim then add let g:syntastic_sh_shellcheck_args = "-x" to your .vimrc. See here.

Google has a useful style guide.


In order to prevent logging info getting mixed up with stdout or stderr we can log to a different bash file descriptor. Then the caller of the script can chose where to redirect the log to.

log() {
    # If a file descriptor exists for stream 3 then log to it
    if command >&3
    then datestring=$(date +'%Y-%m-%d %H:%M:%S')
         echo "$datestring $1" >&3
    fi 2> /dev/null

Invoke the script using ./ 3>log.txt

Functions in Bash

You can create functions in bash but getting output from them is a bit tricky. The return statement in bash just returns a status code from the function. Instead you echo the output you want to stdout. The calling code wraps the call to the function with $( ) and assigns it to a variable. Another way to get information out of a function is to assign values to global variables.

my_func() {
    echo "Output $arg"
    echo "More output"

my_var=$(my_func "arg1")

Source File Organization

You can include scripts from other scripts using the source keyword. This will execute the other script in place. All the function definitions are imported and all non function statements are executed.


Can use getopts to check mandatory command line arguments.

For lots of arguments it may be worth sourcing a config file instead. This allows the same params to be used by multiple different scripts.


 #!/bin/bash w
 # options explanation at
set -euo pipefail

display_usage() {
    cat <<EOF
    Usage: $0 [options]

    -h    Display the help text
    -c    Path to the config file

check_mandatory_argument() {
    if [[ -z "${!1:-}" ]]; then
        echo "Missing required argument ${1}." >&2
        display_usage >&2
        exit 1

check_mandatory_config_option() {
    if [[ -z "${!1:-}" ]]; then
        echo "Config File is missing required option ${1}." >&2
        display_usage >&2
        exit 1

parse_arguments() {
    local OPTIND
    while getopts :hc: opt; do
    case $opt in
            exit 0
        \?) echo "An invalid option has been entered: $OPTARG"
            display_usage >&2
            exit 1
            echo "Option -$OPTARG requires an argument." >&2
            display_usage >&2
            exit 1
            display_usage >&2
            exit 1
echo $config_file_path

check_mandatory_argument "arg1"
check_mandatory_argument "arg2"

results matching ""

    No results matching ""