Skip to content

Instantly share code, notes, and snippets.

@ernstki
Last active September 1, 2023 22:47
Show Gist options
  • Save ernstki/b6e66e2f4163f583387f8656dde0640a to your computer and use it in GitHub Desktop.
Save ernstki/b6e66e2f4163f583387f8656dde0640a to your computer and use it in GitHub Desktop.
[WIP] The standard 'md5sum' implemented as a Bash function for (older?) macOS
#!/usr/bin/env bash
# h/t: https://stackoverflow.com/a/28776166
if ( return 0 2>/dev/null ); then sourced=1; else sourced=0; fi
# fail on unset variables unless sourced from another script
if (( !sourced )); then set -u; fi
# set TRACE=1 in the environment to enable execution tracing
if (( ${TRACE:-} )); then set -x; fi
_md5sum() {
if [[ $(uname -s) == Darwin ]]; then
# && ! command -v md5sum &>/dev/null
if [[ ${1:-} == '-c' ]]; then
# and when 'md5sum' fails it looks like this, so emulate that
# $ md5sum /etc/passwd | tr 1-4 6-9 | md5sum -c -
# /etc/passwd: FAILED
# md5sum: WARNING: 1 of 1 computed checksums did NOT match
# otherwise
# $ md5sum /etc/passwd | md5sum -c -
# /etc/passwd: OK
shift
local failed=0 total=0
while read sum filename; do
if [[ $($FUNCNAME $filename) == $sum ]]; then
echo "$filename: OK"
else
echo "$filename: FAILED"
failed=$(( failed + 1 ))
fi
total=$(( total + 1 ))
done < <( cat ${@:+"$@"} )
# useless use of cat for compatibility with the real 'md5sum',
# which already knows how to read from stdin if any argument is '-'
if (( failed )); then
echo "md5sum: WARNING: $(( failed )) of $(( total )) checksums did NOT match"
return 1
fi
else
# recent *BSD have a '-n' which puts the output in "normal" form;
# (older?) macOS doesn't have this flag
#
# so tranform from md5's format, which is:
# $ md5 /etc/passwd
# MD5 (/etc/passwd) = 578778de897a870096cd897e6f967b5d
# $ echo HI | md5
# 39d2b1ae41c609d04d9bbcde036940d8
#
# if no filenames args are passed, 'md5sum's behavior is to just
# sit there and wait for stdin
if [[ $# -gt 0 ]]; then
for file in "$@"; do
md5 "$file" | sed -E 's/.*\((.+)\) = ([[:xdigit:]]+)/\2 \1/'
done
else
# reading from stdin
echo "$(md5) -"
fi
fi
else
md5sum ${@:+"$@"}
fi
} # _md5sum
# if we were sourced, do nothing; otherwise run with arguments
if (( !sourced )); then
_md5sum ${@:+"$@"}
fi
SHELL = bash
DIM := $(shell tput dim)
RESET := $(shell tput sgr0)
## tell 'md5sum' that it's under test
#TESTING = 1
#export TESTING
check: test
test: have-md5-in-path have-md5sum-in-path md5sum \
read-from-stdin read-filename read-multiple-filenames \
check-from-stdin check-from-filename check-from-multiple-filenames
read-from-stdin:
@echo -e "\n$(DIM)## $@$(RESET)" >&2
diff -u <(echo HI | command md5sum) <(echo HI | ./md5sum)
read-filename: /etc/passwd
@echo -e "\n$(DIM)## $@$(RESET)" >&2
diff -u <(command md5sum $<) <(./md5sum $<)
read-multiple-filenames: /etc/passwd /etc/group /etc/hosts /etc/aliases
@echo -e "\n$(DIM)## $@$(RESET)" >&2
diff -u <(command md5sum $^) <(./md5sum $^)
check-from-stdin:
@echo -e "\n$(DIM)## $@$(RESET)" >&2
diff -u <(echo HI | command md5sum | command md5sum -c) <(echo HI | command md5sum | ./md5sum -c)
check-from-filename: MD5SUMS
@echo -e "\n$(DIM)## $@$(RESET)" >&2
diff -u <(command md5sum -c $<) <(./md5sum -c $<)
check-from-multiple-filenames: MD5SUMS MD5SUMS2
@echo -e "\n$(DIM)## $@$(RESET)" >&2
diff -u <(command md5sum -c $^) <(./md5sum -c $^)
MD5SUMS:
md5sum * > $@
MD5SUMS2: MD5SUMS
# `tail -r` is macOS/BSD thing; otherwise use `tac`
if [[ $$(uname -s) == Darwin ]]; then \
tail -r $< > $@; \
else \
tac $< > $@; \
fi
clean:
-rm MD5SUMS*
have-md5-in-path:
ifeq ($(shell command -v md5),)
$(error Need 'md5' in the search path to run tests)
endif
have-md5sum-in-path:
ifeq ($(shell command -v md5sum),)
$(error Need 'md5sum' in the search path to run tests)
endif
@ernstki
Copy link
Author

ernstki commented Aug 31, 2023

This is a work-in-progress.

macOS doesn't have the standard md5sum utility, except if you install it from Homebrew or MacPorts (package: md5sha1sum).

This bit of shell script implements the standard behavior of md5sum, including reading and writing from a pipe, in Bash script, wrapped around the *BSD md5 utility, which macOS does have.

Testing

It works for some of the common use cases, but others not. Running make -f •Makefile after cloning the gist as a Git repo locally will run the tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment