2 # Desc: Create and sign a checksum
3 # Usage: bksum file.txt
4 # Input: stdin: file list (newline delimited)
5 # arg(s): file paths (IFS delimited)
6 # Output: file containing sha256 hashes and file paths
7 # Depends: bash v5.1.16, date (GNU Coreutils 8.32), gpg v2.2.27, ots v0.7.0
10 declare -Ag appRollCall
# Associative array for storing app status
11 declare -Ag fileRollCall
# Associative array for storing file status
12 declare -Ag dirRollCall
# Associative array for storing dir status
13 declare -ag arrPosArgs
; # positional arguments
14 declare -ag arrStdin
; # standard input lines
15 declare -ag arrInFiles
; # input files
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 # Description: Prints verbose message ("vbm") to stderr if opVerbose is set to "true".
22 # Usage: vbm "DEBUG :verbose message here"
27 # Depends: bash 5.1.16, GNU-coreutils 8.30 (echo, date)
29 if [ "$opVerbose" = "true" ]; then
30 functionTime
="$(date --iso-8601=ns)"; # Save current time in nano seconds.
31 echo "[$functionTime]:$0:""$*" 1>&2; # Display argument text.
35 return 0; # Function finished.
36 } # Displays message if opVerbose true
38 # Desc: Display script usage information
43 # Depends: GNU-coreutils 8.30 (cat)
46 bksum [ options ] [FILE...]
49 Creates sha256 checksum of files.
53 Display help information.
55 Display script version.
57 Display debugging info.
59 Define output file path. By default, the file
60 name includes the full ISO-8601 timestamp
61 without separators, e.g.:
62 20220920T2117+0000..SHA256SUM
64 Sign with GnuPG the checksum file.
66 Timestamp with OpenTimestamps the checksum file
67 (and GnuPG signature if -s/--sign specified).
69 Indicate end of options.
73 bksum file1.txt file2.txt
74 bksum -v -- file1.txt file2.txt
75 bksum -v -t -- file1.txt file2.txt
76 find . -type f | bksum
77 find . -type f | bksum -v -s -t -- file.txt
80 If GNU Coreutils 8.32 `sha256sum` used, checksum file
81 can be verified using:
82 sha256sum --check 20220920T2117+0000..SHA256SUM
84 } # Display information on how to use this script.
86 # Desc: Displays script version and license information.
89 # Input: scriptVersion var containing version string
91 # Depends: vbm(), yell, GNU-coreutils 8.30
94 vbm
"DEBUG:showVersion function called."
98 Copyright (C) 2022 Steven Baltakatei Sandoval
99 License GPLv3: GNU GPL version 3
100 This is free software; you are free to change and redistribute it.
101 There is NO WARRANTY, to the extent permitted by law.
106 vbm
"DEBUG:showVersion function ended."
107 return 0; # Function finished.
108 } # Display script version.
110 # Desc: If arg is a command, save result in assoc array 'appRollCall'
111 # Usage: checkapp arg1 arg2 arg3 ...
113 # Input: global assoc. array 'appRollCall'
114 # Output: adds/updates key(value) to global assoc array 'appRollCall'
115 # Depends: bash 5.0.3
120 if command -v "$arg" 1>/dev
/null
2>&1; then # Check if arg is a valid command
121 appRollCall
[$arg]="true";
122 if ! [ "$returnState" = "false" ]; then returnState
="true"; fi;
124 appRollCall
[$arg]="false"; returnState
="false";
128 #===Determine function return code===
129 if [ "$returnState" = "true" ]; then
134 } # Check that app exists
136 # Desc: If arg is a file path, save result in assoc array 'fileRollCall'
137 # Usage: checkfile arg1 arg2 arg3 ...
139 # Input: global assoc. array 'fileRollCall'
140 # Output: adds/updates key(value) to global assoc array 'fileRollCall';
141 # Output: returns 0 if app found, 1 otherwise
142 # Depends: bash 5.0.3
147 if [ -f "$arg" ]; then
148 fileRollCall
["$arg"]="true";
149 if ! [ "$returnState" = "false" ]; then returnState
="true"; fi;
150 elif [ -z "$arg" ]; then
151 fileRollCall
["(no name)"]="false"; returnState
="false";
153 fileRollCall
["$arg"]="false"; returnState
="false";
157 #===Determine function return code===
158 if [ "$returnState" = "true" ]; then
163 } # Check that file exists
165 # Desc: If arg is a dir path, save result in assoc array 'dirRollCall'
166 # Usage: checkdir arg1 arg2 arg3 ...
168 # Input: global assoc. array 'dirRollCall'
169 # Output: adds/updates key(value) to global assoc array 'dirRollCall';
170 # Output: returns 0 if all args are dirs; 1 otherwise
171 # Depends: Bash 5.0.3
176 if [ -z "$arg" ]; then
177 dirRollCall
["(Unspecified Dirname(s))"]="false"; returnState
="false";
178 elif [ -d "$arg" ]; then
179 dirRollCall
["$arg"]="true";
180 if ! [ "$returnState" = "false" ]; then returnState
="true"; fi
182 dirRollCall
["$arg"]="false"; returnState
="false";
186 #===Determine function return code===
187 if [ "$returnState" = "true" ]; then
192 } # Check that dir exists
194 # Desc: Displays missing apps, files, and dirs
195 # Usage: displayMissing
197 # Input: associative arrays: appRollCall, fileRollCall, dirRollCall
198 # Output: stderr: messages indicating missing apps, file, or dirs
199 # Output: returns exit code 0 if nothing missing; 1 otherwise
200 # Depends: bash 5, checkAppFileDir()
201 local missingApps value appMissing missingFiles fileMissing
202 local missingDirs dirMissing
204 #==BEGIN Display errors==
205 #===BEGIN Display Missing Apps===
206 missingApps
="Missing apps :";
207 #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done
208 for key
in "${!appRollCall[@]}"; do
209 value
="${appRollCall[$key]}";
210 if [ "$value" = "false" ]; then
211 #echo "DEBUG:Missing apps: $key => $value";
212 missingApps
="$missingApps""$key ";
216 if [ "$appMissing" = "true" ]; then # Only indicate if an app is missing.
217 echo "$missingApps" 1>&2;
220 #===END Display Missing Apps===
222 #===BEGIN Display Missing Files===
223 missingFiles
="Missing files:";
224 #for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done
225 for key
in "${!fileRollCall[@]}"; do
226 value
="${fileRollCall[$key]}";
227 if [ "$value" = "false" ]; then
228 #echo "DEBUG:Missing files: $key => $value";
229 missingFiles
="$missingFiles""$key ";
233 if [ "$fileMissing" = "true" ]; then # Only indicate if an app is missing.
234 echo "$missingFiles" 1>&2;
237 #===END Display Missing Files===
239 #===BEGIN Display Missing Directories===
240 missingDirs
="Missing dirs:";
241 #for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done
242 for key
in "${!dirRollCall[@]}"; do
243 value
="${dirRollCall[$key]}";
244 if [ "$value" = "false" ]; then
245 #echo "DEBUG:Missing dirs: $key => $value";
246 missingDirs
="$missingDirs""$key ";
250 if [ "$dirMissing" = "true" ]; then # Only indicate if an dir is missing.
251 echo "$missingDirs" 1>&2;
254 #===END Display Missing Directories===
256 #==END Display errors==
257 #==BEGIN Determine function return code===
258 if [ "$appMissing" == "true" ] ||
[ "$fileMissing" == "true" ] ||
[ "$dirMissing" == "true" ]; then
263 #==END Determine function return code===
264 } # Display missing apps, files, dirs
266 # Desc: Processes arguments provided to script
267 # Usage: processArgs "$@"
268 # Version: 1.0.0 (modified)
269 # Input: "$@" (list of arguments provided to the function)
270 # Output: Sets following variables used by other functions:
271 # opVerbose Indicates verbose mode enable status. (ex: "true", "false")
272 # pathDirOut1 Path to output directory.
273 # pathFileOut1 Path to output file.
274 # opFileOut1_overwrite Indicates whether file pathFileOut1 should be overwritten (ex: "true", "false").
275 # arrPosArgs 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 # arrPosArgs 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 -h |
--help) showUsage
; exit 1;; # Display usage.
296 --version) showVersion
; exit 1;; # Show version
297 -v |
--verbose) opVerbose
="true"; vbm
"DEBUG:Verbose mode enabled.";; # Enable verbose mode. See [1].
298 -o |
--output-file) # Define output file path
299 if [ -f "$2" ]; then # If $2 is file that exists, prompt user to continue to overwrite, set pathFileOut1 to $2, pop $2.
300 yell
"Specified output file $2 already exists. Overwrite? (y/n):"
301 read -r m
; case $m in
302 y | Y |
yes) opFileOut1_overwrite
="true";;
303 n | N | no
) opFileOut1_overwrite
="false";;
304 *) yell
"Invalid selection. Exiting."; exit 1;;
306 if [ "$opFileOut1_overwrite" == "true" ]; then
309 vbm
"DEBUG:Output file pathFileOut1 set to:""$2";
311 yell
"ERORR:Exiting in order to not overwrite output file:""$pathFileOut1";
317 vbm
"DEBUG:Output file pathFileOut1 set to:""$2";
319 -s |
--sign) # sign with gpg
320 opSign
="true"; vbm
"DEBUG:Signing mode enabled.";;
321 -t |
--timestamp) # timestamp with ots
322 opTimestamp
="true"; vbm
"DEBUG:Timestamp mode enabled.";;
323 --) # End of all options. See [2].
326 vbm
"DEBUG:adding to arrPosArgs:$arg";
327 arrPosArgs
+=("$arg");
330 -*) showUsage
; yell
"ERROR: Unrecognized option."; exit 1;; # Display usage
333 vbm
"DEBUG:adding to arrPosArgs:$arg";
334 arrPosArgs
+=("$arg");
342 vbm
"DEBUG:processArgs function ended."
343 return 0; # Function finished.
344 } # Evaluate script options from positional arguments (ex: $1, $2, $3, etc.).
346 # Desc: Save stdin lines to array
347 # Input: stdin standard input
348 # arrStdin array for storing stdin lines
349 # Output: arrStdin array for storing stdin lines
350 # Ref/Attrib: [1] https://unix.stackexchange.com/a/484643 Check if no command line arguments and STDIN is empty
353 return 1; # error if file descriptor 0 (stdin) open
355 while read -r line
; do
356 arrStdin
+=("$line"); done;
359 }; # Save stdin to array
361 # Desc: Check that files (specified by positional arguments and
362 # standard input lines) exist and are in the same directory.
363 # Input: arrPosArgs[@] positional arguments
364 # arrStdin[@] standard input lines
365 # Output: return code 0 success
366 # return code 1 failure
367 # arrInFiles list of verified files
368 # Depends: displayMissing(), checkfile();
369 local flagMissing flagDirErr workDir
;
371 # Check that positional arguments are files
372 for elem
in "${arrPosArgs[@]}"; do
373 if checkfile
"$elem"; then
374 arrInFiles
+=("$elem");
380 # Check that stdin lines are files
381 for elem
in "${arrStdin[@]}"; do
382 if checkfile
"$elem"; then
383 arrInFiles
+=("$elem");
389 # Check that all files are in the same directory
390 if [[ "$flagMissing" != "true" ]]; then
391 workDir
="$( dirname "$
( readlink
-f "${arrInFiles[0]}" )" )";
392 for elem
in "${arrInFiles[@]}"; do
393 elem
="$(readlink -f "$elem")"; # full path
394 if [[ "$(dirname "$elem")" != "$workDir" ]]; then
400 # Return non-zero if displayMissing() reports files missing.
401 if [[ "$flagMissing" == "true" ]]; then
402 displayMissing
; return 1; fi;
403 if [[ "$flagDirErr" == "true" ]]; then
404 yell
"ERROR:All files not in same directory.";
407 }; # Check positional arguments
409 # Desc: Check if expected commands available
411 checkapp
date sha256sum
;
412 if [[ $opSign == "true" ]]; then checkapp gpg
; fi;
413 if [[ $opTimestamp == "true" ]]; then checkapp ots
; fi;
415 # Return failure is displayMissing() reports apps missing.
416 if ! displayMissing
; then return 1; else return 0; fi;
417 }; # Check dependencies
419 # Input: pathFileOut1 option-specified output file path
420 # appRollCall assoc-array for checkapp(), displayMissing()
421 # fileRollCall assoc-array for checkfile(), displayMissing()
422 # dirRollCall assoc-array for checkdir(), displayMissing()
423 # arrPosArgs array for processArgs()
424 # arrStdin array for processStdin()
425 # arrInFiles array for checkInput()
426 # (args) for processArgs()
427 # (stdin) for processStdin()
428 # Output: file written to $pathSumOut
429 # file written to $pathSigOut
431 local fileSumOut dirOut pathSumOut pathSigOut
;
433 # Check dependencies and input
434 if ! checkDepends
; then die
"FATAL:Missing apps."; fi;
437 vbm
"DEBUG:$(declare -p arrPosArgs)";
438 vbm
"DEBUG:$(declare -p arrStdin)";
439 if ! [[ "${#arrPosArgs[@]}" -ge 1 ||
"${#arrStdin[@]}" -ge 1 ]]; then
440 yell
"ERROR:No positional arguments or stdin lines.";
441 showUsage
; exit 1; fi;
442 if ! checkInput
; then die
"FATAL:Invalid file list."; fi;
443 vbm
"DEBUG:$(declare -p arrInFiles)";
446 if [[ -n "$pathFileOut1" ]]; then
447 pathSumOut
="$pathFileOut1";
449 dirOut
="$( dirname "${arrInFiles[0]}" )";
450 fileSumOut
="$(date +%Y%m%dT%H%M%S%z)..SHA256SUMS";
451 pathSumOut
="$dirOut"/"$fileSumOut";
453 pathSigOut
="$pathSumOut".asc
;
454 for file in "${arrInFiles[@]}"; do
455 sha256sum
"$file" >> "$pathSumOut";
459 ## Sign checksum file.
460 if [[ $opSign == "true" ]]; then
461 try gpg
--detach-sign --armor --output "$pathSigOut" "$pathSumOut";
463 ## Timestamp checksum file.
464 if [[ $opTimestamp == "true" ]]; then
465 try ots s
"$pathSumOut"; fi;
467 ## Timestamp checksum signature file.
468 if [[ $opTimestamp == "true" && $opSign == "true" ]]; then
469 try ots s
"$pathSigOut";
477 # Author: Steven Baltakatei Sandoval