Una expresión regular para analizar el número de versión

84

Tengo un número de versión del siguiente formulario:

version.release.modification

donde la versión, el lanzamiento y la modificación son un conjunto de dígitos o el carácter comodín '*'. Además, es posible que falte alguno de estos números (y los anteriores).

Entonces, los siguientes son válidos y se analizan como:

1.23.456 = version 1, release 23, modification 456
1.23     = version 1, release 23, any modification
1.23.*   = version 1, release 23, any modification
1.*      = version 1, any release, any modification
1        = version 1, any release, any modification
*        = any version, any release, any modification

Pero estos no son válidos:

*.12
*123.1
12*
12.*.34

¿Alguien puede proporcionarme una expresión regular no demasiado compleja para validar y recuperar los números de lanzamiento, versión y modificación?

Andrew Borley
fuente
No estoy seguro de que sea posible una "simple".
svrist

Respuestas:

96

Expresaría el formato como:

"1-3 componentes separados por puntos, cada uno numérico excepto que el último puede ser *"

Como expresión regular, eso es:

^(\d+\.)?(\d+\.)?(\*|\d+)$

[Editar para agregar: esta solución es una forma concisa de validar, pero se ha señalado que extraer los valores requiere trabajo adicional. Es cuestión de gustos si tratar con esto complicando la expresión regular o procesando los grupos emparejados.

En mi solución, los grupos capturan la "." personajes. Esto se puede solucionar utilizando grupos que no capturan como en la respuesta de ajborley.

Además, el grupo más a la derecha capturará el último componente, incluso si hay menos de tres componentes, por lo que, por ejemplo, una entrada de dos componentes da como resultado la captura del primer y último grupo y el del medio indefinido. Creo que esto puede ser abordado por grupos que no sean codiciosos si reciben apoyo.

El código Perl para tratar ambos problemas después de la expresión regular podría ser algo como esto:

@version = ();
@groups = ($1, $2, $3);
foreach (@groups) {
    next if !defined;
    s/\.//;
    push @version, $_;
}
($major, $minor, $mod) = (@version, "*", "*");

Lo que en realidad no es más corto que dividirse "." ]

Steve Jessop
fuente
1
Agregar algunos grupos que no capturan (vea mi respuesta a continuación) significa que los grupos de captura no capturan el 'final'. ^ (?: (\ d +) \.)? (?: (\ d +) \.)? (* | \ d +) $ ¡Gracias!
Andrew Borley
El único problema con eso, siendo una propuesta muy agradable y limpia, es que los grupos no están bien porque 1.2 capturará 1 en el primero y 2 en el tercer grupo debido a la codicia.
jrudolph
39

Use regex y ahora tiene dos problemas. Dividiría la cosa en puntos ("."), Luego me aseguraría de que cada parte sea un comodín o un conjunto de dígitos (la expresión regular es perfecta ahora). Si la cosa es válida, devuelve la parte correcta de la división.

Paweł Hajdan
fuente
11

Esto podría funcionar:

^(\*|\d+(\.\d+){0,2}(\.\*)?)$

En el nivel superior, "*" es un caso especial de un número de versión válido. De lo contrario, comienza con un número. Luego hay cero, una o dos secuencias ".nn", seguidas de un ". *" Opcional. Esta expresión regular aceptaría 1.2.3. * Que puede o no estar permitido en su aplicación.

El código para recuperar las secuencias coincidentes, especialmente la (\.\d+){0,2}parte, dependerá de su biblioteca de expresiones regulares en particular.

Greg Hewgill
fuente
¡Gran respuesta! Creo que debería cambiar el * sin escape por {0,2} para evitar la coincidencia de 1.2.3.4. Dependiendo de su biblioteca de expresiones regulares, es posible que desee incluir el patrón en ^ (<patrón>) $ si solo puede hacer una búsqueda en lugar de una coincidencia.
Dave Webb
Una ligera alteración a ^ (* | \ d + (\. \ D +) {0,1} (?: (\. *)? | (\. \ D +)?)) $ Invalidaría 1.2.3. * También
Pieter
2
Pieter: Creo que voy a parar donde estoy por ahora. Esto está entrando rápidamente en territorio de "ahora tienes dos problemas". :)
Greg Hewgill
11

¡Gracias por todas las respuestas! Esto es as :)

Basado en la respuesta de OneByOne (que me pareció la más simple), agregué algunos grupos que no capturan (las partes '(?:', ¡Gracias a VonC por presentarme a los grupos que no capturan!), Por lo que los grupos que capturan solo contener los dígitos o el carácter *.

