[Perf] nvm_version_greater, nvm_version_greater_than_or_equal_to: avoid awk where possible

This commit is contained in:
Cassidy Marble
2026-10-07 15:30:01 -03:00
committed by Jordan Harband
parent 9c8cac4a2e
commit d55a55c387
2 changed files with 101 additions and 14 deletions
+62 -14
View File
@@ -796,33 +796,81 @@ nvm_curl_version() {
command curl -V | command awk '{ if ($1 == "curl") print $2 }' | command sed 's/-.*$//g'
}
# Try to do some shell magic to do semver greater-than maths.
# Fall through to the expensive test by calling `awk` if we can't be sure.
nvm_version_greater() {
command awk 'BEGIN {
local NVM_LEFT
NVM_LEFT="${1#v}"
local NVM_RIGHT
NVM_RIGHT="${2#v}"
local NVM_PLAIN
# a third, more complex, argument of `1` (a flag) compares equal versions as true; `0` or
# no argument compares them as false.
local NVM_OR_EQUAL
NVM_OR_EQUAL="${3:-0}"
NVM_PLAIN='true'
# `x.y.z` operands are compared in shell arithmetic, which avoids a fork,
# except when a group is long enough to overflow it; anything else - partial
# versions, prereleases, and malformed operands - keeps the comparison this
# function has always made
case "${NVM_LEFT}" in
'' | *[!0-9.]* | *..* | .* | *. | *.*.*.*) NVM_PLAIN='false' ;;
*.*.*) ;;
*) NVM_PLAIN='false' ;;
esac
case "${NVM_RIGHT}" in
'' | *[!0-9.]* | *..* | .* | *. | *.*.*.*) NVM_PLAIN='false' ;;
*.*.*) ;;
*) NVM_PLAIN='false' ;;
esac
if [ "${NVM_PLAIN}" = 'true' ] && [ "${#NVM_LEFT}" -lt 19 ] && [ "${#NVM_RIGHT}" -lt 19 ]; then
local NVM_GROUP
local NVM_LEFT_GROUP
local NVM_RIGHT_GROUP
NVM_GROUP=1
while [ "${NVM_GROUP}" -le 3 ]; do
NVM_LEFT_GROUP="${NVM_LEFT%%.*}"
NVM_LEFT="${NVM_LEFT#*.}"
NVM_RIGHT_GROUP="${NVM_RIGHT%%.*}"
NVM_RIGHT="${NVM_RIGHT#*.}"
if [ "${NVM_LEFT_GROUP}" -lt "${NVM_RIGHT_GROUP}" ]; then
return 1
elif [ "${NVM_LEFT_GROUP}" -gt "${NVM_RIGHT_GROUP}" ]; then
return 0
fi
NVM_GROUP=$((NVM_GROUP + 1))
done
if [ "${NVM_OR_EQUAL}" = 1 ]; then
return 0
fi
return 1
fi
command awk -v or_equal="${NVM_OR_EQUAL}" 'BEGIN {
if (ARGV[1] == "" || ARGV[2] == "") exit(1)
split(ARGV[1], a, /\./);
split(ARGV[2], b, /\./);
for (i=1; i<=3; i++) {
if (a[i] && a[i] !~ /^[0-9]+$/) exit(2);
if (b[i] && b[i] !~ /^[0-9]+$/) { exit(0); }
if (b[i] && b[i] !~ /^[0-9]+$/) {
# a non-numeric right-hand group (a prerelease): the left is greater,
# unless equality counts, in which case the comparison below decides
if (or_equal != 1) { exit(0); }
}
if (a[i] < b[i]) exit(3);
else if (a[i] > b[i]) exit(0);
}
if (or_equal == 1) { exit(0); }
exit(4)
}' "${1#v}" "${2#v}"
}' "${NVM_LEFT}" "${NVM_RIGHT}"
}
# Try to do some shell magic to do semver greater-than or equal-to maths.
# Fall through to the expensive test by calling `awk` if we can't be sure.
# see `nvm_version_greater`
nvm_version_greater_than_or_equal_to() {
command awk 'BEGIN {
if (ARGV[1] == "" || ARGV[2] == "") exit(1)
split(ARGV[1], a, /\./);
split(ARGV[2], b, /\./);
for (i=1; i<=3; i++) {
if (a[i] && a[i] !~ /^[0-9]+$/) exit(2);
if (a[i] < b[i]) exit(3);
else if (a[i] > b[i]) exit(0);
}
exit(0)
}' "${1#v}" "${2#v}"
nvm_version_greater "${1-}" "${2-}" 1
}
nvm_version_dir() {
+39
View File
@@ -6,6 +6,8 @@ die () { echo "$@" ; exit 1; }
\. ../../../nvm.sh
nvm_version_greater 0.10.0 0.2.12 || die '"nvm_version_greater 0.10.0 0.2.12" did not return true'
nvm_version_greater 0.20.12 0.10.0 || die '"nvm_version_greater 0.20.12 0.10.0" did not return true'
nvm_version_greater v18.20.0 v18.19.9 || die '"nvm_version_greater v18.20.0 v18.19.9" did not return true'
if nvm_version_greater 0.10.0 0.20.12; then
die '"nvm_version_greater 0.10.0 0.20.12" returned true'
@@ -14,3 +16,40 @@ fi
if nvm_version_greater 0.10.0 0.10.0; then
die '"nvm_version_greater" returned false for the same two versions'
fi
if nvm_version_greater v18.20.0 v18.20.0; then
die '"nvm_version_greater v18.20.0 v18.20.0" returned true for the same two versions'
fi
if nvm_version_greater v18.19.9 v18.20.0; then
die '"nvm_version_greater v18.19.9 v18.20.0" returned true'
fi
# a partial version is not a plain `x.y.z`, so it keeps the awk comparison
nvm_version_greater 0.10 0.2.12 || die '"nvm_version_greater 0.10 0.2.12" did not return true'
# the comparison must not write anything to stdout (zsh's `typeset` prints a
# local variable when it is redeclared)
OUTPUT="$(nvm_version_greater 0.10.0 0.2.12 2>&1)"
[ -z "${OUTPUT}" ] || die "nvm_version_greater 0.10.0 0.2.12 wrote output: '${OUTPUT}'"
# `nvm_version_greater` must not fork a command for a plain `x.y.z` pair:
# mock `awk`, which the comparison used, and fail if it runs
TEST_DIR="$(pwd)/nvm_version_greater_tmp"
[ ! -e "${TEST_DIR}" ] && mkdir "${TEST_DIR}"
MOCK_BIN="${TEST_DIR}/bin"
mkdir -p "${MOCK_BIN}"
{
echo '#!/bin/sh'
echo 'echo "awk was called" >&2'
echo 'exit 1'
} > "${MOCK_BIN}/awk"
chmod +x "${MOCK_BIN}/awk"
PATH="${MOCK_BIN}:${PATH}" nvm_version_greater 0.10.0 0.2.12 \
|| die 'nvm_version_greater 0.10.0 0.2.12 forked a command'
PATH="${MOCK_BIN}:${PATH}" nvm_version_greater v18.20.0 v18.19.9 \
|| die 'nvm_version_greater v18.20.0 v18.19.9 forked a command'
rm -rf "${TEST_DIR}"