2 # Desc: Compresses, encrypts, and writes stdin every 5 seconds 
   4 #==BEGIN Define script parameters== 
   5 #===BEGIN Initialize variables=== 
   7 # Logging Behavior parameters 
   8 bufferTTL
="300"; # Time-to-live (seconds) for each buffer round 
   9 scriptTTL_TE
="day"; # Time element at the end of which script terminates 
  10 dirTmpDefault
="/dev/shm"; # Default parent of working directory 
  13 scriptName
="bklog";             # Define basename of script file. 
  14 scriptVersion
="0.1.25";          # Define version of script. 
  15 scriptURL
="https://gitlab.com/baltakatei/ninfacyzga-01"; # Define wesite hosting this script. 
  16 scriptTimeStart
="$(date +%Y%m%dT%H%M%S.%N)"; # YYYYmmddTHHMMSS.NNNNNNNNN 
  17 scriptHostname
=$
(hostname
);     # Save hostname of system running this script. 
  18 PATH
="$HOME/.local/bin:$PATH";  # Add "$(systemd-path user-binaries)" path in case user apps saved there 
  19 ageVersion
="1.0.0-beta2";       # Define version of age (encryption program) 
  20 ageURL
="https://github.com/FiloSottile/age/releases/tag/v1.0.0-beta2"; # Define website hosting age. 
  23 declare -a buffer          
# array for storing while read buffer 
  24 declare -a argRecPubKeys   
# array for processArguments function 
  25 declare -a recPubKeysValid 
# array for storing both '-r' and '-R' recipient pubkeys 
  26 declare -a argProcStrings argProcFileExts 
# for storing buffer processing strings (ex: "gpsbabel -i nmea -f - -o gpx -F - ") 
  27 declare -Ag appRollCall    
# Associative array for storing app status 
  28 declare -Ag fileRollCall   
# Associative array for storing file status 
  29 declare -Ag dirRollCall    
# Associative array for storing dir status 
  30 declare -a procStrings procFileExts 
# Arrays for storing processing commands and resulting output file extensions 
  33 optionVerbose
=""; optionEncrypt
=""; dirOut
=""; optionEncrypt
=""; dir_tmp
=""; 
  34 cmd_compress
="";cmd_compress_suffix
=""; cmd_encrypt
=""; cmd_encrypt_suffix
=""; 
  36 #===END Initialize variables=== 
  38 #===BEGIN Declare local script functions=== 
  39 yell
() { echo "$0: $*" >&2; }      #o Yell, Die, Try Three-Fingered Claw technique 
  40 die
() { yell 
"$*"; exit 111; }     #o Ref/Attrib: https://stackoverflow.com/a/25515370 
  41 try
() { "$@" || die 
"cannot $*"; } #o 
  43     while [ ! $# -eq 0 ]; do   # While number of arguments ($#) is not (!) equal to (-eq) zero (0). 
  45             -v | 
--verbose) optionVerbose
="true"; vbm 
"DEBUG :Verbose mode enabled.";; # Enable verbose mode. 
  46             -h | 
--help) showUsage
; exit 1;; # Display usage. 
  47             --version) showVersion
; exit 1;; # Show version 
  48             -o | 
--output) if [ -d "$2" ]; then dirOut
="$2"; vbm 
"DEBUG :dirOut:$dirOut"; shift; fi ;; # Define output directory. 
  49             -e | 
--encrypt) optionEncrypt
="true"; vbm 
"DEBUG :Encrypted output mode enabled.";; # Enable encryption 
  50             -r | 
--recipient) optionRecArg
="true"; argRecPubKeys
+=("$2"); vbm 
"STATUS:pubkey added:""$2"; shift;; # Add recipients 
  51             -R | 
--recipient-dir) optionRecDir
="true" && argRecDir
="$2"; shift;; # Add recipient watch dir 
  52             -c | 
--compress) optionCompress
="true"; vbm 
"DEBUG :Compressed output mode enabled.";; # Enable compression 
  53             -z | 
--time-zone) try setTimeZoneEV 
"$2"; shift;; # Set timestamp timezone 
  54             -t | 
--temp-dir) optionTmpDir
="true" && argTempDirPriority
="$2"; shift;; # Set time zone 
  55             -b | 
--buffer-ttl) optionCustomBufferTTL
="true" && argCustomBufferTTL
="$2"; shift;; # Set custom buffer period (default: 300 seconds) 
  56             -B | 
--script-ttl) optionCustomScriptTTL_TE
="true" && argCustomScriptTTL_TE
="$2"; shift;; # Set custom script TTL (default: "day") 
  57             -p | 
--process-string) optionProcString
="true" && argProcStrings
+=("$2") && argProcFileExts
+=("$3") && vbm 
"STATUS:file extension \"$3\" for output of processing string added:\"$2\""; shift; shift;; 
  58             -l | 
--label) optionLabel
="true" && argLabel
="$2"; vbm 
"DEBUG :Custom label received:$argLabel"; shift;; 
  59             -w | 
--store-raw) optionStoreRaw
="true" && argRawFileExt
="$2"; vbm 
"DEBUG :Raw stdin file extension received:$argRawFileExt"; shift;; 
  60             -W | 
--no-store-raw) optionNoStoreRaw
="true"; vbm 
"DEBUG :Option selected to not store raw stdin data."; shift;; 
  61             *) yell 
"ERROR: Unrecognized argument: $1"; yell 
"STATUS:All arguments:$*"; exit 1;; # Handle unrecognized options. 
  65 } # Argument Processing 
  67     # Description: Prints verbose message ("vbm") to stderr if optionVerbose is set to "true". 
  68     # Usage: vbm "DEBUG :verbose message here" 
  73     # Depends: bash 5.0.3, echo 8.30, date 8.30 
  75     if [ "$optionVerbose" = "true" ]; then 
  76         functionTime
=$
(date --iso-8601=ns
); # Save current time in nano seconds. 
  77         echo "[$functionTime]:$0:""$*" 1>&2; # Display argument text. 
  81     return 0; # Function finished. 
  82 } # Displays message if optionVerbose true 
  84     # Desc: If arg is a command, save result in assoc array 'appRollCall' 
  85     # Usage: checkapp arg1 arg2 arg3 ... 
  87     # Input: global assoc. array 'appRollCall' 
  88     # Output: adds/updates key(value) to global assoc array 'appRollCall' 
  94         if command -v "$arg" 1>/dev
/null 
2>&1; then # Check if arg is a valid command 
  95             appRollCall
[$arg]="true"; 
  96             if ! [ "$returnState" = "false" ]; then returnState
="true"; fi; 
  98             appRollCall
[$arg]="false"; returnState
="false"; 
 102     #===Determine function return code=== 
 103     if [ "$returnState" = "true" ]; then 
 108 } # Check that app exists 
 110     # Desc: If arg is a file path, save result in assoc array 'fileRollCall' 
 111     # Usage: checkfile arg1 arg2 arg3 ... 
 113     # Input: global assoc. array 'fileRollCall' 
 114     # Output: adds/updates key(value) to global assoc array 'fileRollCall'; 
 115     # Output: returns 0 if app found, 1 otherwise 
 116     # Depends: bash 5.0.3 
 121         if [ -f "$arg" ]; then 
 122             fileRollCall
["$arg"]="true"; 
 123             if ! [ "$returnState" = "false" ]; then returnState
="true"; fi; 
 125             fileRollCall
["$arg"]="false"; returnState
="false"; 
 129     #===Determine function return code=== 
 130     if [ "$returnState" = "true" ]; then 
 135 } # Check that file exists 
 137     # Desc: If arg is a dir path, save result in assoc array 'dirRollCall' 
 138     # Usage: checkdir arg1 arg2 arg3 ... 
 140     # Input: global assoc. array 'dirRollCall' 
 141     # Output: adds/updates key(value) to global assoc array 'dirRollCall'; 
 142     # Output: returns 0 if app found, 1 otherwise 
 143     # Depends: Bash 5.0.3 
 148         if [ -d "$arg" ]; then 
 149             dirRollCall
["$arg"]="true"; 
 150             if ! [ "$returnState" = "false" ]; then returnState
="true"; fi 
 152             dirRollCall
["$arg"]="false"; returnState
="false"; 
 156     #===Determine function return code=== 
 157     if [ "$returnState" = "true" ]; then 
 162 } # Check that dir exists 
 164     # Desc: Displays missing apps, files, and dirs 
 165     # Usage: displayMissing 
 167     # Input: associative arrays: appRollCall, fileRollCall, dirRollCall 
 168     # Output: stderr: messages indicating missing apps, file, or dirs 
 169     # Depends: bash 5, checkAppFileDir() 
 170     local missingApps value appMissing missingFiles fileMissing
 
 171     local missingDirs dirMissing
 
 173     #==BEGIN Display errors== 
 174     #===BEGIN Display Missing Apps=== 
 175     missingApps
="Missing apps  :"; 
 176     #for key in "${!appRollCall[@]}"; do echo "DEBUG :$key => ${appRollCall[$key]}"; done 
 177     for key 
in "${!appRollCall[@]}"; do 
 178         value
="${appRollCall[$key]}"; 
 179         if [ "$value" = "false" ]; then 
 180             #echo "DEBUG :Missing apps: $key => $value"; 
 181             missingApps