^(?:(\d+)\.)?(?:(\d+)\.)?(\*|\d+)$

¡Muchas gracias a todos!

Andrew Borley
fuente
1
¿Podría agregar esto como una edición a su pregunta? De esa manera, las respuestas correctas están cerca de la parte superior
svrist
1
Con nombres de grupo: ^ (? :(? <mayor> \ d +) \.)? (?
:(
1
soporte semversion (un poco más). - "1.2.3-alpha + abcdedf.lalal" -match "^ (?: (\ D +) \.)? (?: (\ D +) \.)? (* | \ D +)? (?: \ - ([A-Za-z0-9 \.] +))? (?: \ + ([A-Za-z0-9 \.] +))? $ "
Sam
Tenga en cuenta que en el caso de una versión que consta de un solo número, se emparejará con el tercero y (\*|\d+)no con el primer ^(?:(\d+)\.)?grupo.
Piotr Dobrogost
8

Mis 2 centavos: tuve este escenario: tuve que analizar los números de versión de un literal de cadena. (Sé que esto es muy diferente de la pregunta original, pero buscar en Google para encontrar una expresión regular para analizar el número de versión mostró este hilo en la parte superior, así que agregue esta respuesta aquí)

Entonces, la cadena literal sería algo como: "¡La versión de servicio 1.2.35.564 se está ejecutando!"

Tuve que analizar el 1.2.35.564 de este literal. Siguiendo el ejemplo de @ajborley, mi expresión regular es la siguiente:

(?:(\d+)\.)?(?:(\d+)\.)?(?:(\d+)\.\d+)

Un pequeño fragmento de C # para probar esto se ve a continuación:

void Main()
{
    Regex regEx = new Regex(@"(?:(\d+)\.)?(?:(\d+)\.)?(?:(\d+)\.\d+)", RegexOptions.Compiled);

    Match version = regEx.Match("The Service SuperService 2.1.309.0) is Running!");
    version.Value.Dump("Version using RegEx");   // Prints 2.1.309.0        
}
Sudhanshu Mishra
fuente
Sé que está describiendo una situación y un caso alternativos, pero para ser completo: SemVer 'requiere' que la cadena de versión tenga el formato X.Y.Z(por lo tanto, exactamente tres partes), donde X e Y deben ser números enteros no negativos y no ceros iniciales adicionales. Ver semver.org .
Jochem Schulenklopper
1
@JochemSchulenklopper gracias, estoy al tanto de SemVer, aunque la pregunta no menciona nada sobre SemVer.
Sudhanshu Mishra
1
Cierto. Un colega me remitió a esta pregunta sobre el análisis de cadenas de SemVer, por lo que enmarcó mi lectura de las respuestas.
Jochem Schulenklopper
7

No sé en qué plataforma estás, pero en .NET existe la clase System.Version que analizará los números de versión "nnnn" por ti.

Duncan Smart
fuente
No, ha estado ahí desde la versión 1.0
Duncan Smart
5

Tiendo a estar de acuerdo con la sugerencia dividida.

He creado un "probador" para su problema en perl

#!/usr/bin/perl -w


@strings = ( "1.2.3", "1.2.*", "1.*","*" );

%regexp = ( svrist => qr/(?:(\d+)\.(\d+)\.(\d+)|(\d+)\.(\d+)|(\d+))?(?:\.\*)?/,
            onebyone => qr/^(\d+\.)?(\d+\.)?(\*|\d+)$/,
            greg => qr/^(\*|\d+(\.\d+){0,2}(\.\*)?)$/,
            vonc => qr/^((?:\d+(?!\.\*)\.)+)(\d+)?(\.\*)?$|^(\d+)\.\*$|^(\*|\d+)$/,
            ajb => qr/^(?:(\d+)\.)?(?:(\d+)\.)?(\*|\d+)$/,
            jrudolph => qr/^(((\d+)\.)?(\d+)\.)?(\d+|\*)$/
          );

  foreach my $r (keys %regexp){
    my $reg = $regexp{$r};
    print "Using $r regexp\n";
foreach my $s (@strings){
  print "$s : ";

    if ($s =~m/$reg/){
    my ($main, $maj, $min,$rev,$ex1,$ex2,$ex3) = ("any","any","any","any","any","any","any");
    $main = $1 if ($1 && $1 ne "*") ;
    $maj = $2 if ($2 && $2 ne "*") ;
    $min = $3 if ($3 && $3 ne "*") ;
    $rev = $4 if ($4 && $4 ne "*") ;
    $ex1 = $5 if ($5 && $5 ne "*") ;
    $ex2 = $6 if ($6 && $6 ne "*") ;
    $ex3 = $7 if ($7 && $7 ne "*") ;
    print "$main $maj $min $rev $ex1 $ex2 $ex3\n";

  }else{
  print " nomatch\n";
  }
  }
print "------------------------\n";
}

Salida de corriente:

> perl regex.pl
Using onebyone regexp
1.2.3 : 1. 2. 3 any any any any
1.2.* : 1. 2. any any any any any
1.* : 1. any any any any any any
* : any any any any any any any
------------------------
Using svrist regexp
1.2.3 : 1 2 3 any any any any
1.2.* : any any any 1 2 any any
1.* : any any any any any 1 any
* : any any any any any any any
------------------------
Using vonc regexp
1.2.3 : 1.2. 3 any any any any any
1.2.* : 1. 2 .* any any any any
1.* : any any any 1 any any any
* : any any any any any any any
------------------------
Using ajb regexp
1.2.3 : 1 2 3 any any any any
1.2.* : 1 2 any any any any any
1.* : 1 any any any any any any
* : any any any any any any any
------------------------
Using jrudolph regexp
1.2.3 : 1.2. 1. 1 2 3 any any
1.2.* : 1.2. 1. 1 2 any any any
1.* : 1. any any 1 any any any
* : any any any any any any any
------------------------
Using greg regexp
1.2.3 : 1.2.3 .3 any any any any any
1.2.* : 1.2.* .2 .* any any any any
1.* : 1.* any .* any any any any
* : any any any any any any any
------------------------
svrist
fuente
Eso sería bueno, ya que OneByOne parece el más sencillo.
jrudolph
También debe probar los incorrectos. Te perdiste citar los puntos de OneByOne.
jrudolph
Actualizado con los puntos y más expresiones regulares
svrist
4

Esto debería funcionar para lo que estipuló. Depende de la posición del comodín y es una expresión regular anidada:

^((\*)|([0-9]+(\.((\*)|([0-9]+(\.((\*)|([0-9]+)))?)))?))$

http://imgur.com/3E492.png

nomuus
fuente
4

He visto muchas respuestas, pero ... tengo una nueva. Funciona para mí al menos. Agregué una nueva restricción. Los números de versión no pueden comenzar (mayor, menor o parche) con ceros seguidos de otros.

01.0.0 no es válido 1.0.0 es válido 10.0.10 es válido 1.0.0000 no es válido

^(?:(0\\.|([1-9]+\\d*)\\.))+(?:(0\\.|([1-9]+\\d*)\\.))+((0|([1-9]+\\d*)))$

Está basado en uno anterior. Pero veo esta solución mejor ... para mí;)

¡¡¡Disfrutar!!!

Israel Romero
fuente
3

Otro intento:

^(((\d+)\.)?(\d+)\.)?(\d+|\*)$

Esto da las tres partes en grupos 4, 5, 6 PERO: Están alineadas a la derecha. Entonces, el primer no nulo de 4,5 o 6 da el campo de versión.

  • 1.2.3 da 1,2,3
  • 1.2. * Da 1,2, *
  • 1.2 da nulo, 1,2
  • *** da nulo, nulo, *
  • 1. * da nulo, 1, *
jrudolph
fuente
3
^(?:(\d+)\.)?(?:(\d+)\.)?(\*|\d+)$

Quizás uno más conciso podría ser:

^(?:(\d+)\.){0,2}(\*|\d+)$

Esto luego se puede mejorar a 1.2.3.4.5. * O restringir exactamente a XYZ usando * o {2} en lugar de {0,2}

ofaurax
fuente
3

Tenía el requisito de buscar / hacer coincidir los números de versión, que siguen la convención de maven o incluso un solo dígito. Pero no calificativo en ningún caso. Fue peculiar, me tomó tiempo y luego se me ocurrió esto:

'^[0-9][0-9.]*$'

Esto asegura que la versión,

  1. Comienza con un dígito
  2. Puede tener cualquier número de dígitos
  3. Solo dígitos y '.' están permitidos

Un inconveniente es que la versión puede incluso terminar con '.' Pero puede manejar una duración indefinida de la versión (versiones locas si quieres llamarlo así)

Partidos:

  • 1.2.3
  • 1.09.5
  • 3.4.4.5.7.8.8.
  • 23.6.209.234.3

Si no está satisfecho con '.' terminando, puede ser que puedas combinar con fines con lógica

Shiva
fuente
Para deshacerse del último dígito, tal vez le gustaría probar esto:(\d+)(.\d+)*
cassioso
2
(?ms)^((?:\d+(?!\.\*)\.)+)(\d+)?(\.\*)?$|^(\d+)\.\*$|^(\*|\d+)$

Coincide exactamente con sus 6 primeros ejemplos y rechaza los otros 4

  • grupo 1: mayor o mayor, menor o '*'
  • grupo 2 si existe: menor o *
  • grupo 3 si existe: *

Puede eliminar '(? Ms)'
Lo usé para indicar a esta expresión regular que se aplicará en líneas múltiples a través de QuickRex

VonC
fuente
2

Esto también coincide con 1.2.3. *

^ (* | \ d + (. \ d +) {0,2} (. *)?) $

Propondría lo menos elegante:

(* | \ d + (. \ d +)? (. *)?) | \ d +. \ d +. \ d +)

