2012-09-01 138 views
5

說我有一個像下面這樣的正則表達式,但是我把它從一個文件加載到一個變量$ regex中,所以在設計時不知道它的內容是什麼,但是在運行時我發現,它包括 「VERSION1」, 「版本2」, 「版本3」 和 「版本4」 命名組:Powershell:用變量替換名爲組的變量

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

...我有這些變量:

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

.. 。我在文件中遇到以下字符串:

Version 7,7,0,0 

...它存儲在變量$ input中,所以($ input -match $ regex)的計算結果爲$ true。

如果我不知道它們出現在$ regex中的順序,我怎樣才能從字符串$ input中的$ regex替換名爲groups的$ version1,$ version2,$ version3,$ version4的值只知道$ regex包含這些命名組)?

我找不到任何引用,描述用組名作爲匹配索引來替換命名組的語法,這是甚至支持嗎?

編輯: 爲了澄清 - 我們的目標是在任何一種文本文件,其中在給定的文件版本字符串需要更換版本領域的可變數量的替換模板版本字符串(可能是2,3,或全部4個領域)。例如,在一個文件中的文本可能看起來像任何這些(但不限於這些):

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

Version "1.2.3.4" 

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

用戶可以指定一個文件組和正則表達式匹配包含的字段行,與原來的想法是個別領域將被命名組捕獲。該實用程序具有應在文件中替換的各個版本字段值,但必須保留將包含替換的行的原始格式,並僅替換請求的字段。

EDIT 2: 我想我能得到的結果我需要根據每個比賽的位置和程度串計算,但希望PowerShell的替換操作是要救我一些工作。

編輯-3: 所以,下面安斯加爾正確而簡潔的描述,沒有一種方法(僅使用原始輸入字符串,正則表達式關於你只知道命名組,並將所得匹配)使用「替換」操作(或其他正則表達式操作)來執行指定組捕獲的替換,同時保留原始字符串的其餘部分不變。對於這個問題,如果有人好奇,我最終會使用下面的解決方案。 YMMV,其他可能的解決方案。非常感謝Ansgar提供的反饋和選項。

在下面的代碼塊:

  • $輸入是文本的線在其上取代是將要執行
  • $正則表達式是從文件中讀取一個正則表達式(類型的[字符串])已被驗證包含至少一個受支持的命名組
  • $ regexToGroupName是一個哈希表,它將一個正則表達式字符串映射到按照由[regex]返回的數組順序排列的組名數組:: GetGroupNames (),它與它們在表達式
  • 中出現的從左到右的順序相匹配
  • $ groupNameToVersionNumber是一個散列表,它將組名映射到版本號。

對$ regex中指定組的限制只是(我認爲)指定組中的表達式不能嵌套,並且在輸入字符串中最多一次匹配。

# 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 

回答

4

正則表達式不這樣工作,所以你不能。不是直接的,那是。你可以做什麼(短期使用更合適的正則表達式組要保持零件)是提取版本字符串,然後在第二個步驟替換子與新版本字符串:

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

編輯:

如果你甚至不知道結構,必須提取從正則表達式爲好。

$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

一致認爲,這將是很好的,但這是爲用戶指定正則表達式和文件集的實用程序。我不知道正則表達式,我不知道文件內容是什麼樣的,所以我不能在你的答案中使用第一行,而不用重新格式化原始文件內容,這是不可取的。之後我必須保持文件內容不變,只用相應的版本字段替換匹配行上的子字符串。 – Hoobajoob

+0

也許您可以用正確的舊/新數字替換正則表達式中的命名組,然後執行字符串替換。但是,如果正則表達式包含除命名組以外的表達式,那麼這將無法正常工作。 –

+0

這幾乎可行,但我事先並不知道如何定義正則表達式中的命名組(例如,它們可能正在查找\ d,\ d {2},\ d +,一個字面等) 。我可以對指定的組定義引入一些約束,並更改上面for循環中使用的正則表達式,以允許從正則表達式語法中接受一個或多個字符以及字母數字(例如,在正則表達式中替換正則表達式中的「\\ d」 「[a-zA-Z0-9 \\ + \。\ * \?\^\ $ \ \ \ \ \ \ \ \ [\]] +」)的for循環。無論如何,這種方法比子串操作更可取。 – Hoobajoob

1

簡單的解決方案

在你只是想更換您的$input文字的地方發現了一個版本號的情況下,您可以簡單地這樣做:

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

使用命名在PowerShell中捕獲

Re注意你關於命名捕獲的問題,可以使用大括號來完成。即

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

給出:

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

問題與多個捕獲&解決方案

你不能在同一個REPLACE語句替換多個值,因爲替換字符串用於一切。也就是說,如果你這樣做:

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

你會得到:

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. 

...這可能不是你希望的東西。

相反,你必須做每個項目匹配:

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

...獲得:

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

更復雜的解決方案

將這一回你的場景,你實際上並沒有使用捕獲的值;而是你希望用新的價值取代他們所處的空間。對於這一點,你只是想這樣:

$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 

這將使:

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). 

注:以上可以寫成1套,但我已經打破下來,使它更簡單的閱讀。

這利用了正則表達式lookarounds;一種檢查您捕獲的字符串前後的內容的方法,不包括匹配中的內容。即當我們選擇要替換的內容時,我們可以說「匹配出現在單詞版本後面的數字」,而不用說「替換單詞版本」。對那些在這裏

更多信息:http://www.regular-expressions.info/lookaround.html

你的榜樣

適應上述爲您的示例工作(即其中版本​​可以用逗號或圓點隔開,而且也沒有一致性,它們的格式超越是4組數字:

$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 

給出:

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

Version "1.3.5.7" 

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