7

次のような正規表現があるとしますが、それをファイルから変数 $regex にロードしたため、設計時には内容がわかりませんが、実行時に「version1」が含まれていることを発見できます。 「version2」、「version3」、および「version4」の名前付きグループ:

"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 からの名前付きグループを $version1、$version2、$version3、$version4 の値に置き換えるにはどうすればよいですか ($正規表現にはこれらの名前付きグループが含まれます)?

グループ名を一致のインデックスとして使用して、名前付きグループを変数の値に置き換えるための構文を説明している参照が見つかりません。これはサポートされていますか?

編集: 明確にするために-目標は、特定のファイルのバージョン文字列が可変数のバージョンフィールド(2、3、または4つすべてのフィールド)の置換を必要とする、あらゆる種類のテキストファイルでテンプレート化されたバージョン文字列を置き換えることです)。たとえば、ファイル内のテキストは次のいずれかのようになります (ただし、これらに限定されません)。

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

Version "1.2.3.4"

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

ユーザーは、フィールドを含む行に一致するファイル セットと正規表現を指定できます。元のアイデアは、個々のフィールドが名前付きグループによってキャプチャされるというものでした。ユーティリティには、ファイル内で置換する必要がある個々のバージョン フィールド値がありますが、置換を含む行の元の形式を保持し、要求されたフィールドのみを置換する必要があります。

EDIT-2: 各一致の位置と範囲に基づいて部分文字列の計算を行うことで、必要な結果を得ることができると思いますが、Powershell の置換操作によって作業が節約されることを期待していました。

EDIT-3: したがって、Ansgar が以下で正しく簡潔に説明しているように、"- replace" 操作 (または他の正規表現操作) を使用して、元の文字列の残りの部分をそのまま残しながら、名前付きグループのキャプチャの置換を実行します。この問題について、誰かが興味を持っている場合は、以下の解決策を使用することになりました。YMMV、他の解決策が可能です。フィードバックとオプションを提供してくれた Ansgar に感謝します。

次のコード ブロックでは:

  • $input は、置換が実行されるテキスト行です
  • $regex は、サポートされている名前付きグループの少なくとも 1 つを含むことが確認されたファイルから読み取られた (タイプ [string] の) 正規表現です
  • $regexToGroupName は、[regex]::GetGroupNames() によって返される配列の順序に従って並べ替えられたグループ名の配列に正規表現文字列をマップするハッシュ テーブルです。表現
  • $groupNameToVersionNumber は、グループ名をバージョン番号にマップするハッシュ テーブルです。

$regex 内の名前付きグループに対する制約は、(私が思うに) 名前付きグループ内の式はネストできず、入力文字列内で最大 1 回一致する必要があるということだけです。

# 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

2 に答える 2

7

シンプルなソリューション

テキストのどこかにあるバージョン番号を単純に置き換えたいシナリオでは、次の$inputように簡単に実行できます。

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

PowerShell での名前付きキャプチャの使用

名前付きキャプチャに関する質問については、中かっこを使用して行うことができます。すなわち

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

与えます:

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

複数のキャプチャと解決策に関する問題

置換文字列はすべてに使用されるため、同じ置換ステートメントで複数の値を置換することはできません。つまり、これを行った場合:

 '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 つのライナーとして記述できますが、読みやすくするために分割しました。

これは正規表現ルックアラウンドを利用します。キャプチャしている文字列の前後のコンテンツを、一致に含めずにチェックする方法。つまり、何を置換するかを選択するときに、「単語のバージョンを置換する」とは言わずに、「単語のバージョンの後に表示される番号に一致させる」と言うことができます。

ここの詳細情報: 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 }
于 2017-04-06T16:25:54.320 に答える
4

正規表現はそのようには機能しないため、できません。直接ではありません。できること (保持したい部分をグループ化するより適切な正規表現を使用する以外) は、バージョン文字列を抽出し、2 番目のステップでその部分文字列を新しいバージョン文字列に置き換えることです。

$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
于 2012-09-01T10:50:06.800 に答える