="$missingApps""$key "; 
 185     if [ "$appMissing" = "true" ]; then  # Only indicate if an app is missing. 
 186         echo "$missingApps" 1>&2; 
 189     #===END Display Missing Apps=== 
 191     #===BEGIN Display Missing Files=== 
 192     missingFiles
="Missing files:"; 
 193     #for key in "${!fileRollCall[@]}"; do echo "DEBUG :$key => ${fileRollCall[$key]}"; done 
 194     for key 
in "${!fileRollCall[@]}"; do 
 195         value
="${fileRollCall[$key]}"; 
 196         if [ "$value" = "false" ]; then 
 197             #echo "DEBUG :Missing files: $key => $value"; 
 198             missingFiles
="$missingFiles""$key "; 
 202     if [ "$fileMissing" = "true" ]; then  # Only indicate if an app is missing. 
 203         echo "$missingFiles" 1>&2; 
 206     #===END Display Missing Files=== 
 208     #===BEGIN Display Missing Directories=== 
 209     missingDirs
="Missing dirs:"; 
 210     #for key in "${!dirRollCall[@]}"; do echo "DEBUG :$key => ${dirRollCall[$key]}"; done 
 211     for key 
in "${!dirRollCall[@]}"; do 
 212         value
="${dirRollCall[$key]}"; 
 213         if [ "$value" = "false" ]; then 
 214             #echo "DEBUG :Missing dirs: $key => $value"; 
 215             missingDirs
="$missingDirs""$key "; 
 219     if [ "$dirMissing" = "true" ]; then  # Only indicate if an dir is missing. 
 220         echo "$missingDirs" 1>&2; 
 223     #===END Display Missing Directories=== 
 225     #==END Display errors== 
 226 } # Display missing apps, files, dirs 
 229     # Desc: Writes first argument to temporary file with arguments as options, then appends file to tar 
 230     # Usage: appendArgTar "$(echo "Data to be written.")" [name of file to be inserted] [tar path] [temp dir] ([cmd1] [cmd2] [cmd3] [cmd4]...) 
 232     # Input: arg1: data to be written 
 233     #        arg2: file name of file to be inserted into tar 
 234     #        arg3: tar archive path (must exist first) 
 235     #        arg4: temporary working dir 
 236     #        arg5+: command strings (ex: "gpsbabel -i nmea -f - -o kml -F - ") 
 237     # Output: file written to disk 
 238     # Example: decrypt multiple large files in parallel 
 239     #          appendArgTar "$(cat /tmp/largefile1.gpg)" "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" & 
 240     #          appendArgTar "$(cat /tmp/largefile2.gpg)" "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" & 
 241     #          appendArgTar "$(cat /tmp/largefile3.gpg)" "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" & 
 242     # Depends: bash 5, tar 1, yell() 
 243     # Ref/Attrib: Using 'eval' to construct command strings https://askubuntu.com/a/476533 
 245     local fn fileName tarPath tmpDir cmd0 cmd1 cmd2 cmd3 cmd4
 
 249     #yell "DEBUG:STATUS:$fn:Finished appendArgTar()." 
 252     if ! [ -z "$2" ]; then fileName
="$2"; else yell 
"ERROR:$fn:Not enough arguments."; exit 1; fi 
 254     # Check tar path is a file 
 255     if [ -f "$3" ]; then tarPath
="$3"; else yell 
"ERROR:$fn:Tar archive arg not a file."; exit 1; fi 
 258     if ! [ -z "$4" ]; then tmpDir
="$4"; else yell 
"ERROR:$fn:No temporary working dir set."; exit 1; fi 
 260     # Set command strings 
 261     if ! [ -z "$5" ]; then cmd1
="$5"; else cmd1
="cat "; fi # command string 1 
 262     if ! [ -z "$6" ]; then cmd2
="$6"; else cmd2
="cat "; fi # command string 2 
 263     if ! [ -z "$7" ]; then cmd3
="$7"; else cmd3
="cat "; fi # command string 3 
 264     if ! [ -z "$8" ]; then cmd4
="$8"; else cmd4
="cat "; fi # command string 4 
 270     # yell "DEBUG:STATUS:$fn:cmd0:$cmd0" 
 271     # yell "DEBUG:STATUS:$fn:cmd1:$cmd1" 
 272     # yell "DEBUG:STATUS:$fn:cmd2:$cmd2" 
 273     # yell "DEBUG:STATUS:$fn:cmd3:$cmd3" 
 274     # yell "DEBUG:STATUS:$fn:cmd4:$cmd4" 
 275     # yell "DEBUG:STATUS:$fn:fileName:$fileName" 
 276     # yell "DEBUG:STATUS:$fn:tarPath:$tarPath" 
 277     # yell "DEBUG:STATUS:$fn:tmpDir:$tmpDir" 
 279     # Write to temporary working dir 
 280     eval "$cmd0 | $cmd1 | $cmd2 | $cmd3 | $cmd4" > "$tmpDir"/"$fileName"; 
 283     try 
tar --append --directory="$tmpDir" --file="$tarPath" "$fileName"; 
 284     #yell "DEBUG:STATUS:$fn:Finished appendArgTar()." 
 285 } # Append Bash var to file appended to Tar archive 
 287     # Desc: Appends [processed] file to tar 
 288     # Usage: appendFileTar [file path] [name of file to be inserted] [tar path] [temp dir] ([process cmd]) 
 290     # Input: arg1: path of file to be (processed and) written 
 291     #        arg2: name to use for file inserted into tar 
 292     #        arg3: tar archive path (must exist first) 
 293     #        arg4: temporary working dir 
 294     #        arg5: (optional) command string to process file (ex: "gpsbabel -i nmea -f - -o kml -F - ") 
 295     # Output: file written to disk 
 296     # Example: decrypt multiple large files in parallel 
 297     #          appendFileTar /tmp/largefile1.gpg "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" & 
 298     #          appendFileTar /tmp/largefile2.gpg "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" & 
 299     #          appendFileTar /tmp/largefile3.gpg "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" & 
 300     # Depends: bash 5.0.3, tar 1.30, cat 8.30, yell() 
 301     local fn fileName tarPath tmpDir
 
 305     #yell "DEBUG :STATUS:$fn:Started appendFileTar()." 
 308     if ! [ -z "$2" ]; then fileName
="$2"; else yell 
"ERROR:$fn:Not enough arguments."; exit 1; fi 
 309     # Check tar path is a file 
 310     if [ -f "$3" ]; then tarPath
="$3"; else yell 
"ERROR:$fn:Tar archive arg not a file:$3"; exit 1; fi 
 312     if ! [ -z "$4" ]; then tmpDir
="$4"; else yell 
"ERROR:$fn:No temporary working dir set."; exit 1; fi 
 313     # Set command strings 
 314     if ! [ -z "$5" ]; then cmd1
="$5"; else cmd1
="cat "; fi # command string 
 316     # Input command string 
 319     # Write to temporary working dir 
 320     eval "$cmd0 | $cmd1" > "$tmpDir"/"$fileName"; 
 323     try 
tar --append --directory="$tmpDir" --file="$tarPath" "$fileName"; 
 324     #yell "DEBUG :STATUS:$fn:Finished appendFileTar()." 
 325 } # Append [processed] file to Tar archive 
 327     # Desc: Checks if string is an age-compatible pubkey 
 328     # Usage: checkAgePubkey [str pubkey] 
 330     # Input: arg1: string 
 331     # Output: return code 0: string is age-compatible pubkey 
 332     #         return code 1: string is NOT an age-compatible pubkey 
 333     #         age stderr (ex: there is stderr if invalid string provided) 
 334     # Depends: age (v0.1.0-beta2; https://github.com/FiloSottile/age/releases/tag/v1.0.0-beta2 ) 
 338     if echo "test" | age 
-a -r "$argPubkey" 1>/dev
/null
; then 
 345     # Desc: Checks that a valid tar archive exists, creates one otherwise 
 346     # Usage: checkMakeTar [ path ] 
 348     # Input: arg1: path of tar archive 
 349     # Output: exit code 0 : tar readable 
 350     #         exit code 1 : tar missing; created 
 351     #         exit code 2 : tar not readable; moved; replaced 
 352     # Depends: bash 5, date 8, tar 1, try() 
 353     local pathTar returnFlag0 returnFlag1 returnFlag2
 
 356     # Check if file is a valid tar archive 
 357     if tar --list --file="$pathTar" 1>/dev
/null 
2>&1; then 
 358         ## T1: return success 
 359         returnFlag0
="tar valid"; 
 360     elif { sleep 2; tar --list --file="$pathTar" 1>/dev
/null 
2>&1; }; then 
 361         ## F1: Check tar archive again after 2-second sleep 
 362         returnFlag0
="tar valid"; 
 364         ## F2-1: Check if file exists 
 365         if [[ -f "$pathTar" ]]; then 
 367             try 
mv "$pathTar" "$pathTar""--broken--""$(date +%Y%m%dT%H%M%S%z)" && \
 
 368                 returnFlag1
="tar moved"; 
 373         ## F2-1: Create tar archive, return 0 
 374         try 
tar --create --file="$pathTar" --files-from=/dev
/null 
&& \
 
 375             returnFlag2
