The motivation here is to remember bash script best practices, and also to create user friendly scripts. There is a very nice gist that goes into detail many of the things I mention here. This article is also very helpful. There are 2 examples here:

General script magic Link to heading

So here is a strange script that covers most of the things that would be helpful:

#!/usr/bin/env bash

set -e # Exit immediately if some command has a non-zero exit code
set -u # Exit with error if you use variables that have not been defined
set -o pipefail # Prevent errors in pipeline from being masked

#set -euo pipefail # This is the commands above combined

#set -x # Uncomment for debugging

# Handy way of getting information about the script itself
SCRIPT_DIR="$(
    cd "$(dirname "$0")" >/dev/null 2>&1
    pwd -P
)"
SCRIPT_NAME=$(basename $0)
SCRIPT_PATH="$SCRIPT_DIR/$SCRIPT_NAME"

# Nice logging functions with colorized output
log-lines() { for a in "$@"; do printf "\n  $a" 1>&2 ;done ;printf "\n" 1>&2 ;}
log-info() { printf "$(tput setaf 2)INFO$(tput sgr0): $1" 1>&2; log-lines "${@:2}"; }
log-warn() { printf "$(tput setaf 3)WARN$(tput sgr0): $1" 1>&2; log-lines "${@:2}"; }
log-error() { printf "$(tput setaf 1)ERROR$(tput sgr0): $1" 1>&2; log-lines "${@:2}"; exit 1; }
highlight() { d="";	for a in "$@"; do printf "$(tput bold)$(tput setaf 5)$d$a$(tput sgr0)";	d=" "; done; }

# Function to print out error message and usage examples
usage() {
    cat 1>&2 <<EOF
Usage: $SCRIPT_NAME NAME AGE

Examples:
  $ $SCRIPT_NAME Bob 10

EOF
    (log-error "")
    echo -n "  " 1>&2
}

# Easy way for making arguments mandatory
name=${1:?"NAME <- missing parameter $(usage)"}
age=${2:?"AGE <- missing parameter $(usage)"}

# Example of how to use the logging functions
log-info "Running script with parameters:" "SCRIPT_DIR: $SCRIPT_DIR" "SCRIPT_NAME: $SCRIPT_NAME" "SCRIPT_PATH: $(highlight $SCRIPT_PATH)" "NAME: $name" "AGE: $age"

# How to run some cleanup when the script exits
function finish() {
    log-warn "Exiting script"
}
trap finish EXIT

# Creation of arrays
addresses=(
    "738 Ferrell Street"
    "1237 Mapleview Drive"
    "2070 Cambridge Court"
)

# Change internal field seperator to be less eager
IFS=$'\n\t'
for address in ${addresses[@]}; do
    log-info "$address"
done

# Overriding argument to the script
set -- echo Override Arguments

# Execute arguments
$@

# Log error and exit
log-error "Example of failure in the script"

# Replace current shell with command. Has the effect that the finish function is never executed
exec "$@"

Now running the script with no parameters will give you an nice error message:

$ ./script.sh
Usage: script.sh NAME AGE

Examples:
  $ script.sh Bob 10

ERROR: 
  ./script.sh: line 40: 1: NAME <- missing parameter

And running it with the proper parameters gives you something like this:

$ ./script.sh Bob 10
INFO: Running script with parameters:
  SCRIPT_DIR: /home/bob
  SCRIPT_NAME: script.sh
  SCRIPT_PATH: /home/bob/script.sh
  NAME: Bob
  AGE: 10
INFO: 738 Ferrell Street
INFO: 1237 Mapleview Drive
INFO: 2070 Cambridge Court
Override Arguments
ERROR: Example of failure in the script
WARN: Exiting script

Sophisticated argument parsing Link to heading

Sometimes you need more fine grained controll over your argument parsing. Then this is a nice approach:

#!/usr/bin/env bash

set -euo pipefail

SCRIPT_NAME=$(basename $0)

# Nice logging functions with colorized output
log-lines() { for a in "$@"; do printf "\n  $a" 1>&2 ;done ;printf "\n" 1>&2 ;}
log-error() { printf "$(tput setaf 1)ERROR$(tput sgr0): $1" 1>&2; log-lines "${@:2}"; exit 1; }

usage() {
    cat 1>&2 <<EOF
Usage: $SCRIPT_NAME [OPTIONS] FILE LINENR
Print out a specific line number in a file.

Options:
  -i, --iter=  Number of times to print line [default: 1]
  -h, --help   Print this dialog

Examples:
  $ $SCRIPT_NAME /etc/hosts 1
  $ $SCRIPT_NAME /etc/hosts 1 -i 12
EOF
    if [ ! -z $1 ]; then
        echo "" 1>&2
        (log-error "")
        echo -n "  " 1>&2
    fi
    exit 1
}

# Set default iter to 1
ITER=1

# Iterate over arguments and parse them
while [ "$#" -gt 0 ]; do
    case $1 in
    -h | --help)
        usage
        ;;
    -i)
        shift
        ITER="$1"
        ;;
    --iter=*)
        ITER=$(echo $1 | awk '{split($0,r,"="); print r[2]}')
        ;;
    *)
        if [ "${FILE:-unset}" == "unset" ]; then FILE=$1
        elif [ "${LINENR:-unset}" == "unset" ]; then LINENR=$1
        fi
        ;;
    esac
    shift
done

# Make sure required parameters are set
FILE=${FILE:?"<- parameter missing $(usage FILE)"}
LINENR=${LINENR:?"<- parameter missing $(usage LINENR)"}

# Run iterations
for i in $(seq $ITER); do
    sed -n ${LINENR}p $FILE
done

Now you have a very nice interface:

$ ./script.sh
Usage: script.sh [OPTIONS] FILE LINENR
Print out a specific line number in a file.

Options:
  -i, --iter=  Number of times to print line [default: 1]
  -h, --help   Print this dialog

Examples:
  $ script.sh /etc/hosts 1
  $ script.sh /etc/hosts 1 -i 12

ERROR: 
  ./script.sh: line 58: FILE: <- parameter missing

And using correct parameters:

$ ./script.sh /etc/hosts 1 -i 4
# Host addresses
# Host addresses
# Host addresses
# Host addresses