Víctor
fuente
2

Tenga en cuenta que las expresiones regulares son codiciosas, por lo que si solo está buscando dentro de la cadena del número de versión y no dentro de un texto más grande, use ^ y $ para marcar el inicio y el final de su cadena. La expresión regular de Greg parece funcionar bien (solo lo probé rápidamente en mi editor), pero dependiendo de su biblioteca / idioma, la primera parte aún puede coincidir con el "*" dentro de los números de versión incorrectos. Quizás me esté perdiendo algo, ya que no he usado Regexp durante un año más o menos.

Esto debería asegurarse de que solo pueda encontrar los números de versión correctos:

^ (\ * | \ d + (\. \ d +) * (\. \ *)?) $

editar: en realidad, Greg ya los agregó e incluso mejoró su solución, soy demasiado lento :)

FrankS
fuente
2

Parece bastante difícil tener una expresión regular que haga exactamente lo que desea (es decir, aceptar solo los casos que necesita y rechazar todos los demás y devolver algunos grupos para los tres componentes). Lo intenté y se me ocurrió esto:

^(\*|(\d+(\.(\d+(\.(\d+|\*))?|\*))?))$

En mi opinión (no lo he probado ampliamente), esto debería funcionar bien como validador de la entrada, pero el problema es que esta expresión regular no ofrece una forma de recuperar los componentes. Para eso todavía tienes que dividir el período.