="tar created"; 
 378     # Determine function return code 
 379     if [[ "$returnFlag0" = "tar valid" ]]; then 
 381     elif [[ "$returnFlag2" = "tar created" ]] && ! [[ "$returnFlag1" = "tar moved" ]]; then 
 382         return 1; # tar missing so created 
 383     elif [[ "$returnFlag2" = "tar created" ]] && [[ "$returnFlag1" = "tar moved" ]]; then 
 384         return 2; # tar not readable so moved; replaced 
 386 } # checks if arg1 is tar; creates one otherwise 
 388     # Desc: Date without separators (YYYYmmdd) 
 389     # Usage: dateShort ([str date]) 
 391     # Input: arg1: 'date'-parsable timestamp string (optional) 
 392     # Output: stdout: date (ISO-8601, no separators) 
 393     # Depends: bash 5.0.3, date 8.30, yell() 
 394     local argTime timeCurrent timeInput dateCurrentShort
 
 398     timeCurrent
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 399     # Decide to parse current or supplied date 
 400     ## Check if time argument empty 
 401     if [[ -z "$argTime" ]]; then 
 402         ## T: Time argument empty, use current time 
 403         timeInput
="$timeCurrent"; 
 405         ## F: Time argument exists, validate time 
 406         if date --date="$argTime" 1>/dev
/null 
2>&1; then 
 407             ### T: Time argument is valid; use it 
 408             timeInput
="$argTime"; 
 410             ### F: Time argument not valid; exit 
 411             yell 
"ERROR:Invalid time argument supplied. Exiting."; exit 1; 
 414     # Construct and deliver separator-les date string     
 415     dateCurrentShort
="$(date -d "$timeInput" +%Y%m%d)"; # Produce separator-less current date with resolution 1 day. 
 416     echo "$dateCurrentShort"; 
 419     # Desc: Timestamp without separators (YYYYmmddTHHMMSS+zzzz) 
 420     # Usage: dateTimeShort ([str date]) 
 422     # Input: arg1: 'date'-parsable timestamp string (optional) 
 423     # Output: stdout: timestamp (ISO-8601, no separators) 
 425     local argTime timeCurrent timeInput timeCurrentShort
 
 429     timeCurrent
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 430     # Decide to parse current or supplied date 
 431     ## Check if time argument empty 
 432     if [[ -z "$argTime" ]]; then 
 433         ## T: Time argument empty, use current time 
 434         timeInput
="$timeCurrent"; 
 436         ## F: Time argument exists, validate time 
 437         if date --date="$argTime" 1>/dev
/null 
2>&1; then 
 438             ### T: Time argument is valid; use it 
 439             timeInput
="$argTime"; 
 441             ### F: Time argument not valid; exit 
 442             yell 
"ERROR:Invalid time argument supplied. Exiting."; exit 1; 
 445     # Construct and deliver separator-les date string 
 446     timeCurrentShort
="$(date -d "$timeInput" +%Y%m%dT%H%M%S%z)"; 
 447     echo "$timeCurrentShort"; 
 448 } # Get YYYYmmddTHHMMSS±zzzz 
 450     # Desc: Set time zone environment variable TZ 
 451     # Usage: setTimeZoneEV arg1 
 453     # Input: arg1: 'date'-compatible timezone string (ex: "America/New_York") 
 454     #        TZDIR env var (optional; default: "/usr/share/zoneinfo") 
 456     #         exit code 0 on success 
 457     #         exit code 1 on incorrect number of arguments 
 458     #         exit code 2 if unable to validate arg1 
 459     # Depends: yell(), printenv 8.30, bash 5.0.3 
 460     # Tested on: Debian 10 
 461     local tzDir returnState argTimeZone
 
 464     if ! [[ $# -eq 1 ]]; then 
 465         yell 
"ERROR:Invalid argument count."; 
 469     # Read TZDIR env var if available 
 470     if printenv TZDIR 
1>/dev
/null 
2>&1; then 
 471         tzDir
="$(printenv TZDIR)"; 
 473         tzDir
="/usr/share/zoneinfo"; 
 477     if ! [[ -f "$tzDir"/"$argTimeZone" ]]; then 
 478         yell 
"ERROR:Invalid time zone argument."; 
 481     # Export ARG1 as TZ environment variable 
 482         TZ
="$argTimeZone" && export TZ 
&& returnState
="true"; 
 485     # Determine function return code 
 486     if [ "$returnState" = "true" ]; then 
 489 } # Exports TZ environment variable 
 491     yell 
"$scriptVersion" 
 492 } # Display script version. 
 494     # Desc: Given seconds, output ISO-8601 duration string 
 495     # Ref/Attrib: ISO-8601:2004(E), §4.4.4.2 Representations of time intervals by duration and context information 
 496     # Note: "1 month" ("P1M") is assumed to be "30 days" (see ISO-8601:2004(E), §2.2.1.2) 
 497     # Usage: timeDuration [1:seconds] ([2:precision]) 
 499     # Input: arg1: seconds as base 10 integer >= 0  (ex: 3601) 
 500     #        arg2: precision level (optional; default=2) 
 501     # Output: stdout: ISO-8601 duration string (ex: "P1H1S", "P2Y10M15DT10H30M20S") 
 502     #         exit code 0: success 
 503     #         exit code 1: error_input 
 504     #         exit code 2: error_unknown 
 505     # Example: 'timeDuration 111111 3' yields 'P1DT6H51M' 
 506     # Depends: date 8, bash 5, yell, 
 507     local argSeconds argPrecision precision returnState remainder
 
 508     local fullYears fullMonths fullDays fullHours fullMinutes fullSeconds
 
 509     local hasYears hasMonths hasDays hasHours hasMinutes hasSeconds
 
 510     local witherPrecision output
 
 511     local displayYears displayMonths displayDays displayHours displayMinutes displaySeconds
 
 513     argSeconds
="$1"; # read arg1 (seconds) 
 514     argPrecision
="$2"; # read arg2 (precision) 
 515     precision
