4 declare -Ag appRollCall 
# Associative array for storing app status 
   5 declare -Ag fileRollCall 
# Associative array for storing file status 
   6 declare -Ag dirRollCall 
# Associative array for storing dir status 
   7 declare -ag arrayPosArgs 
# Associative array for processArgs() function 
   9 calendars
+=("https://finney.calendar.eternitywall.com"); 
  10 calendars
+=("https://btc.calendar.catallaxy.com"); 
  11 calendars
+=("https://alice.btc.calendar.opentimestamps.org"); 
  12 calendars
+=("https://bob.btc.calendar.opentimestamps.org"); 
  13 declare -a commands 
# array for storing assembled commands 
  14 age_threshold
="60"; # min age to add file; seconds; 
  17 yell
() { echo "$0: $*" >&2; } # print script path and all args to stderr 
  18 die
() { yell 
"$*"; exit 111; } # same as yell() but non-zero exit status 
  19 try
() { "$@" || die 
"cannot $*"; } # runs args as command, reports args if command fails 
  21     # Desc: If arg is a command, save result in assoc array 'appRollCall' 
  22     # Usage: checkapp arg1 arg2 arg3 ... 
  24     # Input: global assoc. array 'appRollCall' 
  25     # Output: adds/updates key(value) to global assoc array 'appRollCall' 
  31         if command -v "$arg" 1>/dev
/null 
2>&1; then # Check if arg is a valid command 
  32             appRollCall
[$arg]="true"; 
  33             if ! [ "$returnState" = "false" ]; then returnState
="true"; fi; 
  35             appRollCall
[$arg]="false"; returnState
="false"; 
  39     #===Determine function return code=== 
  40     if [ "$returnState" = "true" ]; then 
  45 } # Check that app exists 
  47     # Desc: If arg is a file path, save result in assoc array 'fileRollCall' 
  48     # Usage: checkfile arg1 arg2 arg3 ... 
  50     # Input: global assoc. array 'fileRollCall' 
  51     # Output: adds/updates key(value) to global assoc array 'fileRollCall'; 
  52     # Output: returns 0 if app found, 1 otherwise 
  58         if [ -f "$arg" ]; then 
  59             fileRollCall
["$arg"]="true"; 
  60             if ! [ "$returnState" = "false" ]; then returnState
="true"; fi; 
  62             fileRollCall
["$arg"]="false"; returnState
="false"; 
  66     #===Determine function return code=== 
  67     if [ "$returnState" = "true" ]; then 
  72 } # Check that file exists 
  74     # Desc: If arg is a dir path, save result in assoc array 'dirRollCall' 
  75     # Usage: checkdir arg1 arg2 arg3 ... 
  77     # Input: global assoc. array 'dirRollCall' 
  78     # Output: adds/updates key(value) to global assoc array 'dirRollCall'; 
  79     # Output: returns 0 if all args are dirs; 1 otherwise 
  85         if [ -z "$arg" ]; then 
  86             dirRollCall
["(Unspecified Dirname(s))"]="false"; returnState
="false"; 
  87         elif [ -d "$arg" ]; then 
  88             dirRollCall
["$arg"]="true"; 
  89             if ! [ "$returnState" = "false" ]; then returnState
="true"; fi 
  91             dirRollCall
["$arg"]="false"; returnState
="false"; 
  95     #===Determine function return code=== 
  96     if [ "$returnState" = "true" ]; then 
 101 } # Check that dir exists 
 103     # Desc: Displays missing apps, files, and dirs 
 104     # Usage: displayMissing 
 106     # Input: associative arrays: appRollCall, fileRollCall, dirRollCall 
 107     # Output: stderr: messages indicating missing apps, file, or dirs 
 108     # Output: returns exit code 0 if nothing missing; 1 otherwise 
 109     # Depends: bash 5, checkAppFileDir() 
 110     local missingApps value appMissing missingFiles fileMissing
 
 111     local missingDirs dirMissing
 
 113     #==BEGIN Display errors== 
 114     #===BEGIN Display Missing Apps=== 
 115     missingApps
