2012-09-01 23 views
5

Dire che ho un'espressione regolare come la seguente, ma l'ho caricata da un file in una variabile $ regex, e quindi non ho idea in fase di progettazione qual è il suo contenuto, ma in fase di esecuzione posso scoprire che comprende il "version1", "Version2", "versione 3" e "version4" gruppi denominati:PowerShell: sostituzione dell'espressione regolare dei gruppi denominati con le variabili

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

... e non ho queste variabili:

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

.. .e trovo la seguente stringa in un file:

Version 7,7,0,0 

... che viene memorizzato in una variabile $ input, in modo che ($ input -match $ regex) valuti $ true.

Come posso sostituire i gruppi denominati da $ regex nella stringa $ input con i valori di $ versione1, $ versione2, $ versione3, $ versione4 se non conosco l'ordine in cui compaiono in $ regex (I solo sapere che $ regex include questi gruppi con nome)?

Non riesco a trovare alcun riferimento che descriva la sintassi per la sostituzione di un gruppo denominato con il valore di una variabile utilizzando il nome del gruppo come indice per la corrispondenza: è addirittura supportato?

EDIT: Per chiarire - l'obiettivo è quello di sostituire su modelli stringhe di versione in qualsiasi tipo di file di testo in cui la stringa di versione in un dato file richiede la sostituzione di un numero variabile di campi di versione (potrebbe essere 2, 3, o tutti e 4 i campi). Ad esempio, il testo in un file potrebbe apparire come uno di questi (ma non è limitato a questi):

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

Version "1.2.3.4" 

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

Gli utenti possono specificare un insieme di file e un'espressione regolare per abbinare la linea che contiene i campi, con la idea originale è che i singoli campi sarebbero catturati da gruppi con nome. L'utilità ha i valori dei campi della versione individuale che devono essere sostituiti nel file, ma deve conservare il formato originale della riga che conterrà le sostituzioni e sostituire solo i campi richiesti.

EDIT-2: credo di poter ottenere il risultato che ho bisogno con i calcoli di sottostringa in base alla posizione e l'entità di ciascuna delle partite, ma speravo operazione di sostituzione di PowerShell mi avrebbe risparmiare un po 'di lavoro.

EDIT-3: Così, come Ansgar correttamente e succintamente descrive di seguito, non v'è un modo (utilizzando solo la stringa di input originale, un'espressione regolare di cui si conosce solo i gruppi con nome, e la conseguente corrispondenze) per utilizzare l'operazione "-replace" (o altre operazioni regex) per eseguire sostituzioni delle acquisizioni dei gruppi denominati, lasciando intatto il resto della stringa originale. Per questo problema, se qualcuno è curioso, ho finito per usare la soluzione qui sotto. YMMV, altre soluzioni possibili. Mille grazie ad Ansgar per il suo feedback e le opzioni fornite.

Nel seguente blocco di codice:

  • $ input è una linea di testo su cui sostituzione deve essere eseguita
  • $ regex è un'espressione regolare (di tipo [stringa]) leggere un file che è stato verificato per contenere almeno uno dei gruppi denominati supportati
  • $ regexToGroupName è una tabella hash che associa una stringa di espressioni regolari a una matrice di nomi di gruppi ordinati secondo l'ordine della matrice restituita da [regex] :: GetGroupNames(), che corrisponde all'ordine da sinistra a destra in cui appaiono nell'espressione
  • $ groupNameToVersionNumber è una tabella hash che associa un nome di gruppo a un numero di versione.

I vincoli sui gruppi denominati all'interno di $ regex sono solo (penso) che l'espressione all'interno dei gruppi denominati non possa essere nidificata e che debba corrispondere almeno una volta all'interno della stringa di input.

# 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 

risposta

4

Le espressioni regolari non funzionano in questo modo, quindi non è possibile. Non direttamente, cioè. Cosa si può fare (a corto di utilizzare un più appropriata espressione regolare che raggruppa le parti che si desidera mantenere) è di estrarre la stringa di versione e poi in una seconda fase sostituire quella stringa con la nuova stringa di versione:

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

Edit:

Se non si sa nemmeno la struttura, è necessario estrarre che dal l'espressione regolare pure.

$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

Concordato che questo sarebbe bello, ma questo è per un programma di utilità in cui gli utenti specificano un'espressione regolare e un set di file. Non conosco la regex e non so come sia il contenuto del file, quindi non potrei usare la prima riga nella tua risposta senza riformattare il contenuto del file originale, il che sarebbe indesiderabile. Devo lasciare il contenuto del file dopo lo stesso aspetto, sostituendo solo le sottostringhe sulle linee corrispondenti con i singoli campi della versione. – Hoobajoob

+0

Forse è possibile sostituire i gruppi con nome nell'espressione regolare con i numeri vecchi/nuovi effettivi e quindi eseguire una sostituzione di stringa. Ciò non funzionerà correttamente se l'espressione regolare contiene espressioni diverse dai gruppi con nome, comunque. –

+0

Funziona quasi, anche se non so in anticipo come sono effettivamente definiti i gruppi denominati nella regex (ad esempio, potrebbero cercare \ d, \ d {2}, \ d +, un letterale, ecc.) . Posso introdurre alcuni vincoli sulla definizione di gruppo denominata e modificare la regex utilizzata nel ciclo for di cui sopra per ammettere uno o più caratteri dalla sintassi regex e alfanumerici (ad esempio, sostituire "\\ d" nella regex all'interno di i cicli for con "[a-zA-Z0-9 \\ + \. \ * \? \^\ $ \ {\} \ | \ [\]] +"). In ogni caso, questo approccio è preferibile alle operazioni di sottostringa. – Hoobajoob

1

Simple Solution

Nello scenario in cui si vuole semplicemente sostituire un numero di versione che si trova da qualche parte nel testo $input, si potrebbe semplicemente fare questo:

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

utilizzando Named Cattura in PowerShell

Re garding la tua domanda sulle acquisizioni denominate, che può essere fatto utilizzando parentesi graffe. cioè

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

Dà:

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

Problema con la cattura multipla & soluzione

Non è possibile sostituire valori multipli nella stessa istruzione di sostituire, dal momento che la stringa di sostituzione viene utilizzato per tutto . vale a dire, se avete fatto queste cose:

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

Otterreste:

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. 

... che probabilmente non è quello che stai sperando.

Piuttosto, dovreste fare un match per articolo:

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

... per ottenere:

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

più complessa soluzione

Portando questo torna a il tuo scenario, in realtà non stai usando i valori catturati; piuttosto speri di sostituire gli spazi in cui erano con nuovi valori. Per questo, ci si vuole semplicemente questo:

$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 

che darebbe:

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: È possibile che questo potrebbe essere scritto come 1 liner, ma ho rotto verso il basso per renderlo più semplice da leggere.

Questo sfrutta i lookerei di espressioni regolari; un modo per controllare il contenuto prima e dopo la stringa che stai catturando, senza includere quelli nella partita. Ad esempio, quando selezioniamo cosa sostituire, possiamo dire "corrisponde al numero visualizzato dopo la versione della parola" senza dire "sostituisci la versione della parola".

Maggiori informazioni su quelli qui: http://www.regular-expressions.info/lookaround.html

tuo esempio

Adattamento quanto sopra a lavorare per il vostro esempio (cioè dove le versioni possono essere separati da virgole o punti, e non c'è consistenza alla loro formato oltre ad essere 4 gruppi di numeri:

$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 

Dà:

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

Version "1.3.5.7" 

SomeStruct vs = { 1,3,5,7 } 
Problemi correlati