2012-09-01 24 views
5

Powiedzmy, że mam wyrażenie regularne podobne do poniższego, ale wczytałem je z pliku do zmiennej $ regex, więc nie mam pojęcia w czasie projektowania, jaka jest jej zawartość, ale w czasie wykonywania mogę dowiedzieć się, że zawiera ona "version1", "Version2", "version3" i "version4" o nazwie grupy:Powershell: Zastępowanie regex nazwanych grup zmiennymi

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

... i mam te zmienne:

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

.. .i natrafiam na następujący ciąg w pliku:

Version 7,7,0,0 

... która jest przechowywana w zmiennej $ input, dzięki czemu ($ input -match $ regex) jest wartością $ true.

Jak mogę zamienić nazwy grup od $ regex w ciągu znaków $ input z wartościami $ wersja1, $ wersja2, $ wersja3, $ wersja4, jeśli nie znam kolejności, w jakiej pojawiają się w $ regex (I wiesz tylko, że $ regex zawiera te nazwane grupy)?

Nie mogę znaleźć żadnych odniesień opisujących składnię zastępowania nazwanej grupy wartością zmiennej, używając nazwy grupy jako indeksu do dopasowania - czy to jest nawet obsługiwane?

EDIT: Dla wyjaśnienia - celem jest zastąpienie matrycy ciągów Wersja w każdym rodzaju pliku tekstowego, gdzie ciąg wersja danego pliku wymaga wymiany zmienną liczbę pól wersja (może być 2, 3, lub wszystkie 4 pola). Na przykład tekst w pliku może wyglądać jak każdy z nich (ale nie ogranicza się do nich):

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

Version "1.2.3.4" 

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

Użytkownicy mogą określić zestaw plików i wyrażenia regularnego, aby dopasować linię zawierającą pola, z Pierwotny pomysł polegał na tym, że poszczególne pola zostały przechwycone przez nazwane grupy. Narzędzie ma indywidualne wartości pól wersji, które powinny zostać zastąpione w pliku, ale musi zachować oryginalny format wiersza, który będzie zawierał podstawienia, i zastąpić tylko żądane pola.

EDIT-2: myślę, że mogę uzyskać wynik muszę z obliczeń podciągu na podstawie pozycji i zakresu każdego z meczów, ale miał nadzieję zastąpić działanie PowerShell został uratuje mi jakąś pracę.

EDIT-3: Tak, jak Ansgar poprawnie i zwięźle opisuje poniżej, nie jest sposobem (tylko przy użyciu oryginalnego ciąg wejściowy, wyrażenie regularne, o których znasz tylko nazwanych grup, a otrzymaną mecze), aby użyć operacji "-replace" (lub innych operacji wyrażeń regularnych) w celu dokonania podstawienia przechwyceń nazwanych grup, pozostawiając resztę oryginalnego ciągu nietkniętymi. Dla tego problemu, jeśli ktoś jest ciekawy, w końcu skorzystałem z poniższego rozwiązania. YMMV, możliwe inne rozwiązania. Wielkie dzięki dla Ansgara za jego opinie i opcje.

W kolejnym bloku kodu:

  • $ wejściowy jest linia tekstu, w którym podstawienie ma być wykonywana
  • $ regex jest wyrażeniem regularnym (typu [łańcuch]) odczytać z pliku który został zweryfikowany i zawiera co najmniej jedną z obsługiwanych nazwanych grup.
  • $ regexToGroupName jest tablicą asocjacyjną, która odwzorowuje ciąg regex na tablicę nazw grup uporządkowanych zgodnie z kolejnością tablic zwracanych przez [regex] :: GetGroupNames(), które pasuje do kolejności od lewej do prawej, w której występują w wyrażeniu
  • $ groupNameToVersionNumber to tablica skrótów, która odwzorowuje nazwę grupy na numer wersji.

Ograniczenia wymienionych grup w $ regex są tylko (chyba), że wyrażenie w obrębie wymienionych grup nie mogą być zagnieżdżone, i powinna odpowiadać co najwyżej raz w ciągu wejściowego.

# 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 

