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 age_threshold
="60"; # min age to add file; seconds; 
  14 max_job_count
="2"; # default max job count 
  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 must
() { "$@" || 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                 Specify simultaneous job count (default: 2) 
 245                 Consider files in dirs recursively. 
 247                 Display script version. 
 249                 Display debugging info. 
 251                 Mark end of options. Interpret remaining arguments as 
 252                 positional arguments. 
 255         Scans files by file paths or directory paths provided by 
 256         positional arguments to see if Open Timestamps '.ots' file 
 257         exists. If so, attempt to upgrade and verify the '.ots' 
 258         file. If no '.ots' file exists, attempt to create one. 
 260         Files with a dotfile parent directory located anywhere in the 
 261         file path are ignored by default. (e.g. 'HEAD' in 
 262         '/home/user/diary/.git/logs/HEAD' because of '.git'). Dotfiles 
 263         themselves are also ignored by default 
 264         (e.g. '/home/user/.gitconfig'). 
 266         Files modified less than 1 minute ago are ignored. 
 270       bkots foo.txt bar.pdf /home/username/Pictures/ 
 272 } # Display information on how to use this script. 
 274     # Desc: Count and return total number of jobs 
 277     # Output: stdout   integer number of jobs 
 278     # Depends: Bash 5.1.16 
 279     # Example: while [[$(count_jobs) -gt 0]]; do echo "Working..."; sleep 1; done; 
 283     job_count
="$(jobs -r | wc -l | tr -d ' ' )"; 
 284     #yell "DEBUG:job_count:$job_count"; 
 285     if [[ -z $job_count ]]; then job_count
="0"; fi; 
 287 }; # Return number of background jobs 
 289     # Desc: Does not return until count_jobs() falls below $max_job_count 
 290     # Input: var max_job_count 
 291     # Output: return code 0 
 292     # Depends: count_jobs(), yell(); 
 293     while [[ $
(count_jobs
) -ge $max_job_count ]]; do 
 294         printf "\r%d:Working..." "$SECONDS"; 
 300     # Desc: Processes arguments provided to script. 
 301     # Usage: processArgs "$@" 
 303     # Input: "$@"          (list of arguments provided to the function) 
 304     # Output: Sets following variables used by other functions: 
 305     #   opVerbose            Indicates verbose mode enable status.  (ex: "true", "false") 
 306     #   arrayPosArgs         Array of remaining positional argments 
 308     #   yell()           Displays messages to stderr. 
 309     #   vbm()            Displays messsages to stderr if opVerbose set to "true". 
 310     #   showUsage()      Displays usage information about parent script. 
 311     #   showVersion()    Displays version about parent script. 
 312     #   arrayPosArgs     Global array for storing non-option positional arguments (i.e. arguments following the `--` option). 
 313     # External dependencies: bash (5.1.16), echo 
 315     #  [1]: Marco Aurelio (2014-05-08). "echo that outputs to stderr". https://stackoverflow.com/a/23550347 
 316     #  [2]: "Handling positional parameters" (2018-05-12). https://wiki.bash-hackers.org/scripting/posparams 
 318     # Initialize function 
 319     vbm 
"DEBUG:processArgs function called." 
 322     while [ ! $# -eq 0 ]; do   # While number of arguments ($#) is not (!) equal to (-eq) zero (0). 
 323         #yell "DEBUG:Starting processArgs while loop." # Debug stderr message. See [1]. 
 324         #yell "DEBUG:Provided arguments are:""$*"      # Debug stderr message. See [1]. 
 326             --dry-run) # Do not run ots commands 
 327                 option_dry_run
="true"; 
 328                 vbm 
"DEBUG:Option enabled:dry run";; 
 329             -h | 
--help) showUsage
; exit 1;; # Display usage. 
 330             --include-dotfiles) # Include dotfiles 
 331                 option_include_dotfiles
="true"; 
 332                 vbm 
"DEBUG:Option enabled:include dotfiles";; 
 333             -j | 
--jobs) # Specify max_job_count 
 334                 if [[ -n "$2" ]] && [[ "$2" =~ ^
[0-9]+$ 
]]; then 
 336                     vbm 
