2012-09-01 18 views
5

Digamos que tengo una expresión regular como la siguiente, pero la cargué de un archivo en una variable $ regex, por lo que no tengo idea de su contenido en tiempo de diseño, pero en tiempo de ejecución puedo descubrir que incluye el "version1", "versión 2", "versión 3" y "version4" grupos nombrados:Powershell: Reemplazando los grupos denominados expresiones regulares con variables

"Version (?<version1>\d),(?<version2>\d),(?<version3>\d),(?<version4>\d)" 

... y tengo estas variables:

$version1 = "3" 
$version2 = "2" 
$version3 = "1" 
$version4 = "0" 

.. .y me encuentro con la siguiente cadena en un archivo:

Version 7,7,0,0 

... que se almacena en una entrada $ variable, de modo que ($ input -match $ regex) evalúa a $ true.

¿Cómo puedo reemplazar los grupos nombrados de $ regex en la cadena $ input con los valores de $ version1, $ version2, $ version3, $ version4 si no sé el orden en que aparecen en $ regex (I solo se sabe que $ regex incluye estos grupos nombrados)?

No encuentro referencias que describan la sintaxis para reemplazar un grupo con nombre por el valor de una variable utilizando el nombre del grupo como índice de la coincidencia. ¿Esto es compatible?

EDIT: Para aclarar - el objetivo es reemplazar cadenas de versión con plantilla en cualquier tipo de archivo de texto en el que la cadena de versión de un archivo dado requiere el reemplazo de un número variable de campos de versión (pueden ser 2, 3, o los 4 campos). Por ejemplo, el texto en un archivo podría parecerse a cualquiera de estos (pero no está limitado a estos):

#define SOME_MACRO(4, 1, 0, 0) 

Version "1.2.3.4" 

SomeStruct vs = { 99,99,99,99 } 

Los usuarios pueden especificar un conjunto de archivos y una expresión regular para que coincida con la línea que contiene los campos, con el la idea original es que los campos individuales serían capturados por grupos nombrados. La utilidad tiene los valores de campo de versión individuales que deben sustituirse en el archivo, pero debe conservar el formato original de la línea que contendrá las sustituciones y sustituir solo los campos solicitados.

EDITAR-2: creo que puedo conseguir el resultado que necesito con cálculos subcadena en base a la posición y el alcance de cada uno de los partidos, pero tenía la esperanza operación de sustitución de Powershell me iba a ahorrar algo de trabajo.

EDITAR-3: Así que, como Ansgar describe correctamente y sucintamente a continuación, no hay una manera (usando sólo la cadena de entrada original, una expresión regular de la que sólo conoce los grupos nombrados, y la consiguiente coincidencias) para utilizar la operación "-replace" (u otras operaciones de expresiones regulares) para realizar sustituciones de las capturas de los grupos nombrados, mientras se deja intacto el resto de la cadena original. Para este problema, si alguien tiene curiosidad, terminé usando la solución a continuación. YMMV, otras soluciones posibles. Muchas gracias a Ansgar por sus comentarios y opciones.

En el siguiente bloque de código:

  • $ de entrada es una línea de texto en el que la sustitución se va a realizar
  • $ expresiones regulares es una expresión regular (de tipo [cadena]) leer desde un archivo que se ha verificado que contiene al menos uno de los grupos con nombre admitidos
  • $ regexToGroupName es una tabla hash que asigna una cadena regex a una matriz de nombres de grupos ordenados de acuerdo con el orden de la matriz devuelta por [regex] :: GetGroupNames(), que coincide con el orden de izquierda a derecha en el que aparecen en la expresión
  • $ groupNameToVersionNumber es una tabla hash que asigna un nombre de grupo a un número de versión.

Restricciones en los grupos nombrados dentro de $ regex son solo (creo) que la expresión dentro de los grupos nombrados no se puede anidar, y debe coincidir como máximo una vez dentro de la cadena de entrada.

# This will give us the index and extent of each substring 
# that we will be replacing (the parts that we will not keep) 
$matchResults = ([regex]$regex).match($input) 