Odpowiedz

4

Wyrażenia regularne nie działają w ten sposób, więc nie można. Nie bezpośrednio. Co można zrobić (krótka z użyciem bardziej odpowiednie wyrażenie regularne, że grupy te części, które chcesz zachować) znajduje się wyodrębnić ciąg wersji, a następnie w drugim etapie zastąpić ten podciąg z nową wersją wyrażenie:

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

Edit:

Jeśli nawet nie wiem strukturę, że należy wyodrębnić z wyrażenia regularnego, jak również.

$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

Uzgodniono, że byłoby to miłe, ale dotyczy to narzędzia, w którym użytkownicy określają wyrażenie regularne i zbiór plików. Nie znam wyrażenia regularnego i nie wiem, jak wygląda zawartość pliku, więc nie mogłem użyć pierwszej linii w odpowiedzi bez ponownego formatowania oryginalnej zawartości pliku, co byłoby niepożądane. Musimy pozostawić zawartość pliku wyglądającą tak samo później, zastępując tylko podciągi pasujących wierszy polami poszczególnych wersji. – Hoobajoob

+0

Być może możesz zamienić nazwane grupy w wyrażeniu regularnym na rzeczywiste stare/nowe liczby, a następnie zastąp ciąg. To nie będzie działać poprawnie, jeśli wyrażenie regularne zawiera wyrażenia inne niż nazwane grupy. –

+0

To prawie działa, chociaż nie wiem z góry, jak właściwie określone grupy w regex są zdefiniowane (np. Mogą szukać \ d, \ d {2}, \ d +, literału itp.) . Mogę wprowadzić pewne ograniczenia dla nazwanej definicji grupy i zmienić wyrażenie użyte w pętli for, którą posiadasz powyżej, aby przyznać jeden lub więcej znaków ze składni regex oraz alfanumerycznych (np. Zamień "\\ d" w regex ciągu pętle for z "[a-zA-Z0-9 \\ + \. \ * \? \^\ $ \ {\} \ | \ [\]] +"). W każdym razie podejście to jest lepsze niż działanie podłańcuchowe. – Hoobajoob

1

Proste rozwiązanie

W scenariuszu, w którym po prostu chcesz zamienić numer wersji znajdujący się gdzieś w tekście $input, można po prostu to zrobić:

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

pomocą nazwanych Przechwytuje w PowerShell

Re na pytanie o nazwane przechwytywania, które można wykonać za pomocą nawiasów klamrowych. tj

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

Daje:

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

Problem z wieloma przechwytuje & rozwiązanie

Nie można zastąpić wiele wartości w tym samym zastąpić oświadczeniem, ponieważ ciąg zastępczego służy do wszystkiego . to znaczy, jeśli to zrobił:

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

można dostać:

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. 

... co nie jest chyba to, co masz nadzieję.

Raczej trzeba by zrobić mecz za sztukę:

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

... dostać:

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

Bardziej kompleksowe rozwiązanie

Bringing to Tylna Twój scenariusz, w rzeczywistości nie wykorzystujesz przechwyconych wartości; raczej masz nadzieję na zastąpienie przestrzeni, w której się znajdują, nowymi wartościami. Za to, że po prostu chcesz to:

$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 

co dałoby:

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: Powyższe można zapisać jako 1 liner, ale Złamałem go w dół, aby go łatwiejszy do odczytania.

Wykorzystuje to regex lookarounds; sposób sprawdzania zawartości przed i po przechwyconym ciągu, bez uwzględniania tych w meczu. tzn. gdy wybieramy, co zastąpić, możemy powiedzieć "dopasuj liczbę, która pojawia się po wersji słownika", nie mówiąc "zastąp wersję słowa".

Więcej informacji na temat tych tutaj: http://www.regular-expressions.info/lookaround.html

Twój Przykład

Adaptacja wyżej pracować na swoim przykładzie (czyli gdzie wersje mogą być oddzielone przecinkami lub kropek, a nie ma konsystencję do ich formatu poza będąc 4 zestawy liczb:

$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 

daje:

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

Version "1.3.5.7" 

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