Bash Scripting

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

Running

  • Can run directly from the command line e.g. .my_script.sh
  • 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

Linting

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/myscript.sh

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.

Logging

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 ./myscript.sh 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() {
    arg=$1
    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.

Arguments

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.

Sample

 #!/bin/bash w
 # options explanation at http://redsymbol.net/articles/unofficial-bash-strict-mode/
set -euo pipefail
#IFS=$'\n\t'

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

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

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

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

parse_arguments() {
    local OPTIND
    while getopts :hc: opt; do
    case $opt in
        h)
            display_usage
            exit 0
            ;;
        c)
            config_file_path=${OPTARG}
            ;;
        \?) 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
            ;;
    esac
done
echo $config_file_path
}

check_mandatory_argument "arg1"
check_mandatory_argument "arg2"

results matching ""

    No results matching ""