="Missing apps  :"; 
 116     #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done 
 117     for key 
in "${!appRollCall[@]}"; do 
 118         value
="${appRollCall[$key]}"; 
 119         if [ "$value" = "false" ]; then 
 120             #echo "DEBUG:Missing apps: $key => $value"; 
 121             missingApps
="$missingApps""$key "; 
 125     if [ "$appMissing" = "true" ]; then  # Only indicate if an app is missing. 
 126         echo "$missingApps" 1>&2; 
 129     #===END Display Missing Apps=== 
 131     #===BEGIN Display Missing Files=== 
 132     missingFiles
="Missing files:"; 
 133     #for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done 
 134     for key 
in "${!fileRollCall[@]}"; do 
 135         value
="${fileRollCall[$key]}"; 
 136         if [ "$value" = "false" ]; then 
 137             #echo "DEBUG:Missing files: $key => $value"; 
 138             missingFiles
="$missingFiles""$key "; 
 142     if [ "$fileMissing" = "true" ]; then  # Only indicate if an app is missing. 
 143         echo "$missingFiles" 1>&2; 
 146     #===END Display Missing Files=== 
 148     #===BEGIN Display Missing Directories=== 
 149     missingDirs
="Missing dirs:"; 
 150     #for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done 
 151     for key 
in "${!dirRollCall[@]}"; do 
 152         value
="${dirRollCall[$key]}"; 
 153         if [ "$value" = "false" ]; then 
 154             #echo "DEBUG:Missing dirs: $key => $value"; 
 155             missingDirs
="$missingDirs""$key "; 
 159     if [ "$dirMissing" = "true" ]; then  # Only indicate if an dir is missing. 
 160         echo "$missingDirs" 1>&2; 
 163     #===END Display Missing Directories=== 
 165     #==END Display errors== 
 166     #==BEGIN Determine function return code=== 
 167     if [ "$appMissing" == "true" ] || 
[ "$fileMissing" == "true" ] || 
[ "$dirMissing" == "true" ]; then 
 172     #==END Determine function return code=== 
 173 } # Display missing apps, files, dirs 
 175     # Description: Prints verbose message ("vbm") to stderr if opVerbose is set to "true". 
 176     # Usage: vbm "DEBUG :verbose message here" 
 178     # Input: arg1: string 
 181     # Depends: bash 5.0.3, GNU-coreutils 8.30 (echo, date) 
 183     if [ "$opVerbose" = "true" ]; then 
 184         functionTime
="$(date --iso-8601=ns)"; # Save current time in nano seconds. 
 185         echo "[$functionTime]:$0:""$*" 1>&2;  # Display argument text. 
 189     return 0; # Function finished. 
 190 } # Displays message if opVerbose true 
 192     # Desc: Displays script version and license information. 
 195     # Input: scriptVersion   var containing version string 
 197     # Depends: vbm(), yell, GNU-coreutils 8.30 
 199     # Initialize function 
 200     vbm 
"DEBUG:showVersion function called." 
 204 Copyright (C) 2022 Steven Baltakatei Sandoval 
 205 License GPLv3: GNU GPL version 3 
 206 This is free software; you are free to change and redistribute it. 
 207 There is NO WARRANTY, to the extent permitted by law. 
 210     Copyright (C) 2020 Free Software Foundation, Inc. 
 211     License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>. 
 212     This is free software: you are free to change and redistribute it. 
 213     There is NO WARRANTY, to the extent permitted by law. 
 217     vbm 