=2; # set default precision 
 517     # Check that between one and two arguments is supplied 
 518     if ! { [[ $# -ge 1 ]] && [[ $# -le 2 ]]; }; then 
 519         yell 
"ERROR:Invalid number of arguments:$# . Exiting."; 
 520         returnState
="error_input"; fi 
 522     # Check that argSeconds provided 
 523     if [[ $# -ge 1 ]]; then 
 524         ## Check that argSeconds is a positive integer 
 525         if [[ "$argSeconds" =~ ^
[[:digit
:]]+$ 
]]; then 
 528             yell 
"ERROR:argSeconds not a digit."; 
 529             returnState
="error_input"; 
 532         yell 
"ERROR:No argument provided. Exiting."; 
 536     # Consider whether argPrecision was provided 
 537     if  [[ $# -eq 2 ]]; then 
 538         # Check that argPrecision is a positive integer 
 539         if [[ "$argPrecision" =~ ^
[[:digit
:]]+$ 
]] && [[ "$argPrecision" -gt 0 ]]; then 
 540         precision
="$argPrecision"; 
 542             yell 
"ERROR:argPrecision not a positive integer. (is $argPrecision ). Leaving early."; 
 543             returnState
="error_input"; 
 549     remainder
="$argSeconds" ; # seconds 
 550     ## Calculate full years Y, update remainder 
 551     fullYears
=$
(( remainder 
/ (365*24*60*60) )); 
 552     remainder
=$
(( remainder 
- (fullYears
*365*24*60*60) )); 
 553     ## Calculate full months M, update remainder 
 554     fullMonths
=$
(( remainder 
/ (30*24*60*60) )); 
 555     remainder
=$
(( remainder 
- (fullMonths
*30*24*60*60) )); 
 556     ## Calculate full days D, update remainder 
 557     fullDays
=$
(( remainder 
/ (24*60*60) )); 
 558     remainder
=$
(( remainder 
- (fullDays
*24*60*60) )); 
 559     ## Calculate full hours H, update remainder 
 560     fullHours
=$
(( remainder 
/ (60*60) )); 
 561     remainder
=$
(( remainder 
- (fullHours
*60*60) )); 
 562     ## Calculate full minutes M, update remainder 
 563     fullMinutes
=$
(( remainder 
/ (60) )); 
 564     remainder
=$
(( remainder 
- (fullMinutes
*60) )); 
 565     ## Calculate full seconds S, update remainder 
 566     fullSeconds
=$
(( remainder 
/ (1) )); 
 567     remainder
=$
(( remainder 
- (remainder
*1) )); 
 568     ## Check which fields filled 
 569     if [[ $fullYears -gt 0 ]]; then hasYears
="true"; else hasYears
="false"; fi 
 570     if [[ $fullMonths -gt 0 ]]; then hasMonths
="true"; else hasMonths
="false"; fi 
 571     if [[ $fullDays -gt 0 ]]; then hasDays
="true"; else hasDays
="false"; fi 
 572     if [[ $fullHours -gt 0 ]]; then hasHours
="true"; else hasHours
="false"; fi 
 573     if [[ $fullMinutes -gt 0 ]]; then hasMinutes
="true"; else hasMinutes
="false"; fi 
 574     if [[ $fullSeconds -gt 0 ]]; then hasSeconds
="true"; else hasSeconds
="false"; fi 
 576     ## Determine which fields to display (see ISO-8601:2004 §4.4.3.2) 
 577     witherPrecision
="false" 
 580     if $hasYears && [[ $precision -gt 0 ]]; then 
 582         witherPrecision
="true"; 
 584         displayYears
="false"; 
 586     if $witherPrecision; then ((precision--
)); fi; 
 589     if $hasMonths && [[ $precision -gt 0 ]]; then 
 590         displayMonths
="true"; 
 591         witherPrecision
="true"; 
 593         displayMonths
="false"; 
 595     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 596         displayMonths
="true"; 
 598     if $witherPrecision; then ((precision--
)); fi; 
 601     if $hasDays && [[ $precision -gt 0 ]]; then 
 603         witherPrecision
="true"; 
 607     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 610     if $witherPrecision; then ((precision--
)); fi; 
 613     if $hasHours && [[ $precision -gt 0 ]]; then 
 615         witherPrecision
="true"; 
 617         displayHours
="false"; 
 619     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 622     if $witherPrecision; then ((precision--
)); fi; 
 625     if $hasMinutes && [[ $precision -gt 0 ]]; then 
 626         displayMinutes
="true"; 
 627         witherPrecision
="true"; 
 629         displayMinutes
="false"; 
 631     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 632         displayMinutes
="true"; 
 634     if $witherPrecision; then ((precision--
)); fi; 
 638     if $hasSeconds && [[ $precision -gt 0 ]]; then 
 639         displaySeconds
="true"; 
 640         witherPrecision
="true"; 
 642         displaySeconds
="false"; 
 644     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 645         displaySeconds
="true"; 
 647     if $witherPrecision; then ((precision--
)); fi; 
 649     ## Determine whether or not the "T" separator is needed to separate date and time elements 
 650     if ( $displayHours || 
$displayMinutes || 
$displaySeconds); then 
 651         displayDateTime
="true"; else displayDateTime
="false"; fi 
 653     ## Construct duration output string 
 655     if $displayYears; then 
 656         output
=$output$fullYears"Y"; fi 
 657     if $displayMonths; then 
 658         output
=$output$fullMonths"M"; fi 
 659     if $displayDays; then 
 660         output
=$output$fullDays"D"; fi 
 661     if $displayDateTime; then 
 662         output
=$output"T"; fi 
 663     if $displayHours; then 
 664         output
=$output$fullHours"H"; fi 
 665     if $displayMinutes; then 
 666         output
=$output$fullMinutes"M"; fi 
 667     if $displaySeconds; then 
 668         output
=$output$fullSeconds"S"; fi 
 670     ## Output duration string to stdout 
 671     echo "$output" && returnState
="true"; 
 673     #===Determine function return code=== 
 674     if [ "$returnState" = "true" ]; then 
 676     elif [ "$returnState" = "error_input" ]; then 
 680         yell 
"ERROR:Unknown"; 
 684 } # Get duration (ex: PT10M4S ) 
 686     # Desc: Report seconds until next day. 
 688     # Output: stdout: integer seconds until next day 
 689     # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0 
 690     # Usage: timeUntilNextDay 
 691     # Usage: if ! myTTL="$(timeUntilNextDay)"; then yell "ERROR in if statement"; exit 1; fi 
 692     # Depends: date 8, echo 8, yell, try 
 694     local returnState timeCurrent timeNextDay secondsUntilNextDay returnState
 
 695     timeCurrent
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 696     timeNextDay
="$(date -d "$timeCurrent next day
" --iso-8601=date)"; # Produce timestamp of beginning of tomorrow with resolution of 1 second. 
 697     secondsUntilNextDay
="$(( $(date +%s -d "$timeNextDay") - $(date +%s -d "$timeCurrent") ))" ; # Calculate seconds until closest future midnight (res. 1 second). 
 698     if [[ "$secondsUntilNextDay" -gt 0 ]]; then 
 700     elif [[ "$secondsUntilNextDay" -eq 0 ]]; then 
 701         returnState
="warning_zero"; 
 702         yell 
"WARNING:Reported time until next day exactly zero."; 
 703     elif [[ "$secondsUntilNextDay" -lt 0 ]]; then 
 704         returnState
="warning_negative"; 
 705         yell 
"WARNING:Reported time until next day is negative."; 
 708     try 
echo "$secondsUntilNextDay"; # Report 
 710     # Determine function return code 
 711     if [[ "$returnState" = "true" ]]; then 
 713     elif [[ "$returnState" = "warning_zero" ]]; then 
 715     elif [[ "$returnState" = "warning_negative" ]]; then 
 718 } # Report seconds until next day 
 720     # Desc: Report seconds until next hour 
 722     # Output: stdout: integer seconds until next hour 
 723     # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0 
 724     # Usage: timeUntilNextHour 
 725     # Usage: if ! myTTL="$(timeUntilNextHour)"; then yell "ERROR in if statement"; exit 1; fi 
 727     local returnState timeCurrent timeNextHour secondsUntilNextHour
 
 728     timeCurrent
="$(date --iso-8601=seconds)"; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 729     timeNextHour
="$(date -d "$timeCurrent next hour
" --iso-8601=hours)"; # Produce `date`-parsable current time stamp with resolution of 1 second. 
 730     secondsUntilNextHour
="$(( $(date +%s -d "$timeNextHour") - $(date +%s -d "$timeCurrent") ))"; # Calculate seconds until next hour (res. 1 second). 
 731     if [[ "$secondsUntilNextHour" -gt 0 ]]; then 
 733     elif [[ "$secondsUntilNextHour" -eq 0 ]]; then 
 734         returnState
="warning_zero"; 
 735         yell 
"WARNING:Reported time until next hour exactly zero."; 
 736     elif [[ "$secondsUntilNextHour" -lt 0 ]]; then 
 737         returnState
="warning_negative"; 
 738         yell 
"WARNING:Reported time until next hour is negative."; 
 741     try 
echo "$secondsUntilNextHour"; # Report 
 743     # Determine function return code 
 744     if [[ "$returnState" = "true" ]]; then 
 746     elif [[ "$returnState" = "warning_zero" ]]; then 
 748     elif [[ "$returnState" = "warning_negative" ]]; then 
 751 } # Report seconds until next hour 
 753     # Desc: Validates Input 
 754     # Usage: validateInput [str input] [str input type] 
 756     # Input: arg1: string to validate 
 757     #        arg2: string specifying input type (ex:"ssh_pubkey") 
 758     # Output: return code 0: if input string matched specified string type 
 759     # Depends: bash 5, yell() 
 761     local fn argInput argType
 
 769     if [[ $# -gt 2 ]]; then yell 
"ERROR:$0:$fn:Too many arguments."; exit 1; fi; 
 772     if [[ -z "$argInput" ]]; then return 1; fi 
 776     ### Check for alnum/dash base64 (ex: "ssh-rsa AAAAB3NzaC1yc2EAAA") 
 777     if [[ "$argType" = "ssh_pubkey" ]]; then 
 778         if [[ "$argInput" =~ ^
[[:alnum
:]-]*[\ 
]*[[:alnum
:]+/=]*$ 
]]; then 
 782     ### Check for age1[:bech32:] 
 783     if [[ "$argType" = "age_pubkey" ]]; then 
 784         if [[ "$argInput" =~ ^age1
[qpzry9x8gf2tvdw0s3jn54khce6mua7l
]*$ 
]]; then 
 788     if [[ "$argType" = "integer" ]]; then 
 789         if [[ "$argInput" =~ ^
[[:digit
:]]*$ 
]]; then 
 792     ## time element (year, month, week, day, hour, minute, second) 
 793     if [[ "$argType" = "time_element" ]]; then 
 794         if [[ "$argInput" = "year" ]] || \
 
 795                [[ "$argInput" = "month" ]] || \
 
 796                [[ "$argInput" = "week" ]] || \
 
 797                [[ "$argInput" = "day" ]] || \
 
 798                [[ "$argInput" = "hour" ]] || \
 
 799                [[ "$argInput" = "minute" ]] || \
 
 800                [[ "$argInput" = "second" ]]; then 
 803     # Return error if no condition matched. 
 805 } # Validates strings 
 807 magicInitWorkingDir
() { 
 808     # Desc: Determine temporary working directory from defaults or user input 
 809     # Usage: magicInitWorkingDir 
 810     # Input:  vars: optionTmpDir, argTempDirPriority, dirTmpDefault 
 811     # Input:  vars: scriptTimeStart 
 812     # Output: vars: dir_tmp 
 813     # Depends: bash 5.0.3, processArguments(), vbm(), yell() 
 814     # Parse '-t' option (user-specified temporary working dir) 
 815     ## Set dir_tmp_parent to user-specified value if specified 
 816     local fn dir_tmp_parent
 
 821     vbm 
"STATUS:$fn:Starting magicInitWorkingDir() function."; 
 822     if [[ "$optionTmpDir" = "true" ]]; then 
 823         if [[ -d "$argTempDirPriority" ]]; then 
 824             dir_tmp_parent
="$argTempDirPriority";  
 826             yell 
"WARNING:$fn:Specified temporary working directory not valid:$argTempDirPriority"; 
 827             exit 1; # Exit since user requires a specific temp dir and it is not available. 
 830     ## Set dir_tmp_parent to default or fallback otherwise 
 831         if [[ -d "$dirTmpDefault" ]]; then 
 832             dir_tmp_parent
="$dirTmpDefault"; 
 833         elif [[ -d /tmp 
]]; then 
 834             yell 
"WARNING:$fn:$dirTmpDefault not available. Falling back to /tmp ."; 
 835             dir_tmp_parent
="/tmp"; 
 837             yell 
"ERROR:$fn:No valid working directory available. Exiting."; 
 841     ## Set dir_tmp using dir_tmp_parent and nonce (scriptTimeStart) 
 842     dir_tmp
="$dir_tmp_parent"/"$scriptTimeStart""..bkgpslog" && vbm 
"DEBUG :$fn:Set dir_tmp to:$dir_tmp"; # Note: removed at end of main(). 
 843     vbm 
"STATUS:$fn:Finished magicInitWorkingDir() function."; 
 845 magicInitCheckTar
() { 
 846     # Desc: Initializes or checks output tar 
 847     # input: vars: dirOut, bufferTTL, cmd_encrypt_suffix, cmd_compress_suffix 
 848     # input: vars: scriptHostname 
 849     # output: vars: pathout_tar 
 850     # depends: Bash 5.0.3, vbm(), dateShort(), checkMakeTar(), magicWriteVersion() 
 851     local fn checkMakeTarES
 
 856     vbm 
"STATUS:$fn:Starting magicInitCheckTar() function."; 
 858     pathout_tar
="$dirOut"/"$(dateShort "$
(date --date="$bufferTTL seconds ago" --iso-8601=seconds
)")"..
"$scriptHostname""$label""$cmd_compress_suffix""$cmd_encrypt_suffix".
tar && \
 
 859         vbm 
"STATUS:$fn:Set pathout_tar to:$pathout_tar"; 
 860     # Validate pathout_tar as tar. 
 861     checkMakeTar 
"$pathout_tar"; checkMakeTarES
="$?"; 
 862     ## Add VERSION file if checkMakeTar had to create a tar (exited 1) or replace one (exited 2) 
 863     vbm 
"STATUS:$fn:exit status before magicWriteVersion:$checkMakeTarES" 
 864     if [[ "$checkMakeTarES" -eq 1 ]] || 
[[ "$checkMakeTarES" -eq 2 ]]; then magicWriteVersion
; fi 
 865     vbm 
"STATUS:$fn:Finished magicInitCheckTar() function."; 
 866 } # Initialize tar, set pathout_tar 
 867 magicParseCompressionArg
() { 
 868     # Desc: Parses compression arguments specified by '-c' option 
 869     # Input:  vars: optionCompress 
 870     # Output: cmd_compress, cmd_compress_suffix 
 871     # Depends: processArguments(), vbm(), checkapp(), gzip 1.9 
 877     vbm 
"STATUS:$fn:Starting magicParseCompressionArg() function."; 
 878     if [[ "$optionCompress" = "true" ]]; then # Check if compression option active 
 879         if checkapp 
gzip; then # Check if gzip available 
 880             cmd_compress
="gzip " && vbm 
"STATUS:$fn:cmd_compress:$cmd_compress"; 
 881             cmd_compress_suffix
=".gz" && vbm 
"STATUS:$fn:cmd_compress_suffix:$cmd_compress_suffix"; 
 883             yell 
"ERROR:$fn:Compression enabled but \"gzip\" not found. Exiting."; exit 1; 
 886         cmd_compress
="tee /dev/null " && vbm 
"STATUS:$fn:cmd_compress:$cmd_compress"; 
 887         cmd_compress_suffix
="" && vbm 
"STATUS:$fn:cmd_compress_suffix:$cmd_compress_suffix"; 
 888         vbm 
"DEBUG :$fn:Compression not enabled."; 
 890     vbm 
"STATUS:$fn:Finished magicParseCompressionArg() function."; 
 891 } # Form compression cmd string and filename suffix 
 892 magicParseCustomTTL
() { 
 893     # Desc: Set user-specified TTLs for buffer and script 
 894     # Usage: magicParseCustomTTL 
 895     # Input: vars: argCustomBufferTTL (integer), argCustomScriptTTL_TE (string) 
 896     # Input: vars: optionCustomBufferTTL, optionCustomScriptTTL_TE 
 897     # Input: vars: bufferTTL (integer), scriptTTL_TE (string) 
 898     # Output: bufferTTL (integer), scriptTTL_TE (string) 
 899     # Depends: Bash 5.0.3, yell(), vbm(), validateInput(), showUsage() 
 905     vbm 
"STATUS:$fn:Starting magicParseCustomTTL() function."; 
 906     # React to '-b, --buffer-ttl' option 
 907     if [[ "$optionCustomBufferTTL" = "true" ]]; then 
 908         ## T: Check if argCustomBufferTTL is an integer 
 909         if validateInput 
"$argCustomBufferTTL" "integer"; then 
 910             ### T: argCustomBufferTTL is an integer 
 911             bufferTTL
="$argCustomBufferTTL" && vbm 
"STATUS:$fn:Custom bufferTTL from -b:$bufferTTL"; 
 913             ### F: argcustomBufferTTL is not an integer 
 914             yell 
"ERROR:$fn:Invalid integer argument for custom buffer time-to-live."; showUsage
; exit 1; 
 916         ## F: do not change bufferTTL 
 919     # React to '-B, --script-ttl' option 
 920     if [[ "$optionCustomScriptTTL_TE" = "true" ]]; then 
 921         ## T: Check if argCustomScriptTTL is a time element (ex: "day", "hour") 
 922         if validateInput 
"$argCustomScriptTTL_TE" "time_element"; then 
 923             ### T: argCustomScriptTTL is a time element 
 924             scriptTTL_TE
="$argCustomScriptTTL_TE" && vbm 
"STATUS:$fn:Custom scriptTTL_TE from -B:$scriptTTL_TE"; 
 926             ### F: argcustomScriptTTL is not a time element 
 927             yell 
"ERROR:$fn:Invalid time element argument for custom script time-to-live."; showUsage
; exit 1; 
 929         ## F: do not change scriptTTL_TE 
 931     vbm 
"STATUS:$fn:Finished magicParseCustomTTL() function."; 
 932 } # Sets custom script or buffer TTL if specified 
 934     # Desc: Parses -l option to set label 
 935     # In : optionLabel, argLabel 
 937     # Depends: Bash 5.0.3, vbm(), yell() 
 943     vbm 
"STATUS:$fn:Started magicParseLabel() function."; 
 944     # Do nothing if optionLabel not set to true. 
 945     if [[ ! "$optionLabel" = "true" ]]; then 
 946         vbm 
"STATUS:$fn:optionlabel not set to 'true'. Returning early."; 
 949     # Set label if optionLabel is true 
 950     if [[ "$optionLabel" = "true" ]]; then 
 951         label
="_""$argLabel"; 
 952         vbm 
"STATUS:$fn:Set label:$label"; 
 954     vbm 
"STATUS:$fn:Finished magicParseLabel() function."; 
 955 } # Set label used in output file name 
 956 magicParseProcessStrings
() { 
 957     # Desc: Processes user-supplied process strings into process commands for appendFileTar(). 
 958     # Usage: magicParseProcessStrings 
 959     # In : vars: optionProcString optionNoStoreRaw optionStoreRaw argRawFileExt 
 960     #      arry: argProcStrings, argProcFileExts 
 961     # Out: arry: procStrings, procFileExts 
 962     # Depends Bash 5.0.3, yell(), vbm() 
 968     vbm 
"STATUS:$fn:Starting magicParseProcessStrings() function."; 
 969     vbm 
"STATUS:$fn:var:optionProcString:$optionProcString"; 
 970     vbm 
"STATUS:$fn:var:optionNoStoreRaw:$optionNoStoreRaw"; 
 971     vbm 
"STATUS:$fn:var:optionStoreRaw:$optionStoreRaw"; 
 972     vbm 
"STATUS:$fn:var:argRawFileExt:$argRawFileExt"; 
 973     vbm 
"STATUS:$fn:ary:argProcStrings:${argProcStrings[*]}"; 
 974     vbm 
"STATUS:$fn:ary:argProcFileExts:${argProcFileExts[*]}" 
 976     ## Validate argRawFileExt 
 977     if [[ "$argRawFileExt" =~ ^
[.
][[:alnum
:]]*$ 
]]; then 
 978         rawFileExt
="$argRawFileExt" && \
 
 979             vbm 
"DEBUG :$fn:Set rawFileExt to \"$argRawFileExt\""; 
 981         vbm 
"DEBUG :$fn:Validation failure for $argRawFileExt . Not set to rawFileExt."; 
 984     # Add default stdin output file entries for procStrings, procFileExts 
 985     ## Check if user specified that no raw stdin be saved. 
 986     if [[ ! "$optionNoStoreRaw" = "true" ]]; then 
 987         ### T: --no-store-raw not set. Store raw. Append procStrings with cat. 
 988         vbm 
"DEBUG :$fn:--no-store-raw not set. Storing raw."; 
 989         #### Append procStrings array 
 990         procStrings
+=("cat ") && \
 
 991             vbm 
"DEBUG :$fn:Appended \"cat \" to procStrings"; 
 992         vbm 
"DEBUG :$fn:procStrings array:${procStrings[*]}"; 
 993         #### Check if --store-raw set. 
 994         if [[ "$optionStoreRaw" = "true" ]]; then 
 995             ##### T: --store-raw set. Append procFileExts with user-specified file ext 
 996             vbm 
"DEBUG :$fn:--store-raw set."; 
 997             procFileExts
+=("$rawFileExt") && \
 
 998                 vbm 
"DEBUG :$fn:Appended $rawFileExt to procFileExts"; 
 999             vbm 
"STATUS:$fn:procFileExts array:${procFileExts[*]}"; 
1001             ##### F: --store-raw not set. Append procFileExts with default ".stdin" file ext 
1002             ###### Append procFileExts array 
1003             procFileExts
+=(".stdin") && \
 
1004                 vbm 
"DEBUG :$fn:Appended \".stdin\" to procFileExts"; 
1005             vbm 
"STATUS:$fn:procFileExts array:${procFileExts[*]}"; 
1008         ### F: --no-store-raw set. Do not store raw. 
1009         #### Do not append procStrings or procFileExts arrays. 
1010         vbm 
"STATUS:$fn:--no-store-raw set. Not storing raw."; 
1011         vbm 
"STATUS:$fn:procFileExts array:${procFileExts[*]}"; 
1014     # Do nothing more if optionProcString not set to true. 
1015     if [[ ! "$optionProcString" = "true" ]]; then 
1016         vbm 
"STATUS:$fn:optionProcString not set to 'true'. Returning early."; 
1018     # Validate input array indices 
1019     ## Make sure that argProcStrings and argProcFileExts have same index counts 
1020     if ! [[ "${#argProcStrings[@]}" -eq "${#argProcFileExts[@]}" ]]; then 
1021         yell 
"ERROR:$fn:Mismatch in number of elements in arrays argProcStrings and argProcFileExts:${#argProcStrings[@]} DNE ${#argProcFileExts[@]}"; 
1022         yell 
"STATUS:$fn:argProcStrings:${argProcStrings[*]}"; yell 
"STATUS:$fn:argProcFileExts:${argProcFileExts[*]}"; exit 1; fi; 
1023     ## Make sure that no array elements are blank 
1024     for element 
in "${argProcStrings[@]}"; do 
1025         if [[ -z "$element" ]]; then yell 
"ERROR:$fn:Empty process string specified. Exiting."; exit 1; fi; done 
1026     for element 
in "${argProcFileExts[@]}"; do 
1027         if [[ -z "$element" ]]; then yell 
"ERROR:$fn:Empty output file extension specified. Exiting."; exit 1; fi; done 
1028     ## Make sure that no process string starts with '-' (ex: if only one arg supplied after '-p' option) 
1029     for element 
in "${argProcStrings[@]}"; do 
1030         if [[ "$element" =~ ^
[-][[:print
:]]*$ 
]] && [[ ! "$element" =~ ^
[[:print
:]]*$ 
]]; then 
1031             yell 
"ERROR:$fn:Illegal character '-' at start of process string element:\"$element\""; 
1033     vbm 
"STATUS:$fn:Quick check shows argProcStrings and argProcFileExts appear to have valid contents."; 
1034     vbm 
"STATUS:$fn:argProcStrings:${argProcStrings[*]}" 
1035     vbm 
"STATUS:$fn:argProcFileExts:${argProcFileExts[*]}" 
1036     procStrings
+=("${argProcStrings[@]}"); # Export process command strings 
1037     procFileExts
+=("${argProcFileExts[@]}"); # Export process command strings 
1038     vbm 
"STATUS:$fn:procStrings:${procStrings[*]}" 
1039     vbm 
"STATUS:$fn:procFileExts:${procFileExts[*]}" 
1040     vbm 
"STATUS:$fn:Finished magicParseProcessStrings() function."; 
1041 } # Validate and save process strings and file extensions to arrays procStrings, procFileExts 
1042 magicParseRecipients
() { 
1043     # Desc: Parses recipient arguments specified by '-r' or '-R' options 
1044     # Usage: magicParseRecipients 
1045     # In : vars: optionEncrypt, optionRecArg, optionRecDir 
1046     #      arry: argRecPubKeys (-r), argRecDir (-R) 
1047     # Out: vars: cmd_encrypt, cmd_encrypt_suffix 
1048     # Depends: head 8.30, checkapp(), checkAgePubkey(), validateInput() 
1049     local fn recipients recipientDir recFileLine updateRecipients
 
1050     local -a recPubKeysValid candRecPubKeysValid
 
1052     # Save function name 
1053     fn
="${FUNCNAME[0]}"; 
1054     vbm 
"STATUS:$fn:Starting magicParseRecipients() function."; 
1056     # Catch illegal option combinations 
1057     ## Catch case if '-e' is set but neither '-r' nor '-R' is set 
1058     if [[ "$optionEncrypt" = "true" ]] && \
 
1059            ! { [[ "$optionRecArg" = "true" ]] || 
[[ "$optionRecDir" = "true" ]]; }; then 
1060         yell 
"ERROR:$fn:\\'-e\\' set but no \\'-r\\' or \\'-R\\' set."; exit 1; fi; 
1061     ## Catch case if '-r' or '-R' set but '-e' is not 
1062     if [[ ! "$optionEncrypt" = "true" ]] && \
 
1063            { [[ "$optionRecArg" = "true" ]] || 
[[ "$optionRecDir" = "true" ]]; }; then 
1064         yell 
"ERROR:$fn:\\'-r\\' or \\'-R\\' set but \\'-e\\' is not set."; exit 1; fi; 
1066     # Handle no encryption cases 
1067     if [[ ! "$optionEncrypt" = "true" ]]; then 
1068         cmd_encrypt
="cat " && vbm 
"STATUS:$fn:cmd_encrypt:$cmd_encrypt"; 
1069         cmd_encrypt_suffix
="" && vbm 
"STATUS:$fn:cmd_encrypt_suffix:$cmd_encrypt_suffix"; 
1070         vbm 
"DEBUG :$fn:Encryption not enabled."; 
1073     # Handle encryption cases 
1074     ## Check age availability 
1075     if ! checkapp age
; then yell 
"ERROR:$fn:age not available. Exiting."; exit 1; fi 
1076     ## Parse '-r' options: validate and append pubkeys from argRecPubKeys to recPubKeysValid 
1077     if [[ "$optionRecArg" = "true" ]]; then 
1078         for pubkey 
in "${argRecPubKeys[@]}"; do # Validate recipient pubkey strings by forming test message 
1079             vbm 
"DEBUG :$fn:Testing pubkey string:$pubkey"; 
1080             if checkAgePubkey 
"$pubkey" && \
 
1081                     ( validateInput 
"$pubkey" "ssh_pubkey" || validateInput 
"$pubkey" "age_pubkey"); then 
1082                 #### Add validated pubkey to recPubKeysValid array 
1083                 recPubKeysValid
+=("$pubkey") && \
 
1084                     vbm 
"DEBUG :$fn:recPubkeysValid:pubkey added:$pubkey"; 
1086                 yell 
"ERROR:$fn:Exit code ""$?"". Invalid recipient pubkey string. Exiting."; exit 1; 
1089         vbm 
"STATUS:$fn:Finished processing argRecPubKeys array"; 
1090         vbm 
"DEBUG :$fn:Array of validated pubkeys:${recPubKeysValid[*]}"; 
1092     ## Parse '-R' options: validate and append pubkeys in argRecDir to recPubKeysValid 
1093     if [[ "$optionRecDir" = "true" ]]; then 
1094         ### Check that argRecDir is a directory 
1095         if [[ -d "$argRecDir" ]]; then 
1096             recipientDir
="$argRecDir" && \
 
1097                 vbm 
"STATUS:$fn:Recipient watch directory detected:\"$recipientDir\""; 
1098             #### Initialize variable indicating outcome of pubkey review 
1099             unset updateRecipients
 
1100             #### Add existing recipients from '-r' option 
1101             candRecPubKeysValid
=("${recPubKeysValid[@]}"); 
1102             #### Parse files in recipientDir 
1103             for file in "$recipientDir"/*; do 
1104                 ##### Read first line of each file 
1105                 recFileLine
="$(head -n1 "$file")" && \
 
1106                     vbm 
"STATUS:$fn:Checking if pubkey:\"$recFileLine\""; 
1107                 ##### check if first line is a valid pubkey 
1108                 if checkAgePubkey 
"$recFileLine" && \
 
1109                         ( validateInput 
"$recFileLine" "ssh_pubkey" || validateInput 
"$recFileLine" "age_pubkey"); then 
1110                     ###### T: add candidate pubkey to candRecPubKeysValid 
1111                     candRecPubKeysValid
+=("$recFileLine") && \
 
1112                         vbm 
"STATUS:$fn:RecDir pubkey is valid pubkey:\"$recFileLine\""; 
1114                     ###### F: throw warning; 
1115                     yell 
"ERROR:$fn:Invalid recipient file detected. Not modifying recipient list:$recFileLine"; 
1116                     updateRecipients
="false"; 
1119             #### Write candRecPubKeysValid array to recPubKeysValid if no invalid key detected 
1120             if ! [[ "$updateRecipients" = "false" ]]; then 
1121                 recPubKeysValid
=("${candRecPubKeysValid[@]}") && \
 
1122                     vbm 
"STATUS:$fn:Wrote candRecPubkeysValid to recPubKeysValid:\"${recPubKeysValid[*]}\""; 
1127     ## Form age recipient string from recPubKeysValid 
1128     for pubkey 
in "${recPubKeysValid[@]}"; do 
1129         recipients
="$recipients""-r '$pubkey' "; 
1130         vbm 
"STATUS:$fn:Added pubkey for forming age recipient string:""$pubkey"; 
1131         vbm 
"DEBUG :$fn:recipients:""$recipients";       
1134     ## Output cmd_encrypt, cmd_encrypt_suffix from recipients 
1135     cmd_encrypt
="age ""$recipients " && vbm 
"STATUS:$fn:cmd_encrypt:$cmd_encrypt"; 
1136     cmd_encrypt_suffix
=".age" && vbm 
"STATUS:$fn:cmd_encrypt_suffix:$cmd_encrypt_suffix"; 
1138     vbm 
"STATUS:$fn:Finished magicParseRecipients() function."; 
1139 } # Sets cmd_encrypt, cmd_encrypt_suffix from -r, -R args 
1140 magicSetScriptTTL
() { 
1141     #Desc: Sets script_TTL seconds from provided time_element string argument 
1142     #Usage: magicSetScriptTTL [str time_element] 
1143     #Input: arg1: string (Ex: scriptTTL_TE; "day" or "hour") 
1144     #Output: var: scriptTTL (integer seconds) 
1145     #Depends: timeUntilNextHour, timeUntilNextDay 
1146     local fn argTimeElement
 
1148     # Save function name 
1149     fn
="${FUNCNAME[0]}"; 
1151     vbm 
"STATUS:$fn:Starting magicSetScriptTTL() function."; 
1152     argTimeElement
="$1"; 
1153     if [[ "$argTimeElement" = "day" ]]; then 
1154         # Set script lifespan to end at start of next day 
1155         vbm 
"STATUS:$fn:Setting script lifespan to end at start of next day. argTimeElement:$argTimeElement"; 
1156         if ! scriptTTL
="$(timeUntilNextDay)"; then # sets scriptTTL, then checks exit code 
1157             if [[ "$scriptTTL" -eq 0 ]]; then 
1158                 ((scriptTTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout. 
1159                 vbm 
"STATUS:$fn:scriptTTL:$scriptTTL"; 
1161             yell 
"ERROR:$fn:timeUntilNextDay exit code $?"; exit 1; 
1164     elif [[ "$argTimeElement" = "hour" ]]; then 
1165         # Set script lifespan to end at start of next hour 
1166         vbm 
"STATUS:$fn:Setting script lifespan to end at start of next hour. argTimeElement:$argTimeElement"; 
1167         if ! scriptTTL
="$(timeUntilNextHour)"; then # sets scriptTTL, then checks exit code 
1168             if [[ "$scriptTTL" -eq 0 ]]; then 
1169                 ((scriptTTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout. 
1170                 vbm 
"STATUS:$fn:scriptTTL:$scriptTTL"; 
1172                 yell 
"ERROR:$fn:timeUntilNextHour exit code $?"; exit 1; 
1176         yell 
"ERROR:$fn:Invalid argument for setScriptTTL function:$argTimeElement"; exit 1; 
1178     vbm 
"STATUS:$fn:Finished magicSetScriptTTL() function."; 
1179 } # Set scriptTTL in seconds until next (day|hour). 
1180 magicWriteVersion
() { 
1181     # Desc: Appends time-stamped VERSION to pathout_tar 
1182     # Usage: magicWriteVersion 
1183     # Input: vars: pathout_tar, dir_tmp 
1184     # Input: vars: scriptVersion, scriptURL, ageVersion, ageURL, scriptHostname 
1185     # Input: array: recPubKeysValid 
1186     # Output: appends tar (pathout_tar) 
1187     # Depends: bash 5.0.3, dateTimeShort(), appendArgTar() 
1188     local fn fileoutVersion contentVersion pubKeyIndex pubKeyIndex
 
1190     # Save function name 
1191     fn
="${FUNCNAME[0]}"; 
1193     vbm 
"STATUS:$fn:Starting magicWriteVersion() function."; 
1194     # Set VERSION file name 
1195     fileoutVersion
="$(dateTimeShort)..VERSION"; 
1197     # Gather VERSION data in contentVersion 
1198     contentVersion
="scriptVersion=$scriptVersion"; 
1199     #contentVersion="$contentVersion""\\n"; 
1200     contentVersion
="$contentVersion""\\n""scriptName=$scriptName"; 
1201     contentVersion
="$contentVersion""\\n""scriptURL=$scriptURL"; 
1202     contentVersion
="$contentVersion""\\n""ageVersion=$ageVersion"; 
1203     contentVersion
="$contentVersion""\\n""ageURL=$ageURL"; 
1204     contentVersion
="$contentVersion""\\n""date=$(date --iso-8601=seconds)"; 
1205     contentVersion
="$contentVersion""\\n""hostname=$scriptHostname"; 
1206     ## Add list of recipient pubkeys 
1207     for pubkey 
in "${recPubKeysValid[@]}"; do 
1209         contentVersion
="$contentVersion""\\n""PUBKEY_$pubKeyIndex=$pubkey"; 
1211     ## Process newline escapes 
1212     contentVersion
="$(echo -e "$contentVersion")" 
1214     # Write contentVersion as file fileoutVersion and write-append to pathout_tar 
1215     appendArgTar 
"$contentVersion" "$fileoutVersion" "$pathout_tar" "$dir_tmp" && \
 
1216         vbm 
"STATUS:$fn:Appended $fileoutVersion to $pathout_tar"; 
1217     vbm 
"STATUS:$fn:Finished magicWriteVersion() function."; 
1218 } # write version data to pathout_tar via appendArgTar() 
1219 magicProcessWriteBuffer
() { 
1220     # Desc: process and write buffer 
1221     # In : vars: bufferTTL scriptHostname label dir_tmp SECONDS 
1222     #    : vars: timeBufferStartEpoch timeBufferEndEpoch 
1224     # Out: file:(pathout_tar) 
1225     # Depends: Bash 5.0.3, date 8.30, yell(), vbm(), dateTimeShort(), 
1226     ### Note: These arrays should all have the same number of elements: 
1227     ###       pathouts, fileouts, procFileExts, procStrings 
1229     local fn timeBufferStartLong timeBufferStart bufferDuration bufferDurationStr fileoutBasename
 
1230     local -a fileouts pathouts
 
1231     local writeCmd1 writeCmd2 writeCmd3 writeCmd4
 
1233     # Debug:Get function name 
1234     fn
="${FUNCNAME[0]}"; 
1236     vbm 
"STATUS:$fn:Started magicProcessWriteBuffer()."; 
1237     vbm 
"DEBUG :$fn:buffer array element count:${#buffer[@]}"; 
1238     vbm 
"DEBUG :$fn:buffer array first element:${buffer[0]}"; 
1239     vbm 
"DEBUG :$fn:buffer array last element :${buffer[-1]}"; 
1241     # Determine file paths (time is start of buffer period) 
1242     ## Calculate start time 
1243     timeBufferStartLong
="$(date --date="@
$timeBufferStartEpoch" --iso-8601=seconds)" && \
 
1244         vbm 
"DEBUG :$fn:timeBufferStartLong:$timeBufferStartLong"; # Note start time in 'date' parsable ISO-8601 
1245     timeBufferStart
="$(dateTimeShort "$timeBufferStartLong" )" && \
 
1246         vbm 
"DEBUG :$fn:timeBufferStart:$timeBufferStart"; # Note start time YYYYmmddTHHMMSS+zzzz (no separators) 
1247     ## Calculate buffer duration string (ISO-8601 duration) 
1248     bufferDuration
="$((timeBufferEndEpoch - timeBufferStartEpoch))" && \
 
1249         vbm 
"DEBUG :$fn:bufferDuration:$bufferDuration"; # length of time (seconds) stdin was read 
1250     bufferDurationStr
="$(timeDuration "$bufferDuration")" && \
 
1251         vbm 
"DEBUG :$fn:bufferDurationStr:$bufferDurationStr"; # buffer duration (ISO-8601) 
1252     ## Set common basename 
1253     fileoutBasename
="$timeBufferStart""--""$bufferDurationStr""..""$scriptHostname""$label" && \
 
1254         vbm 
"STATUS:$fn:Set fileoutBasename to:$fileoutBasename"; 
1255     ## Determine output file name array 
1256     ### in: fileOutBasename cmd_compress_suffix cmd_encrypt_suffix procFileExts 
1257     for fileExt 
in "${procFileExts[@]}"; do 
1258         fileouts
+=("$fileoutBasename""$fileExt""$cmd_compress_suffix""$cmd_encrypt_suffix") && \
 
1259             vbm 
"STATUS:$fn:Added $fileExt to fileouts:${fileouts[*]}"; 
1261     for fileName 
in "${fileouts[@]}"; do 
1262         pathouts
+=("$dir_tmp"/"$fileName") && \
 
1263             vbm 
"STATUS:$fn:Added $fileName to pathouts:${pathouts[*]}"; 
1265     ## Update pathout_tar 
1268     # Process and write buffers to dir_tmp 
1269     ## Prepare command strings 
1270     writeCmd1
="printf \"%s\\\\n\" \"\${buffer[@]}\""; # printf "%s\\n" "${buffer[@]}" 
1271     #writeCmd2="" # NOTE: Specified by parsing array procStrings 
1272     writeCmd3
="$cmd_compress"; 
1273     writeCmd4
="$cmd_encrypt"; 
1275     ## Process buffer and write to dir_tmp 
1276     vbm 
"DEBUG :$fn:fileouts    element count:${#fileouts[@]}"; 
1277     vbm 
"DEBUG :$fn:pathouts    element count:${#pathouts[@]}"; 
1278     vbm 
"DEBUG :$fn:procStrings element count:${#pathouts[@]}"; 
1279     vbm 
"DEBUG :$fn:fileouts    contents:${fileouts[*]}"; 
1280     vbm 
"DEBUG :$fn:pathouts    contents:${pathouts[*]}"; 
1281     vbm 
"DEBUG :$fn:procStrings contents:${pathouts[*]}"; 
1282     for index 
in "${!pathouts[@]}"; do 
1283         writeCmd2
="${procStrings[$index]}"; 
1284         writeCmdAll
="$writeCmd1 | $writeCmd2 | $writeCmd3 | $writeCmd4" && vbm 
"STATUS:$fn:Assembled command:\"$writeCmdAll\""; 
1285         eval "$writeCmd1 | $writeCmd2 | $writeCmd3 | $writeCmd4" > "${pathouts[$index]}" && vbm 
"STATUS:$fn:Wrote command output to ${pathouts[$index]}"; 
1288     # Append dir_tmp files to pathout_tar 
1289     wait; # Wait to avoid collision with older magicProcessWriteBuffer() instances (see https://www.tldp.org/LDP/abs/html/x9644.html ) 
1290     for index 
in "${!pathouts[@]}"; do 
1291         tar --append --directory="$dir_tmp" --file="$pathout_tar" "${fileouts[$index]}" && \
 
1292             vbm 
"STATUS:$fn:Appended ${pathouts[$index]} to $pathout_tar"; 
1293         #appendFileTar "${pathouts[$index]}" "${fileouts[$index]}" "$pathout_tar" "$dir_tmp" && \ 
1296     # Remove secured chunks from dir_tmp 
1297     for path 
in "${pathouts[@]}"; do 
1298         rm "$path" && vbm 
"STATUS:$fn:Removed:$path"; 
1301     vbm 
"STATUS:$fn:Finished magicProcessWriteBuffer()."; 
1302 } # Process and Write buffer 
1306         cmd | bklog [ options ] 
1310                 Display help information. 
1312                 Display script version. 
1314                 Display debugging info. 
1317         -r, --recipient [ string pubkey ] 
1318                 Specify recipient. May be age or ssh pubkey. 
1319                 May be specified multiple times for multiple pubkeys. 
1320                 See https://github.com/FiloSottile/age 
1321         -o, --output [ path dir ] 
1322                 Specify output directory to save logs. This option is required 
1324         -p, --process-string [ filter command ] [ output file extension]  
1325                 Specify how to create and name a processed version of the stdin. 
1326                 For example, if stdin is 'nmea' location data: 
1328                 -p "gpsbabel -i nmea -f - -o gpx -F - " ".gpx" 
1330                 This option would cause the stdin to 'bklog' to be piped into 
1331                 the 'gpsbabel' command, interpreted as 'nmea' data, converted 
1332                 into 'gpx' format, and then appended to the output tar file 
1333                 as a file with a '.gpx' extension. 
1334                 This option may be specified multiple times in order to output 
1335                 results of multiple different processing methods. 
1336         -l, --label [ string ] 
1337                 Specify a label to be included in all output file names. 
1338                 Ex: 'location' if stdin is location data. 
1339         -w, --store-raw [ file extension ] 
1340                 Specify file extension of file within output tar that contains 
1341                 raw stdin data. The default behavior is to always save raw stdin 
1342                 data in a '.stdin' file. Example usage when 'bklog' receives 
1343                 'nmea' data from 'gpspipe -r': 
1347                 Stdin data is saved in a '.nmea' file within the output tar. 
1349                 Do not store raw stdin in output tar. 
1351                 Compress output with gzip (before encryption if enabled). 
1353                 Specify time zone. (ex: "America/New_York") 
1354         -t, --temp-dir [path dir] 
1355                 Specify parent directory for temporary working directory. 
1357         -R, --recipient-dir [path dir] 
1358                 Specify directory containing files whose first lines are 
1359                 to be interpreted as pubkey strings (see '-r' option). Only 
1360                 one directory may be specified. 
1361         -b, --buffer-ttl [integer] 
1362                 Specify custom buffer period in seconds (default: 300 seconds) 
1363         -B, --script-ttl [time element string] 
1364                 Specify custom script time-to-live in seconds (default: "day") 
1365                 Valid values: "day", "hour" 
1367     EXAMPLE: (bash script lines) 
1368     $ gpspipe -r | /bin/bash bklog -v -e -c -z "UTC" -t "/dev/shm" \ 
1369     -r age1mrmfnwhtlprn4jquex0ukmwcm7y2nxlphuzgsgv8ew2k9mewy3rs8u7su5 \ 
1370     -r age1ala848kqrvxc88rzaauc6vc5v0fqrvef9dxyk79m0vjea3hagclswu0lgq \ 
1371     -R ~/.config/bklog/recipients -w ".nmea" -b 300 -B "day" \ 
1372     -o ~/Sync/Logs -l "location" \ 
1373     -p "gpsbabel -i nmea -f - -o gpx -F - " ".gpx" \ 
1374     -p "gpsbabel -i nmea -f - -o kml -F - " ".kml" 
1376 } # Display information on how to use this script. 
1379     # Desc: Main function 
1382     # Outputs: file (pathout_tar) 
1386     # Debug:Get function name 
1387     fn
="${FUNCNAME[0]}"; 
1389     vbm 
"STATUS:$fn:Started function main()."; 
1391     processArguments 
"$@"; 
1392     ## Determine working directory 
1393     magicInitWorkingDir
; # Sets dir_tmp from argTempDirPriority 
1394     ## Set output encryption and compression option strings 
1395     ### React to "-e", "-r", and "-R" (encryption recipients) options 
1396     magicParseRecipients
; # Update cmd_encrypt, cmd_encrypt_suffix 
1397     ### React to "-c" ("compression") option 
1398     magicParseCompressionArg
; # Updates cmd_compress[_suffix] 
1399     ## React to "-b" and "-B" (custom buffer and script TTL) options 
1400     magicParseCustomTTL
; # Sets custom scriptTTL_TE and/or bufferTTL if specified 
1401     ## React to "-p" (user-supplied process command and file extension strings) options 
1402     magicParseProcessStrings
; # Sets arrays: procStrings, procFileExts 
1403     ## React to "-l" (output file label) option 
1404     magicParseLabel
; # sets label (ex: "_location") 
1406     # Perform secondary setup operations 
1407     ## Set script lifespan (scriptTTL from scriptTTL_TE) 
1408     magicSetScriptTTL 
"$scriptTTL_TE"; 
1409     ## Adjust SECONDS so buffer rounds align with time elements 
1410     ### Advance SECONDS the remainder seconds for dividend timeUntilNextDay, divisor bufferTTL 
1411     if [[ "$(timeUntilNextDay)" -gt "$bufferTTL" ]]; then 
1412         vbm 
"DEBUG :$fn:SECONDS currently  :$SECONDS"; 
1413         SECONDS
="$(( $(timeUntilNextDay) % bufferTTL ))" && \
 
1414             vbm 
"DEBUG :$fn:SECONDS advanced to:$SECONDS"; 
1416     ## Init temp working dir 
1417     try mkdir 
"$dir_tmp" && vbm 
"DEBUG :$fn:Working dir created at dir_tmp:$dir_tmp"; 
1418     ## Initialize output tar (set pathout_tar) 
1420     ## Append VERSION file to tar 
1423     # Check vital apps, files, dirs 
1424     if ! checkapp 
tar && ! checkdir 
"$dirOut" "dir_tmp"; then 
1425         yell 
"ERROR:$fn:Critical components missing."; 
1426         displayMissing
; yell 
"Exiting."; exit 1; fi 
1428     # MAIN LOOP: Run until script TTL seconds pass 
1430     while [[ $SECONDS -lt "scriptTTL" ]]; do 
1431         vbm 
"STATUS:$fn:Starting buffer round:$bufferRound"; 
1432         bufferTOD
="$(( (1+bufferRound)*bufferTTL ))" && vbm 
"DEBUG :$fn:bufferTOD:$bufferTOD"; # Set buffer round time-of-death 
1433         # Note start time of data collection 
1434         timeBufferStartEpoch
="$(date +%s)" && vbm 
"DEBUG :$fn:timeBufferStartEpoch:$timeBufferStartEpoch"; 
1435         # Consume stdin to fill buffer until buffer time-of-death (TOD) arrives 
1436         while read -r -t "$bufferTTL" line 
&& [[ $SECONDS -lt "$bufferTOD" ]]; do 
1437             # Append line to buffer array 
1440         # Note end time of data collection 
1441         timeBufferEndEpoch
="$(date +%s)" && vbm 
"DEBUG :$fn:timeBufferEndEpoch:$timeBufferEndEpoch"; 
1442         # Create dir_tmp if missing 
1443         if ! [[ -d "$dir_tmp" ]]; then 
1444             yell 
"ERROR:$fn:dir_tmp existence failure:$dir_tmp"; 
1445             try mkdir 
"$dir_tmp" && vbm 
"DEBUG :$fn:Working dir recreated dir_tmp:$dir_tmp"; fi 
1446         # Update cmd_encrypt, cmd_encrypt_suffix 
1447         magicParseRecipients
; 
1448         # Export buffer to asynchronous processing. 
1449         magicProcessWriteBuffer 
& 
1450         unset buffer
; # Clear buffer array for next bufferRound 
1451         # Increment buffer round 
1457     try 
rm -r "$dir_tmp" && vbm 
"STATUS:$fn:Removed dir_tmp:$dir_tmp"; 
1459     vbm 
"STATUS:$fn:Finished function main()."; 
1462 #===END Declare local script functions=== 
1463 #==END Define script parameters== 
1465 #==BEGIN Perform work and exit== 
1466 main 
"$@" # Run main function. 
1468 #==END Perform work and exit== 
1470 # Author: Steven Baltakatei Sandoval;