3 # Desc: Records gps data until midnight 
   4 # Author: Steven Baltakatei Sandoval; License: GPLv3+ 
   5 # Usage: bkgpslog -o [output dir] 
   7 #==BEGIN Define script parameters== 
   8 ## Logging Behavior parameters 
   9 BUFFER_TTL
="300"; # time between file writes 
  10 SCRIPT_TTL_TE
="day"; # (day|hour) 
  11 #### TZ="UTC"; export TZ; # Default time zone; overridden by '--time-zone=[str]' option 
  12 DIR_TMP_DEFAULT
="/dev/shm"; # Default parent of working directory 
  14 SCRIPT_TIME_START
=$(date +%Y%m%dT%H%M%S.%N); 
  15 PATH
="$HOME/.local/bin:$PATH";   # Add "$(systemd-path user-binaries)" path in case apps saved there 
  16 SCRIPT_HOSTNAME
=$(hostname);     # Save hostname of system running this script. 
  17 SCRIPT_VERSION
="0.5.1";          # Define version of script. 
  18 SCRIPT_NAME
="bkgpslog";          # Define basename of script file. 
  19 SCRIPT_URL
="https://gitlab.com/baltakatei/ninfacyzga-01"; # Define wesite hosting this script. 
  20 AGE_VERSION
="1.0.0-beta2";       # Define version of age (encryption program) 
  21 AGE_URL
="https://github.com/FiloSottile/age/releases/tag/v1.0.0-beta2"; # Define website hosting age. 
  23 declare -Ag appRollCall 
# Associative array for storing app status 
  24 declare -Ag fileRollCall 
# Associative array for storing file status 
  25 declare -Ag dirRollCall 
# Associative array for storing dir status 
  26 declare -a argRecPubKeys 
# for processArguments function 
  27 # declare -a errorHistory # for correcting buffer lag 
  29 ## Initialize variables 
  30 OPTION_VERBOSE
=""; OPTION_ENCRYPT
=""; OPTION_COMPRESS
=""; OPTION_TMPDIR
=""; 
  31 errReset
=0; BUFFER_TTL_ADJ_FLOAT
=""; 
  33 #===BEGIN Declare local script functions=== 
  35     # Desc: If arg is a command, save result in assoc array 'appRollCall' 
  36     # Usage: checkapp arg1 arg2 arg3 ... 
  37     # Input: global assoc. array 'appRollCall' 
  38     # Output: adds/updates key(value) to global assoc array 'appRollCall' 
  40     #echo "DEBUG:$(date +%S.%N)..Starting checkapp function." 
  41     #echo "DEBUG:args: $@" 
  42     #echo "DEBUG:returnState:$returnState" 
  46         #echo "DEBUG:processing arg:$arg" 
  47         if command -v "$arg" 1>/dev
/null 
2>&1; then # Check if arg is a valid command 
  48             appRollCall
[$arg]="true"; 
  49             #echo "DEBUG:appRollCall[$arg]:"${appRollCall[$arg]} 
  50             if ! [ "$returnState" = "false" ]; then returnState
="true"; fi 
  52             appRollCall
[$arg]="false"; returnState
="false"; 
  56     #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done 
  57     #echo "DEBUG:evaluating returnstate. returnState:"$returnState 
  59     #===Determine function return code=== 
  60     if [ "$returnState" = "true" ]; then 
  61         #echo "DEBUG:checkapp returns true for $arg"; 
  64         #echo "DEBUG:checkapp returns false for $arg"; 
  67 } # Check that app exists 
  69     # Desc: If arg is a file path, save result in assoc array 'fileRollCall' 
  70     # Usage: checkfile arg1 arg2 arg3 ... 
  71     # Input: global assoc. array 'fileRollCall' 
  72     # Output: adds/updates key(value) to global assoc array 'fileRollCall'; 
  73     # Output: returns 0 if app found, 1 otherwise 
  78         #echo "DEBUG:processing arg:$arg" 
  79         if [ -f "$arg" ]; then 
  80             fileRollCall
["$arg"]="true"; 
  81             #echo "DEBUG:fileRollCall[\"$arg\"]:"${fileRollCall["$arg"]} 
  82             if ! [ "$returnState" = "false" ]; then returnState
="true"; fi 
  84             fileRollCall
["$arg"]="false"; returnState
="false"; 
  88     #for key in "${!fileRollCall[@]}"; do echo "DEBUG:fileRollCall key [$key] is:${fileRollCall[$key]}"; done 
  89     #echo "DEBUG:evaluating returnstate. returnState:"$returnState 
  91     #===Determine function return code=== 
  92     if [ "$returnState" = "true" ]; then 
  93         #echo "DEBUG:checkapp returns true for $arg"; 
  96         #echo "DEBUG:checkapp returns false for $arg"; 
  99 } # Check that file exists 
 101     # Desc: If arg is a dir path, save result in assoc array 'dirRollCall' 
 102     # Usage: checkdir arg1 arg2 arg3 ... 
 103     # Input: global assoc. array 'dirRollCall' 
 104     # Output: adds/updates key(value) to global assoc array 'dirRollCall'; 
 105     # Output: returns 0 if app found, 1 otherwise 
 110         #echo "DEBUG:processing arg:$arg" 
 111         if [ -d "$arg" ]; then 
 112             dirRollCall
["$arg"]="true"; 
 113             #echo "DEBUG:dirRollCall[\"$arg\"]:"${dirRollCall["$arg"]} 
 114             if ! [ "$returnState" = "false" ]; then returnState
="true"; fi 
 115         elif [ "$arg" = "" ]; then 
 116             dirRollCall
["$arg"]="false"; returnState
="false"; 
 122     #for key in "${!dirRollCall[@]}"; do echo "DEBUG:dirRollCall key [$key] is:${dirRollCall[$key]}"; done 
 123     #echo "DEBUG:evaluating returnstate. returnState:"$returnState 
 125     #===Determine function return code=== 
 126     if [ "$returnState" = "true" ]; then 
 127         #echo "DEBUG:checkapp returns true for $arg"; 
 130         #echo "DEBUG:checkapp returns false for $arg"; 
 133 } # Check that dir exists 
 135 # Yell, Die, Try Three-Fingered Claw technique 
 136 # Ref/Attrib: https://stackoverflow.com/a/25515370 
 137 yell
() { echo "$0: $*" >&2; } 
 138 die
() { yell 
"$*"; exit 111; } 
 139 try
() { "$@" || die 
"cannot $*"; } 
 142     echo "$@" 1>&2; # Define stderr echo function. 
 143 } # Define stderr message function. 
 146     echoerr 
"    bkgpslog [ options ]" 
 149     echoerr 
"    -h, --help" 
 150     echoerr 
"            Display help information." 
 152     echoerr 
"            Display script version." 
 153     echoerr 
"    -v, --verbose" 
 154     echoerr 
"            Display debugging info." 
 155     echoerr 
"    -e, --encrypt" 
 156     echoerr 
"            Encrypt output." 
 157     echoerr 
"    -r, --recipient [ string pubkey ]" 
 158     echoerr 
"            Specify recipient. May be age or ssh pubkey." 
 159     echoerr 
"            May be specified multiple times for multiple pubkeys." 
 160     echoerr 
"            See https://github.com/FiloSottile/age" 
 161     echoerr 
"    -o, --output [ path dir ]" 
 162     echoerr 
"            Specify output directory to save logs." 
 163     echoerr 
"    -c, --compress" 
 164     echoerr 
"            Compress output with gzip (before encryption if enabled)." 
 165     echoerr 
"    -z, --time-zone" 
 166     echoerr 
"            Specify time zone. (ex: \"America/New_York\")" 
 167     echoerr 
"    -t, --temp-dir [path dir]" 
 168     echoerr 
"            Specify parent directory for temporary working directory." 
 169     echoerr 
"            Default: \"/dev/shm\"" 
 170     echoerr 
"    -R, --recipient-dir [path dir]" 
 171     echoerr 
"            Specify directory containing files whose first lines are" 
 172     echoerr 
"            to be interpreted as pubkey strings (see \\'-r\\' option)." 
 173     echoerr 
"    -b, --buffer-ttl [integer]" 
 174     echoerr 
"            Specify custom buffer period in seconds (default: 300 seconds)" 
 175     echoerr 
"    -B, --script-ttl [integer]" 
 176     echoerr 
"            Specify custom script time-to-live in seconds (default: \"day\")" 
 178     echoerr 
"EXAMPLE: (bash script lines)" 
 179     echoerr 
"/bin/bash bkgpslog -v -e -c \\" 
 180     echoerr 
"-z \"UTC\" -t \"/dev/shm\" \\" 
 181     echoerr 
"-r age1mrmfnwhtlprn4jquex0ukmwcm7y2nxlphuzgsgv8ew2k9mewy3rs8u7su5 \\" 
 182     echoerr 
"-r age1ala848kqrvxc88rzaauc6vc5v0fqrvef9dxyk79m0vjea3hagclswu0lgq \\" 
 183     echoerr 
"-o ~/Sync/Location" 
 184 } # Display information on how to use this script. 
 186     echoerr 
"$SCRIPT_VERSION" 
 187 } # Display script version. 
 189     # Usage: vbm "DEBUG:verbose message here" 
 190     # Description: Prints verbose message ("vbm") to stderr if OPTION_VERBOSE is set to "true". 
 192     #   - OPTION_VERBOSE  variable set by processArguments function. (ex: "true", "false") 
 193     #   - "$@"            positional arguments fed to this function. 
 195     # Script function dependencies: echoerr 
 196     # External function dependencies: echo 
 197     # Last modified: 2020-04-11T23:57Z 
 198     # Last modified by: Steven Baltakatei Sandoval 
 202     if [ "$OPTION_VERBOSE" = "true" ]; then 
 203         FUNCTION_TIME
