2 # Desc: Create and sign a checksum
3 # Input: stdin: file list (newline delimited)
4 # arg(s): file paths (IFS delimited)
5 # Output: file containing sha256 hashes and file paths
6 # Depends: bash v5.1.16, date (GNU Coreutils 8.32), gpg v2.2.27, ots v0.7.0
9 declare -Ag appRollCall
# Associative array for storing app status
10 declare -Ag fileRollCall
# Associative array for storing file status
11 declare -Ag dirRollCall
# Associative array for storing dir status
12 declare -ag arrPosArgs
; # positional arguments
13 declare -ag arrStdin
; # standard input lines
14 declare -ag arrInFiles
; # input files
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 # Description: Prints verbose message ("vbm") to stderr if opVerbose is set to "true".
21 # Usage: vbm "DEBUG :verbose message here"
26 # Depends: bash 5.1.16, GNU-coreutils 8.30 (echo, date)
28 if [ "$opVerbose" = "true" ]; then
29 functionTime
="$(date --iso-8601=ns)"; # Save current time in nano seconds.
30 echo "[$functionTime]:$0:""$*" 1>&2; # Display argument text.
34 return 0; # Function finished.
35 } # Displays message if opVerbose true
37 # Desc: Display script usage information
42 # Depends: GNU-coreutils 8.30 (cat)
45 bksumsign.sh [ options ] [FILE...]
48 Creates sha256 checksum of files.
52 Display help information.
54 Display script version.
56 Display debugging info.
58 Define output file path. By default, the file
59 name includes the full ISO-8601 timestamp
60 without separators, e.g.:
61 20220920T2117+0000..SHA256SUM
63 Sign with GnuPG the checksum file.
65 Timestamp with OpenTimestamps the checksum file
66 (and GnuPG signature if -s/--sign specified).
68 Indicate end of options.
72 bksumsign.sh file1.txt file2.txt
73 bksumsign.sh -v -- file1.txt file2.txt
74 bksumsign.sh -v -t -- file1.txt file2.txt
75 find . -type f | bksumsign.sh
76 find . -type f | bksumsign.sh -v -s -t -- file.txt
79 If GNU Coreutils 8.32 `sha256sum` used, checksum file
80 can be verified using:
81 sha256sum --check 20220920T2117+0000..SHA256SUM
83 } # Display information on how to use this script.
85 # Desc: Displays script version and license information.
88 # Input: scriptVersion var containing version string
90 # Depends: vbm(), yell, GNU-coreutils 8.30
93 vbm
"DEBUG:showVersion function called."
97 Copyright (C) 2022 Steven Baltakatei Sandoval
98 License GPLv3: GNU GPL version 3
99 This is free software; you are free to change and redistribute it.
100 There is NO WARRANTY, to the extent permitted by law.
105 vbm
"DEBUG:showVersion function ended."
106 return 0; # Function finished.
107 } # Display script version.
109 # Desc: If arg is a command, save result in assoc array 'appRollCall'
110 # Usage: checkapp arg1 arg2 arg3 ...
112 # Input: global assoc. array 'appRollCall'
113 # Output: adds/updates key(value) to global assoc array 'appRollCall'
114 # Depends: bash 5.0.3
119 if command -v "$arg" 1>/dev
/null
2>&1; then # Check if arg is a valid command
120 appRollCall
[$arg]="true";
121 if ! [ "$returnState" = "false" ]; then returnState
="true"; fi;
123 appRollCall
[$arg]="false"; returnState
="false";
127 #===Determine function return code===
128 if [ "$returnState" = "true" ]; then
133 } # Check that app exists
135 # Desc: If arg is a file path, save result in assoc array 'fileRollCall'
136 # Usage: checkfile arg1 arg2 arg3 ...
138 # Input: global assoc. array 'fileRollCall'
139 # Output: adds/updates key(value) to global assoc array 'fileRollCall';
140 # Output: returns 0 if app found, 1 otherwise
141 # Depends: bash 5.0.3
146 if [ -f "$arg" ]; then
147 fileRollCall
["$arg"]="true";
148 if ! [ "$returnState" = "false" ]; then returnState
="true"; fi;
149 elif [ -z "$arg" ]; then
150 fileRollCall
["(no name)"]="false"; returnState
="false";
152 fileRollCall
["$arg"]="false"; returnState
="false";
156 #===Determine function return code===
157 if [ "$returnState" = "true" ]; then
162 } # Check that file exists
164 # Desc: If arg is a dir path, save result in assoc array 'dirRollCall'
165 # Usage: checkdir arg1 arg2 arg3 ...
167 # Input: global assoc. array 'dirRollCall'
168 # Output: adds/updates key(value) to global assoc array 'dirRollCall';
169 # Output: returns 0 if all args are dirs; 1 otherwise
170 # Depends: Bash 5.0.3
175 if [ -z "$arg" ]; then
176 dirRollCall
["(Unspecified Dirname(s))"]="false"; returnState
="false";
177 elif [ -d "$arg" ]; then
178 dirRollCall
["$arg"]="true";
179 if ! [ "$returnState" = "false" ]; then returnState
="true"; fi
181 dirRollCall
["$arg"]="false"; returnState
="false";
185 #===Determine function return code===
186 if [ "$returnState" = "true" ]; then
191 } # Check that dir exists
193 # Desc: Displays missing apps, files, and dirs
194 # Usage: displayMissing
196 # Input: associative arrays: appRollCall, fileRollCall, dirRollCall
197 # Output: stderr: messages indicating missing apps, file, or dirs
198 # Output: returns exit code 0 if nothing missing; 1 otherwise
199 # Depends: bash 5, checkAppFileDir()
200 local missingApps value appMissing missingFiles fileMissing
201 local missingDirs dirMissing
203 #==BEGIN Display errors==
204 #===BEGIN Display Missing Apps===
205 missingApps
="Missing apps :";
206 #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done
207 for key
in "${!appRollCall[@]}"; do
208 value
="${appRollCall[$key]}";
209 if [ "$value" = "false" ]; then
210 #echo "DEBUG:Missing apps: $key => $value";
211 missingApps
="$missingApps""$key ";
215 if [ "$appMissing" = "true" ]; then # Only indicate if an app is missing.
216 echo "$missingApps" 1>&2;
219 #===END Display Missing Apps===
221 #===BEGIN Display Missing Files===
222 missingFiles
="Missing files:";
223 #for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done
224 for key
in "${!fileRollCall[@]}"; do
225 value
="${fileRollCall[$key]}";
226 if [ "$value" = "false" ]; then
227 #echo "DEBUG:Missing files: $key => $value";
228 missingFiles
="$missingFiles""$key ";
232 if [ "$fileMissing" = "true" ]; then # Only indicate if an app is missing.
233 echo "$missingFiles" 1>&2;
236 #===END Display Missing Files===
238 #===BEGIN Display Missing Directories===
239 missingDirs
="Missing dirs:";
240 #for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done
241 for key
in "${!dirRollCall[@]}"; do
242 value
="${dirRollCall[$key]}";
243 if [ "$value" = "false" ]; then
244 #echo "DEBUG:Missing dirs: $key => $value";
245 missingDirs
="$missingDirs""$key ";
249 if [ "$dirMissing" = "true" ]; then # Only indicate if an dir is missing.
250 echo "$missingDirs" 1>&2;
253 #===END Display Missing Directories===
255 #==END Display errors==
256 #==BEGIN Determine function return code===
257 if [ "$appMissing" == "true" ] ||
[ "$fileMissing" == "true" ] ||
[ "$dirMissing" == "true" ]; then
262 #==END Determine function return code===
263 } # Display missing apps, files, dirs
265 # Desc: Processes arguments provided to script
266 # Usage: processArgs "$@"
267 # Version: 1.0.0 (modified)
268 # Input: "$@" (list of arguments provided to the function)
269 # Output: Sets following variables used by other functions:
270 # opVerbose Indicates verbose mode enable status. (ex: "true", "false")
271 # pathDirOut1 Path to output directory.
272 # pathFileOut1 Path to output file.
273 # opFileOut1_overwrite Indicates whether file pathFileOut1 should be overwritten (ex: "true", "false").
274 # arrPosArgs Array of remaining positional argments
276 # yell() Displays messages to stderr.
277 # vbm() Displays messsages to stderr if opVerbose set to "true".
278 # showUsage() Displays usage information about parent script.
279 # showVersion() Displays version about parent script.
280 # arrPosArgs Global array for storing non-option positional arguments (i.e. arguments following the `--` option).
281 # External dependencies: bash (5.1.16), echo
283 # [1]: Marco Aurelio (2014-05-08). "echo that outputs to stderr". https://stackoverflow.com/a/23550347
284 # [2]: "Handling positional parameters" (2018-05-12). https://wiki.bash-hackers.org/scripting/posparams
286 # Initialize function
287 vbm
"DEBUG:processArgs function called."
290 while [ ! $# -eq 0 ]; do # While number of arguments ($#) is not (!) equal to (-eq) zero (0).
291 #yell "DEBUG:Starting processArgs while loop." # Debug stderr message. See [1].
292 #yell "DEBUG:Provided arguments are:""$*" # Debug stderr message. See [1].
294 -h |
--help) showUsage
; exit 1;; # Display usage.
295 --version) showVersion
; exit 1;; # Show version
296 -v |
--verbose) opVerbose
="true"; vbm
"DEBUG:Verbose mode enabled.";; # Enable verbose mode. See [1].
297 -o |
--output-file) # Define output file path
298 if [ -f "$2" ]; then # If $2 is file that exists, prompt user to continue to overwrite, set pathFileOut1 to $2, pop $2.
299 yell
"Specified output file $2 already exists. Overwrite? (y/n):"
300 read -r m
; case $m in
301 y | Y |
yes) opFileOut1_overwrite
="true";;
302 n | N | no
) opFileOut1_overwrite
="false";;
303 *) yell
"Invalid selection. Exiting."; exit 1;;
305 if [ "$opFileOut1_overwrite" == "true" ]; then
308 vbm
"DEBUG:Output file pathFileOut1 set to:""$2";
310 yell
"ERORR:Exiting in order to not overwrite output file:""$pathFileOut1";
316 vbm
"DEBUG:Output file pathFileOut1 set to:""$2";
318 -s |
--sign) # sign with gpg
319 opSign
="true"; vbm
"DEBUG:Signing mode enabled.";;
320 -t |
--timestamp) # timestamp with ots
321 opTimestamp
="true"; vbm
"DEBUG:Timestamp mode enabled.";;
322 --) # End of all options. See [2].
325 vbm
"DEBUG:adding to arrPosArgs:$arg";
326 arrPosArgs
+=("$arg");
329 -*) showUsage
; yell
"ERROR: Unrecognized option."; exit 1;; # Display usage
332 vbm
"DEBUG:adding to arrPosArgs:$arg";
333 arrPosArgs
+=("$arg");
341 vbm
"DEBUG:processArgs function ended."
342 return 0; # Function finished.
343 } # Evaluate script options from positional arguments (ex: $1, $2, $3, etc.).
345 # Desc: Save stdin lines to array
346 # Input: stdin standard input
347 # arrStdin array for storing stdin lines
348 # Output: arrStdin array for storing stdin lines
349 # Ref/Attrib: [1] https://unix.stackexchange.com/a/484643 Check if no command line arguments and STDIN is empty
352 return 1; # error if file descriptor 0 (stdin) open
354 while read -r line
; do
355 arrStdin
+=("$line"); done;
358 }; # Save stdin to array
360 # Desc: Check that files (specified by positional arguments and
361 # standard input lines) exist and are in the same directory.
362 # Input: arrPosArgs[@] positional arguments
363 # arrStdin[@] standard input lines
364 # Output: return code 0 success
365 # return code 1 failure
366 # arrInFiles list of verified files
367 # Depends: displayMissing(), checkfile();
368 local flagMissing flagDirErr workDir
;
370 # Check that positional arguments are files
371 for elem
in "${arrPosArgs[@]}"; do
372 if checkfile
"$elem"; then
373 arrInFiles
+=("$elem");
379 # Check that stdin lines are files
380 for elem
in "${arrStdin[@]}"; do
381 if checkfile
"$elem"; then
382 arrInFiles
+=("$elem");
388 # Check that all files are in the same directory
389 if [[ "$flagMissing" != "true" ]]; then
390 workDir
="$( dirname "$
( readlink
-f "${arrInFiles[0]}" )" )";
391 for elem
in "${arrInFiles[@]}"; do
392 elem
="$(readlink -f "$elem")"; # full path
393 if [[ "$(dirname "$elem")" != "$workDir" ]]; then
399 # Return non-zero if displayMissing() reports files missing.
400 if [[ "$flagMissing" == "true" ]]; then
401 displayMissing
; return 1; fi;
402 if [[ "$flagDirErr" == "true" ]]; then
403 yell
"ERROR:All files not in same directory.";
406 }; # Check positional arguments
408 # Desc: Check if expected commands available
410 checkapp
date sha256sum
;
411 if [[ $opSign == "true" ]]; then checkapp gpg
; fi;
412 if [[ $opTimestamp == "true" ]]; then checkapp ots
; fi;
414 # Return failure is displayMissing() reports apps missing.
415 if ! displayMissing
; then return 1; else return 0; fi;
416 }; # Check dependencies
418 # Input: pathFileOut1 option-specified output file path
419 # appRollCall assoc-array for checkapp(), displayMissing()
420 # fileRollCall assoc-array for checkfile(), displayMissing()
421 # dirRollCall assoc-array for checkdir(), displayMissing()
422 # arrPosArgs array for processArgs()
423 # arrStdin array for processStdin()
424 # arrInFiles array for checkInput()
425 # (args) for processArgs()
426 # (stdin) for processStdin()
427 # Output: file written to $pathSumOut
428 # file written to $pathSigOut
430 local fileSumOut dirOut pathSumOut pathSigOut
;
432 # Check dependencies and input
433 if ! checkDepends
; then die
"FATAL:Missing apps."; fi;
436 vbm
"DEBUG:$(declare -p arrPosArgs)";
437 vbm
"DEBUG:$(declare -p arrStdin)";
438 if ! [[ "${#arrPosArgs[@]}" -ge 1 ||
"${#arrStdin[@]}" -ge 1 ]]; then
439 yell
"ERROR:No positional arguments or stdin lines.";
440 showUsage
; exit 1; fi;
441 if ! checkInput
; then die
"FATAL:Invalid file list."; fi;
442 vbm
"DEBUG:$(declare -p arrInFiles)";
445 if [[ -n "$pathFileOut1" ]]; then
446 pathSumOut
="$pathFileOut1";
448 dirOut
="$( dirname "${arrInFiles[0]}" )";
449 fileSumOut
="$(date +%Y%m%dT%H%M%S%z)..SHA256SUMS";
450 pathSumOut
="$dirOut"/"$fileSumOut";
452 pathSigOut
="$pathSumOut".asc
;
453 for file in "${arrInFiles[@]}"; do
454 sha256sum
"$file" >> "$pathSumOut";
458 ## Sign checksum file.
459 if [[ $opSign == "true" ]]; then
460 try gpg
--detach-sign --armor --output "$pathSigOut" "$pathSumOut";
462 ## Timestamp checksum file.
463 if [[ $opTimestamp == "true" ]]; then
464 try ots s
"$pathSumOut"; fi;
466 ## Timestamp checksum signature file.
467 if [[ $opTimestamp == "true" && $opSign == "true" ]]; then
468 try ots s
"$pathSigOut";
476 # Author: Steven Baltakatei Sandoval