/commands/semver/compare-two-versions-bash
Compare two version numbers in bash (is version A greater than B?)
- tool
- SemVer
- versions
- GNU coreutils 7+ (sort -V), semver (npm) 7, python-semver 3
- os
- linux, macos, windows
The portable way to compare two version numbers in a bash script is GNU version sort: printf '%s\n' "$a" "$b" | sort -V -C exits with 0 when $a is lower than or equal to $b, because -C checks that the input is already in order. It handles 1.10.0 versus 1.9.0 correctly. For versions with pre-release tags (1.2.0-rc.1), sort -V gets the order wrong; use a SemVer-aware tool such as npx semver or Python's semver package.
Helper functions with sort -V
version_le() { printf '%s\n' "$1" "$2" | sort -V -C; }
version_ge() { version_le "$2" "$1"; }
version_lt() { [ "$1" != "$2" ] && version_le "$1" "$2"; }
for pair in "1.9.0 1.10.0" "2.0.0 2.0.0" "3.1 3.0.9" "1.2.0-rc.1 1.2.0"; do
set -- $pair
version_lt "$1" "$2" && echo "$1 < $2" || { version_le "$2" "$1" && [ "$1" != "$2" ] && echo "$1 > $2" || echo "$1 == $2"; }
done
echo "--- string comparison gets it wrong"
[[ 1.10.0 > 1.9.0 ]] && echo "1.10.0 > 1.9.0" || echo "string compare says 1.10.0 < 1.9.0"Output
1.9.0 < 1.10.0 2.0.0 == 2.0.0 3.1 > 3.0.9 1.2.0-rc.1 > 1.2.0 --- string comparison gets it wrong string compare says 1.10.0 < 1.9.0
The last pair shows the limit: by SemVer rules 1.2.0-rc.1 is lower than 1.2.0, but sort -V puts the shorter string first and reports the opposite.
SemVer-aware comparison
a=1.2.0-rc.1 b=1.2.0
echo "npx semver -r: $(npx -y semver@7 -r "<$b" "$a" >/dev/null && echo "$a < $b" || echo "no match")"
echo "npx semver -p -r: $(npx -y semver@7 -p -r "<$b" "$a" >/dev/null && echo "$a < $b" || echo "no match")"
uv run -q --no-project --python 3.13 --with semver==3.0.4 python -c '
import sys, semver
a, b = sys.argv[1], sys.argv[2]
print("Version.parse(a).compare(b):", semver.Version.parse(a).compare(b))
print("Version.parse(a) < b:", semver.Version.parse(a) < b)' "$a" "$b"Output
npx semver -r: no match npx semver -p -r: 1.2.0-rc.1 < 1.2.0 Version.parse(a).compare(b): -1 Version.parse(a) < b: True
npx semver -r RANGE VERSION prints the version and exits with 0 when it satisfies the range, so -r "<$b" works as a less-than test. By default, though, a pre-release version matches a range only if one of its comparators has a pre-release tag on the same MAJOR.MINOR.PATCH; <1.2.0 has none, so the first test reports no match. -p (--include-prerelease) turns that rule off and gives the SemVer answer. Version.compare returns -1, 0 or 1, and Version objects also support <, == and > against strings.
Options
| Method | Command | Handles pre-releases? |
|---|---|---|
| GNU sort | printf "%s\n" A B | sort -V -C | No; fine for plain X.Y.Z and X.Y. |
| npm semver | npx semver -p -r "<B" A | Yes with -p; without it, ranges skip most pre-releases. |
| Node.js | semver.gt(a, b), semver.compare(a, b) | Yes. |
| Python semver | semver.Version.parse(a).compare(b) | Yes; raises ValueError for invalid versions. |
| Python packaging | Version(a) < Version(b) | PEP 440 rules, for Python package versions. |
| dpkg (Debian) | dpkg --compare-versions A lt B | Debian rules: a ~ suffix sorts before the release. |
Pitfalls
- Invalid input fails differently per tool.
sort -Vcompares anything;semver.Version.parse("1.2")raises ValueError: 1.2 is not valid SemVer string;npx semverprints nothing and exits with 1. - Strip a leading
vfrom git tags with${tag#v}before comparing, unless every value has it. - Build metadata is ignored for precedence.
1.0.0+aand1.0.0+bcompare equal in SemVer tools, but differ as strings and insort -V. set -- $pairsplits on spaces. It is used here to unpack test pairs; in real scripts pass the two versions as separate arguments.
FAQ
Why not compare versions with [[ $a > $b ]]?
Inside [[ ]], > compares strings character by character, so 1.10.0 is less than 1.9.0. The -gt operator compares integers only and fails on dotted versions. Use sort -V or a SemVer tool.
Does sort -V work on macOS?
macOS ships a BSD-derived sort, not GNU sort. Test that sort -V -C behaves as expected on the macOS versions you support, or install GNU coreutils (gsort) or use npx semver when the script must run on any Mac.
How do I check a minimum version of a tool in a script?
Extract the number from the tool's --version output first (for example with grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1), then run the version_ge function above against the minimum you need.