=$(date --iso-8601=ns); # Save current time in nano seconds. 
 204         echoerr 
"[$FUNCTION_TIME] ""$*"; # Display argument text. 
 208     return 0; # Function finished. 
 209 } # Verbose message display function. 
 211     while [ ! $# -eq 0 ]; do   # While number of arguments ($#) is not (!) equal to (-eq) zero (0). 
 212         #echoerr "DEBUG:Starting processArguments while loop." 
 213         #echoerr "DEBUG:Provided arguments are:""$*" 
 215             -h | --help) showUsage
; exit 1;; # Display usage. 
 216             --version) showVersion
; exit 1;; # Show version 
 217             -v | --verbose) OPTION_VERBOSE
="true"; vbm 
"DEBUG:Verbose mode enabled.";; # Enable verbose mode. 
 218             -o | --output) if [ -d "$2" ]; then DIR_OUT
="$2"; vbm 
"DEBUG:DIR_OUT:$DIR_OUT"; shift; fi ;; # Define output directory. 
 219             -e | --encrypt) OPTION_ENCRYPT
="true"; vbm 
"DEBUG:Encrypted output mode enabled.";; # Enable encryption 
 220             -r | --recipient) OPTION_RECIPIENTS
="true"; argRecPubKeys
+=("$2"); vbm 
"STATUS:pubkey added:""$2"; shift;; # Add recipients 
 221             -c | --compress) OPTION_COMPRESS
="true"; vbm 
"DEBUG:Compressed output mode enabled.";; # Enable compression 
 222             -z | --time-zone) try setTimeZoneEV 
"$2"; shift;; # Set timestamp timezone 
 223             -t | --temp-dir) OPTION_TMPDIR
="true" && argTempDirPriority
="$2"; shift;; # Set time zone 
 224             -R | --recipient-dir) OPTION_RECIPIENTS
="true"; OPTION_RECDIR
="true" && argRecDir
="$2"; shift;; # Add recipient watch dir 
 225             -b | --buffer-ttl) OPTION_CUSTOM_BUFFERTTL
="true" && argCustomBufferTTL
="$2"; shift;; # Set custom buffer period (default: 300 seconds) 
 226             -B | --script-ttl) OPTION_CUSTOM_SCRIPTTTL_TE
="true" && argCustomScriptTTL
="$2"; shift;; # Set custom script TTL (default: "day") 
 227             *) echoerr 
"ERROR: Unrecognized argument: $1"; echoerr 
"STATUS:All arguments:$*"; exit 1;; # Handle unrecognized options. 
 231 } # Argument Processing 
 233     # Desc: Set time zone environment variable TZ 
 234     # Usage: setTimeZoneEV arg1 
 235     # Input: arg1: 'date'-compatible timezone string (ex: "America/New_York") 
 236     #        TZDIR env var (optional; default: "/usr/share/zoneinfo") 
 238     #         exit code 0 on success 
 239     #         exit code 1 on incorrect number of arguments 
 240     #         exit code 2 if unable to validate arg1 
 241     # Depends: yell, printenv, bash 5 
 242     # Tested on: Debian 10 
 244     local tzDir returnState
 
 245     if ! [[ $# -eq 1 ]]; then 
 246         yell 
"ERROR:Invalid argument count."; 
 250     # Read TZDIR env var if available 
 251     if printenv TZDIR 
1>/dev
/null 
2>&1; then 
 252         tzDir
="$(printenv TZDIR)"; 
 254         tzDir
="/usr/share/zoneinfo"; 
 258     if ! [[ -f "$tzDir"/"$ARG1" ]]; then 
 259         yell 
"ERROR:Invalid time zone argument."; 
 262     # Export ARG1 as TZ environment variable 
 263         TZ
="$ARG1" && export TZ 
&& returnState
="true"; 
 266     # Determine function return code 
 267     if [ "$returnState" = "true" ]; then 
 270 } # Exports TZ environment variable 
 272     # Desc: Report seconds until next day. 
 274     # Output: stdout: integer seconds until next day 
 275     # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0 
 276     # Usage: timeUntilNextDay 
 277     # Usage: if ! myTTL="$(timeUntilNextDay)"; then yell "ERROR in if statement"; exit 1; fi 
 278     # Depends: date 8, echo 8, yell, try 
 280     local returnState TIME_CURRENT TIME_NEXT_DAY SECONDS_UNTIL_NEXT_DAY
 
 282     TIME_CURRENT
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 283     TIME_NEXT_DAY
="$(date -d "$TIME_CURRENT next day" --iso-8601=date)"; # Produce timestamp of beginning of tomorrow with resolution of 1 second. 
 284     SECONDS_UNTIL_NEXT_DAY
="$(( $(date +%s -d "$TIME_NEXT_DAY") - $(date +%s -d "$TIME_CURRENT") ))" ; # Calculate seconds until closest future midnight (res. 1 second). 
 285     if [[ "$SECONDS_UNTIL_NEXT_DAY" -gt 0 ]]; then 
 287     elif [[ "$SECONDS_UNTIL_NEXT_DAY" -eq 0 ]]; then 
 288         returnState
="warning_zero"; 
 289         yell 
"WARNING:Reported time until next day exactly zero."; 
 290     elif [[ "$SECONDS_UNTIL_NEXT_DAY" -lt 0 ]]; then 
 291         returnState
="warning_negative"; 
 292         yell 
"WARNING:Reported time until next day is negative."; 
 295     try 
echo "$SECONDS_UNTIL_NEXT_DAY"; # Report 
 297     # Determine function return code 
 298     if [[ "$returnState" = "true" ]]; then 
 300     elif [[ "$returnState" = "warning_zero" ]]; then 
 302     elif [[ "$returnState" = "warning_negative" ]]; then 
 305 } # Report seconds until next day 
 307     # Desc: Report seconds until next hour 
 309     # Output: stdout: integer seconds until next hour 
 310     # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0 
 311     # Usage: timeUntilNextHour 
 312     # Usage: if ! myTTL="$(timeUntilNextHour)"; then yell "ERROR in if statement"; exit 1; fi 
 314     local returnState TIME_CURRENT TIME_NEXT_HOUR SECONDS_UNTIL_NEXT_HOUR
 
 315     TIME_CURRENT
="$(date --iso-8601=seconds)"; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 316     TIME_NEXT_HOUR
="$(date -d "$TIME_CURRENT next hour" --iso-8601=hours)"; # Produce `date`-parsable current time stamp with resolution of 1 second. 
 317     SECONDS_UNTIL_NEXT_HOUR
="$(( $(date +%s -d "$TIME_NEXT_HOUR") - $(date +%s -d "$TIME_CURRENT") ))"; # Calculate seconds until next hour (res. 1 second). 
 318     if [[ "$SECONDS_UNTIL_NEXT_HOUR" -gt 0 ]]; then 
 320     elif [[ "$SECONDS_UNTIL_NEXT_HOUR" -eq 0 ]]; then 
 321         returnState
="warning_zero"; 
 322         yell 
"WARNING:Reported time until next hour exactly zero."; 
 323     elif [[ "$SECONDS_UNTIL_NEXT_HOUR" -lt 0 ]]; then 
 324         returnState
="warning_negative"; 
 325         yell 
"WARNING:Reported time until next hour is negative."; 
 328     try 
echo "$SECONDS_UNTIL_NEXT_HOUR"; # Report 
 330     # Determine function return code 
 331     if [[ "$returnState" = "true" ]]; then 
 333     elif [[ "$returnState" = "warning_zero" ]]; then 
 335     elif [[ "$returnState" = "warning_negative" ]]; then 
 338 } # Report seconds until next hour 
 340     # Desc: Timestamp without separators (YYYYmmddTHHMMSS+zzzz) 
 341     # Usage: dateTimeShort ([str date]) 
 343     # Input: arg1: 'date'-parsable timestamp string (optional) 
 344     # Output: stdout: timestamp (ISO-8601, no separators) 
 346     local TIME_CURRENT TIME_CURRENT_SHORT
 
 350     TIME_CURRENT
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 351     # Decide to parse current or supplied date 
 352     ## Check if time argument empty 
 353     if [[ -z "$argTime" ]]; then 
 354         ## T: Time argument empty, use current time 
 355         TIME_INPUT
="$TIME_CURRENT"; 
 357         ## F: Time argument exists, validate time 
 358         if date --date="$argTime" 1>/dev
/null 
2>&1; then 
 359             ### T: Time argument is valid; use it 
 360             TIME_INPUT
="$argTime"; 
 362             ### F: Time argument not valid; exit 
 363             yell 
"ERROR:Invalid time argument supplied. Exiting."; exit 1; 
 366     # Construct and deliver separator-les date string 
 367     TIME_CURRENT_SHORT
="$(date -d "$TIME_INPUT" +%Y%m%dT%H%M%S%z)"; 
 368     echo "$TIME_CURRENT_SHORT"; 
 369 } # Get YYYYmmddTHHMMSS±zzzz 
 371     # Desc: Date without separators (YYYYmmdd) 
 372     # Usage: dateShort ([str date]) 
 374     # Input: arg1: 'date'-parsable timestamp string (optional) 
 375     # Output: stdout: date (ISO-8601, no separators) 
 377     local TIME_CURRENT DATE_CURRENT_SHORT
 
 381     TIME_CURRENT
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 382     # Decide to parse current or supplied date 
 383     ## Check if time argument empty 
 384     if [[ -z "$argTime" ]]; then 
 385         ## T: Time argument empty, use current time 
 386         TIME_INPUT
