Bash scripting

A template, some notes and links about Bash scripts

Published on updated on

This post contains a practical template script and some useful links for Bash scripts.

and my posts related to Bash.

Important note: The script is now hosted in my Bash Scripts repository. Check the repo for the current version of the script. Any modification will be pushed there, this page will remain unmodified.

Template script

#!/bin/bash
#
# Template script that handle options and arguments
#
# Version: 0.9.1
# Copyright (C) 2023 Calin Radoni
# License GNU GPLv3 (https://choosealicense.com/licenses/gpl-3.0/)

# Initialize all option and argument variables, this operation
# prevents a possible contamination from the environment.
# These variables are set by the 'parse_options' function.
declare -a ARGS=()
declare -i verbose=0
declare infile=''

# Print a message then exit the program
# Arguments:
#   - exit code (optional), defaults to 1.
#     If the passed exit code is not numeric an error message is printed.
#   - message (optional), requires exit code to be provided.
exit_with_message() {
    declare exit_code="${1:-1}"
    if [[ -n "$2" ]]; then
        printf -- '%s\n' "$2" >&2
    fi
    if [[ "$exit_code" != +([[:digit:]]) ]]; then
        printf 'Incorrect exit code!\n' >&2
        exit 1
    fi
    exit "$exit_code"
}

# Show the usage (help) for this script
show_usage() {
    cat << EOF
Usage: ${0##*/} [-h] [-v] [-f INFILE] [ARGs] ...
Description of what this script does
Options:
    -h, --help         display this help message and exit
    -v, --verbose      verbose mode. Use multipletimes for increased verbosity
    -f, --file INFILE  select INFILE as the input file
EOF
}

# Parse options and their arguments
# Globals:
#   ARGS will hold the arguments
# Arguments:
#   the options to be parsed
# Example:
#   parse_options "$@"
parse_options() {
    while :; do
        case $1 in
            -h|--help)
                show_usage
                exit 0
                ;;
            -v|--verbose)
                ((verbose++));;
            -f|--file)
                if [[ -z "$2" ]]; then
                    exit_with_message 1 "[$1] needs an argument!"
                    exit 1
                fi
                if [[ "$2" == '--' ]]; then
                    exit_with_message 1 "[$1] needs an argument!"
                    exit 1
                fi
                infile="$2"
                shift
                ;;
            --) # explicit end of all options, break out of the loop
                shift
                break
                ;;
            -?*)
                exit_with_message 1 "[$1] is an invalid option!"
                exit 1
                ;;
            *)  # this is the default processing case
                # there are no more options, break out of the loop
                break
        esac
        shift
    done

    if (($# > 0)); then
        ARGS=("$@")
    else
        ARGS=()
    fi
}

parse_options "$@"

# demo usage code start

if ((verbose > 0)); then
    printf 'Verbosity level is set to %d\n' "$verbose"
fi

if [[ -n "$infile" ]]; then
    printf 'The input file is %s\n' "$infile"

    # test if the file is readable (use -w to test if the file is writable)
    if [[ ! -r "$infile" ]]; then
        printf -- '%s is not readable !\n' "$infile" >&2
    fi
fi

if ((${#ARGS[@]} > 0)); then
    printf 'The are %d remaining arguments:\n' "${#ARGS[@]}"
    printf -- '%s\n' "${ARGS[*]}"
fi
for arg in "${ARGS[@]}"; do
    printf '<%s>\n' "$arg"
done
for ((i=0; i<"${#ARGS[@]}"; i++)); do
    printf '%d: <%s>\n' "$i" "${ARGS[$i]}"
done

# demo usage code end

exit 0

Running the script with these options and arguments: -v -v -f aaa bbb ccc or -v -v -f aaa -- bbb ccc will output:

Verbosity level is set to 2
The input file is aaa
aaa is not readable !
The are 2 remaining arguments:
bbb ccc
<bbb>
<ccc>
0: <bbb>
1: <ccc>

Using the script

1. Declare variables for options and for arguments expected by options. In the template script the declarations are:

declare -i verbose=0
declare infile=''

Use declare:

  • declare -i for integers
  • declare -a for indexed arrays
  • declare -A for associative arrays
  • declare for other types

2. Update the show_usage function for your options and arguments.

3. Update the parse_options function for your options and arguments.

4. Replace the demo usage code with your code.

1. As with any programming language try to follow a coding style guide for that language. Here is the Google’s Shell Style Guide

2. Use a static code analysis tool, a linter, to check your code. ShellCheck or ShellCheck is a GPLv3 tool that gives warnings and suggestions for bash/sh shell scripts.

3. Start with the basics, do not reinvent the wheel, evolve as needed.

From the huge number of available resources: