Regex: Semantic version
Semantic versioning writes a version as MAJOR.MINOR.PATCH, optionally followed by a pre-release after - and build metadata after +. This is the pattern published on semver.org: numbers have no leading zeros, and neither do numeric parts of a pre-release.
Pattern
^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$ How it works
- ^
- Start of the string.
- (0|[1-9]\d*)
- MAJOR: 0, or a number without a leading zero.
- \.
- A dot.
- (0|[1-9]\d*)
- MINOR, the same way.
- \.
- A dot.
- (0|[1-9]\d*)
- PATCH, the same way.
- (?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?
- Optionally a pre-release after
-: dot-separated parts, each a number without a leading zero or a run of letters, digits and dashes (beta.1,rc-2). - (?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?
- Optionally build metadata after
+: dot-separated runs of letters, digits and dashes. - $
- End of the string.
Matches
- 1.0.0
- 2.10.3-beta.1
- 1.0.0-alpha+001
- 0.1.0+build.5
Doesn't match
- 1.0
- v1.0.0
- 01.0.0
- 1.0.0-01
- 1.2.3.4
In your language
- JavaScript
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/A literal;
new RegExp(source, flags)builds the same from a string.- Python
re.compile(r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$", re.ASCII)A raw string, so backslashes reach
reas written.re.ASCIIkeeps\dto0-9, as in JavaScript (Python matches any Unicode digit otherwise). Usere.fullmatchto test a whole string.- Java
Pattern.compile("^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$")A normal string literal, so every backslash is doubled.
matcher(s).matches()tests the whole string.- Go
regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`)A raw string in backticks. RE2 runs in linear time but has no lookaround and no backreferences.
- PHP
preg_match('/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/D', $input)PCRE with
/delimiters inside a single-quoted string. Without theDmodifier,$also matches before a final newline, so it is added to patterns that end in$.- C#
new Regex(@"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$", RegexOptions.ECMAScript)A verbatim string: backslashes stay, a quote is doubled.
RegexOptions.ECMAScriptkeeps\dto0-9, as in JavaScript.$also matches before a final newline; to reject one, end the pattern with\zinstead.
Common mistakes
A leading v is not part of the version
Git tags are often
v1.2.3, but the version is1.2.3, as the SemVer FAQ says. Strip thevbefore matching.Comparing needs a library
Text order puts
1.10.0before1.9.0, and1.0.0-alphais lower than1.0.0though it sorts after it as text. Use a semver library:semveron npm and on PyPI,golang.org/x/mod/semverin Go.Ranges are another syntax
^1.2.0and~1.2.0inpackage.jsonare ranges, not versions, and fail this pattern by design.