="$TIME_CURRENT"; 
 388         ## F: Time argument exists, validate time 
 389         if date --date="$argTime" 1>/dev
/null 
2>&1; then 
 390             ### T: Time argument is valid; use it 
 391             TIME_INPUT
="$argTime"; 
 393             ### F: Time argument not valid; exit 
 394             yell 
"ERROR:Invalid time argument supplied. Exiting."; exit 1; 
 397     # Construct and deliver separator-les date string     
 398     DATE_CURRENT_SHORT
="$(date -d "$TIME_INPUT" +%Y%m%d)"; # Produce separator-less current date with resolution 1 day. 
 399     echo "$DATE_CURRENT_SHORT"; 
 402     # Desc: Given seconds, output ISO-8601 duration string 
 403     # Ref/Attrib: ISO-8601:2004(E), §4.4.4.2 Representations of time intervals by duration and context information 
 404     # Note: "1 month" ("P1M") is assumed to be "30 days" (see ISO-8601:2004(E), §2.2.1.2) 
 405     # Usage: timeDuration [1:seconds] ([2:precision]) 
 407     # Input: arg1: seconds as base 10 integer >= 0  (ex: 3601) 
 408     #        arg2: precision level (optional; default=2) 
 409     # Output: stdout: ISO-8601 duration string (ex: "P1H1S", "P2Y10M15DT10H30M20S") 
 410     #         exit code 0: success 
 411     #         exit code 1: error_input 
 412     #         exit code 2: error_unknown 
 413     # Example: 'timeDuration 111111 3' yields 'P1DT6H51M' 
 414     # Depends: date 8 (gnucoreutils), yell,  
 415     local returnState argSeconds argPrecision remainder precision witherPrecision
 
 416     local fullYears fullMonths fullDays fullHours fullMinutes fullSeconds
 
 417     local displayYears displayMonths displayDays displayHours displayMinutes displaySeconds
 
 418     local hasYears hasMonths hasDays hasHours hasMinutes hasSeconds
 
 420     argSeconds
="$1"; # read arg1 (seconds) 
 421     argPrecision
="$2"; # read arg2 (precision) 
 422     precision