"STATUS:Max job count set to:$max_job_count"; 
 340                     die 
"FATAL:Invalid job count:$2"; 
 342             -r | 
--recursive) # Specify recursive option 
 343                 option_recursive
="true"; 
 344                 vbm 
"DEBUG:option enabled:include files in dirs recursively";; 
 345             --version) showVersion
; exit 1;; # Show version 
 346             -v | 
--verbose) opVerbose
="true"; vbm 
"DEBUG:Verbose mode enabled.";; # Enable verbose mode. See [1]. 
 347             --) # End of all options. See [2]. 
 350                     vbm 
"DEBUG:adding to arrayPosArgs:$arg"; 
 351                     arrayPosArgs
+=("$arg"); 
 354             -*) showUsage
; yell 
"ERROR: Unrecognized option."; exit 1;; # Display usage 
 355             *) # Assume remaining arguments are positional arguments 
 357                     vbm 
"DEBUG:adding to arrayPosArgs:$arg"; 
 358                     arrayPosArgs
+=("$arg"); 
 361             #*) showUsage; yell "ERROR: Unrecognized argument."; exit 1;; # Handle unrecognized options. See [1]. 
 367     vbm 
"DEBUG:processArgs function ended." 
 368     return  0; # Function finished. 
 369 }; # Evaluate script options from positional arguments (ex: $1, $2, $3, etc.). 
 370 get_parent_dirnames