# This will hold substrings from $input that were not captured 
# by any of the supported named groups, as well as the replacement 
# version strings, properly ordered, but will omit substrings captured 
# by the named groups 
$lineParts = @() 
$startingIndex = 0 
foreach ($groupName in $regexToGroupName.$regex) 
{ 
    # Excise the substring leading up to the match for this group... 
    $lineParts = $lineParts + $input.Substring($startingIndex, $matchResults.groups[$groupName].Index - $startingIndex) 

    # Instead of the matched substring, we'll use the substitution 
    $lineParts = $lineParts + $groupNameToVersionNumber.$groupName 

    # Set the starting index of the next substring that we will keep... 
    $startingIndex = $matchResults.groups[$groupName].Index + $matchResults.groups[$groupName].Length 
} 

# Keep the end of the original string (if there's anything left) 
$lineParts = $lineParts + $input.Substring($startingIndex, $input.Length - $startingIndex) 

$newLine = "" 
foreach ($part in $lineParts) 
{ 
    $newLine = $newLine + $part 
} 
$input= $newLine 

Respuesta

4

Las expresiones regulares no funcionan de esa manera, por lo que no puede. No directamente, eso es. Lo que puede hacer (abreviatura de usar una expresión regular más apropiado que los grupos de las partes que desea mantener) es extraer la cadena de versión y luego en una segunda etapa sustituir a la subcadena con la nueva versión de cadena:

$oldver = $input -replace $regexp, '$1,$2,$3,$4' 
$newver = $input -replace $oldver, "$Version1,$Version2,$Version3,$Version4" 

Editar:

Si no sabes ni la estructura, debe extraer que a partir de la expresión regular también.

$version = @($version1, $version2, $version3, $version4) 
$input -match $regexp 
$oldver = $regexp 
$newver = $regexp 
for ($i = 1; $i -le 4; $i++) { 
    $oldver = $oldver -replace "\(\?<version$i>\\d\)", $matches["version$i"] 
    $newver = $newver -replace "\(\?<version$i>\\d\)", $version[$i-1] 
} 
$input -replace $oldver, $newver 
+0

De acuerdo en que esto sería bueno, pero esto es para una utilidad donde los usuarios especifican una expresión regular y un conjunto de archivos. No conozco la expresión regular, y no sé cómo se ve el contenido del archivo, por lo que no pude usar la primera línea en su respuesta sin reformatear el contenido original del archivo, lo que sería indeseable. Debo dejar el contenido del archivo con el mismo aspecto luego, reemplazando solo las subcadenas en las líneas correspondientes con los campos de versión individuales. – Hoobajoob

+0

Quizás pueda reemplazar los grupos nombrados en la expresión regular con los números antiguos/nuevos reales y luego hacer una cadena reemplazar. Sin embargo, eso no funcionará correctamente si la expresión regular contiene expresiones distintas de los grupos nombrados. –

+0

Esto casi funciona, aunque no sé de antemano cómo se definen realmente los grupos nombrados en la expresión regular (por ejemplo, podrían estar buscando \ d, \ d {2}, \ d +, un literal, etc.) . Puedo introducir algunas restricciones en la definición del grupo nombrado y cambiar la expresión regular utilizada en el ciclo for que tiene arriba para admitir uno o más caracteres de la sintaxis de expresiones regulares así como alfanuméricos (por ejemplo, reemplace "\\ d" en la expresión regular dentro de los bucles for con "[a-zA-Z0-9 \\ + \. \ * \? \^\ $ \ {\} \ | \ [\]] +"). En cualquier caso, este enfoque es preferible a las operaciones de subcadenas. – Hoobajoob

1

solución simple

En el escenario en el que simplemente desea reemplazar un número de versión que se encuentra en algún lugar de su texto $input, usted podría simplemente hacer esto:

$input -replace '(Version\s+)\d+,\d+,\d+,\d+',"`$1$Version1,$Version2,$Version3,$Version4" 

Uso Named Capturas en PowerShell

Re Acerca de su pregunta sobre las capturas con nombre, eso se puede hacer mediante el uso de corchetes. es decir

'dogcatcher' -replace '(?<pet>dog|cat)','I have a pet ${pet}. ' 

Da:

I have a pet dog. I have a pet cat. cher 

Problema con múltiples capturas & solución

No se puede reemplazar varios valores en la misma declaración sustituir, ya que la cadena de reemplazo se utiliza para todo . es decir, si usted hizo esto:

'dogcatcher' -replace '(?<pet>dog|cat)|(?<singer>cher)','I have a pet ${pet}. I like ${singer}''s songs. ' 

Te obtener:

I have a pet dog. I like 's songs. I have a pet cat. I like 's songs. I have a pet . I like cher's songs. 

... que probablemente no es lo que está esperando.

Más bien, habría que hacer un partido por artículo:

'dogcatcher' -replace '(?<pet>dog|cat)','I have a pet ${pet}. ' -replace '(?<singer>cher)', 'I like ${singer}''s songs. ' 

... Para obtener:

I have a pet dog. I have a pet cat. I like cher's songs. 

Más Solución Complejo

Llevando esto a su escenario, no está realmente usando los valores capturados; más bien estás esperando reemplazar los espacios en los que estaban con nuevos valores. Para esto, puede que simplemente quiere esto:

$input = 'I''m running Programmer''s Notepad version 2.4.2.1440, and am a big fan. I also have Chrome v 56.0.2924.87 (64-bit).' 

$version1 = 1 
$version2 = 3 
$version3 = 5 
$version4 = 7 

$v1Pattern = '(?<=\bv(?:ersion)?\s+)\d+(?=\.\d+\.\d+\.\d+)' 
$v2Pattern = '(?<=\bv(?:ersion)?\s+\d+\.)\d+(?=\.\d+\.\d+)' 
$v3Pattern = '(?<=\bv(?:ersion)?\s+\d+\.\d+\.)\d+(?=\.\d+)' 
$v4Pattern = '(?<=\bv(?:ersion)?\s+\d+\.\d+\.\d+\.)\d+' 

$input -replace $v1Pattern, $version1 -replace $v2Pattern, $version2 -replace $v3Pattern,$version3 -replace $v4Pattern,$version4 

que daría:

I'm running Programmer's Notepad version 1.3.5.7, and am a big fan. I also have Chrome v 1.3.5.7 (64-bit). 

NB: El arriba puede ser escrito como un revestimiento 1, pero me ha roto hacia abajo para que sea más simple de leer.

Esto aprovecha las vistas alternativas de expresiones regulares; una forma de verificar el contenido antes y después de la cadena que está capturando, sin incluir los que están en el partido. es decir, cuando seleccionamos qué reemplazar podemos decir "coincida con el número que aparece después de la versión de la palabra" sin decir "reemplazar la versión de la palabra".

Más información sobre los que están aquí: http://www.regular-expressions.info/lookaround.html

Su Ejemplo

La adaptación de la anterior para trabajar por su ejemplo (es decir, donde las versiones pueden estar separados por comas o puntos, y no hay consistencia a su formato más allá de ser 4 series de números:

$input = @' 
#define SOME_MACRO(4, 1, 0, 0) 

Version "1.2.3.4" 

SomeStruct vs = { 99,99,99,99 } 
'@ 

$version1 = 1 
$version2 = 3 
$version3 = 5 
$version4 = 7 

$v1Pattern = '(?<=\b)\d+(?=\s*[\.,]\s*\d+\s*[\.,]\s*\d+\s*[\.,]\s*\d+\b)' 
$v2Pattern = '(?<=\b\d+\s*[\.,]\s*)\d+(?=\s*[\.,]\s*\d+\s*[\.,]\s*\d+\b)' 
$v3Pattern = '(?<=\b\d+\s*[\.,]\s*\d+\s*[\.,]\s*)\d+(?=\s*[\.,]\s*\d+\b)' 
$v4Pattern = '(?<=\b\d+\s*[\.,]\s*\d+\s*[\.,]\s*\d+\s*[\.,]\s*)\d+\b' 

$input -replace $v1Pattern, $version1 -replace $v2Pattern, $version2 -replace $v3Pattern,$version3 -replace $v4Pattern,$version4 

Da:

#define SOME_MACRO(1, 3, 5, 7) 

Version "1.3.5.7" 

SomeStruct vs = { 1,3,5,7 } 
Cuestiones relacionadas