"DEBUG:showVersion function ended." 
 218     return 0; # Function finished. 
 219 } # Display script version. 
 221     # Desc: Display script usage information 
 226     # Depends: GNU-coreutils 8.30 (cat) 
 229         bkots [ options ] [PATH...] 
 231     POSITIONAL ARGUMENTS: 
 232         PATH    Path(s) of file(s) or directory(ies) 
 236                 Do everything except run 'ots' commands. 
 238                 Display help information. 
 240                 Include files and directories starting with '.' (not 
 241                 included by default). 
 243                 Consider files in dirs recursively. 
 245                 Display script version. 
 247                 Display debugging info. 
 249                 Mark end of options. Interpret remaining arguments as 
 250                 positional arguments. 
 253         Scans files by file paths or directory paths provided by 
 254         positional arguments to see if Open Timestamps '.ots' file 
 255         exists. If so, attempt to upgrade and verify the '.ots' 
 256         file. If no '.ots' file exists, attempt to create one. 
 258         Files with a dotfile parent directory located anywhere in the 
 259         file path are ignored by default. (e.g. 'HEAD' in 
 260         '/home/user/diary/.git/logs/HEAD' because of '.git'). Dotfiles 
 261         themselves are also ignored by default 
 262         (e.g. '/home/user/.gitconfig'). 
 264         Files modified less than 1 minute ago are ignored. 
 268       bkots foo.txt bar.pdf /home/username/Pictures/ 
 270 } # Display information on how to use this script. 
 272     # Desc: Processes arguments provided to script. 
 273     # Usage: processArgs "$@" 
 275     # Input: "$@"          (list of arguments provided to the function) 
 276     # Output: Sets following variables used by other functions: 
 277     #   opVerbose            Indicates verbose mode enable status.  (ex: "true", "false") 
 278     #   arrayPosArgs         Array of remaining positional argments 
 280     #   yell()           Displays messages to stderr. 
 281     #   vbm()            Displays messsages to stderr if opVerbose set to "true". 
 282     #   showUsage()      Displays usage information about parent script. 
 283     #   showVersion()    Displays version about parent script. 
 284     #   arrayPosArgs     Global array for storing non-option positional arguments (i.e. arguments following the `--` option). 
 285     # External dependencies: bash (5.1.16), echo 
 287     #  [1]: Marco Aurelio (2014-05-08). "echo that outputs to stderr". https://stackoverflow.com/a/23550347 
 288     #  [2]: "Handling positional parameters" (2018-05-12). https://wiki.bash-hackers.org/scripting/posparams 
 290     # Initialize function 
 291     vbm 
"DEBUG:processArgs function called." 
 294     while [ ! $# -eq 0 ]; do   # While number of arguments ($#) is not (!) equal to (-eq) zero (0). 
 295         #yell "DEBUG:Starting processArgs while loop." # Debug stderr message. See [1]. 
 296         #yell "DEBUG:Provided arguments are:""$*"      # Debug stderr message. See [1]. 
 298             --dry-run) # Do not run ots commands 
 299                 option_dry_run
="true"; 
 300                 vbm 
"DEBUG:Option enabled:dry run";; 
 301             -h | 
--help) showUsage
; exit 1;; # Display usage. 
 302             --include-dotfiles) # Include dotfiles 
 303                 option_include_dotfiles
="true"; 
 304                 vbm 
"DEBUG:Option enabled:include dotfiles";; 
 305             -r | 
--recursive) # Specify recursive option 
 306                 option_recursive
="true"; 
 307                 vbm 
"DEBUG:option enabled:include files in dirs recursively";; 
 308             --version) showVersion
; exit 1;; # Show version 
 309             -v | 
--verbose) opVerbose
="true"; vbm 
"DEBUG:Verbose mode enabled.";; # Enable verbose mode. See [1]. 
 310             --) # End of all options. See [2]. 
 313                     vbm 
"DEBUG:adding to arrayPosArgs:$arg"; 
 314                     arrayPosArgs
+=("$arg"); 
 317             -*) showUsage
; yell 
"ERROR: Unrecognized option."; exit 1;; # Display usage 
 318             *) # Assume remaining arguments are positional arguments 
 320                     vbm 
"DEBUG:adding to arrayPosArgs:$arg"; 
 321                     arrayPosArgs
