johnslattery
8/25/2016 - 3:05 PM

Bash template. Probably appropriate for moderately or more involved scripts.

Bash template. Probably appropriate for moderately or more involved scripts.

#!/bin/bash

# Description.

a_function () {
  # Function description.
  
  # Arguments
  # $1 Description.
  # $2 Really
  #    long description.
  
  local arg1="$1"
  local -i arg2="$2"
     
  # Function body.
}

configure() {
  # Configure the process.

  # The order of configuration:
  # - Script global configuration variable declaration and initialization.
  # - Configuration files.
  #   - System configuration file.
  #   - User configuration file.
  #   - Special purpose configuration file.
  # - Environment variables overriding variable assignments in script or
  #   configuration files. The value of the environment variable should only
  #   be relevant at the time of configuration.
  # - Command line arguments.

  # Arguments
  # $1 Script file path.
  # $2 Script command line arguments.

  # Declare and initialize global configuration variables.

  # Readonly string requiring no option, of course:
  declare -gr var_readonly="value_var_readonly"

  # Flag with default value of true:
  declare -gi var_bool=0
  # Flag with default value of false:
  #declare -gi var_bool=1

  # String with required value:
  declare -g var_required="some value"

  # String with required value, not null:
  declare -g var_required_not_null="some value"

  # Allow environment variable to override assignement.
  declare -g var_env_override="${ENV_VAR:-"some value"}"

  # Flag with optional, associated value.
  declare -gi var_bool_optional_value=0
  declare -g var_bool_optional_associated_value="some value"

  # Canonicalize arguments with getopt.
  local script_file="$1"
  shift 1
  local args=
  args="$(getopt --name "$script_file" --options bBr:n:e:o::O \
    --longoptions conf-file:,no-sys-conf-file,no-usr-conf-file \
    \
    --longoptions var-bool,no-var-bool \
    --longoptions var-required: \
    --longoptions var-required-not-null: \
    --longoptions var-env-override: \
    --longoptions var-bool-optional-value::,no-var-bool-optional-value \
    \
    -- "$@")" \
    || return
  
  # Evaluate configuration file options to determine which files will be
  # applied, if any.

  # Configuration file variables.
  local -r conf_filename_base="$(basename "$script_file")"
  local -r sys_conf_file="/etc/$conf_filename_base" 
  local -r usr_conf_file=~/".$conf_filename_base.conf"
  local conf_file=

  eval set -- "$args"
  while true; do
    case $1 in

      # Don't read the system configuration file.
      --no-sys-conf-file )
        sys_conf_file=
        shift
        ;;

      # Don't read the user configuration file.
      --no-usr-conf-file )
        usr_conf_file=
        shift
        ;;

      # Special purpose configuration file.
      --conf-file )
        conf_file="${2:?"Option '$1' requires a non-null argument."}"
        shift 2
        ;;

      # Skip non-option arguments and break when done.
      -- )
        shift
        until [[ $# -eq 0 ]]; do
          shift
        done
        break
        ;;

      # Skip other options and option arguments.
      * )
        shift
        ;;

    esac
  done

  # Source system configuration file.
  [[ -f $sys_conf_file ]] && {
    [[ $sys_conf_file == \
      $(find "$sys_conf_file" -maxdepth 0 -type f -user root \
      ! -perm /022) ]] || {
        printf "%s: System configuration file '%s' must be owned by root and writable by no user other than root.\n" \
        "$FUNCNAME" "$sys_conf_file" >&2
        return 1
    }
    source "$sys_conf_file" || return
  }

  # Source user configuration file.
  [[ -f $usr_conf_file ]] && {
    [[ -O $usr_conf_file && $usr_conf_file == \
      $(find "$usr_conf_file" -maxdepth 0 ! -perm /022) ]] || {
        printf "%s: User configuration file '%s' must be owned by the user and writable by no user other than the user\n" \
          "$FUNCNAME" "$usr_conf_file" >&2
        return 1
    }
    source "$usr_conf_file" || return
  }

  # Source user special configuration file.
  [[ $conf_file ]] && {
    [[ -f $conf_file && -O $conf_file && $conf_file == \
      $(find "$conf_file" -maxdepth 0 ! -perm /022) ]] || {
        printf "%s: User special configuration file '%s' must exist, be owned by the user, and be writable by no user other than the user.\n" \
          "$FUNCNAME" "$conf_file" >&2
        return 1
    }
    source "$conf_file" || return
  }

  # Evaluate the remainder of commnad line arguments.
  local -a non_option_args=()
  eval set -- "$args"
  while true; do
    case $1 in

      # Skip configuration file options and option arguments as they have
      # already been evaluated.
      --conf-file ) shift 2 ;;
      --no-sys-conf-file | --no-usr-conf-file) shift 1 ;;

      # Process options, option arguments, and non-option arguments.

      # Flag
      -b | --var-bool )
        var_bool=0
        shift
        ;;

      -B | --no-var-bool )
        var_bool=1
        shift
        ;;

      # Required value:
      -r | --var-required )
        var_required="$2"
        shift 2
        ;;

      # Required value, not null:
      -n | --var-required-not-null )
        var_required_not_null=\
          "${2:?"Option '$1' requires a non-null argument."}"
        shift 2
        ;;

      # Environment variable overrides configuration, not command line
      # arguments.
      -e | --var-env-override )
        var_env_override="$2"
        shift 2
        ;;

      # Flag with optional value.
      -o | --var-bool-optional-value )
        var_bool_optional_value=0
        var_bool_optional_associated_value="$2"
        shift 2
        ;;

      -O | --no-var-bool-optional-value )
        var_bool_optional_value=1
        # Ignore null in $2.
        shift 2
        ;;

      -- )
        shift
        until [[ $# -eq 0 ]]; do
          non_option_args+=("$1")
          shift
        done
        break
        ;;

      # In case we forget to handle an option.
      -? | --* )
        printf "%s: Option '%s' is unhandled.\n" "$FUNCNAME" "$1" >&2
        return 1
        ;;

    esac
  done

}

main () {
  # Call to this function is entry point.
  
  # Arguments
  # $1 Script file path.
  # $2 Command line arguments.
  
  configure "$@" || return
  
  # Body, function calls.
}

main "$0" "$@"