Match a semantic version
Match a semantic version with the pattern from semver.org, which runs in grep -E. It rejects leading zeros, a bare 1.2 and a v prefix, and takes pre-release tags.
A version string has to be checked before it is compared, and a loose pattern like three numbers with dots accepts values the specification forbids.
re='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*))?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?$'
for v in 1.2.3 1.2.3-rc.1+build.5 01.2.3 1.2 v1.2.3 1.2.3-01 1.2.3-alpha-1; do
if printf '%s\n' "$v" | grep -qE "$re"; then echo "valid: $v"; else echo "invalid: $v"; fi
donevalid: 1.2.3
valid: 1.2.3-rc.1+build.5
invalid: 01.2.3
invalid: 1.2
invalid: v1.2.3
invalid: 1.2.3-01
valid: 1.2.3-alpha-1The semantic versioning specification publishes one regular expression, and the version above is that pattern without the named groups, so it works in extended regular expressions. Each number is 0 or starts with a digit other than zero, which rules out 01.2.3. The optional hyphen part holds pre-release identifiers, where a numeric identifier cannot have a leading zero, so 1.2.3-01 fails, and the plus part holds build metadata.
A leading v, common in Git tags, is not part of a semantic version, so strip it first with ${tag#v}. The pattern says whether a string is valid and not which of two versions is newer; sort -V orders many versions, but does not follow the precedence rules for pre-releases exactly. In Python or JavaScript, the capture groups give the parts.