+#!/usr/bin/env bash
+
+# Define variables
+declare -Ag appRollCall # Associative array for storing app status
+declare -Ag fileRollCall # Associative array for storing file status
+declare -Ag dirRollCall # Associative array for storing dir status
+declare -ag arrayPosArgs # Associative array for processArgs() function
+
+# Declare functions
+yell() { echo "$0: $*" >&2; } # print script path and all args to stderr
+die() { yell "$*"; exit 111; } # same as yell() but non-zero exit status
+try() { "$@" || die "cannot $*"; } # runs args as command, reports args if command fails
+checkapp() {
+ # Desc: If arg is a command, save result in assoc array 'appRollCall'
+ # Usage: checkapp arg1 arg2 arg3 ...
+ # Version: 0.1.1
+ # Input: global assoc. array 'appRollCall'
+ # Output: adds/updates key(value) to global assoc array 'appRollCall'
+ # Depends: bash 5.0.3
+ local returnState
+
+ #===Process Args===
+ for arg in "$@"; do
+ if command -v "$arg" 1>/dev/null 2>&1; then # Check if arg is a valid command
+ appRollCall[$arg]="true";
+ if ! [ "$returnState" = "false" ]; then returnState="true"; fi;
+ else
+ appRollCall[$arg]="false"; returnState="false";
+ fi;
+ done;
+
+ #===Determine function return code===
+ if [ "$returnState" = "true" ]; then
+ return 0;
+ else
+ return 1;
+ fi;
+} # Check that app exists
+checkfile() {
+ # Desc: If arg is a file path, save result in assoc array 'fileRollCall'
+ # Usage: checkfile arg1 arg2 arg3 ...
+ # Version: 0.1.1
+ # Input: global assoc. array 'fileRollCall'
+ # Output: adds/updates key(value) to global assoc array 'fileRollCall';
+ # Output: returns 0 if app found, 1 otherwise
+ # Depends: bash 5.0.3
+ local returnState
+
+ #===Process Args===
+ for arg in "$@"; do
+ if [ -f "$arg" ]; then
+ fileRollCall["$arg"]="true";
+ if ! [ "$returnState" = "false" ]; then returnState="true"; fi;
+ else
+ fileRollCall["$arg"]="false"; returnState="false";
+ fi;
+ done;
+
+ #===Determine function return code===
+ if [ "$returnState" = "true" ]; then
+ return 0;
+ else
+ return 1;
+ fi;
+} # Check that file exists
+checkdir() {
+ # Desc: If arg is a dir path, save result in assoc array 'dirRollCall'
+ # Usage: checkdir arg1 arg2 arg3 ...
+ # Version 0.1.2
+ # Input: global assoc. array 'dirRollCall'
+ # Output: adds/updates key(value) to global assoc array 'dirRollCall';
+ # Output: returns 0 if all args are dirs; 1 otherwise
+ # Depends: Bash 5.0.3
+ local returnState
+
+ #===Process Args===
+ for arg in "$@"; do
+ if [ -z "$arg" ]; then
+ dirRollCall["(Unspecified Dirname(s))"]="false"; returnState="false";
+ elif [ -d "$arg" ]; then
+ dirRollCall["$arg"]="true";
+ if ! [ "$returnState" = "false" ]; then returnState="true"; fi
+ else
+ dirRollCall["$arg"]="false"; returnState="false";
+ fi
+ done
+
+ #===Determine function return code===
+ if [ "$returnState" = "true" ]; then
+ return 0;
+ else
+ return 1;
+ fi
+} # Check that dir exists
+displayMissing() {
+ # Desc: Displays missing apps, files, and dirs
+ # Usage: displayMissing
+ # Version 1.0.0
+ # Input: associative arrays: appRollCall, fileRollCall, dirRollCall
+ # Output: stderr: messages indicating missing apps, file, or dirs
+ # Output: returns exit code 0 if nothing missing; 1 otherwise
+ # Depends: bash 5, checkAppFileDir()
+ local missingApps value appMissing missingFiles fileMissing
+ local missingDirs dirMissing
+
+ #==BEGIN Display errors==
+ #===BEGIN Display Missing Apps===
+ missingApps="Missing apps :";
+ #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done
+ for key in "${!appRollCall[@]}"; do
+ value="${appRollCall[$key]}";
+ if [ "$value" = "false" ]; then
+ #echo "DEBUG:Missing apps: $key => $value";
+ missingApps="$missingApps""$key ";
+ appMissing="true";
+ fi;
+ done;
+ if [ "$appMissing" = "true" ]; then # Only indicate if an app is missing.
+ echo "$missingApps" 1>&2;
+ fi;
+ unset value;
+ #===END Display Missing Apps===
+
+ #===BEGIN Display Missing Files===
+ missingFiles="Missing files:";
+ #for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done
+ for key in "${!fileRollCall[@]}"; do
+ value="${fileRollCall[$key]}";
+ if [ "$value" = "false" ]; then
+ #echo "DEBUG:Missing files: $key => $value";
+ missingFiles="$missingFiles""$key ";
+ fileMissing="true";
+ fi;
+ done;
+ if [ "$fileMissing" = "true" ]; then # Only indicate if an app is missing.
+ echo "$missingFiles" 1>&2;
+ fi;
+ unset value;
+ #===END Display Missing Files===
+
+ #===BEGIN Display Missing Directories===
+ missingDirs="Missing dirs:";
+ #for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done
+ for key in "${!dirRollCall[@]}"; do
+ value="${dirRollCall[$key]}";
+ if [ "$value" = "false" ]; then
+ #echo "DEBUG:Missing dirs: $key => $value";
+ missingDirs="$missingDirs""$key ";
+ dirMissing="true";
+ fi;
+ done;
+ if [ "$dirMissing" = "true" ]; then # Only indicate if an dir is missing.
+ echo "$missingDirs" 1>&2;
+ fi;
+ unset value;
+ #===END Display Missing Directories===
+
+ #==END Display errors==
+ #==BEGIN Determine function return code===
+ if [ "$appMissing" == "true" ] || [ "$fileMissing" == "true" ] || [ "$dirMissing" == "true" ]; then
+ return 1;
+ else
+ return 0;
+ fi
+ #==END Determine function return code===
+} # Display missing apps, files, dirs
+vbm() {
+ # Description: Prints verbose message ("vbm") to stderr if opVerbose is set to "true".
+ # Usage: vbm "DEBUG :verbose message here"
+ # Version 0.2.0
+ # Input: arg1: string
+ # vars: opVerbose
+ # Output: stderr
+ # Depends: bash 5.0.3, GNU-coreutils 8.30 (echo, date)
+
+ if [ "$opVerbose" = "true" ]; then
+ functionTime="$(date --iso-8601=ns)"; # Save current time in nano seconds.
+ echo "[$functionTime]:$0:""$*" 1>&2; # Display argument text.
+ fi
+
+ # End function
+ return 0; # Function finished.
+} # Displays message if opVerbose true
+showVersion() {
+ # Desc: Displays script version and license information.
+ # Usage: showVersion
+ # Version: 0.0.1
+ # Input: scriptVersion var containing version string
+ # Output: stdout
+ # Depends: vbm(), yell, GNU-coreutils 8.30
+
+ # Initialize function
+ vbm "DEBUG:showVersion function called."
+
+ cat <<'EOF'
+bkots 0.0.1
+Copyright (C) 2022 Steven Baltakatei Sandoval
+License GPLv3: GNU GPL version 3
+This is free software; you are free to change and redistribute it.
+There is NO WARRANTY, to the extent permitted by law.
+
+ GNU Coreutils 8.32
+ Copyright (C) 2020 Free Software Foundation, Inc.
+ License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
+ This is free software: you are free to change and redistribute it.
+ There is NO WARRANTY, to the extent permitted by law.
+EOF
+
+ # End function
+ vbm "DEBUG:showVersion function ended."
+ return 0; # Function finished.
+} # Display script version.
+showUsage() {
+ # Desc: Display script usage information
+ # Usage: showUsage
+ # Version 0.0.1
+ # Input: none
+ # Output: stdout
+ # Depends: GNU-coreutils 8.30 (cat)
+ cat <<'EOF'
+ USAGE:
+ bkots [ options ] [PATH...]
+
+ POSITIONAL ARGUMENTS:
+ PATH Path(s) of file(s) or directory(ies)
+
+ OPTIONS:
+ --dry-run
+ Do everything except run 'ots' commands.
+ -h, --help
+ Display help information.
+ --include-dotfiles
+ Include files and directories starting with '.' (not
+ included by default).
+ -r, --recursive
+ Consider files in dirs recursively.
+ --version
+ Display script version.
+ -v, --verbose
+ Display debugging info.
+ --
+ Mark end of options. Interpret remaining arguments as
+ positional arguments.
+
+ DESCRIPTION:
+ Scans files by file paths or directory paths provided by
+ positional arguments to see if Open Timestamps '.ots' file
+ exists. If so, attempt to upgrade and verify the '.ots'
+ file. If no '.ots' file exists, attempt to create one.
+
+ Files with a dotfile parent directory located anywhere in the
+ file path are ignored by default. (e.g. 'HEAD' in
+ '/home/user/diary/.git/logs/HEAD' because of '.git'). Dotfiles
+ themselves are also ignored by default
+ (e.g. '/home/user/.gitconfig').
+
+ EXAMPLES:
+ bkots -v foo.txt
+ bkots foo.txt bar.pdf /home/username/Pictures/
+EOF
+} # Display information on how to use this script.
+processArgs() {
+ # Desc: Processes arguments provided to script.
+ # Usage: processArgs "$@"
+ # Version: 1.0.0
+ # Input: "$@" (list of arguments provided to the function)
+ # Output: Sets following variables used by other functions:
+ # opVerbose Indicates verbose mode enable status. (ex: "true", "false")
+ # arrayPosArgs Array of remaining positional argments
+ # Depends:
+ # yell() Displays messages to stderr.
+ # vbm() Displays messsages to stderr if opVerbose set to "true".
+ # showUsage() Displays usage information about parent script.
+ # showVersion() Displays version about parent script.
+ # arrayPosArgs Global array for storing non-option positional arguments (i.e. arguments following the `--` option).
+ # External dependencies: bash (5.1.16), echo
+ # Ref./Attrib.:
+ # [1]: Marco Aurelio (2014-05-08). "echo that outputs to stderr". https://stackoverflow.com/a/23550347
+ # [2]: "Handling positional parameters" (2018-05-12). https://wiki.bash-hackers.org/scripting/posparams
+
+ # Initialize function
+ vbm "DEBUG:processArgs function called."
+
+ # Perform work
+ while [ ! $# -eq 0 ]; do # While number of arguments ($#) is not (!) equal to (-eq) zero (0).
+ #yell "DEBUG:Starting processArgs while loop." # Debug stderr message. See [1].
+ #yell "DEBUG:Provided arguments are:""$*" # Debug stderr message. See [1].
+ case "$1" in
+ --dry-run) # Do not run ots commands
+ option_dry_run="true";
+ vbm "DEBUG:Option enabled:dry run";;
+ -h | --help) showUsage; exit 1;; # Display usage.
+ --include-dotfiles) # Include dotfiles
+ option_include_dotfiles="true";
+ vbm "DEBUG:Option enabled:include dotfiles";;
+ -r | --recursive) # Specify recursive option
+ option_recursive="true";
+ vbm "DEBUG:option enabled:include files in dirs recursively";;
+ --version) showVersion; exit 1;; # Show version
+ -v | --verbose) opVerbose="true"; vbm "DEBUG:Verbose mode enabled.";; # Enable verbose mode. See [1].
+ --) # End of all options. See [2].
+ shift;
+ for arg in "$@"; do
+ vbm "DEBUG:adding to arrayPosArgs:$arg";
+ arrayPosArgs+=("$arg");
+ done;
+ break;;
+ -*) showUsage; yell "ERROR: Unrecognized option."; exit 1;; # Display usage
+ *) # Assume remaining arguments are positional arguments
+ for arg in "$@"; do
+ vbm "DEBUG:adding to arrayPosArgs:$arg";
+ arrayPosArgs+=("$arg");
+ done;
+ break;;
+ #*) showUsage; yell "ERROR: Unrecognized argument."; exit 1;; # Handle unrecognized options. See [1].
+ esac
+ shift
+ done
+
+ # End function
+ vbm "DEBUG:processArgs function ended."
+ return 0; # Function finished.
+}; # Evaluate script options from positional arguments (ex: $1, $2, $3, etc.).
+get_parent_dirnames() {
+ # Desc: Provides newline-delimited list of each parent dir of a file or dir
+ # Usage: get_parent_dirnames arg1
+ # Input: arg1 input path
+ # Output: stdout newline-delimited list of parent dirs
+ # Version: 0.0.1
+ # Depends: yell(), die(), try()
+ local path
+
+ # Check input
+ if [[ $# -ne 1 ]]; then die "FATAL:Incorrect number of arguments:$#"; fi;
+ if ! { [[ -f $1 ]] || [[ -d $1 ]]; }; then die "FATAL:Not a file or dir:$1"; fi;
+
+ # Process path
+ path="$1";
+ while [[ -f $path ]] || [[ -d $path ]]; do
+ path="$(dirname "$path")";
+ name_base_previous="$name_base";
+ name_base="$(basename "$path")";
+ ## Check for stop condition (dirname returns same result as previous iteration)
+ if [[ $name_base == "$name_base_previous" ]]; then break; fi;
+ echo "$name_base";
+ done;
+}; # Output parent dirnames to stdout
+main() {
+ # Desc: Creates `.ots` file:
+ # - for each file specified in arrayPosArgs array
+ # - for each file in each dir specified in arrayPosArgs array
+ # Output file created alongside each file or in output directory specified by pathDirIn1
+ # Usage: main "$@";
+ # Input: arrayPosArgs array with positional arguments
+ # pathDirOut1 path for output `.ots` files (if pathDirOut1 is specified and is a path)
+ # Output: file(s) creates `.ots` file alongside specified files
+ # Depends: find (GNU findutils) 4.8.0, GNU Coreutils 8.32 (sort)
+ # Ref/Attrib: [1] How to create an array of unique elements from a string/array in bash https://unix.stackexchange.com/a/167194
+ # [2] How to find files containing newlines in their names https://stackoverflow.com/a/21727028
+ local -a file_list file_list_pruned;
+ local -a files_to_verify files_to_upgrade files_to_stamp
+ local -a files_to_verify_pruned files_to_upgrade_pruned files_to_stamp_pruned
+
+ # Process args
+ processArgs "$@";
+
+ # Check dependencies
+ if ! checkapp ots find; then
+ displayMissing;
+ die "FATAL:Missing dependencies.";
+ fi;
+
+ # Check arguments
+ ## Mark if output dir option specified
+ if [[ -v pathDirOut1 ]]; then
+ vbm "DEBUG:output directory specified:pathDirOut1:$pathDirOut1";
+ if [[ -d $pathDirOut1 ]]; then
+ vbm "DEBUG:pathDirOut1:$pathDirOut1";
+ config_output_dir="true";
+ else
+ die "ERROR:Not a dir:$pathDirOut1";
+ fi;
+ fi;
+
+ # Display ots details
+ vbm "$(type ots)"; # show how 'ots' is defined
+ #TODO: add option to define 'ots' as a bash function that
+ #populates the ots option '--bitcoin-node FILE' with a
+ #user-specified FILE.
+
+ # Populate file_list
+ vbm "DEBUG:begin populate file_list array";
+ for item in "${arrayPosArgs[@]}"; do
+ vbm "DEBUG:adding to file list:item:$item";
+
+ ## Get full canonicalized path (follow symlinks)
+ item="$(readlink -f "$item")";
+ vbm "DEBUG:item full path:item:$item";
+
+ ## Add to list: files
+ if [[ -f $item ]]; then
+ vbm "DEBUG:is a file:item:$item";
+ file_list+=("$item");
+ vbm "DEBUG:added to file_list:$item";
+ ## Add to list: files in dirs
+ elif [[ -d $item ]]; then
+ vbm "DEBUG:is a dir:item:$item";
+ ### Check for recursive flag
+ if [[ "$option_recursive" == "true" ]]; then
+ vbm "DEBUG:option_recursive:$option_recursive";
+ while read -r line; do
+ file_list+=("$line");
+ vbm "DEBUG:added to file_list:$line";
+ done < <(find "$item" -type f);
+ else
+ while read -r line; do
+ file_list+=("$line");
+ vbm "DEBUG:added to file_list:$line";
+ done < <(find "$item" -maxdepth 1 -type f);
+ fi;
+ else
+ die "ERROR:Not a file or dir:item:$item";
+ fi;
+ done;
+ if [[ $opVerbose == "true" ]]; then
+ vbm "DEBUG:file_list:";
+ printf "%s\n" "${file_list[@]}";
+ fi;
+
+ # Prune file_list
+ for item in "${file_list[@]}"; do
+ if ! [[ $option_include_dotfiles == "true" ]]; then
+ ## Ignore files located beneath a dotfile directory (e.g. '/home/my_repo/.git/config')
+ unset flag_contains_dotfile_parent;
+ while read -r line; do
+ ### Check line from output of get_parent_dirnames
+ pattern="^\.";
+ if [[ $line =~ $pattern ]]; then
+ #### line starts with '.'
+ vbm "DEBUG:Dotfile parent detected. Not including in file_list_pruned:$item";
+ vbm "DEBUG:Dotfile in path:item:$item";
+ vbm "DEBUG:Dotfile parent:line:$line";
+ flag_contains_dotfile_parent="true";
+ break
+ fi;
+ done < <(get_parent_dirnames "$item");
+ if [[ $flag_contains_dotfile_parent == "true" ]]; then
+ unset flag_contains_dotfile_parent;
+ continue; # skip to next item (i.e. don't add to file_list_pruned)
+ fi;
+
+ ## Ignore dotfiles themselves
+ item_basename="$(basename "$item")";
+ pattern="^\.";
+ if [[ $item_basename =~ $pattern ]]; then
+ vbm "INFO :Skipping dotfile:item:$item";
+ continue; # skip to next item
+ fi;
+ fi;
+
+ ## Ignore files with newlines present in filename. See [2].
+ if [[ $item =~ $'\n' ]]; then
+ yell "INFO :Skipping file name with newline:$item";
+ continue; # skip to next item
+ fi;
+
+ ## Ignore files that end in '~'.
+ if [[ $item =~ ~$ ]]; then
+ yell "INFO :Skipping file ending in tilde:$item";
+ continue; # skip to next item
+ fi;
+
+ ## Add item to file_list_pruned
+ file_list_pruned+=("$item");
+ done;
+ if [[ $opVerbose == "true" ]]; then
+ vbm "DEBUG:file_list_pruned:";
+ printf "%s\n" "${file_list_pruned[@]}";
+ fi;
+
+ # Decide what actions to take for items in file_list_pruned
+ for item in "${file_list_pruned[@]}"; do
+ vbm "DEBUG:considering action to take for item:$item";
+ unset path_src path_prf dir_parent dir_source;
+
+ ## Check file extension
+ if [[ $item =~ .ots$ ]]; then
+ ### item ends in '.ots'. Item is proof file.
+ vbm "DEBUG:item ends in '.ots'. Item is proof file:item:$item";
+ if [[ -f ${item%.ots} ]]; then
+ #### Proof file (item) is adjacent to source file
+ vbm "DEBUG:Proof file (item) is adjacent to source file.";
+ ##### Upgrade and verify proof file against adjacent source file
+ vbm "DEBUG:Marking proof file to be upgraded and verified.";
+ path_src="${item%.ots}";
+ path_prf="$item";
+ files_to_upgrade+=("$(printf "%s" "$path_prf")");
+ files_to_verify+=("$(printf "%s\n%s" "$path_src" "$path_prf")");
+ else
+ #### Proof file (item) is not adjacent to source file
+ vbm "DEBUG:Proof file (item) is not adjacent to source file.";
+ #### Check if source file in parent dir
+ dir_parent="$(dirname "$(dirname "$item")" )";
+ cand_src_filename="$(basename "$item")";
+ cand_src_path="$dir_parent/$cand_src_filename";
+ if [[ -f "$cand_src_path" ]]; then
+ ##### source file in parent dir
+ vbm "DEBUG:found source file in parent:cand_src_path:$cand_src_path";
+ path_src="$cand_src_path";
+ path_prf="$item";
+ files_to_upgrade+=("$(printf "%s" "$path_prf")");
+ files_to_verify+=("$(printf "%s\n%s" "$path_src" "$path_prf")");
+ else
+ #### Throw non-fatal error
+ vbm "DEBUG:Source file not found for proof file:item:$item";
+ yell "ERROR:Item is proof file but source filei not adjacent in parent dir. item:$item";
+ #### Attempt upgrade only
+ vbm "DEBUG:Marking proof file to be upgraded.";
+ path_prf="$item";
+ files_to_upgrade+=("$(printf "%s" "$path_prf")");
+ fi;
+ fi;
+ else
+ ### item does not end in '.ots'. Item is source file.
+ vbm "DEBUG:item does NOT end in '.ots'. Item is source file.";
+ if [[ -f "$item".ots ]]; then
+ #### Proof file is adjacent to source file (item).
+ vbm "DEBUG:Proof file is adjacent to source file (item).";
+ ##### Upgrade and verify proof file against adjacent source file.
+ vbm "DEBUG:Marking proof file to be upgraded and verified.";
+ path_src="$item";
+ path_prf="$item.ots";
+ files_to_upgrade+=("$(printf "%s" "$path_prf")");
+ files_to_verify+=("$(printf "%s\n%s" "$path_src" "$path_prf")");
+ else
+ #### Proof file is not adjacent to source file (item).
+ #### Check if proof file is in subdir
+ vbm "DEBUG:checking if proof file for source file (item) is in subdir:item:$item";
+ unset flag_proof_in_subdir;
+ dir_item="$(dirname "$item")";
+ cand_prf_filename="$(basename "$item")".ots;
+ while read -r line; do
+ line_basename="$(basename "$line")";
+ if [[ $line_basename == "$cand_prf_filename" ]]; then
+ flag_proof_in_subdir="true";
+ path_prf="$line";
+ vbm "DEBUG:proof found in subdir at:line:$line";
+ break;
+ fi;
+ done < <(find "$dir_item" -mindepth 2 -maxdepth 2 -type f)
+ if [[ $flag_proof_in_subdir == "true" ]]; then
+ ##### Proof file is in subdir
+ vbm "DEBUG:Proof file detected in subdir relative to source file (item)";
+ #path_prf="$path_prf"; # set in while loop
+ path_src="$item";
+ files_to_upgrade+=("$(printf "%s" "$path_prf")");
+ files_to_verify+=("$(printf "%s\n%s" "$path_src" "$path_prf")");
+ else
+ ##### Proof file is not in subdir
+ vbm "DEBUG:Proof file not detected in subdir relative to source file (item).";
+ #### Stamp source file
+ vbm "DEBUG:Marking source file to be stamped.";
+ path_src="$item";
+ files_to_stamp+=("$(printf "%s" "$path_src")")
+ fi;
+ unset flag_proof_in_subdir;
+ fi;
+ fi;
+ done;
+ unset path_src path_prf dir_item dir_parent cand_prf_filename cand_src_filename line_basename cand_src_path
+
+ # Prune action lists.
+ ## Sort and prune file action arrays
+ ### files to upgrade
+ while read -r -d $'\0' line; do
+ vbm "DEBUG:adding to files_to_upgrade_pruned:line:$line";
+ files_to_upgrade_pruned+=("$line");
+ done < <(printf "%s\0" "${files_to_upgrade[@]}" | sort -zu | shuf -z); # See [1]
+ if [[ $opVerbose == "true" ]]; then
+ vbm "DEBUG:files_to_upgrade_pruned:";
+ printf "%s\n" "${files_to_upgrade_pruned[@]}";
+ fi;
+
+ ### files to verify
+ while read -r -d $'\0' line; do
+ vbm "DEBUG:adding to files_to_verify_pruned:line:$line";
+ files_to_verify_pruned+=("$line");
+ done < <(printf "%s\0" "${files_to_verify[@]}" | sort -zu | shuf -z); # See [1]
+ if [[ $opVerbose == "true" ]]; then
+ vbm "DEBUG:files_to_verify_pruned:";
+ printf "%s\n\n" "${files_to_verify_pruned[@]}";
+ fi;
+
+ ### files to stamp
+ while read -r -d $'\0' line; do
+ vbm "DEBUG:adding to files_to_stamp_pruned:line:$line";
+ files_to_stamp_pruned+=("$line");
+ done < <(printf "%s\0" "${files_to_stamp[@]}" | sort -zu | shuf -z); # See [1]
+ if [[ $opVerbose == "true" ]]; then
+ vbm "DEBUG:files_to_stamp_pruned:";
+ printf "%s\n" "${files_to_stamp_pruned[@]}";
+ fi;
+
+ # Act on files
+ ## Upgrade files
+ for item in "${files_to_upgrade_pruned[@]}"; do
+ path_prf="$(cut -d $'\n' -f1 < <(echo "$item"))";
+ if [[ -z "$path_prf" ]]; then
+ yell "ERROR:blank upgrade item encountered. Skipping:item:$item";
+ continue;
+ fi;
+ vbm "DEBUG:Attempting to upgrade proof file:path_prf:$path_prf";
+ if [[ ! $option_dry_run == "true" ]]; then
+ ots upgrade "$path_prf";
+ else
+ yell "DEBUG:DRY RUN:Not running:\"ots upgrade $path_prf\"";
+ fi;
+
+ done;
+
+ ## Verify files
+ for item in "${files_to_verify_pruned[@]}"; do
+ path_src="$(cut -d $'\n' -f1 < <(echo "$item"))";
+ path_prf="$(cut -d $'\n' -f2 < <(echo "$item"))";
+ if [[ -z "$path_src" ]] || [[ -z "$path_prf" ]]; then
+ yell "ERROR:blank verify item encountered. Skipping:item:$item";
+ continue;
+ fi;
+ vbm "DEBUG:Attempting to verify source file:path_src:$path_src";
+ vbm "DEBUG: against proof file: path_prf:$path_prf";
+ if [[ ! $option_dry_run == "true" ]]; then
+ ots verify -f "$path_src" "$path_prf";
+ else
+ yell "DEBUG:DRY RUN:Not running:\"ots verify -f $path_src $path_prf\"";
+ fi;
+
+ done;
+
+ ## Stamp files
+ for item in "${files_to_stamp_pruned[@]}"; do
+ path_src="$(cut -d $'\n' -f1 < <(echo "$item"))";
+ if [[ -z "$path_src" ]]; then
+ yell "ERROR:blank stamp item encountered. Skipping:item:$item";
+ continue;
+ fi;
+ vbm "DEBUG:Attempting to stamp source file:path_src:$path_src";
+ if [[ ! $option_dry_run == "true" ]]; then
+ ots stamp "$item";
+ else
+ yell "DEBUG:DRY RUN:Not running:\"ots stamp $item\"";
+ fi;
+
+ done;
+
+}; # main program
+
+# Run program
+main "$@";