() { 
 371     # Desc: Provides newline-delimited list of each parent dir of a file or dir 
 372     # Usage: get_parent_dirnames arg1 
 373     # Input: arg1  input  path 
 374     # Output: stdout   newline-delimited list of parent dirs 
 376     # Depends: yell(), die(), must() 
 380     if [[ $# -ne 1 ]]; then die 
"FATAL:Incorrect number of arguments:$#"; fi; 
 381     if ! { [[ -f $1 ]] || 
[[ -d $1 ]]; }; then die 
"FATAL:Not a file or dir:$1"; fi; 
 385     while [[ -f $path ]] || 
[[ -d $path ]]; do 
 386         path
="$(dirname "$path")"; 
 387         name_base_previous
="$name_base"; 
 388         name_base
="$(basename "$path")"; 
 389         ## Check for stop condition (dirname returns same result as previous iteration) 
 390         if [[ $name_base == "$name_base_previous" ]]; then break; fi; 
 393 }; # Output parent dirnames to stdout 
 395     # Desc: Creates `.ots` file: 
 396     #     - for each file specified in arrayPosArgs array 
 397     #     - for each file in each dir specified in arrayPosArgs array 
 398     #   Output file created alongside each file or in output directory specified by pathDirIn1 
 400     # Input: arrayPosArgs   array with positional arguments 
 401     #        pathDirOut1    path for output `.ots` files (if pathDirOut1 is specified and is a path) 
 402     #        age_threshold  var: mininum age in seconds to timestamp file 
 404     # Output: file(s) creates `.ots` file alongside specified files 
 405     # Depends: find (GNU findutils) 4.8.0, GNU Coreutils 8.32 (sort), GNU Parallel 20210822 
 406     # Ref/Attrib: [1] How to create an array of unique elements from a string/array in bash https://unix.stackexchange.com/a/167194 
 407     #             [2] How to find files containing newlines in their names https://stackoverflow.com/a/21727028 
 408     #             [3] Get mtime of specific file using Bash? https://stackoverflow.com/a/4774377 
 409     #             [4] Search/Replace with string substitution instead of sed. https://www.shellcheck.net/wiki/SC2001 
 410     local -a file_list file_list_pruned
; 
 411     local -a files_to_verify files_to_upgrade files_to_stamp
 
 412     local -a files_to_verify_pruned files_to_upgrade_pruned files_to_stamp_pruned
 
 418     if ! checkapp ots 
find parallel
; then 
 420         die 
"FATAL:Missing dependencies."; 
 424     for arg 
in "${arrayPosArgs[@]}"; do 
 425         arg
="$(readlink -f "$arg")"; 
 426         if ! { [[ -d $arg ]] || 
[[ -f $arg ]]; }; then 
 427             die 
"FATAL:Not a file or dir:arg:$arg"; 
 431     # Display ots details 
 432     vbm 
"$(type ots)"; # show how 'ots' is defined 
 433         #TODO: add option to define 'ots' as a bash function that 
 434         #populates the ots option '--bitcoin-node FILE' with a 
 435         #user-specified FILE. 
 438     vbm 
"DEBUG:begin populate file_list array"; 
 439     for item 
in "${arrayPosArgs[@]}"; do 
 440         vbm 
"DEBUG:adding to file list:item:$item"; 
 442         ## Get full canonicalized path (follow symlinks) 
 443         item
="$(readlink -f "$item")"; 
 444         vbm 
"DEBUG:item full path:item:$item"; 
 446         ## Add to list: files 
 447         if [[ -f $item ]]; then 
 448             vbm 
"DEBUG:is a file:item:$item"; 
 449             file_list
+=("$item"); 
 450             vbm 
"DEBUG:added to file_list:$item"; 
 451         ## Add to list: files in dirs 
 452         elif [[ -d $item ]]; then 
 453             vbm 
"DEBUG:is a dir:item:$item"; 
 454             ### Check for recursive flag 
 455             if [[ "$option_recursive" == "true" ]]; then 
 456                 vbm 
"DEBUG:option_recursive:$option_recursive"; 
 457                 while read -r line
; do 
 458                     file_list
+=("$line"); 
 459                     vbm 
"DEBUG:added to file_list:$line"; 
 460                 done < <(find "$item" -type f
); 
 462                 while read -r line
; do 
 463                     file_list
+=("$line"); 
 464                     vbm 
"DEBUG:added to file_list:$line"; 
 465                 done < <(find "$item" -maxdepth 1 -type f
); 
 468             die 
"FATAL:Not a file or dir:item:$item"; 
 471     if [[ $opVerbose == "true" ]]; then 
 472         vbm 
"DEBUG:file_list:"; 
 473         printf "%s\n" "${file_list[@]}"; 
 477     for item 
in "${file_list[@]}"; do 
 478         ## Ignore files that end in '.ots.bak'. 
 479         if [[ $item =~ 
'.ots.bak'$ 
]]; then 
 480             vbm 
"DEBUG:Skipping file ending in '.ots.bak':item:$item"; 
 481             continue; # skip to next item 
 485         if ! [[ $option_include_dotfiles == "true" ]]; then 
 486             ### Ignore files if '/.' contained within canonical path 
 487             pattern
="/\."; # a dot after a forward slash 
 488             if [[ $item =~ 
$pattern ]]; then 
 492             # ### Ignore files located beneath a dotfile directory (e.g. '/home/my_repo/.git/config') 
 493             # unset flag_contains_dotfile_parent; 
 494             # while read -r line; do 
 495             #     #### Check line from output of get_parent_dirnames 
 497             #     if [[ $line =~ $pattern ]]; then 
 498             #         ##### line starts with '.' 
 499             #         vbm "DEBUG:Dotfile parent detected. Not including in file_list_pruned:$item"; 
 500             #         vbm "DEBUG:Dotfile in path:item:$item"; 
 501             #         vbm "DEBUG:Dotfile parent:line:$line"; 
 502             #         flag_contains_dotfile_parent="true"; 
 505             # done < <(get_parent_dirnames "$item"); 
 506             # if [[ $flag_contains_dotfile_parent == "true" ]]; then 
 507             #     unset flag_contains_dotfile_parent; 
 508             #     continue; # skip to next item (i.e. don't add to file_list_pruned) 
 511             # ### Ignore dotfiles themselves 
 512             # item_basename="$(basename "$item")"; 
 514             # if [[ $item_basename =~ $pattern ]]; then 
 515             #     vbm "INFO :Skipping dotfile:item:$item"; 
 516             #     continue; # skip to next item 
 520         ## Ignore files with newlines present in filename. See [2]. 
 521         if [[ $item =~ $
'\n' ]]; then 
 522             vbm 
"DEBUG:Skipping file name with newline:$item"; 
 523             continue; # skip to next item 
 526         ## Ignore files that end in '~'. 
 527         if [[ $item =~ ~$ 
]]; then 
 528             vbm 
"DEBUG:Skipping file ending in tilde:$item"; 
 529             continue; # skip to next item 
 532         ## Ignore files modified less than age_threshold. See [3]. 
 533         time_now
="$(date +%s)"; # epoch seconds 
 534         item_age
="$(stat -c %Y "$item")"; # age in seconds 
 535         if [[ $
(( time_now 
- item_age 
)) -lt "$age_threshold" ]]; then 
 536             yell 
"INFO :Skipping file modified less than $age_threshold seconds ago:item:$item"; 
 537             continue; # skip to next item 
 540         ## Add item to file_list_pruned 
 541         file_list_pruned
+=("$item"); 
 543     if [[ $opVerbose == "true" ]]; then 
 544         vbm 
"DEBUG:file_list_pruned:"; 
 545         printf "%s\n" "${file_list_pruned[@]}"; 
 548     # Decide what actions to take for items in file_list_pruned 
 549     for item 
in "${file_list_pruned[@]}"; do 
 550         vbm 
"DEBUG:considering action to take for item:$item"; 
 551         unset path_src path_prf dir_parent dir_source
; 
 553         ## Check file extension 
 554         if [[ $item =~ .ots$ 
]]; then 
 555             ### item ends in '.ots'. Item is proof file. 
 556             vbm 
"DEBUG:item ends in '.ots'. Item is proof file:item:$item"; 
 557             if [[ -f ${item%.ots} ]]; then 
 558                 #### Proof file (item) is adjacent to source file 
 559                 vbm 
"DEBUG:Proof file (item) is adjacent to source file."; 
 560                 ##### Upgrade and verify proof file against adjacent source file 
 561                 vbm 
"DEBUG:Marking proof file to be upgraded and verified."; 
 562                 path_src
="${item%.ots}"; 
 564                 files_to_upgrade
+=("$(printf "%s
" "$path_prf")"); 
 565                 files_to_verify
+=("$(printf "%s
\n%s
" "$path_src" "$path_prf")"); 
 567                 #### Proof file (item) is not adjacent to source file 
 568                 vbm 
"DEBUG:Proof file (item) is not adjacent to source file."; 
 569                 #### Check if source file in parent dir 
 570                 dir_parent
="$(dirname "$
(dirname "$item")" )"; 
 571                 cand_src_filename
="$(basename "$item")"; 
 572                 cand_src_path
="$dir_parent/$cand_src_filename"; 
 573                 if [[ -f "$cand_src_path" ]]; then 
 574                     ##### source file in parent dir 
 575                     vbm 
"DEBUG:found source file in parent:cand_src_path:$cand_src_path"; 
 576                     path_src
="$cand_src_path"; 
 578                     files_to_upgrade
+=("$(printf "%s
" "$path_prf")"); 
 579                     files_to_verify
+=("$(printf "%s
\n%s
" "$path_src" "$path_prf")"); 
 581                     #### Throw non-fatal error 
 582                     vbm 
"DEBUG:Source file not found for proof file:item:$item"; 
 583                     yell 
"ERROR:Item is proof file but source filei not adjacent in parent dir. item:$item"; 
 584                     #### Attempt upgrade only 
 585                     vbm 
"DEBUG:Marking proof file to be upgraded."; 
 587                     files_to_upgrade
+=("$(printf "%s
" "$path_prf")"); 
 591             ### item does not end in '.ots'. Item is source file. 
 592             vbm 
"DEBUG:item does NOT end in '.ots'. Item is source file."; 
 593             if [[ -f "$item".ots 
]]; then 
 594                 #### Proof file is adjacent to source file (item). 
 595                 vbm 
"DEBUG:Proof file is adjacent to source file (item)."; 
 596                 ##### Upgrade and verify proof file against adjacent source file. 
 597                 vbm 
"DEBUG:Marking proof file to be upgraded and verified."; 
 599                 path_prf
="$item.ots";                 
 600                 files_to_upgrade
+=("$(printf "%s
" "$path_prf")"); 
 601                 files_to_verify
+=("$(printf "%s
\n%s
" "$path_src" "$path_prf")"); 
 603                 #### Proof file is not adjacent to source file (item). 
 604                 #### Check if proof file is in subdir 
 605                 vbm 
"DEBUG:checking if proof file for source file (item) is in subdir:item:$item"; 
 606                 unset flag_proof_in_subdir
; 
 607                 dir_item
="$(dirname "$item")"; 
 608                 cand_prf_filename
="$(basename "$item")".ots
; 
 609                 while read -r line
; do 
 610                     line_basename
="$(basename "$line")"; 
 611                     if [[ $line_basename == "$cand_prf_filename" ]]; then 
 612                         flag_proof_in_subdir
="true"; 
 614                         vbm 
"DEBUG:proof found in subdir at:line:$line"; 
 617                 done < <(find "$dir_item" -mindepth 2 -maxdepth 2 -type f
) 
 618                 if [[ $flag_proof_in_subdir == "true" ]]; then 
 619                     ##### Proof file is in subdir 
 620                     vbm 
"DEBUG:Proof file detected in subdir relative to source file (item)"; 
 621                     #path_prf="$path_prf"; # set in while loop 
 623                     files_to_upgrade
+=("$(printf "%s
" "$path_prf")"); 
 624                     files_to_verify
+=("$(printf "%s
\n%s
" "$path_src" "$path_prf")"); 
 626                     ##### Proof file is not in subdir 
 627                     vbm 
"DEBUG:Proof file not detected in subdir relative to source file (item)."; 
 628                     #### Stamp source file 
 629                     vbm 
"DEBUG:Marking source file to be stamped."; 
 631                     files_to_stamp
+=("$(printf "%s
" "$path_src")") 
 633                 unset flag_proof_in_subdir
; 
 637     unset path_src path_prf dir_item dir_parent cand_prf_filename cand_src_filename line_basename cand_src_path
 
 639     # Prune action lists. 
 640     ## Sort and prune file action arrays 
 642     while read -r -d $
'\0' line
; do 
 643         vbm 
"DEBUG:adding to files_to_upgrade_pruned:line:$line"; 
 644         files_to_upgrade_pruned
+=("$line"); 
 645     done < <(printf "%s\0" "${files_to_upgrade[@]}" | 
sort -zu | shuf 
-z); # See [1] 
 646     if [[ $opVerbose == "true" ]]; then 
 647         vbm 
"DEBUG:files_to_upgrade_pruned:"; 
 648         printf "%s\n" "${files_to_upgrade_pruned[@]}"; 
 652     while read -r -d $
'\0' line
; do 
 653         vbm 
"DEBUG:adding to files_to_verify_pruned:line:$line"; 
 654         files_to_verify_pruned
+=("$line"); 
 655     done < <(printf "%s\0" "${files_to_verify[@]}" | 
sort -zu | shuf 
-z); # See [1] 
 656     if [[ $opVerbose == "true" ]]; then 
 657         vbm 
"DEBUG:files_to_verify_pruned:"; 
 658         printf "%s\n\n" "${files_to_verify_pruned[@]}"; 
 662     while read -r -d $
'\0' line
; do 
 663         vbm 
"DEBUG:adding to files_to_stamp_pruned:line:$line"; 
 664         files_to_stamp_pruned
+=("$line"); 
 665     done < <(printf "%s\0" "${files_to_stamp[@]}" | 
sort -zu | shuf 
-z); # See [1] 
 666     if [[ $opVerbose == "true" ]]; then 
 667         vbm 
"DEBUG:files_to_stamp_pruned:"; 
 668         printf "%s\n" "${files_to_stamp_pruned[@]}"; 
 672     ## Assemble and execute upgrade file commands 
 673     for item 
in "${files_to_upgrade_pruned[@]}"; do 
 674         path_prf
="$(cut -d $'\n' -f1 < <(echo "$item"))"; 
 675         path_prf_sesc
="${path_prf//\"/\\\"}"; # escape path double quotes. See [4]. 
 676         if [[ -z "$path_prf" ]]; then 
 677             yell 
"ERROR:blank upgrade item encountered. Skipping:item:$item"; 
 680         vbm 
"DEBUG:Attempting to upgrade proof file:path_prf:$path_prf"; 
 681         if [[ ! $option_dry_run == "true" ]]; then 
 682             ### Try upgrade with known calendars in random order 
 683             while read -r url
; do 
 684                 vbm 
"DEBUG:Upgrading with calendar:url:$url"; 
 686                 #### assemble command 
 689                 if [[ "$opVerbose" = "true" ]]; then cmd_temp
+=("-v"); fi; 
 690                 cmd_temp
+=("-l" "$url" "--no-default-whitelist"); 
 691                 cmd_temp
+=("upgrade" "$path_prf_sesc"); 
 692                 if [[ "$opVerbose" = "true" ]]; then declare -p cmd_temp
; fi; 
 695                 wait_for_jobslot 
&& must 
"${cmd_temp[@]}" & 
 698                 #ots -l "$url" --no-default-whitelist upgrade "$path_prf" && break; 
 699             done < <(printf "%s\n" "${calendars[@]}" | shuf
); 
 701             yell 
"DEBUG:DRY RUN:Not running:\"ots upgrade $path_prf\""; 
 705     ## Assemble and execute verify file commands 
 706     for item 
in "${files_to_verify_pruned[@]}"; do 
 707         path_src
="$(cut -d $'\n' -f1 < <(echo "$item"))"; 
 708         path_prf
="$(cut -d $'\n' -f2 < <(echo "$item"))"; 
 709         path_src_sesc
="${path_src//\"/\\\"}"; # escape path double quotes. See [4]. 
 710         path_prf_sesc
="${path_prf//\"/\\\"}"; # escape path double quotes. See [4]. 
 711         if [[ -z "$path_src" ]] || 
[[ -z "$path_prf" ]]; then 
 712             yell 
"ERROR:blank verify item encountered. Skipping:item:$item"; 
 715         vbm 
"DEBUG:Attempting to verify source file:path_src:$path_src"; 
 716         vbm 
"DEBUG:    against proof file:          path_prf:$path_prf"; 
 717         if [[ ! $option_dry_run == "true" ]]; then 
 718             ### Try verify with known calendars in random order 
 719             while read -r url
; do 
 720                 vbm 
"DEBUG:Verifying with calendar:url:$url"; 
 722                 #### assemble command 
 725                 if [[ "$opVerbose" = "true" ]]; then cmd_temp
+=("-v"); fi; 
 726                 cmd_temp
+=("-l" "$url" "--no-default-whitelist"); 
 727                 cmd_temp
+=("verify" "-f" "$path_src_sesc" "$path_prf_sesc"); 
 728                 if [[ "$opVerbose" = "true" ]]; then declare -p cmd_temp
; fi; 
 731                 wait_for_jobslot 
&& must 
"${cmd_temp[@]}" & 
 734                 #ots -l "$url" --no-default-whitelist verify -f "$path_src" "$path_prf" && break; 
 735             done < <(printf "%s\n" "${calendars[@]}" | shuf
); 
 737             yell 
"DEBUG:DRY RUN:Not running:\"ots verify -f $path_src $path_prf\""; 
 741     ## Assemble and execute stamp file commands 
 742     for item 
in "${files_to_stamp_pruned[@]}"; do 
 743         path_src
="$(cut -d $'\n' -f1 < <(echo "$item"))"; 
 744         path_src_sesc
="${path_src//\"/\\\"}"; # escape path double quotes. See [4]. 
 745         if [[ -z "$path_src" ]]; then 
 746             yell 
"ERROR:blank stamp item encountered. Skipping:item:$item"; 
 749         vbm 
"DEBUG:Attempting to stamp source file:path_src:$path_src"; 
 750         if [[ ! $option_dry_run == "true" ]]; then 
 752             #### assemble command 
 755             if [[ "$opVerbose" = "true" ]]; then cmd_temp
+=("-v"); fi; 
 756             cmd_temp
+=("stamp" "$path_src_sesc"); 
 757             if [[ "$opVerbose" = "true" ]]; then declare -p cmd_temp
; fi; 
 760             wait_for_jobslot 
&& must 
"${cmd_temp[@]}" & 
 762             #ots stamp "$path_src"; 
 764             yell 
"DEBUG:DRY RUN:Not running:\"ots stamp $path_src\"";