|
#!/bin/sh |
|
|
|
# |
|
# usage: semver.sh A B |
|
# |
|
# Compares two semantic version strings and prints: |
|
# |
|
# -1 if A<B |
|
# 0 if A=B |
|
# 1 if A>B |
|
# |
|
# A non-zero exit code means there was an error comparing the |
|
# provided values. |
|
# |
|
# The script may also be executed with TEST_SEMVER_COMP=1 to |
|
# run several unit tests that validate the comparison logic. |
|
# For example: |
|
# |
|
# $ TEST_SEMVER_COMP=1 semver.sh |
|
# a b exp act |
|
# v2.1.3 2.1.3.0 0 0 |
|
# v10.0 v1.0 1 1 |
|
# v1.0 v10.0 -1 -1 |
|
# |
|
|
|
set -e |
|
set -o pipefail |
|
|
|
# A basic regex pattern for matching a semantic version string. |
|
semverPatt='^\(v\{0,1\}\)\([[:digit:]]\{1,\}\)\{0,1\}\(.[[:digit:]]\{1,\}\)\{0,1\}\(.[[:digit:]]\{1,\}\)\{0,1\}\(.[[:digit:]]\{1,\}\)\{0,1\}\(.\{0,\}\)$' |
|
|
|
# Returns a successful exit code IFF the provided string is a semver. |
|
is_semver() { |
|
echo "${1}" | grep -q "${semverPatt}" |
|
} |
|
|
|
# Extracts the MAJOR component of a semver. |
|
get_major() { |
|
echo "${1}" | sed -e 's/'"${semverPatt}"'/\2/g' |
|
} |
|
# Extracts the MINOR component of a semver. |
|
get_minor() { |
|
_v=$(echo "${1}" | sed -e 's/'"${semverPatt}"'/\3/g' | tr -d '.') |
|
[ -n "${_v}" ] || _v=0; echo "${_v}" |
|
} |
|
# Extracts the PATCH component of a semver. |
|
get_patch() { |
|
_v=$(echo "${1}" | sed -e 's/'"${semverPatt}"'/\4/g' | tr -d '.') |
|
[ -n "${_v}" ] || _v=0; echo "${_v}" |
|
} |
|
# Extracts the BUILD component of a semver. |
|
get_build() { |
|
_v=$(echo "${1}" | sed -e 's/'"${semverPatt}"'/\5/g' | tr -d '.') |
|
[ -n "${_v}" ] || _v=0; echo "${_v}" |
|
} |
|
# Extracts the SUFFIX component of a semver. |
|
get_suffix() { |
|
echo "${1}" | sed -e 's/'"${semverPatt}"'/\6/g' |
|
} |
|
# Extracts the MAJOR.MINOR.PATCH.BUILD portion of a semver. |
|
get_major_minor_patch_build() { |
|
printf '%d.%d.%d.%d' \ |
|
"$(get_major "${1}")" \ |
|
"$(get_minor "${1}")" \ |
|
"$(get_patch "${1}")" \ |
|
"$(get_build "${1}")" |
|
} |
|
|
|
# Returns 0 if $1>$2 |
|
version_gt() { |
|
test "$(printf '%s\n' "${@}" | sort -V | head -n 1)" != "${1}" |
|
} |
|
|
|
# Compares two semantic version strings: |
|
# -1 if a<b |
|
# 0 if a=b |
|
# 1 if a>b |
|
semver_comp() { |
|
is_semver "${1}" || { echo "invalid semver: ${1}" 1>&2; return 1; } |
|
is_semver "${2}" || { echo "invalid semver: ${2}" 1>&2; return 1; } |
|
|
|
# Get the MAJOR.MINOR.PATCH.BUILD string for each version. |
|
_a_mmpb="$(get_major_minor_patch_build "${1}")" |
|
_b_mmpb="$(get_major_minor_patch_build "${2}")" |
|
|
|
# Record whether or not the two MAJOR.MINOR.PATCH.BUILD are equal. |
|
[ "${_a_mmpb}" = "${_b_mmpb}" ] && _a_eq_b=1 |
|
|
|
# Get the suffix components for each version. |
|
_a_suffix="$(get_suffix "${1}")" |
|
_b_suffix="$(get_suffix "${2}")" |
|
|
|
# Reconstitute $1 and $2 as $_va and $_vb by filling in any |
|
# components missing from the original semver values. |
|
_va="${_a_mmpb}${_a_suffix}" |
|
_vb="${_b_mmpb}${_b_suffix}" |
|
|
|
# If the two reconstituted version strings are equal then the versions |
|
# are equal. |
|
if [ "${_va}" = "${_vb}" ]; then |
|
_result=0 |
|
|
|
# If neither version have a suffix or if both versions have a suffix |
|
# then the versions may be compared with sort -V. |
|
elif { [ -z "${_a_suffix}" ] && [ -z "${_b_suffix}" ]; } || \ |
|
{ [ -n "${_a_suffix}" ] && [ -n "${_b_suffix}" ]; }; then |
|
{ version_gt "${_va}" "${_vb}" && _result=1; } || _result=-1 |
|
|
|
# If $1 does not have a suffix and the two MAJOR.MINOR.PATCH.BUILD |
|
# version strings are equal, then $1>$2. |
|
elif [ -z "${_a_suffix}" ] && [ -n "${_a_eq_b}" ]; then |
|
_result=1 |
|
|
|
# If $1 does have a suffix and the two MAJOR.MINOR.PATCH.BUILD |
|
# version strings are equal, then $1<$2. |
|
elif [ -n "${_a_suffix}" ] && [ -n "${_a_eq_b}" ]; then |
|
_result=-1 |
|
|
|
# Otherwise compare the two versions using sort -V. |
|
else |
|
{ version_gt "${_va}" "${_vb}" && _result=1; } || _result=-1 |
|
fi |
|
|
|
echo "${_result}" |
|
} |
|
|
|
[ -z "${TEST_SEMVER_COMP}" ] && { semver_comp "${@}"; exit "${?}"; } |
|
|
|
printf '%-35s %-35s % 5s % 5s\n' 'a' 'b' 'exp' 'act' |
|
test_semver_comp() { |
|
result="$(semver_comp "${1}" "${2}")" |
|
printf '%-35s %-35s % 5d % 5d\n' \ |
|
"${1}" "${2}" "${3}" "${result}" |
|
} |
|
|
|
test_semver_comp v1.0 v1.0 0 |
|
test_semver_comp v2.1.3 v2.1.3.0 0 |
|
test_semver_comp v2.1.3 2.1.3.0 0 |
|
test_semver_comp v10.0 v1.0 1 |
|
test_semver_comp v1.0 v10.0 -1 |
|
test_semver_comp v1.14 v1.14.alpha1 1 |
|
test_semver_comp v1.14 v1.14.0.alpha1 1 |
|
test_semver_comp v1.14.0 v1.14.alpha1 1 |
|
test_semver_comp v1.14.alpha1 v1.14 -1 |
|
test_semver_comp 1.14.alpha1 v1.14 -1 |
|
test_semver_comp v1.140.alpha1 v1.14 1 |
|
test_semver_comp v1.14 v1.14.0-alpha.1.363 1 |
|
test_semver_comp v1.14.0-alpha.1.363+8bce3620b02b2a 1.14 -1 |
|
|
|
exit 0 |
Hi @chriswells0,
Feel free to reuse it however you wish. If you want to link back to this gist in a comment at the top for credit, that's fine too. I am fine with either license you pick between the two you listed. As for how the license should appear, you can refer to https://github.com/akutz/simple-k8s-test-env/blob/master/sk8.sh for an example of how I usually add license info to the top of scripts.
You could also just add us each as an author to the top of the file, ex.
Ultimately it's up to you. Still, thank you for reaching out and asking. Quite a nice thing to do 😄