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
8 declare -g ots_delay
; ots_delay
=1 # minimum time in seconds between ots operations
10 calendars
+=("https://finney.calendar.eternitywall.com");
11 calendars
+=("https://btc.calendar.catallaxy.com");
12 calendars
+=("https://alice.btc.calendar.opentimestamps.org");
13 calendars
+=("https://bob.btc.calendar.opentimestamps.org");
16 yell
() { echo "$0: $*" >&2; } # print script path and all args to stderr
17 die
() { yell
"$*"; exit 111; } # same as yell() but non-zero exit status
18 try
() { "$@" || die
"cannot $*"; } # runs args as command, reports args if command fails
20 # Desc: If arg is a command, save result in assoc array 'appRollCall'
21 # Usage: checkapp arg1 arg2 arg3 ...
23 # Input: global assoc. array 'appRollCall'
24 # Output: adds/updates key(value) to global assoc array 'appRollCall'
30 if command -v "$arg" 1>/dev
/null
2>&1; then # Check if arg is a valid command
31 appRollCall
[$arg]="true";
32 if ! [ "$returnState" = "false" ]; then returnState
="true"; fi;
34 appRollCall
[$arg]="false"; returnState
="false";
38 #===Determine function return code===
39 if [ "$returnState" = "true" ]; then
44 } # Check that app exists
46 # Desc: If arg is a file path, save result in assoc array 'fileRollCall'
47 # Usage: checkfile arg1 arg2 arg3 ...
49 # Input: global assoc. array 'fileRollCall'
50 # Output: adds/updates key(value) to global assoc array 'fileRollCall';
51 # Output: returns 0 if app found, 1 otherwise
57 if [ -f "$arg" ]; then
58 fileRollCall
["$arg"]="true";
59 if ! [ "$returnState" = "false" ]; then returnState
="true"; fi;
61 fileRollCall
["$arg"]="false"; returnState
="false";
65 #===Determine function return code===
66 if [ "$returnState" = "true" ]; then
71 } # Check that file exists
73 # Desc: If arg is a dir path, save result in assoc array 'dirRollCall'
74 # Usage: checkdir arg1 arg2 arg3 ...
76 # Input: global assoc. array 'dirRollCall'
77 # Output: adds/updates key(value) to global assoc array 'dirRollCall';
78 # Output: returns 0 if all args are dirs; 1 otherwise
84 if [ -z "$arg" ]; then
85 dirRollCall
["(Unspecified Dirname(s))"]="false"; returnState
="false";
86 elif [ -d "$arg" ]; then
87 dirRollCall
["$arg"]="true";
88 if ! [ "$returnState" = "false" ]; then returnState
="true"; fi
90 dirRollCall
["$arg"]="false"; returnState
="false";
94 #===Determine function return code===
95 if [ "$returnState" = "true" ]; then
100 } # Check that dir exists
102 # Desc: Displays missing apps, files, and dirs
103 # Usage: displayMissing
105 # Input: associative arrays: appRollCall, fileRollCall, dirRollCall
106 # Output: stderr: messages indicating missing apps, file, or dirs
107 # Output: returns exit code 0 if nothing missing; 1 otherwise
108 # Depends: bash 5, checkAppFileDir()
109 local missingApps value appMissing missingFiles fileMissing
110 local missingDirs dirMissing
112 #==BEGIN Display errors==
113 #===BEGIN Display Missing Apps===
114 missingApps
="Missing apps :";
115 #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done
116 for key
in "${!appRollCall[@]}"; do
117 value
="${appRollCall[$key]}";
118 if [ "$value" = "false" ]; then
119 #echo "DEBUG:Missing apps: $key => $value";
120 missingApps
="$missingApps""$key ";
124 if [ "$appMissing" = "true" ]; then # Only indicate if an app is missing.
125 echo "$missingApps" 1>&2;
128 #===END Display Missing Apps===
130 #===BEGIN Display Missing Files===
131 missingFiles
="Missing files:";
132 #for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done
133 for key
in "${!fileRollCall[@]}"; do
134 value
="${fileRollCall[$key]}";
135 if [ "$value" = "false" ]; then
136 #echo "DEBUG:Missing files: $key => $value";
137 missingFiles
="$missingFiles""$key ";
141 if [ "$fileMissing" = "true" ]; then # Only indicate if an app is missing.
142 echo "$missingFiles" 1>&2;
145 #===END Display Missing Files===
147 #===BEGIN Display Missing Directories===
148 missingDirs
="Missing dirs:";
149 #for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done
150 for key
in "${!dirRollCall[@]}"; do
151 value
="${dirRollCall[$key]}";
152 if [ "$value" = "false" ]; then
153 #echo "DEBUG:Missing dirs: $key => $value";
154 missingDirs
="$missingDirs""$key ";
158 if [ "$dirMissing" = "true" ]; then # Only indicate if an dir is missing.
159 echo "$missingDirs" 1>&2;
162 #===END Display Missing Directories===
164 #==END Display errors==
165 #==BEGIN Determine function return code===
166 if [ "$appMissing" == "true" ] ||
[ "$fileMissing" == "true" ] ||
[ "$dirMissing" == "true" ]; then
171 #==END Determine function return code===
172 } # Display missing apps, files, dirs
174 # Description: Prints verbose message ("vbm") to stderr if opVerbose is set to "true".
175 # Usage: vbm "DEBUG :verbose message here"
177 # Input: arg1: string
180 # Depends: bash 5.0.3, GNU-coreutils 8.30 (echo, date)
182 if [ "$opVerbose" = "true" ]; then
183 functionTime
="$(date --iso-8601=ns)"; # Save current time in nano seconds.
184 echo "[$functionTime]:$0:""$*" 1>&2; # Display argument text.
188 return 0; # Function finished.
189 } # Displays message if opVerbose true
191 # Desc: Displays script version and license information.
194 # Input: scriptVersion var containing version string
196 # Depends: vbm(), yell, GNU-coreutils 8.30
198 # Initialize function
199 vbm
"DEBUG:showVersion function called."
203 Copyright (C) 2022 Steven Baltakatei Sandoval
204 License GPLv3: GNU GPL version 3
205 This is free software; you are free to change and redistribute it.
206 There is NO WARRANTY, to the extent permitted by law.
209 Copyright (C) 2020 Free Software Foundation, Inc.
210 License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
211 This is free software: you are free to change and redistribute it.
212 There is NO WARRANTY, to the extent permitted by law.
216 vbm
"DEBUG:showVersion function ended."
217 return 0; # Function finished.
218 } # Display script version.
220 # Desc: Display script usage information
225 # Depends: GNU-coreutils 8.30 (cat)
228 bkots [ options ] [PATH...]
230 POSITIONAL ARGUMENTS:
231 PATH Path(s) of file(s) or directory(ies)
235 Do everything except run 'ots' commands.
237 Display help information.
239 Include files and directories starting with '.' (not
240 included by default).
242 Consider files in dirs recursively.
244 Display script version.
246 Display debugging info.
248 Mark end of options. Interpret remaining arguments as
249 positional arguments.
252 Scans files by file paths or directory paths provided by
253 positional arguments to see if Open Timestamps '.ots' file
254 exists. If so, attempt to upgrade and verify the '.ots'
255 file. If no '.ots' file exists, attempt to create one.
257 Files with a dotfile parent directory located anywhere in the
258 file path are ignored by default. (e.g. 'HEAD' in
259 '/home/user/diary/.git/logs/HEAD' because of '.git'). Dotfiles
260 themselves are also ignored by default
261 (e.g. '/home/user/.gitconfig').
265 bkots foo.txt bar.pdf /home/username/Pictures/
267 } # Display information on how to use this script.
269 # Desc: Processes arguments provided to script.
270 # Usage: processArgs "$@"
272 # Input: "$@" (list of arguments provided to the function)
273 # Output: Sets following variables used by other functions:
274 # opVerbose Indicates verbose mode enable status. (ex: "true", "false")
275 # arrayPosArgs Array of remaining positional argments
277 # yell() Displays messages to stderr.
278 # vbm() Displays messsages to stderr if opVerbose set to "true".
279 # showUsage() Displays usage information about parent script.
280 # showVersion() Displays version about parent script.
281 # arrayPosArgs Global array for storing non-option positional arguments (i.e. arguments following the `--` option).
282 # External dependencies: bash (5.1.16), echo
284 # [1]: Marco Aurelio (2014-05-08). "echo that outputs to stderr". https://stackoverflow.com/a/23550347
285 # [2]: "Handling positional parameters" (2018-05-12). https://wiki.bash-hackers.org/scripting/posparams
287 # Initialize function
288 vbm
"DEBUG:processArgs function called."
291 while [ ! $# -eq 0 ]; do # While number of arguments ($#) is not (!) equal to (-eq) zero (0).
292 #yell "DEBUG:Starting processArgs while loop." # Debug stderr message. See [1].
293 #yell "DEBUG:Provided arguments are:""$*" # Debug stderr message. See [1].
295 --dry-run) # Do not run ots commands
296 option_dry_run
="true";
297 vbm
"DEBUG:Option enabled:dry run";;
298 -h |
--help) showUsage
; exit 1;; # Display usage.
299 --include-dotfiles) # Include dotfiles
300 option_include_dotfiles
="true";
301 vbm
"DEBUG:Option enabled:include dotfiles";;
302 -r |
--recursive) # Specify recursive option
303 option_recursive
="true";
304 vbm
"DEBUG:option enabled:include files in dirs recursively";;
305 --version) showVersion
; exit 1;; # Show version
306 -v |
--verbose) opVerbose
="true"; vbm
"DEBUG:Verbose mode enabled.";; # Enable verbose mode. See [1].
307 --) # End of all options. See [2].
310 vbm
"DEBUG:adding to arrayPosArgs:$arg";
311 arrayPosArgs
+=("$arg");
314 -*) showUsage
; yell
"ERROR: Unrecognized option."; exit 1;; # Display usage
315 *) # Assume remaining arguments are positional arguments
317 vbm
"DEBUG:adding to arrayPosArgs:$arg";
318 arrayPosArgs
+=("$arg");
321 #*) showUsage; yell "ERROR: Unrecognized argument."; exit 1;; # Handle unrecognized options. See [1].
327 vbm
"DEBUG:processArgs function ended."
328 return 0; # Function finished.
329 }; # Evaluate script options from positional arguments (ex: $1, $2, $3, etc.).
330 get_parent_dirnames
() {
331 # Desc: Provides newline-delimited list of each parent dir of a file or dir
332 # Usage: get_parent_dirnames arg1
333 # Input: arg1 input path
334 # Output: stdout newline-delimited list of parent dirs
336 # Depends: yell(), die(), try()
340 if [[ $# -ne 1 ]]; then die
"FATAL:Incorrect number of arguments:$#"; fi;
341 if ! { [[ -f $1 ]] ||
[[ -d $1 ]]; }; then die
"FATAL:Not a file or dir:$1"; fi;
345 while [[ -f $path ]] ||
[[ -d $path ]]; do
346 path
="$(dirname "$path")";
347 name_base_previous
="$name_base";
348 name_base
="$(basename "$path")";
349 ## Check for stop condition (dirname returns same result as previous iteration)
350 if [[ $name_base == "$name_base_previous" ]]; then break; fi;
353 }; # Output parent dirnames to stdout
355 # Desc: Creates `.ots` file:
356 # - for each file specified in arrayPosArgs array
357 # - for each file in each dir specified in arrayPosArgs array
358 # Output file created alongside each file or in output directory specified by pathDirIn1
360 # Input: arrayPosArgs array with positional arguments
361 # pathDirOut1 path for output `.ots` files (if pathDirOut1 is specified and is a path)
362 # Output: file(s) creates `.ots` file alongside specified files
363 # Depends: find (GNU findutils) 4.8.0, GNU Coreutils 8.32 (sort)
364 # Ref/Attrib: [1] How to create an array of unique elements from a string/array in bash https://unix.stackexchange.com/a/167194
365 # [2] How to find files containing newlines in their names https://stackoverflow.com/a/21727028
366 local -a file_list file_list_pruned
;
367 local -a files_to_verify files_to_upgrade files_to_stamp
368 local -a files_to_verify_pruned files_to_upgrade_pruned files_to_stamp_pruned
374 if ! checkapp ots
find; then
376 die
"FATAL:Missing dependencies.";
380 ## Mark if output dir option specified
381 if [[ -v pathDirOut1
]]; then
382 vbm
"DEBUG:output directory specified:pathDirOut1:$pathDirOut1";
383 if [[ -d $pathDirOut1 ]]; then
384 vbm
"DEBUG:pathDirOut1:$pathDirOut1";
385 config_output_dir
="true";
387 die
"ERROR:Not a dir:$pathDirOut1";
391 # Display ots details
392 vbm
"$(type ots)"; # show how 'ots' is defined
393 #TODO: add option to define 'ots' as a bash function that
394 #populates the ots option '--bitcoin-node FILE' with a
395 #user-specified FILE.
398 vbm
"DEBUG:begin populate file_list array";
399 for item
in "${arrayPosArgs[@]}"; do
400 vbm
"DEBUG:adding to file list:item:$item";
402 ## Get full canonicalized path (follow symlinks)
403 item
="$(readlink -f "$item")";
404 vbm
"DEBUG:item full path:item:$item";
406 ## Add to list: files
407 if [[ -f $item ]]; then
408 vbm
"DEBUG:is a file:item:$item";
409 file_list
+=("$item");
410 vbm
"DEBUG:added to file_list:$item";
411 ## Add to list: files in dirs
412 elif [[ -d $item ]]; then
413 vbm
"DEBUG:is a dir:item:$item";
414 ### Check for recursive flag
415 if [[ "$option_recursive" == "true" ]]; then
416 vbm
"DEBUG:option_recursive:$option_recursive";
417 while read -r line
; do
418 file_list
+=("$line");
419 vbm
"DEBUG:added to file_list:$line";
420 done < <(find "$item" -type f
);
422 while read -r line
; do
423 file_list
+=("$line");
424 vbm
"DEBUG:added to file_list:$line";
425 done < <(find "$item" -maxdepth 1 -type f
);
428 die
"ERROR:Not a file or dir:item:$item";
431 if [[ $opVerbose == "true" ]]; then
432 vbm
"DEBUG:file_list:";
433 printf "%s\n" "${file_list[@]}";
437 for item
in "${file_list[@]}"; do
438 if ! [[ $option_include_dotfiles == "true" ]]; then
439 ## Ignore files located beneath a dotfile directory (e.g. '/home/my_repo/.git/config')
440 unset flag_contains_dotfile_parent
;
441 while read -r line
; do
442 ### Check line from output of get_parent_dirnames
444 if [[ $line =~
$pattern ]]; then
445 #### line starts with '.'
446 vbm
"DEBUG:Dotfile parent detected. Not including in file_list_pruned:$item";
447 vbm
"DEBUG:Dotfile in path:item:$item";
448 vbm
"DEBUG:Dotfile parent:line:$line";
449 flag_contains_dotfile_parent
="true";
452 done < <(get_parent_dirnames
"$item");
453 if [[ $flag_contains_dotfile_parent == "true" ]]; then
454 unset flag_contains_dotfile_parent
;
455 continue; # skip to next item (i.e. don't add to file_list_pruned)
458 ## Ignore dotfiles themselves
459 item_basename
="$(basename "$item")";
461 if [[ $item_basename =~
$pattern ]]; then
462 vbm
"INFO :Skipping dotfile:item:$item";
463 continue; # skip to next item
467 ## Ignore files with newlines present in filename. See [2].
468 if [[ $item =~ $
'\n' ]]; then
469 yell
"INFO :Skipping file name with newline:$item";
470 continue; # skip to next item
473 ## Ignore files that end in '~'.
474 if [[ $item =~ ~$
]]; then
475 yell
"INFO :Skipping file ending in tilde:$item";
476 continue; # skip to next item
479 ## Ignore files that end in '.ots.bak'.
480 if [[ $item =~
'.ots.bak'$
]]; then
481 yell
"INFO :Skipping file ending in '.ots.bak':item:$item";
482 continue; # skip to next item
485 ## Add item to file_list_pruned
486 file_list_pruned
+=("$item");
488 if [[ $opVerbose == "true" ]]; then
489 vbm
"DEBUG:file_list_pruned:";
490 printf "%s\n" "${file_list_pruned[@]}";
493 # Decide what actions to take for items in file_list_pruned
494 for item
in "${file_list_pruned[@]}"; do
495 vbm
"DEBUG:considering action to take for item:$item";
496 unset path_src path_prf dir_parent dir_source
;
498 ## Check file extension
499 if [[ $item =~ .ots$
]]; then
500 ### item ends in '.ots'. Item is proof file.
501 vbm
"DEBUG:item ends in '.ots'. Item is proof file:item:$item";
502 if [[ -f ${item%.ots} ]]; then
503 #### Proof file (item) is adjacent to source file
504 vbm
"DEBUG:Proof file (item) is adjacent to source file.";
505 ##### Upgrade and verify proof file against adjacent source file
506 vbm
"DEBUG:Marking proof file to be upgraded and verified.";
507 path_src
="${item%.ots}";
509 files_to_upgrade
+=("$(printf "%s
" "$path_prf")");
510 files_to_verify
+=("$(printf "%s
\n%s
" "$path_src" "$path_prf")");
512 #### Proof file (item) is not adjacent to source file
513 vbm
"DEBUG:Proof file (item) is not adjacent to source file.";
514 #### Check if source file in parent dir
515 dir_parent
="$(dirname "$
(dirname "$item")" )";
516 cand_src_filename
="$(basename "$item")";
517 cand_src_path
="$dir_parent/$cand_src_filename";
518 if [[ -f "$cand_src_path" ]]; then
519 ##### source file in parent dir
520 vbm
"DEBUG:found source file in parent:cand_src_path:$cand_src_path";
521 path_src
="$cand_src_path";
523 files_to_upgrade
+=("$(printf "%s
" "$path_prf")");
524 files_to_verify
+=("$(printf "%s
\n%s
" "$path_src" "$path_prf")");
526 #### Throw non-fatal error
527 vbm
"DEBUG:Source file not found for proof file:item:$item";
528 yell
"ERROR:Item is proof file but source filei not adjacent in parent dir. item:$item";
529 #### Attempt upgrade only
530 vbm
"DEBUG:Marking proof file to be upgraded.";
532 files_to_upgrade
+=("$(printf "%s
" "$path_prf")");
536 ### item does not end in '.ots'. Item is source file.
537 vbm
"DEBUG:item does NOT end in '.ots'. Item is source file.";
538 if [[ -f "$item".ots
]]; then
539 #### Proof file is adjacent to source file (item).
540 vbm
"DEBUG:Proof file is adjacent to source file (item).";
541 ##### Upgrade and verify proof file against adjacent source file.
542 vbm
"DEBUG:Marking proof file to be upgraded and verified.";
544 path_prf
="$item.ots";
545 files_to_upgrade
+=("$(printf "%s
" "$path_prf")");
546 files_to_verify
+=("$(printf "%s
\n%s
" "$path_src" "$path_prf")");
548 #### Proof file is not adjacent to source file (item).
549 #### Check if proof file is in subdir
550 vbm
"DEBUG:checking if proof file for source file (item) is in subdir:item:$item";
551 unset flag_proof_in_subdir
;
552 dir_item
="$(dirname "$item")";
553 cand_prf_filename
="$(basename "$item")".ots
;
554 while read -r line
; do
555 line_basename
="$(basename "$line")";
556 if [[ $line_basename == "$cand_prf_filename" ]]; then
557 flag_proof_in_subdir
="true";
559 vbm
"DEBUG:proof found in subdir at:line:$line";
562 done < <(find "$dir_item" -mindepth 2 -maxdepth 2 -type f
)
563 if [[ $flag_proof_in_subdir == "true" ]]; then
564 ##### Proof file is in subdir
565 vbm
"DEBUG:Proof file detected in subdir relative to source file (item)";
566 #path_prf="$path_prf"; # set in while loop
568 files_to_upgrade
+=("$(printf "%s
" "$path_prf")");
569 files_to_verify
+=("$(printf "%s
\n%s
" "$path_src" "$path_prf")");
571 ##### Proof file is not in subdir
572 vbm
"DEBUG:Proof file not detected in subdir relative to source file (item).";
573 #### Stamp source file
574 vbm
"DEBUG:Marking source file to be stamped.";
576 files_to_stamp
+=("$(printf "%s
" "$path_src")")
578 unset flag_proof_in_subdir
;
582 unset path_src path_prf dir_item dir_parent cand_prf_filename cand_src_filename line_basename cand_src_path
584 # Prune action lists.
585 ## Sort and prune file action arrays
587 while read -r -d $
'\0' line
; do
588 vbm
"DEBUG:adding to files_to_upgrade_pruned:line:$line";
589 files_to_upgrade_pruned
+=("$line");
590 done < <(printf "%s\0" "${files_to_upgrade[@]}" |
sort -zu | shuf
-z); # See [1]
591 if [[ $opVerbose == "true" ]]; then
592 vbm
"DEBUG:files_to_upgrade_pruned:";
593 printf "%s\n" "${files_to_upgrade_pruned[@]}";
597 while read -r -d $
'\0' line
; do
598 vbm
"DEBUG:adding to files_to_verify_pruned:line:$line";
599 files_to_verify_pruned
+=("$line");
600 done < <(printf "%s\0" "${files_to_verify[@]}" |
sort -zu | shuf
-z); # See [1]
601 if [[ $opVerbose == "true" ]]; then
602 vbm
"DEBUG:files_to_verify_pruned:";
603 printf "%s\n\n" "${files_to_verify_pruned[@]}";
607 while read -r -d $
'\0' line
; do
608 vbm
"DEBUG:adding to files_to_stamp_pruned:line:$line";
609 files_to_stamp_pruned
+=("$line");
610 done < <(printf "%s\0" "${files_to_stamp[@]}" |
sort -zu | shuf
-z); # See [1]
611 if [[ $opVerbose == "true" ]]; then
612 vbm
"DEBUG:files_to_stamp_pruned:";
613 printf "%s\n" "${files_to_stamp_pruned[@]}";
618 for item
in "${files_to_upgrade_pruned[@]}"; do
619 path_prf
="$(cut -d $'\n' -f1 < <(echo "$item"))";
620 if [[ -z "$path_prf" ]]; then
621 yell
"ERROR:blank upgrade item encountered. Skipping:item:$item";
624 vbm
"DEBUG:Attempting to upgrade proof file:path_prf:$path_prf";
625 if [[ ! $option_dry_run == "true" ]]; then
626 ### Try upgrade with known calendars in random order
627 while read -r url
; do
628 vbm
"DEBUG:Upgrading with calendar:url:$url";
629 ots
-l "$url" --no-default-whitelist upgrade
"$path_prf" && break;
630 done < <(printf "%s\n" "${calendars[@]}" | shuf
);
632 yell
"DEBUG:DRY RUN:Not running:\"ots upgrade $path_prf\"";
638 for item
in "${files_to_verify_pruned[@]}"; do
639 path_src
="$(cut -d $'\n' -f1 < <(echo "$item"))";
640 path_prf
="$(cut -d $'\n' -f2 < <(echo "$item"))";
641 if [[ -z "$path_src" ]] ||
[[ -z "$path_prf" ]]; then
642 yell
"ERROR:blank verify item encountered. Skipping:item:$item";
645 vbm
"DEBUG:Attempting to verify source file:path_src:$path_src";
646 vbm
"DEBUG: against proof file: path_prf:$path_prf";
647 if [[ ! $option_dry_run == "true" ]]; then
648 ### Try verify with known calendars in random order
649 while read -r url
; do
650 vbm
"DEBUG:Verifying with calendar:url:$url";
651 ots
-l "$url" --no-default-whitelist verify
-f "$path_src" "$path_prf" && break;
652 done < <(printf "%s\n" "${calendars[@]}" | shuf
);
654 yell
"DEBUG:DRY RUN:Not running:\"ots verify -f $path_src $path_prf\"";
660 for item
in "${files_to_stamp_pruned[@]}"; do
661 path_src
="$(cut -d $'\n' -f1 < <(echo "$item"))";
662 if [[ -z "$path_src" ]]; then
663 yell
"ERROR:blank stamp item encountered. Skipping:item:$item";
666 vbm
"DEBUG:Attempting to stamp source file:path_src:$path_src";
667 if [[ ! $option_dry_run == "true" ]]; then
670 yell
"DEBUG:DRY RUN:Not running:\"ots stamp $item\"";