+=("$arg"); 
 324             #*) showUsage; yell "ERROR: Unrecognized argument."; exit 1;; # Handle unrecognized options. See [1]. 
 330     vbm 
"DEBUG:processArgs function ended." 
 331     return  0; # Function finished. 
 332 }; # Evaluate script options from positional arguments (ex: $1, $2, $3, etc.). 
 333 get_parent_dirnames
() { 
 334     # Desc: Provides newline-delimited list of each parent dir of a file or dir 
 335     # Usage: get_parent_dirnames arg1 
 336     # Input: arg1  input  path 
 337     # Output: stdout   newline-delimited list of parent dirs 
 339     # Depends: yell(), die(), try() 
 343     if [[ $# -ne 1 ]]; then die 
"FATAL:Incorrect number of arguments:$#"; fi; 
 344     if ! { [[ -f $1 ]] || 
[[ -d $1 ]]; }; then die 
"FATAL:Not a file or dir:$1"; fi; 
 348     while [[ -f $path ]] || 
[[ -d $path ]]; do 
 349         path
="$(dirname "$path")"; 
 350         name_base_previous
="$name_base"; 
 351         name_base
="$(basename "$path")"; 
 352         ## Check for stop condition (dirname returns same result as previous iteration) 
 353         if [[ $name_base == "$name_base_previous" ]]; then break; fi; 
 356 }; # Output parent dirnames to stdout 
 358     # print command to stderr 
 363 }; # print and execute string together 
 364 export -f cmdwrap
; # export cmdwrap for use in other functions 
 366     # Desc: Creates `.ots` file: 
 367     #     - for each file specified in arrayPosArgs array 
 368     #     - for each file in each dir specified in arrayPosArgs array 
 369     #   Output file created alongside each file or in output directory specified by pathDirIn1 
 371     # Input: arrayPosArgs   array with positional arguments 
 372     #        pathDirOut1    path for output `.ots` files (if pathDirOut1 is specified and is a path) 
 373     #        age_threshold  var: mininum age in seconds to timestamp file 
 375     # Output: file(s) creates `.ots` file alongside specified files 
 376     # Depends: find (GNU findutils) 4.8.0, GNU Coreutils 8.32 (sort), GNU Parallel 20210822 
 377     # Ref/Attrib: [1] How to create an array of unique elements from a string/array in bash https://unix.stackexchange.com/a/167194 
 378     #             [2] How to find files containing newlines in their names https://stackoverflow.com/a/21727028 
 379     #             [3] Get mtime of specific file using Bash? https://stackoverflow.com/a/4774377 
 380     #             [4] Search/Replace with string substitution instead of sed. https://www.shellcheck.net/wiki/SC2001 
 381     local -a file_list file_list_pruned
; 
 382     local -a files_to_verify files_to_upgrade files_to_stamp
 
 383     local -a files_to_verify_pruned files_to_upgrade_pruned files_to_stamp_pruned
 
 389     if ! checkapp ots 
find parallel
; then 
 391         die 
"FATAL:Missing dependencies."; 
 395     for arg 
in "${arrayPosArgs[@]}"; do 
 396         arg
="$(readlink -f "$arg")"; 
 397         if ! { [[ -d $arg ]] || 
[[ -f $arg ]]; }; then 
 398             die 
"FATAL:Not a file or dir:arg:$arg"; 
 402     # Display ots details 
 403     vbm 
"$(type ots)"; # show how 'ots' is defined 
 404         #TODO: add option to define 'ots' as a bash function that 
 405         #populates the ots option '--bitcoin-node FILE' with a 
 406         #user-specified FILE. 
 409     vbm 
"DEBUG:begin populate file_list array"; 
 410     for item 
in "${arrayPosArgs[@]}"; do 
 411         vbm 
"DEBUG:adding to file list:item:$item"; 
 413         ## Get full canonicalized path (follow symlinks) 
 414         item
="$(readlink -f "$item")"; 
 415         vbm 
"DEBUG:item full path:item:$item"; 
 417         ## Add to list: files 
 418         if [[ -f $item ]]; then 
 419             vbm 
"DEBUG:is a file:item:$item"; 
 420             file_list
+=("$item"); 
 421             vbm 
"DEBUG:added to file_list:$item"; 
 422         ## Add to list: files in dirs 
 423         elif [[ -d $item ]]; then 
 424             vbm 
"DEBUG:is a dir:item:$item"; 
 425             ### Check for recursive flag 
 426             if [[ "$option_recursive" == "true" ]]; then 
 427                 vbm 
"DEBUG:option_recursive:$option_recursive"; 
 428                 while read -r line
; do 
 429                     file_list
+=("$line"); 
 430                     vbm 
"DEBUG:added to file_list:$line"; 
 431                 done < <(find "$item" -type f
); 
 433                 while read -r line
; do 
 434                     file_list
+=("$line"); 
 435                     vbm 
"DEBUG:added to file_list:$line"; 
 436                 done < <(find "$item" -maxdepth 1 -type f
); 
 439             die 
"FATAL:Not a file or dir:item:$item"; 
 442     if [[ $opVerbose == "true" ]]; then 
 443         vbm 
"DEBUG:file_list:"; 
 444         printf "%s\n" "${file_list[@]}"; 
 448     for item 
in "${file_list[@]}"; do 
 449         ## Ignore files that end in '.ots.bak'. 
 450         if [[ $item =~ 
'.ots.bak'$ 
]]; then 
 451             vbm 
"DEBUG:Skipping file ending in '.ots.bak':item:$item"; 
 452             continue; # skip to next item 
 456         if ! [[ $option_include_dotfiles == "true" ]]; then 
 457             ### Ignore files if '/.' contained within canonical path 
 458             pattern
="/\."; # a dot after a forward slash 
 459             if [[ $item =~ 
$pattern ]]; then 
 463             # ### Ignore files located beneath a dotfile directory (e.g. '/home/my_repo/.git/config') 
 464             # unset flag_contains_dotfile_parent; 
 465             # while read -r line; do 
 466             #     #### Check line from output of get_parent_dirnames 
 468             #     if [[ $line =~ $pattern ]]; then 
 469             #         ##### line starts with '.' 
 470             #         vbm "DEBUG:Dotfile parent detected. Not including in file_list_pruned:$item"; 
 471             #         vbm "DEBUG:Dotfile in path:item:$item"; 
 472             #         vbm "DEBUG:Dotfile parent:line:$line"; 
 473             #         flag_contains_dotfile_parent="true"; 
 476             # done < <(get_parent_dirnames "$item"); 
 477             # if [[ $flag_contains_dotfile_parent == "true" ]]; then 
 478             #     unset flag_contains_dotfile_parent; 
 479             #     continue; # skip to next item (i.e. don't add to file_list_pruned) 
 482             # ### Ignore dotfiles themselves 
 483             # item_basename="$(basename "$item")"; 
 485             # if [[ $item_basename =~ $pattern ]]; then 
 486             #     vbm "INFO :Skipping dotfile:item:$item"; 
 487             #     continue; # skip to next item 
 491         ## Ignore files with newlines present in filename. See [2]. 
 492         if [[ $item =~ $
'\n' ]]; then 
 493             vbm 
"DEBUG:Skipping file name with newline:$item"; 
 494             continue; # skip to next item 
 497         ## Ignore files that end in '~'. 
 498         if [[ $item =~ ~$ 
]]; then 
 499             vbm 
"DEBUG:Skipping file ending in tilde:$item"; 
 500             continue; # skip to next item 
 503         ## Ignore files modified less than age_threshold. See [3]. 
 504         time_now
="$(date +%s)"; # epoch seconds 
 505         item_age
="$(stat -c %Y "$item")"; # age in seconds 
 506         if [[ $
(( time_now 
- item_age 
)) -lt "$age_threshold" ]]; then 
 507             yell 
"INFO :Skipping file modified less than $age_threshold seconds ago:item:$item"; 
 508             continue; # skip to next item 
 511         ## Add item to file_list_pruned 
 512         file_list_pruned
+=("$item"); 
 514     if [[ $opVerbose == "true" ]]; then 
 515         vbm 
"DEBUG:file_list_pruned:"; 
 516         printf "%s\n" "${file_list_pruned[@]}"; 
 519     # Decide what actions to take for items in file_list_pruned 
 520     for item 
in "${file_list_pruned[@]}"; do 
 521         vbm 
"DEBUG:considering action to take for item:$item"; 
 522         unset path_src path_prf dir_parent dir_source
; 
 524         ## Check file extension 
 525         if [[ $item =~ .ots$ 
]]; then 
 526             ### item ends in '.ots'. Item is proof file. 
 527             vbm 
"DEBUG:item ends in '.ots'. Item is proof file:item:$item"; 
 528             if [[ -f ${item%.ots} ]]; then 
 529                 #### Proof file (item) is adjacent to source file 
 530                 vbm 
"DEBUG:Proof file (item) is adjacent to source file."; 
 531                 ##### Upgrade and verify proof file against adjacent source file 
 532                 vbm 
"DEBUG:Marking proof file to be upgraded and verified."; 
 533                 path_src
="${item%.ots}"; 
 535                 files_to_upgrade
+=("$(printf "%s
" "$path_prf")"); 
 536                 files_to_verify
+=("$(printf "%s
\n%s
" "$path_src" "$path_prf")"); 
 538                 #### Proof file (item) is not adjacent to source file 
 539                 vbm 
"DEBUG:Proof file (item) is not adjacent to source file."; 
 540                 #### Check if source file in parent dir 
 541                 dir_parent
="$(dirname "$
(dirname "$item")" )"; 
 542                 cand_src_filename
="$(basename "$item")"; 
 543                 cand_src_path
="$dir_parent/$cand_src_filename"; 
 544                 if [[ -f "$cand_src_path" ]]; then 
 545                     ##### source file in parent dir 
 546                     vbm 
"DEBUG:found source file in parent:cand_src_path:$cand_src_path"; 
 547                     path_src
="$cand_src_path"; 
 549                     files_to_upgrade
+=("$(printf "%s
" "$path_prf")"); 
 550                     files_to_verify
+=("$(printf "%s
\n%s
" "$path_src" "$path_prf")"); 
 552                     #### Throw non-fatal error 
 553                     vbm 
"DEBUG:Source file not found for proof file:item:$item"; 
 554                     yell 
"ERROR:Item is proof file but source filei not adjacent in parent dir. item:$item"; 
 555                     #### Attempt upgrade only 
 556                     vbm 
"DEBUG:Marking proof file to be upgraded."; 
 558                     files_to_upgrade
+=("$(printf "%s
" "$path_prf")"); 
 562             ### item does not end in '.ots'. Item is source file. 
 563             vbm 
"DEBUG:item does NOT end in '.ots'. Item is source file."; 
 564             if [[ -f "$item".ots 
]]; then 
 565                 #### Proof file is adjacent to source file (item). 
 566                 vbm 
"DEBUG:Proof file is adjacent to source file (item)."; 
 567                 ##### Upgrade and verify proof file against adjacent source file. 
 568                 vbm 
"DEBUG:Marking proof file to be upgraded and verified."; 
 570                 path_prf
="$item.ots";                 
 571                 files_to_upgrade
+=("$(printf "%s
" "$path_prf")"); 
 572                 files_to_verify
+=("$(printf "%s
\n%s
" "$path_src" "$path_prf")"); 
 574                 #### Proof file is not adjacent to source file (item). 
 575                 #### Check if proof file is in subdir 
 576                 vbm 
"DEBUG:checking if proof file for source file (item) is in subdir:item:$item"; 
 577                 unset flag_proof_in_subdir
; 
 578                 dir_item
="$(dirname "$item")"; 
 579                 cand_prf_filename
="$(basename "$item")".ots
; 
 580                 while read -r line
; do 
 581                     line_basename
="$(basename "$line")"; 
 582                     if [[ $line_basename == "$cand_prf_filename" ]]; then 
 583                         flag_proof_in_subdir
="true"; 
 585                         vbm 
"DEBUG:proof found in subdir at:line:$line"; 
 588                 done < <(find "$dir_item" -mindepth 2 -maxdepth 2 -type f
) 
 589                 if [[ $flag_proof_in_subdir == "true" ]]; then 
 590                     ##### Proof file is in subdir 
 591                     vbm 
"DEBUG:Proof file detected in subdir relative to source file (item)"; 
 592                     #path_prf="$path_prf"; # set in while loop 
 594                     files_to_upgrade
+=("$(printf "%s
" "$path_prf")"); 
 595                     files_to_verify
+=("$(printf "%s
\n%s
" "$path_src" "$path_prf")"); 
 597                     ##### Proof file is not in subdir 
 598                     vbm 
"DEBUG:Proof file not detected in subdir relative to source file (item)."; 
 599                     #### Stamp source file 
 600                     vbm 
"DEBUG:Marking source file to be stamped."; 
 602                     files_to_stamp
+=("$(printf "%s
" "$path_src")") 
 604                 unset flag_proof_in_subdir
; 
 608     unset path_src path_prf dir_item dir_parent cand_prf_filename cand_src_filename line_basename cand_src_path
 
 610     # Prune action lists. 
 611     ## Sort and prune file action arrays 
 613     while read -r -d $
'\0' line
; do 
 614         vbm 
"DEBUG:adding to files_to_upgrade_pruned:line:$line"; 
 615         files_to_upgrade_pruned
+=("$line"); 
 616     done < <(printf "%s\0" "${files_to_upgrade[@]}" | 
sort -zu | shuf 
-z); # See [1] 
 617     if [[ $opVerbose == "true" ]]; then 
 618         vbm 
"DEBUG:files_to_upgrade_pruned:"; 
 619         printf "%s\n" "${files_to_upgrade_pruned[@]}"; 
 623     while read -r -d $
'\0' line
; do 
 624         vbm 
"DEBUG:adding to files_to_verify_pruned:line:$line"; 
 625         files_to_verify_pruned
+=("$line"); 
 626     done < <(printf "%s\0" "${files_to_verify[@]}" | 
sort -zu | shuf 
-z); # See [1] 
 627     if [[ $opVerbose == "true" ]]; then 
 628         vbm 
"DEBUG:files_to_verify_pruned:"; 
 629         printf "%s\n\n" "${files_to_verify_pruned[@]}"; 
 633     while read -r -d $
'\0' line
; do 
 634         vbm 
"DEBUG:adding to files_to_stamp_pruned:line:$line"; 
 635         files_to_stamp_pruned
+=("$line"); 
 636     done < <(printf "%s\0" "${files_to_stamp[@]}" | 
sort -zu | shuf 
-z); # See [1] 
 637     if [[ $opVerbose == "true" ]]; then 
 638         vbm 
"DEBUG:files_to_stamp_pruned:"; 
 639         printf "%s\n" "${files_to_stamp_pruned[@]}"; 
 643     ## Assemble upgrade file commands 
 644     for item 
in "${files_to_upgrade_pruned[@]}"; do 
 645         path_prf
="$(cut -d $'\n' -f1 < <(echo "$item"))"; 
 646         path_prf_sesc
="${path_prf//\"/\\\"}"; # escape path double quotes. See [4]. 
 647         if [[ -z "$path_prf" ]]; then 
 648             yell 
"ERROR:blank upgrade item encountered. Skipping:item:$item"; 
 651         vbm 
"DEBUG:Attempting to upgrade proof file:path_prf:$path_prf"; 
 652         if [[ ! $option_dry_run == "true" ]]; then 
 653             ### Try upgrade with known calendars in random order 
 654             while read -r url
; do 
 655                 vbm 
"DEBUG:Upgrading with calendar:url:$url"; 
 656                 if [[ "$opVerbose" = "true" ]]; then 
 657                     commands
+=("cmdwrap ots -v -l $url --no-default-whitelist upgrade \"$path_prf_sesc\"") && break; 
 659                     commands
+=("cmdwrap ots -l $url --no-default-whitelist upgrade \"$path_prf_sesc\"") && break; 
 661                 #ots -l "$url" --no-default-whitelist upgrade "$path_prf" && break; 
 662             done < <(printf "%s\n" "${calendars[@]}" | shuf
); 
 664             yell 
"DEBUG:DRY RUN:Not running:\"ots upgrade $path_prf\""; 
 668     ## Assemble verify file commands 
 669     for item 
in "${files_to_verify_pruned[@]}"; do 
 670         path_src
="$(cut -d $'\n' -f1 < <(echo "$item"))"; 
 671         path_prf
="$(cut -d $'\n' -f2 < <(echo "$item"))"; 
 672         path_src_sesc
="${path_src//\"/\\\"}"; # escape path double quotes. See [4]. 
 673         path_prf_sesc
="${path_prf//\"/\\\"}"; # escape path double quotes. See [4]. 
 674         if [[ -z "$path_src" ]] || 
[[ -z "$path_prf" ]]; then 
 675             yell 
"ERROR:blank verify item encountered. Skipping:item:$item"; 
 678         vbm 
"DEBUG:Attempting to verify source file:path_src:$path_src"; 
 679         vbm 
"DEBUG:    against proof file:          path_prf:$path_prf"; 
 680         if [[ ! $option_dry_run == "true" ]]; then 
 681             ### Try verify with known calendars in random order 
 682             while read -r url
; do 
 683                 vbm 
"DEBUG:Verifying with calendar:url:$url"; 
 684                 if [[ "$opVerbose" = "true" ]]; then 
 685                     commands
+=("cmdwrap ots -v -l $url --no-default-whitelist verify -f \"$path_src_sesc\" \"$path_prf_sesc\"") && break; 
 687                     commands
+=("cmdwrap ots -l $url --no-default-whitelist verify -f \"$path_src_sesc\" \"$path_prf_sesc\"") && break; 
 689                 #ots -l "$url" --no-default-whitelist verify -f "$path_src" "$path_prf" && break; 
 690             done < <(printf "%s\n" "${calendars[@]}" | shuf
); 
 692             yell 
"DEBUG:DRY RUN:Not running:\"ots verify -f $path_src $path_prf\""; 
 696     ## Assemble stamp file commands 
 697     for item 
in "${files_to_stamp_pruned[@]}"; do 
 698         path_src
="$(cut -d $'\n' -f1 < <(echo "$item"))"; 
 699         path_src_sesc
="${path_src//\"/\\\"}"; # escape path double quotes. See [4]. 
 700         if [[ -z "$path_src" ]]; then 
 701             yell 
"ERROR:blank stamp item encountered. Skipping:item:$item"; 
 704         vbm 
"DEBUG:Attempting to stamp source file:path_src:$path_src"; 
 705         if [[ ! $option_dry_run == "true" ]]; then 
 706             if [[ "$opVerbose" = "true" ]]; then 
 707                 commands
+=("cmdwrap ots -v stamp \"$path_src_sesc\""); 
 709                 commands
+=("cmdwrap ots stamp \"$path_src_sesc\""); 
 711             #ots stamp "$path_src"; 
 713             yell 
"DEBUG:DRY RUN:Not running:\"ots stamp $path_src\""; 
 718     #yell "DEBUG:commands:$(printf "%s\n" "${commands[@]}")"; 
 719     printf "%s\n" "${commands[@]}" | parallel 
--group;