=2; # set default precision 
 424     # Check that between one and two arguments is supplied 
 425     if ! { [[ $# -ge 1 ]] && [[ $# -le 2 ]]; }; then 
 426         yell 
"ERROR:Invalid number of arguments:$# . Exiting."; 
 427         returnState
="error_input"; fi 
 429     # Check that argSeconds provided 
 430     if [[ $# -ge 1 ]]; then 
 431         ## Check that argSeconds is a positive integer 
 432         if [[ "$argSeconds" =~ ^
[[:digit
:]]+$ 
]]; then 
 435             yell 
"ERROR:argSeconds not a digit."; 
 436             returnState
="error_input"; 
 439         yell 
"ERROR:No argument provided. Exiting."; 
 443     # Consider whether argPrecision was provided 
 444     if  [[ $# -eq 2 ]]; then 
 445         # Check that argPrecision is a positive integer 
 446         if [[ "$argPrecision" =~ ^
[[:digit
:]]+$ 
]] && [[ "$argPrecision" -gt 0 ]]; then 
 447         precision
="$argPrecision"; 
 449             yell 
"ERROR:argPrecision not a positive integer. (is $argPrecision ). Leaving early."; 
 450             returnState
="error_input"; 
 456     remainder
="$argSeconds" ; # seconds 
 457     ## Calculate full years Y, update remainder 
 458     fullYears
=$(( remainder / (365*24*60*60) )); 
 459     remainder
=$(( remainder - (fullYears*365*24*60*60) )); 
 460     ## Calculate full months M, update remainder 
 461     fullMonths
=$(( remainder / (30*24*60*60) )); 
 462     remainder
=$(( remainder - (fullMonths*30*24*60*60) )); 
 463     ## Calculate full days D, update remainder 
 464     fullDays
=$(( remainder / (24*60*60) )); 
 465     remainder
=$(( remainder - (fullDays*24*60*60) )); 
 466     ## Calculate full hours H, update remainder 
 467     fullHours
=$(( remainder / (60*60) )); 
 468     remainder
=$(( remainder - (fullHours*60*60) )); 
 469     ## Calculate full minutes M, update remainder 
 470     fullMinutes
=$(( remainder / (60) )); 
 471     remainder
=$(( remainder - (fullMinutes*60) )); 
 472     ## Calculate full seconds S, update remainder 
 473     fullSeconds
=$(( remainder / (1) )); 
 474     remainder
=$(( remainder - (remainder*1) )); 
 475     ## Check which fields filled 
 476     if [[ $fullYears -gt 0 ]]; then hasYears
="true"; else hasYears
="false"; fi 
 477     if [[ $fullMonths -gt 0 ]]; then hasMonths
="true"; else hasMonths
="false"; fi 
 478     if [[ $fullDays -gt 0 ]]; then hasDays
="true"; else hasDays
="false"; fi 
 479     if [[ $fullHours -gt 0 ]]; then hasHours
="true"; else hasHours
="false"; fi 
 480     if [[ $fullMinutes -gt 0 ]]; then hasMinutes
="true"; else hasMinutes
="false"; fi 
 481     if [[ $fullSeconds -gt 0 ]]; then hasSeconds
="true"; else hasSeconds
="false"; fi 
 483     ## Determine which fields to display (see ISO-8601:2004 §4.4.3.2) 
 484     witherPrecision
="false" 
 487     if $hasYears && [[ $precision -gt 0 ]]; then 
 489         witherPrecision
="true"; 
 491         displayYears
="false"; 
 493     if $witherPrecision; then ((precision
--)); fi; 
 496     if $hasMonths && [[ $precision -gt 0 ]]; then 
 497         displayMonths
="true"; 
 498         witherPrecision
="true"; 
 500         displayMonths
="false"; 
 502     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 503         displayMonths
="true"; 
 505     if $witherPrecision; then ((precision
--)); fi; 
 508     if $hasDays && [[ $precision -gt 0 ]]; then 
 510         witherPrecision
="true"; 
 514     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 517     if $witherPrecision; then ((precision
--)); fi; 
 520     if $hasHours && [[ $precision -gt 0 ]]; then 
 522         witherPrecision
="true"; 
 524         displayHours
="false"; 
 526     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 529     if $witherPrecision; then ((precision
--)); fi; 
 532     if $hasMinutes && [[ $precision -gt 0 ]]; then 
 533         displayMinutes
="true"; 
 534         witherPrecision
="true"; 
 536         displayMinutes
="false"; 
 538     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 539         displayMinutes
="true"; 
 541     if $witherPrecision; then ((precision
--)); fi; 
 545     if $hasSeconds && [[ $precision -gt 0 ]]; then 
 546         displaySeconds
="true"; 
 547         witherPrecision
="true"; 
 549         displaySeconds
="false"; 
 551     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 552         displaySeconds
="true"; 
 554     if $witherPrecision; then ((precision
--)); fi; 
 556     ## Determine whether or not the "T" separator is needed to separate date and time elements 
 557     if ( $displayHours || $displayMinutes || $displaySeconds); then 
 558         displayDateTime
="true"; else displayDateTime
="false"; fi 
 560     ## Construct duration output string 
 562     if $displayYears; then 
 563         OUTPUT
=$OUTPUT$fullYears"Y"; fi 
 564     if $displayMonths; then 
 565         OUTPUT
=$OUTPUT$fullMonths"M"; fi 
 566     if $displayDays; then 
 567         OUTPUT
=$OUTPUT$fullDays"D"; fi 
 568     if $displayDateTime; then 
 569         OUTPUT
=$OUTPUT"T"; fi 
 570     if $displayHours; then 
 571         OUTPUT
=$OUTPUT$fullHours"H"; fi 
 572     if $displayMinutes; then 
 573         OUTPUT
=$OUTPUT$fullMinutes"M"; fi 
 574     if $displaySeconds; then 
 575         OUTPUT
=$OUTPUT$fullSeconds"S"; fi 
 577     ## Output duration string to stdout 
 578     echo "$OUTPUT" && returnState
="true"; 
 580     #===Determine function return code=== 
 581     if [ "$returnState" = "true" ]; then 
 583     elif [ "$returnState" = "error_input" ]; then 
 587         yell 
"ERROR:Unknown"; 
 591 } # Get duration (ex: PT10M4S ) 
 593     # Desc: Displays missing apps, files, and dirs 
 594     # Usage: displayMissing 
 595     # Input: associative arrays: appRollCall, fileRollCall, dirRollCall 
 596     # Output: stderr messages 
 597     #==BEGIN Display errors== 
 598     #===BEGIN Display Missing Apps=== 
 599     missingApps
="Missing apps  :" 
 600     #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done 
 601     for key 
in "${!appRollCall[@]}"; do 
 602         value
="${appRollCall[$key]}" 
 603         if [ "$value" = "false" ]; then 
 604             #echo "DEBUG:Missing apps: $key => $value"; 
 605             missingApps
="$missingApps""$key " 
 609     if [ "$appMissing" = "true" ]; then  # Only indicate if an app is missing. 
 610         echo "$missingApps" 1>&2; 
 612     #===END Display Missing Apps=== 
 614     #===BEGIN Display Missing Files=== 
 615     missingFiles
="Missing files:" 
 616     #for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done 
 617     for key 
in "${!fileRollCall[@]}"; do 
 618         value
="${fileRollCall[$key]}" 
 619         if [ "$value" = "false" ]; then 
 620             #echo "DEBUG:Missing files: $key => $value"; 
 621             missingFiles
="$missingFiles""$key " 
 625     if [ "$fileMissing" = "true" ]; then  # Only indicate if an app is missing. 
 626         echo "$missingFiles" 1>&2; 
 628     #===END Display Missing Files=== 
 630     #===BEGIN Display Missing Directories=== 
 631     missingDirs
="Missing dirs:" 
 632     #for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done 
 633     for key 
in "${!dirRollCall[@]}"; do 
 634         value
="${dirRollCall[$key]}" 
 635         if [ "$value" = "false" ]; then 
 636             #echo "DEBUG:Missing dirs: $key => $value"; 
 637             missingDirs
="$missingDirs""$key " 
 641     if [ "$dirMissing" = "true" ]; then  # Only indicate if an dir is missing. 
 642         echo "$missingDirs" 1>&2; 
 644     #===END Display Missing Directories=== 
 646     #==END Display errors== 
 647 } # Display missing apps, files, dirs 
 648 magicSetScriptTTL
() { 
 649     #Desc: Sets script_TTL seconds from provided time_element string argument 
 650     #Usage: magicSetScriptTTL [str time_element] 
 651     #Input: arg1: string (Ex: SCRIPT_TTL_TE; "day" or "hour") 
 652     #Output: var: SCRIPT_TTL (integer seconds) 
 653     #Depends: timeUntilNextHour, timeUntilNextDay 
 656     if [[ "$argTimeElement" = "day" ]]; then 
 657             # Set script lifespan to end at start of next day 
 658         if ! SCRIPT_TTL
="$(timeUntilNextDay)"; then 
 659             if [[ "$SCRIPT_TTL" -eq 0 ]]; then 
 660             ((SCRIPT_TTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout. 
 662             yell 
"ERROR: timeUntilNextDay exit code $?"; exit 1; 
 665     elif [[ "$argTimeElement" = "hour" ]]; then 
 666         # Set script lifespan to end at start of next hour 
 667         if ! SCRIPT_TTL
="$(timeUntilNextHour)"; then 
 668             if [[ "$SCRIPT_TTL" -eq 0 ]]; then 
 669                 ((SCRIPT_TTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout. 
 671                 yell 
"ERROR: timeUntilNextHour exit code $?"; exit 1; 
 675         yell 
"ERROR:Invalid argument for setScriptTTL function:$argTimeElement"; exit 1; 
 677 } # Seconds until next (day|hour). 
 679     # Desc: Checks that a valid tar archive exists, creates one otherwise 
 680     # Usage: checkMakeTar [ path ] 
 682     # Input: arg1: path of tar archive 
 683     # Output: exit code 0 : tar readable 
 684     #         exit code 1 : tar missing; created 
 685     #         exit code 2 : tar not readable; moved; replaced 
 686     # Depends: try, tar, date 
 687     local PATH_TAR returnFlag0 returnFlag1 returnFlag2
 
 690     # Check if file is a valid tar archive 
 691     if tar --list --file="$PATH_TAR" 1>/dev
/null 
2>&1; then 
 692         ## T1: return success 
 693         returnFlag0
="tar valid"; 
 695         ## F1: Check if file exists 
 696         if [[ -f "$PATH_TAR" ]]; then 
 698             try 
mv "$PATH_TAR" "$PATH_TAR""--broken--""$(date +%Y%m%dT%H%M%S)" && \
 
 699                 returnFlag1
="tar moved"; 
 704         ## F2: Create tar archive, return 0 
 705         try 
tar --create --file="$PATH_TAR" --files-from=/dev
/null 
&& \
 
 706             returnFlag2
="tar created"; 
 709     # Determine function return code 
 710     if [[ "$returnFlag0" = "tar valid" ]]; then 
 712     elif [[ "$returnFlag2" = "tar created" ]] && ! [[ "$returnFlag1" = "tar moved" ]]; then 
 713         return 1; # tar missing so created 
 714     elif [[ "$returnFlag2" = "tar created" ]] && [[ "$returnFlag1" = "tar moved" ]]; then 
 715         return 2; # tar not readable so moved; replaced 
 717 } # checks if arg1 is tar; creates one otherwise 
 719     # Desc: Writes first argument to temporary file with arguments as options, then appends file to tar 
 720     # Usage: appendArgTar "$(echo "Data to be written.")" [name of file to be inserted] [tar path] [temp dir] ([cmd1] [cmd2] [cmd3] [cmd4]...) 
 722     # Input: arg1: data to be written 
 723     #        arg2: file name of file to be inserted into tar 
 724     #        arg3: tar archive path (must exist first) 
 725     #        arg4: temporary working dir 
 726     #        arg5+: command strings (ex: "gpsbabel -i nmea -f - -o kml -F - ") 
 727     # Output: file written to disk 
 728     # Example: decrypt multiple large files in parallel 
 729     #          appendArgTar "$(cat /tmp/largefile1.gpg)" "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" & 
 730     #          appendArgTar "$(cat /tmp/largefile2.gpg)" "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" & 
 731     #          appendArgTar "$(cat /tmp/largefile3.gpg)" "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" & 
 733     # Ref/Attrib: Using 'eval' to construct command strings https://askubuntu.com/a/476533 
 736     local FN
="${FUNCNAME[0]}"; 
 737     #yell "DEBUG:STATUS:$FN:Finished appendArgTar()." 
 740     if ! [ -z "$2" ]; then FILENAME
="$2"; else yell 
"ERROR:$FN:Not enough arguments."; exit 1; fi 
 742     # Check tar path is a file 
 743     if [ -f "$3" ]; then TAR_PATH
="$3"; else yell 
"ERROR:$FN:Tar archive arg not a file."; exit 1; fi 
 746     if ! [ -z "$4" ]; then TMP_DIR
="$4"; else yell 
"ERROR:$FN:No temporary working dir set."; exit 1; fi 
 748     # Set command strings 
 749     if ! [ -z "$5" ]; then CMD1
="$5"; else CMD1
="tee /dev/null "; fi # command string 1 
 750     if ! [ -z "$6" ]; then CMD2
="$6"; else CMD2
="tee /dev/null "; fi # command string 2 
 751     if ! [ -z "$7" ]; then CMD3
="$7"; else CMD3
="tee /dev/null "; fi # command string 3 
 752     if ! [ -z "$8" ]; then CMD4
="$8"; else CMD4
="tee /dev/null "; fi # command string 4 
 758     # yell "DEBUG:STATUS:$FN:CMD0:$CMD0" 
 759     # yell "DEBUG:STATUS:$FN:CMD1:$CMD1" 
 760     # yell "DEBUG:STATUS:$FN:CMD2:$CMD2" 
 761     # yell "DEBUG:STATUS:$FN:CMD3:$CMD3" 
 762     # yell "DEBUG:STATUS:$FN:CMD4:$CMD4" 
 763     # yell "DEBUG:STATUS:$FN:FILENAME:$FILENAME" 
 764     # yell "DEBUG:STATUS:$FN:TAR_PATH:$TAR_PATH" 
 765     # yell "DEBUG:STATUS:$FN:TMP_DIR:$TMP_DIR" 
 767     # Write to temporary working dir 
 768     eval "$CMD0"" | ""$CMD1"" | ""$CMD2"" | ""$CMD3"" | ""$CMD4" > "$TMP_DIR"/"$FILENAME"; 
 771     try 
tar --append --directory="$TMP_DIR" --file="$TAR_PATH" "$FILENAME"; 
 772     #yell "DEBUG:STATUS:$FN:Finished appendArgTar()." 
 773 } # Append Bash var to file appended to Tar archive 
 775     # Desc: Processes first file and then appends to tar 
 776     # Usage: appendFileTar [file path] [name of file to be inserted] [tar path] [temp dir] ([cmd1] [cmd2] [cmd3] [cmd4]...) 
 778     # Input: arg1: path of file to be (processed and) written 
 779     #        arg2: name to use for file inserted into tar 
 780     #        arg3: tar archive path (must exist first) 
 781     #        arg4: temporary working dir 
 782     #        arg5+: command strings (ex: "gpsbabel -i nmea -f - -o kml -F - ") 
 783     # Output: file written to disk 
 784     # Example: decrypt multiple large files in parallel 
 785     #          appendFileTar /tmp/largefile1.gpg "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" & 
 786     #          appendFileTar /tmp/largefile2.gpg "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" & 
 787     #          appendFileTar /tmp/largefile3.gpg "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" & 
 791     local FN
="${FUNCNAME[0]}"; 
 792     #yell "DEBUG:STATUS:$FN:Finished appendFileTar()." 
 795     if ! [ -z "$2" ]; then FILENAME
="$2"; else yell 
"ERROR:$FN:Not enough arguments."; exit 1; fi 
 796     # Check tar path is a file 
 797     if [ -f "$3" ]; then TAR_PATH
="$3"; else yell 
"ERROR:$FN:Tar archive arg not a file."; exit 1; fi 
 799     if ! [ -z "$4" ]; then TMP_DIR
="$4"; else yell 
"ERROR:$FN:No temporary working dir set."; exit 1; fi 
 800     # Set command strings 
 801     if ! [ -z "$5" ]; then CMD1
="$5"; else CMD1
="tee /dev/null "; fi # command string 1 
 802     if ! [ -z "$6" ]; then CMD2
="$6"; else CMD2
="tee /dev/null "; fi # command string 2 
 803     if ! [ -z "$7" ]; then CMD3
="$7"; else CMD3
="tee /dev/null "; fi # command string 3 
 804     if ! [ -z "$8" ]; then CMD4
="$8"; else CMD4
="tee /dev/null "; fi # command string 4 
 806     # Input command string 
 810     # yell "DEBUG:STATUS:$FN:CMD0:$CMD0" 
 811     # yell "DEBUG:STATUS:$FN:CMD1:$CMD1" 
 812     # yell "DEBUG:STATUS:$FN:CMD2:$CMD2" 
 813     # yell "DEBUG:STATUS:$FN:CMD3:$CMD3" 
 814     # yell "DEBUG:STATUS:$FN:CMD4:$CMD4" 
 815     # yell "DEBUG:STATUS:$FN:FILENAME:$FILENAME" 
 816     # yell "DEBUG:STATUS:$FN:TAR_PATH:$TAR_PATH" 
 817     # yell "DEBUG:STATUS:$FN:TMP_DIR:$TMP_DIR" 
 819     # Write to temporary working dir 
 820     eval "$CMD0 | $CMD1 | $CMD2 | $CMD3 | $CMD4" > "$TMP_DIR"/"$FILENAME"; 
 823     try 
tar --append --directory="$TMP_DIR" --file="$TAR_PATH" "$FILENAME"; 
 824     #yell "DEBUG:STATUS:$FN:Finished appendFileTar()." 
 825 } # Append file to Tar archive 
 827     # Desc: Checks if string is an age-compatible pubkey 
 828     # Usage: checkAgePubkey [str pubkey] 
 830     # Input: arg1: string 
 831     # Output: return code 0: string is age-compatible pubkey 
 832     #         return code 1: string is NOT an age-compatible pubkey 
 833     #         age stderr (ex: there is stderr if invalid string provided) 
 834     # Depends: age (v0.1.0-beta2; https://github.com/FiloSottile/age/releases/tag/v1.0.0-beta2 ) 
 838     if echo "test" | age 
-a -r "$argPubkey" 1>/dev
/null
; then 
 845     # Desc: Validates Input 
 846     # Usage: validateInput [str input] [str input type] 
 848     # Input: arg1: string to validate 
 849     #        arg2: string specifying input type (ex:"ssh_pubkey") 
 850     # Output: return code 0: if input string matched specified string type 
 851     # Depends: bash 5, yell 
 854     local FN
="${FUNCNAME[0]}"; 
 859     if [[ $# -gt 2 ]]; then yell 
"ERROR:$0:$FN:Too many arguments."; exit 1; fi; 
 862     if [[ -z "$argInput" ]]; then return 1; fi 
 866     ### Check for alnum/dash base64 (ex: "ssh-rsa AAAAB3NzaC1yc2EAAA") 
 867     if [[ "$argType" = "ssh_pubkey" ]]; then 
 868         if [[ "$argInput" =~ ^
[[:alnum
:]-]*[\ ]*[[:alnum
:]+/=]*$ 
]]; then 
 872     ### Check for age1[:bech32:] 
 873     if [[ "$argType" = "age_pubkey" ]]; then 
 874         if [[ "$argInput" =~ ^age1
[qpzry9x8gf2tvdw0s3jn54khce6mua7l
]*$ 
]]; then 
 878     if [[ "$argType" = "integer" ]]; then 
 879         if [[ "$argInput" =~ ^
[[:digit
:]]*$ 
]]; then 
 882     ## time element (year, month, week, day, hour, minute, second) 
 883     if [[ "$argType" = "time_element" ]]; then 
 884         if [[ "$argInput" = "year" ]] || \
 
 885                [[ "$argInput" = "month" ]] || \
 
 886                [[ "$argInput" = "week" ]] || \
 
 887                [[ "$argInput" = "day" ]] || \
 
 888                [[ "$argInput" = "hour" ]] || \
 
 889                [[ "$argInput" = "minute" ]] || \
 
 890                [[ "$argInput" = "second" ]]; then 
 893     # Return error if no condition matched. 
 895 } # Validates strings 
 897     # Desc: Get epoch nanoseconds 
 900     # Input: arg1: 'date'-parsable timestamp string (optional) 
 901     # Output: Nanoseconds since 1970-01-01 
 902     # Depends: date 8, yell() 
 903     # Ref/Attrib: Force base 10 Bash arith with '10#'. https://stackoverflow.com/a/24777667 
 904     local TIME_CURRENT TIME_INPUT TIME_EPOCH_FLOAT TIME_EPOCH_NSFRAC
 
 910     TIME_CURRENT
="$(date --iso-8601=ns)"; # Produce `date`-parsable current timestamp with resolution of 1 nanosecond. 
 912     # Decide to parse current or supplied time 
 913     ## Check if time argument empty 
 914     if [[ -z "$argTime" ]]; then 
 915         ## T: Time argument empty, use current time 
 916         TIME_INPUT
="$TIME_CURRENT"; 
 918         ## F: Time argument exists, validate time 
 919         if date --date="$argTime" 1>/dev
/null 
2>&1; then 
 920             ### T: Time argument is valid; use it 
 921             TIME_INPUT
="$argTime"; 
 923             ### F: Time argument not valid; exit 
 924             yell 
"ERROR:Invalid time argument supplied. Exiting."; exit 1; 
 927     # Construct and deliver nanoseconds since 1970-01-01     
 928     TIME_EPOCH_FLOAT
="$(date --date="$TIME_INPUT" +%s.%N)"; # Save ssss.NNNNNNNNN 
 929     TIME_EPOCH_INT
="$(echo "$TIME_EPOCH_FLOAT" | cut -d. -f1)"; # Get ssss 
 930     TIME_EPOCH_NSFRAC
="$(echo "$TIME_EPOCH_FLOAT" | cut -d. -f2)"; # Get NNNNNNNNN 
 931     TIME_EPOCH_NS
="$(( (10#"$TIME_EPOCH_INT" * 10**9) + (10#"$TIME_EPOCH_NSFRAC") ))"; 
 932     echo "$TIME_EPOCH_NS"; 
 933 } # Nanoseconds since 1970-01-01 
 934 magicBufferSleepPID
() { 
 935     # Desc: Compensates for lag so buffer rounds start every BUFFER_TTL seconds 
 936     # Input: vars: BUFFER_TTL, errResetx10e3, K_P, T_I, T_D 
 937     # # Input: array: errorHistory 
 938     # Output: vars: BUFFER_TTL_ADJ_FLOAT 
 939     # Re/Attrib: https://en.wikipedia.org/wiki/PID_controller#Standard_versus_parallel_(ideal)_form 
 941     local timeBufferStartNS timeBufferStartNSExp errNS errNSx10e3
 
 942     local errResetx10e3 errRatex10e3 ADJ BUFFER_TTL_ADJ_NS BUFFER_TTL_ADJ_INT
 
 943     local BUFFER_TTL_ADJ_FLOATFRAC
 
 944     # local errorHistorySize 
 946     # ## Define errorHistorySize 
 947     # errorHistorySize=100; 
 948     ## Define BUFFER_TTL in nanoseconds 
 949     BUFFER_TTL_NS
=$((BUFFER_TTL * 10**9)) && vbm 
"BUFFER_TTL_NS:$BUFFER_TTL_NS"; 
 951     ### PID Control factors 
 952     K_P
=1; # Gain for compensating buffer round lag 
 953     T_I
="$(((4)*BUFFER_TTL_NS/(1)))"; # Consider this number of past nanoseconds to eliminate error 
 954     T_D
="$(((1)*BUFFER_TTL_NS/(1)))"; # Predict value this number of nanoseconds into the future 
 956     # Calculate Error, errNS, in nanoseconds 
 958     timeBufferStartNS
="$(timeEpochNS)" && vbm 
"timeBufferStartNS   :$timeBufferStartNS"; 
 959     ## Calculate expected time (from start time, current buffer round number, nominal BUFFER_TTL) 
 960     timeBufferStartNSExp
="$(( (timeBufferFirstNS) + (BUFFER_TTL_NS * bufferRound) ))" && vbm 
"timeBufferStartNSExp:$timeBufferStartNSExp"; 
 961     ## Calculate error (diff between timeBufferStartNSExp and timeBufferStartNS; usually negative) 
 962     errNS
="$(( timeBufferStartNSExp - timeBufferStartNS ))" && vbm 
"errNS:$errNS"; 
 963 #    errNSx10e3="$((errNS*10**3))" && vbm "errNSx10e3:$errNSx10e3"; 
 964     # ## Append error to errorHistory 
 965     # errorHistory+=("errNS"); 
 966     # ### Trim errorHistory array if over errorHistorySize 
 967     # while [[ "${#errorHistory[@]}" -gt "errorHistorySize" ]]; then do 
 968     #   unset "errorHistory[0]"; # remove oldest entry, creating sparse array 
 969     #   errorHistory=("${errorHistory[@]}"); # reindex sparse array 
 970     #   vbm "STATUS:Trimmed errorHistory array. Entry count:${#errorHistory[@]}"; 
 973     # Calculate errReset in nanoseconds^2 
 974     ## errReset = int(errHistory(t),wrt(delta_BUFFER_TTL)) 
 975     ## Integrate errorHistory with respect to time 
 976     # for value in "${errorHistory[@]}"; do 
 977     #   errReset=$(( errReset + ( value*BUFFER_TTL_NS ) )); 
 979     vbm 
"errReset(orig):$errReset" 
 980     errReset
="$(( (errReset + (errNS*BUFFER_TTL_NS)) ))" && vbm 
"errReset(post):$errReset"; 
 981 #    errResetx10e3="$(( ( errResetx10e3 + ( errNSx10e3 * BUFFER_TTL_NS ) )*10**3 ))" && vbm "errResetx10e3:$errResetx10e3"; 
 983     # Calculate errRate in nanoseconds per nanosecond 
 984     errRate
="$(( errNS / BUFFER_TTL_NS ))" && vbm 
"errRate:$errRate"; 
 985 #    errRatex10e3="$(( ( errNSx10e3 ) / BUFFER_TTL_NS ))" && vbm "errRatex10e3:$errRatex10e3"; 
 989     vbm 
"errResetTerm:$((errReset/T_I))"; 
 990     vbm 
"errRateTerm :$((errRate*T_D))"; 
 992     # Calculate PID control signal 
 993     ## ADJ = K_P * (errNS + errReset/T_I + errRate*T_D) 
 994     ADJ
="$(( K_P*(errNS + errReset/T_I + errRate*T_D) ))" && vbm 
"ADJ:$ADJ"; 
 995 #    ADJ="$((K_P*(errNSx10e3 + (errResetx10e3/T_I) + (errRatex10e3*T_D) )/(10**3)))" && vbm "ADJ:$ADJ"; 
 997     # Calculate BUFFER_TTL_ADJ_FLOAT from ADJ (ns) 
 998     ## Calculate BUFFER_TTL_ADJ in nanoseconds (BUFFER_TTL_ADJ_NS = BUFFER_TTL_NS + ADJ) 
 999     BUFFER_TTL_ADJ_NS
="$((BUFFER_TTL_NS + ADJ))" && vbm 
"BUFFER_TTL_ADJ_NS:$BUFFER_TTL_ADJ_NS"; 
1000     ## Calculate integer seconds 
1001     BUFFER_TTL_ADJ_INT
="$((BUFFER_TTL_ADJ_NS/(10**9)))" && vbm 
"BUFFER_TTL_ADJ_INT:$BUFFER_TTL_ADJ_INT"; 
1002     ### Catch negative integer seconds, set minimum of BUFFER_TTL/10 seconds 
1003     if [[ "$BUFFER_TTL_ADJ_INT" -le "$((BUFFER_TTL/10))" ]]; then 
1004         BUFFER_TTL_ADJ_INT
="$((BUFFER_TTL/10))"; 
1005         yell 
"WARNING:Buffer lag adjustment yielded negative seconds."; 
1007     ## Calculate nanosecond remainder 
1009     BUFFER_TTL_ADJ_FLOATFRAC
="$((BUFFER_TTL_ADJ_NS - (BUFFER_TTL_ADJ_INT*(10**9)) ))" && vbm 
"BUFFER_TTL_ADJ_FLOATFRAC:$BUFFER_TTL_ADJ_FLOATFRAC"; 
1010     ### Calc absolute value of fraction (by removing '-' if present; see https://stackoverflow.com/a/47240327 
1011     BUFFER_TTL_ADJ_FLOATFRAC
="${BUFFER_TTL_ADJ_FLOATFRAC#-}" && vbm 
"BUFFER_TTL_ADJ_FLOATFRAC:$BUFFER_TTL_ADJ_FLOATFRAC"; 
1012     ## Form float BUFFER_TTL_ADJ_FLOAT 
1013     BUFFER_TTL_ADJ_FLOAT
="$BUFFER_TTL_ADJ_INT".
"$BUFFER_TTL_ADJ_FLOATFRAC" && vbm 
"BUFFER_TTL_ADJ_FLOAT:$BUFFER_TTL_ADJ_FLOAT"; 
1014     vbm 
"STATUS:Calculated adjusted BUFFER_TTL (seconds):$BUFFER_TTL_ADJ_FLOAT"; 
1015 } # Calc BUFFER_TTL_ADJ_FLOAT so buffer starts every BUFFER_TTL seconds 
1016 magicWriteVersion
() { 
1017     # Desc: Appends time-stamped VERSION to PATHOUT_TAR 
1018     # Usage: magicWriteVersion 
1020     # Input: CONTENT_VERSION, FILEOUT_VERSION, PATHOUT_TAR, DIR_TMP 
1021     # Input: SCRIPT_VERSION, SCRIPT_URL, AGE_VERSION, AGE_URL, SCRIPT_HOSTNAME 
1022     # Output: appends tar PATHOUT_TAR 
1023     # Depends: dateTimeShort, appendArgTar 
1024     local CONTENT_VERSION pubKeyIndex
 
1026     # Set VERSION file name 
1027     FILEOUT_VERSION
="$(dateTimeShort)..VERSION"; 
1029     # Gather VERSION data in CONTENT_VERSION 
1030     CONTENT_VERSION
="SCRIPT_VERSION=$SCRIPT_VERSION"; 
1031     #CONTENT_VERSION="$CONTENT_VERSION""\\n"; 
1032     CONTENT_VERSION
="$CONTENT_VERSION""\\n""SCRIPT_NAME=$SCRIPT_NAME"; 
1033     CONTENT_VERSION
="$CONTENT_VERSION""\\n""SCRIPT_URL=$SCRIPT_URL"; 
1034     CONTENT_VERSION
="$CONTENT_VERSION""\\n""AGE_VERSION=$AGE_VERSION"; 
1035     CONTENT_VERSION
="$CONTENT_VERSION""\\n""AGE_URL=$AGE_URL"; 
1036     CONTENT_VERSION
="$CONTENT_VERSION""\\n""DATE=$(date --iso-8601=seconds)"; 
1037     CONTENT_VERSION
="$CONTENT_VERSION""\\n""HOSTNAME=$SCRIPT_HOSTNAME"; 
1038     ## Add list of recipient pubkeys 
1039     for pubkey 
in "${recPubKeysValid[@]}"; do 
1041         CONTENT_VERSION
="$CONTENT_VERSION""\\n""PUBKEY_$pubKeyIndex=$pubkey"; 
1043     ## Process newline escapes 
1044     CONTENT_VERSION
="$(echo -e "$CONTENT_VERSION")" 
1046     # Write CONTENT_VERSION as file FILEOUT_VERSION and write-append to PATHOUT_TAR 
1047     appendArgTar 
"$CONTENT_VERSION" "$FILEOUT_VERSION" "$PATHOUT_TAR" "$DIR_TMP"; 
1049 } # bkgpslog: write version data to PATHOUT_TAR via appendArgTar() 
1050 magicGatherWriteBuffer
() { 
1051     # Desc: bkgpslog-specific meta function for writing data to DIR_TMP then appending each file to PATHOUT_TAR 
1052     # Inputs: vars: PATHOUT_TAR FILEOUT_{NMEA,GPX,KML} CMD_CONV_{NMEA,GPX,KML} CMD_{COMPRESS,ENCRYPT} DIR_TMP, 
1053     # Inputs: vars: BUFFER_TTL bufferTTL_STR SCRIPT_HOSTNAME CMD_COMPRESS_SUFFIX CMD_ENCRYPT_SUFFIX 
1054     # Output: file: (PATHOUT_TAR) 
1055     # Depends: yell(), try(), vbm(), appendArgTar(), tar 1, sleep 8, checkMakeTar() 
1056     # Depends: magicWriteVersion(), appendFileTar() 
1059     # Debug:Get function name 
1060     FN
="${FUNCNAME[0]}"; 
1062     # Create buffer file with unique name 
1063     PATHOUT_BUFFER
="$DIR_TMP/buffer$SECONDS"; 
1065     timeout 
"$BUFFER_TTL"s gpspipe 
-r -o "$PATHOUT_BUFFER" ; 
1066     timeBufferStart
="$(dateTimeShort  "$(date --date="$BUFFER_TTL seconds ago")")"; # Note start time 
1067     vbm "DEBUG
:STATUS
:$FN:Started magicWriteBuffer
().
"; 
1068     # Determine file paths (time is start of buffer period) 
1069     FILEOUT_BASENAME="$timeBufferStart""--""$bufferTTL_STR""..
""$SCRIPT_HOSTNAME""_location
" && vbm "STATUS
:Set FILEOUT_BASENAME to
:$FILEOUT_BASENAME"; 
1070     ## Files saved to DIR_TMP 
1071     FILEOUT_NMEA="$FILEOUT_BASENAME".nmea"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm "STATUS
:Set FILEOUT_NMEA to
:$FILEOUT_NMEA"; 
1072     FILEOUT_GPX="$FILEOUT_BASENAME".gpx"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm "STATUS
:Set FILEOUT_GPX to
:$FILEOUT_GPX"; 
1073     FILEOUT_KML="$FILEOUT_BASENAME".kml"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm "STATUS
:Set FILEOUT_KML to
:$FILEOUT_KML"; 
1074     PATHOUT_NMEA="$DIR_TMP"/"$FILEOUT_NMEA" && vbm "STATUS
:Set PATHOUT_NMEA to
:$PATHOUT_NMEA"; 
1075     PATHOUT_GPX="$DIR_TMP"/"$FILEOUT_GPX" && vbm "STATUS
:Set PATHOUT_GPX to
:$PATHOUT_GPX"; 
1076     PATHOUT_KML="$DIR_TMP"/"$FILEOUT_KML" && vbm "STATUS
:Set PATHOUT_KML to
:$PATHOUT_KML"; 
1077     ## Files saved to disk (DIR_OUT) 
1078     ### one file per day (Ex: "20200731..hostname_location.
[.gpx.gz
].
tar") 
1079     PATHOUT_TAR="$DIR_OUT"/"$(dateShort "$(date --date="$BUFFER_TTL seconds ago")")"..
"$SCRIPT_HOSTNAME""_location""$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX".
tar && \
 
1080         vbm 
"STATUS:Set PATHOUT_TAR to:$PATHOUT_TAR"; 
1082     vbm 
"STATUS:DIR_TMP     :$DIR_TMP"; 
1083     vbm 
"STATUS:PATHOUT_TAR :$PATHOUT_TAR"; 
1084     vbm 
"STATUS:PATHOUT_NMEA:$PATHOUT_NMEA"; 
1085     vbm 
"STATUS:PATHOUT_GPX:$PATHOUT_GPX"; 
1086     vbm 
"STATUS:PATHOUT_KML:$PATHOUT_KML"; 
1089     # Validate PATHOUT_TAR as tar. 
1090     checkMakeTar 
"$PATHOUT_TAR"; 
1091     ## Add VERSION file if checkMakeTar had to create a tar (exited 1) or replace one (exited 2) 
1092     if [[ $? 
-eq 1 ]] || [[ $? 
-eq 2 ]]; then magicWriteVersion
; fi 
1094     # Write bufferBash to PATHOUT_TAR 
1095     wait; # Wait to avoid collision with older magicWriteBuffer() instances (see https://www.tldp.org/LDP/abs/html/x9644.html ) 
1096     appendFileTar 
"$PATHOUT_BUFFER" "$FILEOUT_NMEA" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_NMEA" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write NMEA data 
1097     appendFileTar 
"$PATHOUT_BUFFER" "$FILEOUT_GPX" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_GPX" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write GPX file 
1098     appendFileTar 
"$PATHOUT_BUFFER" "$FILEOUT_KML" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_KML" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write KML file 
1100     # Remove secured chunks from DIR_TMP 
1101     rm "$PATHOUT_BUFFER" "$PATHOUT_NMEA" "$PATHOUT_GPX" "$PATHOUT_KML"; 
1102     vbm 
"DEBUG:STATUS:$FN:Finished magicWriteBuffer()."; 
1103 } # write buffer to disk 
1104 magicParseRecipientDir
() { 
1105     # Desc: Updates recPubKeysValid with pubkeys in dir specified by '-R' option ("recipient directory") 
1106     # Inputs:  vars: OPTION_RECDIR, argRecDir, OPTION_ENCRYPT 
1107     #          arry: recPubKeysValid 
1108     # Outputs: arry: recPubKeysValid 
1109     # Depends: processArguments, 
1110     local recFileLine updateRecipients recipientDir
 
1111     declare -a candRecPubKeysValid
 
1113     # Check that '-e' and '-R' set 
1114     if [[ "$OPTION_ENCRYPT" = "true" ]] && [[ "$OPTION_RECDIR" = "true" ]]; then 
1115         ### Check that argRecDir is a directory. 
1116         if [[ -d "$argRecDir" ]]; then 
1117             recipientDir
="$argRecDir" && vbm 
"STATUS:Recipient watch directory detected:\"$recipientDir\""; 
1118             #### Initialize variable indicating outcome of pubkey review 
1119             unset updateRecipients
 
1120             #### Add existing recipients 
1121             candRecPubKeysValid
=("${recPubKeysValidStatic[@]}"); 
1122             #### Parse files in recipientDir 
1123             for file in "$recipientDir"/*; do 
1124                 ##### Read first line of each file 
1125                 recFileLine
="$(head -n1 "$file")" && vbm 
"STATUS:Checking if pubkey:\"$recFileLine\""; 
1126                 ##### check if first line is a valid pubkey 
1127                 if checkAgePubkey 
"$recFileLine" && \
 
1128                         ( validateInput 
"$recFileLine" "ssh_pubkey" || validateInput 
"$recFileLine" "age_pubkey"); then 
1129                     ###### T: add candidate pubkey to candRecPubKeysValid 
1130                     candRecPubKeysValid
+=("$recFileLine") && vbm 
"STATUS:RecDir pubkey is valid pubkey:\"$recFileLine\""; 
1132                     ###### F: throw warning; 
1133                     yell 
"ERROR:Invalid recipient file detected. Not modifying recipient list." 
1134                     updateRecipients
="false"; 
1137             #### Write updated recPubKeysValid array to recPubKeysValid if no failure detected 
1138             if ! [[ "$updateRecipients" = "false" ]]; then 
1139                 recPubKeysValid
=("${candRecPubKeysValid[@]}") && vbm 
"STATUS:Wrote candRecPubkeysValid to recPubKeysValid:\"${recPubKeysValid[*]}\""; 
1142             yell 
"ERROR:$0:Recipient directory $argRecDir does not exist. Exiting."; exit 1; 
1145     # Handle case if '-R' set but '-e' not set 
1146     if [[ ! "$OPTION_ENCRYPT" = "true" ]] && [[ "$OPTION_RECDIR" = "true" ]]; then 
1147         yell 
"ERROR: \\'-R\\' is set but \\'-e\\' is not set."; fi; 
1148 } # Update recPubKeysValid with argRecDir 
1149 magicParseRecipientArgs
() { 
1150     # Desc: Parses recipient arguments specified by '-r' option 
1151     # Input:  vars: OPTION_ENCRYPT from processArguments() 
1152     #         arry: argRecPubKeys from processArguments() 
1153     # Output: vars: CMD_ENCRYPT, CMD_ENCRYPT_SUFFIX 
1154     #         arry: recPubKeysValid, recPubKeysValidStatic 
1155     # Depends: checkapp(), checkAgePubkey(), validateInput(), processArguments() 
1158     # Check if encryption option active. 
1159     if [[ "$OPTION_ENCRYPT" = "true" ]] && [[ "$OPTION_RECIPIENTS" = "true" ]]; then  
1160         if checkapp age
; then # Check that age is available. 
1161             for pubkey 
in "${argRecPubKeys[@]}"; do # Validate recipient pubkey strings by forming test message 
1162                 vbm 
"DEBUG:Testing pubkey string:$pubkey"; 
1163                 if checkAgePubkey 
"$pubkey" && \
 
1164                         ( validateInput 
"$pubkey" "ssh_pubkey" || validateInput 
"$pubkey" "age_pubkey"); then 
1165                     #### Form age recipient string 
1166                     recipients
="$recipients""-r '$pubkey' "; 
1167                     vbm 
"STATUS:Added pubkey for forming age recipient string:""$pubkey"; 
1168                     vbm 
"DEBUG:recipients:""$recipients"; 
1169                     #### Add validated pubkey to recPubKeysValid array 
1170                     recPubKeysValid
+=("$pubkey") && vbm 
"DEBUG:recPubkeysValid:pubkey added:$pubkey"; 
1172                     yell 
"ERROR:Exit code ""$?"". Invalid recipient pubkey string. Exiting."; exit 1; 
1175             vbm 
"DEBUG:Finished processing argRecPubKeys array"; 
1176             vbm 
"STATUS:Array of validated pubkeys:${recPubKeysValid[*]}"; 
1177             recPubKeysValidStatic
=("${recPubKeysValid[@]}"); # Save static image of pubkeys validated by this function 
1179             ##  Form age command string 
1180             CMD_ENCRYPT
="age ""$recipients " && vbm 
"CMD_ENCRYPT:$CMD_ENCRYPT"; 
1181             CMD_ENCRYPT_SUFFIX
=".age" && vbm 
"CMD_ENCRYPT_SUFFIX:$CMD_ENCRYPT_SUFFIX"; 
1183             yell 
"ERROR:Encryption enabled but \"age\" not found. Exiting."; exit 1; 
1186         CMD_ENCRYPT
="tee /dev/null " && vbm 
"CMD_ENCRYPT:$CMD_ENCRYPT"; 
1187         CMD_ENCRYPT_SUFFIX
="" && vbm 
"CMD_ENCRYPT_SUFFIX:$CMD_ENCRYPT_SUFFIX"; 
1188         vbm 
"DEBUG:Encryption not enabled." 
1190     # Catch case if '-e' is set but '-r' or '-R' is not 
1191     if [[ "$OPTION_ENCRYPT" = "true" ]] && [[ ! "$OPTION_RECIPIENTS" = "true" ]]; then 
1192         yell 
"ERROR:\\'-e\\' set but no \\'-r\\' or \\'-R\\' set."; exit 1; fi; 
1193     # Catch case if '-r' or '-R' set but '-e' is not 
1194     if [[ ! "$OPTION_ENCRYPT" = "true" ]] && [[ "$OPTION_RECIPIENTS" = "true" ]]; then 
1195         yell 
"ERROR:\\'-r\\' or \\'-R\\' set but \\'-e\\' is not set."; exit 1; fi;  
1196 } # Populate recPubKeysValid with argRecPubKeys; form encryption cmd string and filename suffix 
1197 magicParseCompressionArg
() { 
1198     # Desc: Parses compression arguments specified by '-c' option 
1199     # Input:  vars: OPTION_COMPRESS 
1200     # Output: CMD_COMPRESS, CMD_COMPRESS_SUFFIX 
1201     # Depends: checkapp(), vbm(), gzip,  
1202     if [[ "$OPTION_COMPRESS" = "true" ]]; then # Check if compression option active 
1203         if checkapp 
gzip; then # Check if gzip available 
1204             CMD_COMPRESS
="gzip " && vbm 
"CMD_COMPRESS:$CMD_COMPRESS"; 
1205             CMD_COMPRESS_SUFFIX
=".gz" && vbm 
"CMD_COMPRESS_SUFFIX:$CMD_COMPRESS_SUFFIX"; 
1207             yell 
"ERROR:Compression enabled but \"gzip\" not found. Exiting."; exit 1; 
1210         CMD_COMPRESS
="tee /dev/null " && vbm 
"CMD_COMPRESS:$CMD_COMPRESS"; 
1211         CMD_COMPRESS_SUFFIX
="" && vbm 
"CMD_COMPRESS_SUFFIX:$CMD_COMPRESS_SUFFIX"; 
1212         vbm 
"DEBUG:Compression not enabled."; 
1214 } # Form compression cmd string and filename suffix 
1215 magicInitWorkingDir
() { 
1216     # Desc: Determine temporary working directory from defaults or user input 
1217     # Usage: magicInitWorkignDir 
1218     # Input:  vars: OPTION_TEMPDIR, argTempDirPriority, DIR_TMP_DEFAULT 
1219     # Input:  vars: SCRIPT_TIME_START 
1220     # Output: vars: DIR_TMP 
1221     # Depends: processArguments(), vbm(), yell() 
1222     # Parse '-t' option (user-specified temporary working dir) 
1223     ## Set DIR_TMP_PARENT to user-specified value if specified 
1224     local DIR_TMP_PARENT
 
1226     if [[ "$OPTION_TMPDIR" = "true" ]]; then 
1227         if [[ -d "$argTempDirPriority" ]]; then 
1228             DIR_TMP_PARENT
="$argTempDirPriority";  
1230             yell 
"WARNING:Specified temporary working directory not valid:$argTempDirPriority"; 
1231             exit 1; # Exit since user requires a specific temp dir and it is not available. 
1234     ## Set DIR_TMP_PARENT to default or fallback otherwise 
1235         if [[ -d "$DIR_TMP_DEFAULT" ]]; then 
1236             DIR_TMP_PARENT
="$DIR_TMP_DEFAULT"; 
1237         elif [[ -d /tmp 
]]; then 
1238             yell 
"WARNING:$DIR_TMP_DEFAULT not available. Falling back to /tmp ."; 
1239             DIR_TMP_PARENT
="/tmp"; 
1241             yell 
"ERROR:No valid working directory available. Exiting."; 
1245     ## Set DIR_TMP using DIR_TMP_PARENT and nonce (SCRIPT_TIME_START) 
1246     DIR_TMP
="$DIR_TMP_PARENT"/"$SCRIPT_TIME_START""..bkgpslog" && vbm 
"DEBUG:Set DIR_TMP to:$DIR_TMP"; # Note: removed at end of main().     
1247 } # Sets working dir 
1248 magicParseCustomTTL
() { 
1249     # Desc: Set user-specified TTLs for buffer and script 
1250     # Input: vars: argCustomBufferTTL (integer), argCustomScriptTTL_TE (string) 
1251     # Input: vars: OPTION_CUSTOM_BUFFERTTL, OPTION_CUSTOM_SCRIPTTTL 
1252     # Input: vars: BUFFER_TTL (integer), SCRIPT_TTL_TE (string) 
1253     # Output: BUFFER_TTL (integer), SCRIPT_TTL_TE (string) 
1254     # Depends validateInput(), showUsage(), yell 
1256     # React to '-b, --buffer-ttl' option 
1257     if [[ "$OPTION_CUSTOM_BUFFERTTL" = "true" ]]; then 
1258         ## T: Check if argCustomBufferTTL is an integer 
1259         if validateInput 
"$argCustomBufferTTL" "integer"; then 
1260             ### T: argCustomBufferTTL is an integer 
1261             BUFFER_TTL
="$argCustomBufferTTL"; 
1263             ### F: argcustomBufferTTL is not an integer 
1264             yell 
"ERROR:Invalid integer argument for custom buffer time-to-live."; showUsage
; exit 1; 
1266         ## F: do not change BUFFER_TTL 
1269     # React to '-B, --script-ttl' option 
1270     if [[ "$OPTION_CUSTOM_SCRIPTTTL_TE" = "true" ]]; then 
1271         ## T: Check if argCustomScriptTTL is a time element (ex: "day", "hour") 
1272         if validateInput 
"$argCustomScriptTTL" "time_element"; then 
1273             ### T: argCustomScriptTTL is a time element 
1274             SCRIPT_TTL_TE
="$argCustomScriptTTL"; 
1276             ### F: argcustomScriptTTL is not a time element 
1277             yell 
"ERROR:Invalid time element argument for custom script time-to-live."; showUsage
; exit 1; 
1279         ## F: do not change SCRIPT_TTL_TE 
1281 } # Sets custom script or buffer TTL if specified 
1286     processArguments 
"$@"; 
1287     ## Act upon arguments 
1288     ### Determine working directory 
1289     magicInitWorkingDir
; # Sets DIR_TMP from argTempDirPriority 
1290     ### Set output encryption and compression option strings 
1291     #### React to "-r" ("encryption recipients") option 
1292     magicParseRecipientArgs
; # Updates recPubKeysValid, CMD_ENCRYPT[_SUFFIX] from argRecPubKeys 
1293     #### React to "-c" ("compression") option 
1294     magicParseCompressionArg
; # Updates CMD_COMPRESS[_SUFFIX] 
1295     #### React to "-R" ("recipient directory") option 
1296     magicParseRecipientDir
; # Updates recPubKeysValid 
1297     #### React to custom buffer and script TTL options ("-b", "-B") 
1298     magicParseCustomTTL
; # Sets custom SCRIPT_TTL_TE and/or BUFFER_TTL if specified 
1300     # Check that critical apps and dirs are available, display missing ones. 
1301     if ! checkapp gpspipe 
tar && ! checkdir 
"$DIR_OUT" "DIR_TMP"; then 
1302         yell 
"ERROR:Critical components missing."; 
1303         displayMissing
; yell 
"Exiting."; exit 1; fi 
1305     # Set script lifespan (SCRIPT_TTL from SCRIPT_TTL_TE) 
1306     magicSetScriptTTL 
"$SCRIPT_TTL_TE"; 
1307     ## Note: SCRIPT_TTL_TE is time element string (ex: "day") while SCRIPT_TTL is integer seconds 
1309     # File name substring (ISO-8601 duration from BUFFER_TTL) 
1310     bufferTTL_STR
="$(timeDuration "$BUFFER_TTL")"; 
1312     # Init temp working dir 
1313     try 
mkdir "$DIR_TMP" && vbm 
"DEBUG:Working dir creatd at:$DIR_TMP"; 
1315     # Initialize 'tar' archive 
1316     ## Define output tar path (note: each day gets *one* tar file (Ex: "20200731..hostname_location.[.gpx.gz].tar")) 
1317     PATHOUT_TAR
="$DIR_OUT"/"$(dateShort)"..
"$SCRIPT_HOSTNAME""_location""$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX".
tar && \
 
1318         vbm 
"STATUS:Set PATHOUT_TAR to:$PATHOUT_TAR"; 
1319     ## Check that PATHOUT_TAR is a tar. Rename old and create empty one otherwise. 
1320     checkMakeTar 
"$PATHOUT_TAR" && vbm 
"DEBUG:Confirmed or Created to be a tar:$PATHOUT_TAR"; 
1321     ## Append VERSION file to PATHOUT_TAR 
1324     # Define GPS conversion commands 
1325     CMD_CONV_NMEA
="tee /dev/null " && vbm 
"STATUS:Set CMD_CONV_NMEA to:$CMD_CONV_NMEA"; # tee as passthrough 
1326     CMD_CONV_GPX
="gpsbabel -i nmea -f - -o gpx -F - " && vbm 
"STATUS:Set CMD_CONV_GPX to:$CMD_CONV_GPX"; # convert NMEA to GPX 
1327     CMD_CONV_KML
="gpsbabel -i nmea -f - -o kml -F - " && vbm 
"STATUS:Set CMD_CONV_KML to:$CMD_CONV_KML"; # convert NMEA to KML 
1329     # MAIN LOOP:Record gps data until script lifespan ends 
1330     timeBufferFirstNS
="$(timeEpochNS)"; bufferRound
=0; BUFFER_TTL_ADJ_FLOAT
="10.0"; 
1331     while [[ "$SECONDS" -lt "$SCRIPT_TTL" ]]; do 
1332         magicParseRecipientDir
; 
1333         magicGatherWriteBuffer 
& 
1334         sleep "$BUFFER_TTL_ADJ_FLOAT"; # adjusted by magicBufferSleepPID 
1336         magicBufferSleepPID
; # Calculates BUFFER_TTL_ADJ_FLOAT from BUFFER_TTL given buffer expected start time vs. actual 
1341     try 
rm -r "$DIR_TMP"; 
1343     vbm 
"STATUS:Main function finished."; 
1345 #===END Declare local script functions=== 
1346 #==END Define script parameters== 
1349 #==BEGIN Perform work and exit== 
1350 main 
"$@" # Run main function. 
1352 #==END Perform work and exit==