Esta solución no es todo en uno, pero la mayoría de las veces en la programación no es necesario. Por supuesto, esto depende de otras restricciones que pueda tener en su código.

rslite
fuente
2

Especificar elementos XSD:

<xs:simpleType>
    <xs:restriction base="xs:string">
        <xs:pattern value="[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}(\..*)?"/>
    </xs:restriction>
</xs:simpleType>
Emmerson
fuente
2

Mi opinión sobre esto, como un buen ejercicio: vparse , que tiene una fuente pequeña , con una función simple:

function parseVersion(v) {
    var m = v.match(/\d*\.|\d+/g) || [];
    v = {
        major: +m[0] || 0,
        minor: +m[1] || 0,
        patch: +m[2] || 0,
        build: +m[3] || 0
    };
    v.isEmpty = !v.major && !v.minor && !v.patch && !v.build;
    v.parsed = [v.major, v.minor, v.patch, v.build];
    v.text = v.parsed.join('.');
    return v;
}
vitaly-t
fuente
2

Para analizar los números de versión que siguen estas reglas: - Son solo dígitos y puntos - No pueden comenzar ni terminar con un punto - No pueden ser dos puntos juntos

Este me hizo el truco.

^(\d+)((\.{1}\d+)*)(\.{0})$

Los casos válidos son:

1, 0,1, 1,2,1

pegpeg
fuente
1

Una solución más:

^[1-9][\d]*(.[1-9][\d]*)*(.\*)?|\*$
Oleksandr Yarushevskyi
fuente
1

A veces, los números de versión de menor importancia pueden contener información alfanumérica (por ejemplo 1.2.0b o 1.2.0-beta ). En este caso, estoy usando esta expresión regular:

([0-9]{1,4}(\.[0-9a-z]{1,6}){1,5})
Marc Ruef
fuente
0

Encontré esto y me funciona:

/(\^|\~?)(\d|x|\*)+\.(\d|x|\*)+\.(\d|x|\*)+
O Assayag
fuente