#!/bin/bash # Author: Steven Baltakatei Sandoval # Description: Template for bash scripts. # Note: Use hide-show-block function to aid readability. (ex: https://www.emacswiki.org/emacs/HideShow ). #== Variable Initialization == #== Global constants == SCRIPT_TTL=10 # Limit script life to this in seconds. PATH="/usr/local/bin/:$PATH" # Add user binary executable directory to PATH. PATH="/opt/bktei:$PATH" # Add 'optional' executable directory to PATH. SCRIPT_HOSTNAME=$(hostname) # Save hostname of system running this script. SCRIPT_VERSION="bktemplate.sh 0.0.0" # Define version of script. Used by function 'showVersion'. SCRIPT_TIME_SHORT="$(date +%Y%m%dT%H%M%S%z)" # Save current date & time in ISO-8601 format (YYYYmmddTHHMMSS+zzzz). SCRIPT_DATE_SHORT="$(date +%Y%m%d)" # Save current date in ISO-8601 format. #==BEGIN Define script parameters declare -Ag appRollCall # Associative array for storing app status (function checkapp()) declare -Ag fileRollCall # Associative array for storing file status (function checkfile()) #==END Define script parameters #== Function Definitions == yell() { echo "$0: $*" >&2; } # Yell, Die, Try Three-Fingered Claw technique; # Ref/Attrib: https://stackoverflow.com/a/25515370 die() { yell "$*"; exit 111; } try() { "$@" || die "cannot $*"; } echoerr() { # Usage: echo [ arguments ] # Description: Prints provided arguments to stderr. # Input: unspecified # Output: stderr # Script function dependencies: none # External function dependencies: echo # Last modified: 2020-04-11T21:16Z # Last modified by: Steven Baltakatei Sandoval # License: GPLv3+ # Ref./Attrib: # [1]: # Roth, James (2010-06-07). ["How to print text to stderr instead of stdout"](https://stackoverflow.com/a/2990533). Licensed CC BY-SA 4.0. echo "$@" 1>&2; # Define stderr echo function. See [1]. return 0; # Function finished. } # Define stderr message function. vbm() { # Usage: vbm "DEBUG:verbose message here" # Description: Prints verbose message ("vbm") to stderr if OPTION_VERBOSE is set to "true". # Input: # - OPTION_VERBOSE variable set by processArguments function. (ex: "true", "false") # - "$@" positional arguments fed to this function. # Output: stderr # Script function dependencies: echoerr # External function dependencies: echo # Last modified: 2020-04-11T23:57Z # Last modified by: Steven Baltakatei Sandoval # License: GPLv3+ # Ref./Attrib: if [ "$OPTION_VERBOSE" = "true" ]; then FUNCTION_TIME=$(date --iso-8601=ns); # Save current time in nano seconds. echoerr "[$FUNCTION_TIME] ""$*"; # Display argument text. fi # End function return 0; # Function finished. } # Verbose message display function. run() { # Usage: run [cmd] # Description: Runs command; reports success if command exit code 0; exits otherwise. # Input: none # Output: stdout # Script function dependencies: none # Last modified: 2020-06-19T22:09Z # Last modified by: Steven Baltakatei Sandoval # License: GPLv3+ # Ref.Attrib: https://stackoverflow.com/a/18880585 cmd_output=$(eval $1) return_value=$? if [ $return_value != 0 ]; then echo "Command $1 failed"; exit -1; else echo "output: $cmd_output"; echo "Command succeeded."; fi return $return_value } # showUsage() { # Usage: showUsage # Description: Displays script usage information. # Input: none # Output: stderr # Script function dependencies: echoerr # External dependencies: bash (5.0.3), echo # Last modified: 2020-05-04T16:11Z # Last modified by: Steven Baltakatei Sandoval # License: GPLv3+ # Ref./Attrib.: echoerr "USAGE:" echoerr " bktemplate.sh [ options ]" echoerr echoerr "OPTIONS:" echoerr " -h, --help" echoerr " Display help information." echoerr echoerr " --version" echoerr " Display script version." echoerr echoerr " -v, --verbose" echoerr " Display debugging info." echoerr echoerr " -o, --output-file [ file ]" echoerr " Specify output file." echoerr echoerr " -i, --input-file [ file ]" echoerr " Specify input file." echoerr echoerr " -o, --output-dir [ directory ]" echoerr " Specify output directory." echoerr echoerr " -i, --input-dir [ directory ]" echoerr " Specify input directory." echoerr # End function return 0; # Function finished. } # Display information on how to use this script. showVersion() { # Usage: showVersion # Descriptoin: Displays script version and license information. # Input: unspecified # Output: stderr # Script function dependencies: echoerr # External function dependencies: echo # Last modified: 2020-04-11T23:57Z # Last modified by: Steven Baltakatei Sandoval # License: GPLv3+ # Ref./Attrib: # Initialize function vbm "DEBUG:showVersion function called." # Perform work OUTPUT="$SCRIPT_VERSION" # Display results echoerr "$OUTPUT"; # End function vbm "DEBUG:showVersion function ended." return 0; # Function finished. } # Display script version. processArguments() { # Usage: processArguments "$@" # Description: Processes provided arguments in order to set script option variables useful for # changing how other functions behave. For example, it may: # 1. Activate verbose mode # Input: "$@" (list of arguments provided to the function) # Output: Sets following variables used by other functions: # OPTION_VERBOSE Indicates verbose mode enable status. (ex: "true", "false") # DIROUT1 Path to output directory. # FILEOUT1 Path to output file. # DIRIN1 Path to input directory. # FILEIN1 Path to input file. # OPTION_FILEOUT1_OVERWRITE Indicates whether file FILEOUT1 should be overwritten (ex: "true", "false') # Script function dependencies: # - echoerr Displays messages to stderr. # - vbm Displays messsages to stderr if OPTION_VERBOSE set to "true". # External dependencies: bash (5.0.3), echo # Last modified: 2020-05-04T14:41Z # Last modified by: Steven Baltakatei Sandoval # License: GPLv3+ # Ref./Attrib.: # [1]: Marco Aurelio (2014-05-08). "echo that outputs to stderr". https://stackoverflow.com/a/23550347 # Initialize function #vbm "DEBUG:processArguments function called." # Perform work while [ ! $# -eq 0 ]; do # While number of arguments ($#) is not (!) equal to (-eq) zero (0). #1>&2 echo "DEBUG:Starting processArguments while loop." # Debug stderr message. See [1]. #1>&2 echo "DEBUG:Provided arguments are:""$*" # Debug stderr message. See [1]. case "$1" in -h | --help) showUsage; exit 1;; # Display usage. --version) showVersion; exit 1;; # Show version -v | --verbose) OPTION_VERBOSE="true"; vbm "DEBUG:Verbose mode enabled.";; # Enable verbose mode. See [1]. -i | --input-file) # Define input file path if [ -f "$2" ]; then # If $2 is file that exists, set FILEIN1 to $2, pop $2. FILEIN1="$2"; shift; vbm "DEBUG:Input file FILEIN1 set to:""$2"; else echoerr "ERROR: Specified input file does not exist:""$2"; echoerr "Exiting."; exit 1; fi ;; -I | --input-dir) # Define input directory path if [ -d "$2" ]; then # If $2 is dir that exists, set DIRIN1 to $2, pop $2. DIRIN1="$2"; shift; vbm "DEBUG:Input directory DIRIN1 set to:""$2"; else # Display error if $2 is not a valid dir. echoerr "ERROR:Specified input directory does not exist:""$2"; echoerr "Exiting."; exit 1; fi ;; -o | --output-file) # Define output file path if [ -f "$2" ]; then # If $2 is file that exists, prompt user to continue to overwrite, set FILEOUT1 to $2, pop $2. echoerr "Specified output file $2 already exists. Overwrite? (y/n):" read m; case $m in y | Y | yes) OPTION_FILEOUT1_OVERWRITE="true";; n | N | no) OPTION_FILEOUT1_OVERWRITE="false";; *) echoerr "Invalid selection. Exiting."; exit 1;; esac if [ "$OPTION_FILEOUT1_OVERWRITE" == "true" ]; then FILEOUT1="$2"; shift; vbm "DEBUG:Output file FILEOUT1 set to:""$2"; else echoerr "ERORR:Exiting in order to not overwrite output file:""$FILEOUT1"; exit 1; fi else FILEOUT1="$2"; shift; vbm "DEBUG:Output file FILEOUT1 set to:""$2"; fi ;; -O | --output-dir) # Define output directory path if [ -d "$2" ]; then # If $2 is dir that exists, set DIROUT1 to $2, pop $2 DIROUT1="$2"; shift; vbm "DEBUG:Output directory DIROUT1 set to:""$2"; else echoerr "ERROR:Specified output directory is not valid:""$2"; echoerr "Exiting."; exit 1; fi ;; *) echoerr "ERROR: Unrecognized argument."; exit 1;; # Handle unrecognized options. See [1]. esac shift done # End function vbm "DEBUG:processArguments function ended." return 0; # Function finished. } # Evaluate script options from positional arguments (ex: $1, $2, $3, etc.). checkExecutables() { # Usage: checkExecutables [ command1 ] [ command2 ] [...] [ commandN ] # Description: Checks that provided commands exist and displays those that do not exist. # Input: # - command names (arguments) # Output: commands that don't exist (stderr) # Script function dependencies: # - echoerr for displaying errors via stderr # - processArguments for setting OPTION_VERBOSE # - vbm for displaying verbose messages if OPTION_VERBOSE is "true" # External dependencies: bash (5.0.3), command # Last modified: 2020-04-11T23:59Z # Last modified by: Steven Baltakatei Sandoval # License: GPLv3+ # Ref./Attrib.: # [1]: SiegeX (2010-12-12). ["Difference between return and exit in Bash functions."](https://stackoverflow.com/a/4419971). Licensed CC BY-SA 4.0. # [2]: Darryl Hein (2009-12-23). ["Add a new element to an array without specifying the index in Bash"](https://stackoverflow.com/a/1951523). Licensed CC BY-SA 4.0. # [3]: kojiro (2012-10-03). ["Convert command line arguments into an array in Bash"](https://stackoverflow.com/a/12711853) # [4]: niieani (2016-01-12). ["How to copy an array in Bash?"](https://stackoverflow.com/a/34733375/10850071). Licensed CC BY-SA 4.0. # Initialize function vbm "DEBUG:checkExecutables function called." declare -a candidateCommandsNames # Initialize array for storing positional arguments provided to this function. candidateCommandsNames=("$@") # Save positional arguments to variable as string. See [3]. vbm "DEBUG:candidateCommandsNames:""$*" vbm "DEBUG:candidateCommandsNames[0]:""${candidateCommandsNames[0]}" vbm "DEBUG:candidateCommandsNames[1]:""${candidateCommandsNames[1]}" vbm "DEBUG:candidateCommandsNames[2]:""${candidateCommandsNames[2]}" declare -a outputInvalidCommandsArray # Initialize arary for storing names of invalid commands. declare -a outputValidCommandsArray # Initialize array for storing names of valid commands. # Perform work for candidateCommandName in "${candidateCommandsNames[@]}"; do # Search through all space-delimited text for valid commands. if command -v "$candidateCommandName" 1>/dev/null 2>/dev/null; then # Check if a command is valid or not. outputValidCommandsArray+=("$candidateCommandName") ; # See [2]. vbm "DEBUG:Adding $candidateCommandName to outputValidCommandsArray." else outputInvalidCommandsArray+=("$candidateCommandName") ; # See [2]. vbm "DEBUG:Adding $candidateCommandName to outputInvalidCommandsArray." fi done # Output results if [ ${#outputInvalidCommandsArray[@]} -gt 0 ]; then # if number of elements in outputInvalidCommandsArray greater than 0, then display offending commands and exit 1. echoerr "ERROR: Invalid commands found:""${outputInvalidCommandsArray[@]}"; # display invalid commands as error exit 1; # See [1]. elif [ ${#outputInvalidCommandsArray[@]} -eq 0 ]; then # if number of elements in outputInvalidCommandsArray equals zero, then return 0. vbm "DEBUG: Valid commands are:""${outputValidCommandsArray[@]}"; # display valid commands if verbose mode enabled return 0; # See [1]. else echoerr "ERROR: Check outputInvalidCommandsArray."; fi # End function vbm "DEBUG:checkExecutables function ended." return 0; # Function finished. } # Check that certain executables exist. updateTimeConstants() { # Usage: updateTimeConstants # Description: Updates time-related variables for use by other scripts. # Input: (none) # Output: Sets following variables: # TIME_CURRENT Current time in long ISO-8601 format. (ex: YYYY-mm-ddTHH:MM:SS+ZZZZ) # TIME_CURRENT_SHORT Current time in short ISO-8601 format. (ex: YYYYmmddTHHMMSS+ZZZZ) # DATE_CURRENT Current date in ISO-8601 format. (ex: YYYY-mm-dd) # DATE_CURRENT_SHORT Current date in short ISO-8601 format. (ex: YYYYmmdd) # DATE_TOMORROW Tomorrow's date in ISO-8601 format. (ex: YYYY-mm-dd) # TIME_NEXT_MIDNIGHT Time of tomorrow's midnight in long (ex: YYYY-mm-ddTHH:MM:SS+ZZZZ) # ISO-861 format. # SEC_TIL_MIDNIGHT Seconds until next midnight. # Script function dependencies: # - echoerr for displaying errors via stderr # - processArguments for setting OPTION_VERBOSE # - vbm for displaying verbose messages if OPTION_VERBOSE is "true" # External dependencies: bash (5.0.3), date, echo # Last modified: 2020-04-11T23:59Z # Last modified by: Steven Baltakatei Sandoval # License: GPLv3+ # Ref./Attrib.: # Initialize function vbm "DEBUG:updateTimeConstants function called." # Perform work TIME_CURRENT="$(date --iso-8601=seconds)" ; TIME_CURRENT_SHORT="$(date -d "$TIME_CURRENT" +%Y%m%dT%H%M%S%z)" DATE_CURRENT="$(date -d "$TIME_CURRENT" --iso-8601=date)" ; DATE_CURRENT_SHORT="$(date -d "$TIME_CURRENT" +%Y%m%d)" ; DATE_TOMORROW="$(date -d "$TIME_CURRENT next day" --iso-8601=date)" ; TIME_NEXT_MIDNIGHT="$(date -d "$DATE_TOMORROW" --iso-8601=seconds)" ; SECONDS_UNTIL_NEXT_MIDNIGHT="$(( $(date +%s -d "$TIME_NEXT_MIDNIGHT") - $(date +%s -d "$TIME_CURRENT") ))" ; # End function vbm "DEBUG:updateTimeConstants function ended." return 0; # Function finished. } # Update time constants checkapp() { # Desc: If arg is a command, save result in assoc array 'appRollCall' # Usage: checkapp arg1 arg2 arg3 ... # Input: global assoc. array 'appRollCall' # Output: adds/updates key(value) to global assoc array 'appRollCall' local returnState #echo "DEBUG:$(date +%S.%N)..Starting checkapp function." #echo "DEBUG:args: $*" #echo "DEBUG:returnState:$returnState" #===Process Args=== for arg in "$@"; do #echo "DEBUG:processing arg:$arg" if command -v $arg 1>/dev/null 2>&1; then # Check if arg is a valid command appRollCall[$arg]="true"; #echo "DEBUG:appRollCall[$arg]:"${appRollCall[$arg]} if ! [ "$returnState" = "false" ]; then returnState="true"; fi else appRollCall[$arg]="false"; returnState="false"; fi done #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done #echo "DEBUG:evaluating returnstate. returnState:"$returnState #===Determine function return code=== if [ "$returnState" = "true" ]; then #echo "DEBUG:checkapp returns true for $arg"; return 0; else #echo "DEBUG:checkapp returns false for $arg"; return 1; fi } # Check that app exists checkfile() { # Desc: If arg is a file path, save result in assoc array 'fileRollCall' # Usage: checkfile arg1 arg2 arg3 ... # Input: global assoc. array 'fileRollCall' # Output: adds/updates key(value) to global assoc array 'fileRollCall'; # Output: returns 0 if app found, 1 otherwise local returnState #===Process Args=== for arg in "$@"; do #echo "DEBUG:processing arg:$arg" if [ -f "$arg" ]; then fileRollCall["$arg"]="true"; #echo "DEBUG:fileRollCall[\"$arg\"]:"${fileRollCall["$arg"]} if ! [ "$returnState" = "false" ]; then returnState="true"; fi else fileRollCall["$arg"]="false"; returnState="false"; fi done #for key in "${!fileRollCall[@]}"; do echo "DEBUG:fileRollCall key [$key] is:${fileRollCall[$key]}"; done #echo "DEBUG:evaluating returnstate. returnState:"$returnState #===Determine function return code=== if [ "$returnState" = "true" ]; then #echo "DEBUG:checkapp returns true for $arg"; return 0; else #echo "DEBUG:checkapp returns false for $arg"; return 1; fi } # Check that file exists #== Main Program Definition == main() { # Usage: main "$@" # See [1]. # Input: unspecified (note: all Global Constants declared at beginning of script assumed to be available) # OPTION_VERBOSE (used by vbm, set by processArguments) (ex: "true", "false") # Output: unspecified # Script function dependencies: # - echoerr for displaying errors via stderr # - vbm for displaying verbose messages if OPTION_VERBOSE is "true" # - processArguments for setting OPTION_VERBOSE # - checkExecutables for checking that specified commands are available # External dependencies: bash (5.0.3), echo # Last modified: 2020-05-04T16:50Z # Last modified by: Steven Baltakatei Sandoval # License: GPLv3+ # Ref./Attrib.: # [1]: ErichBSchulz (2011-11-20). [How to correctly pass bash script arguments to functions](https://stackoverflow.com/a/8198970). Licensed CC BY-SA 4.0. # Initialize function processArguments "$@" # Process arguments. vbm "DEBUG:main function called." vbm "DEBUG:main function arguments are:""$@" checkExecutables "echo" "date" "cut" "awk" # Confirm that executables necessary to run are available. Exit if any fail. # Process inputs if [ -v FILEIN1 ]; then # VERBOSE: Display contents of FILEIN1 if FILEIN1 has been set. vbm "DEBUG:main function detects input file:""$FILEIN1" ; vbm "DEBUG:contents of following file is displayed below:""$FILEIN1" ; if [ "$OPTION_VERBOSE" == "true" ]; then # display FILEIN1 contents via cat if verbose mode specified echoerr "========""$FILEIN1"" START""========" ; cat "$FILEIN1" 1>&2 ; echoerr "========""$FILEIN1"" END""========" ; fi fi # Perform work OUTPUT="$OUTPUT""Hello world.\n" if [ -v FILEIN1 ]; then OUTPUT="$OUTPUT""The b2sum hash of $FILEIN1 is: $(b2sum "$FILEIN1" | awk '{print $1}').\n"; fi OUTPUT="$OUTPUT""The current time is:""$SCRIPT_TIME_SHORT\n" # Output results echo -e "$OUTPUT" if [ -v FILEOUT1 ]; then # if FILEOUT1 set, write to this file. vbm "DEBUG:main function detects output file:""$FILEOUT1" ; echo -e "$OUTPUT" > "$FILEOUT1" ; vbm "DEBUG:main funtion wrote OUTPUT to:""$FILEOUT1" ; elif [ -v DIROUT1 ]; then # else if DIROUT1 set, set FILEOUT1 to timestamped filename, combine into PATHOUT1, write output to PATHOUT1. vbm "DEBUG:main function detects output directory:""$DIROUT1" ; FILEOUT1="$SCRIPT_TIME_SHORT"..output PATHOUT1="$DIROUT1"/"$FILEOUT1" echo -e "$OUTPUT" > $PATHOUT1 ; vbm "DEBUG:main function wrote output file to:""$PATHOUT1" ; fi # End function vbm "DEBUG:main function ended." return 0; # Function finished. } #== Perform work and exit == main "$@" # Run main function. exit 0;