Поиграв какое-то время с некоторым скриптом powershell, мне было интересно, есть ли версия этого без использования С#. Такое ощущение, что мне не хватает некоторой информации о том, как правильно передавать вещи.
$packages = Get-ChildItem "C:\Users\A\Downloads" -Filter "*.nupkg" |
%{ $_.Name }
# Select-String -Pattern "(?<packageId>[^\d]+)\.(?<version>[\w\d\.-]+)(?=.nupkg)" |
# %{ @($_.Matches[0].Groups["packageId"].Value, $_.Matches[0].Groups["version"].Value) }
foreach ($package in $packages){
$match = [System.Text.RegularExpressions.Regex]::Match($package, "(?<packageId>[^\d]+)\.(?<version>[\w\d\.-]+)(?=.nupkg)")
Write-Host "$($match.Groups["packageId"].Value) - $($match.Groups["version"].Value)"
}
Первоначально я пытался сделать это только с помощью powershell и думал, что с помощью @(1,2,3) вы можете создать массив.
В итоге я обошел проблему, выполнив регулярное выражение с С# вместо powershell, что работает, но мне любопытно, как это можно было бы сделать только с помощью powershell.
В то время как есть 4 пакета, только версия powershell произвела 8 строк. Таким образом, доступ к моим данным, таким как $packages[0][0], для получения идентификатора пакета никогда не работал, потому что 8 строк были строками, в то время как я ожидал, что будут возвращены 4 массива.
@MathiasR.Jessen [System.Text.RegularExpressions.Regex]::Match — это функциональность C#, а не powershell
Это действительно не «функциональность С#», это общая функциональность :)
Примечание по терминологии без использования С#: вы имеете в виду без прямого использования .NET API. Напротив, C# — это просто еще один язык на основе .NET, который может использовать такие API, как и сам PowerShell.
Примечание:
Следующий раздел отвечает на следующий вопрос: как избежать прямых вызовов API-интерфейсов .NET для моего кода сопоставления регулярных выражений в пользу использования собственных команд PowerShell (операторов, автоматических переменных)?
См. Нижний раздел для решения Select-String, которое было вашей истинной целью; tl;dr:
# Note the `, `, which ensures that the array is output *as a single object*
%{ , @($_.Matches[0].Groups["packageId"].Value, $_.Matches[0].Groups["version"].Value) }
Собственный (почти) PowerShell-эквивалент вашего кода (обратите внимание, что предполагается, что $package
содержит содержимое входного файла):
# Caveat: -match is case-INSENSITIVE; use -cmatch for case-sensitive matching.
if ($package -match '(?<packageId>[^\d]+)\.(?<version>[\w\d\.-]+)(?=.nupkg)') {
"$($Matches['packageId']) - $($Matches['Version'])"
}
-match
, оператор сопоставления регулярных выражений , является эквивалентом [System.Text.RegularExpressions.Regex]::Match() (который можно сократить до [regex]::Match()
) в том смысле, что он ищет только (не более ) одно совпадение.
Предостережение относительно учета регистра: -match
(и его редко используемый псевдоним -imatch
) по умолчанию нечувствительны к регистру, как и все операторы PowerShell; для соответствия с учетом регистра используйте вариант с префиксом c
, -cmatch
.
Напротив, API-интерфейсы .NET по умолчанию чувствительны к регистру; вам нужно будет передать флаг [System.Text.RegularExpressions.RegexOptions]::IgnoreCase
в [regex]::Match()
для совпадения без учета регистра (вы можете использовать 'IgnoreCase'
, который PowerShell автоматически конвертирует для вас).
Начиная с PowerShell 7.2.x, нет оператора, который был бы эквивалентен соответствующему API return-ALL-matches .NET, [regex]::Matches() . См. GitHub issue #7867, чтобы получить зеленый свет, но еще не реализованное предложение по внедрению одного из них под названием -matchall
.
Однако вместо того, чтобы напрямую возвращать объект, описывающий, что было (или не было) сопоставлено, -match
возвращает логическое значение, то есть $true
или $false
, чтобы указать, удалось ли сопоставление.
Информация о совпадении становится доступной только в том случае, если -match
возвращает $true
, а именно через автоматическую переменную $Matches , которая представляет собой хеш-таблицу, отражающую совпадающие части входной строки: запись 0
всегда является полным совпадением, с необязательным дополнительные записи, отражающие то, что захватили какие-либо группы захвата ((...)
), либо по индексу, если они анонимные (начиная с 1
), либо, как в вашем случае, для именованных групп захвата ((?<name>...)
) по имени.
Примечание по синтаксису. Учитывая, что PowerShell позволяет использовать точечную нотацию (синтаксис доступа к свойствам) даже с хеш-таблицами, приведенная выше команда могла бы использовать, например, $Matches.packageId
вместо $Matches['packageId']
, что также работает с числовыми (на основе индекса) записями, например, $Matches.0
вместо $Matches[0]
Предостережение: если в качестве операнда LHS используется массив (перечисляемый), поведение -match
меняется:
$Matches
не заполняется.Обратите внимание, что хеш-таблица $Matches
предоставляет только совпадающие строки, а не метаданные, такие как индекс и длина, как в возвращаемом объекте [regex]::Match()
, который имеет тип [System.Text.RegularExpressions.Match].
$packages |
Select-String '(?<packageId>[^\d]+)\.(?<version>[\w\d\.-]+)(?=.nupkg)' |
ForEach-Object {
"$($_.Matches[0].Groups['packageId'].Value) - $($_.Matches[0].Groups['version'].Value)"
}
Select-String
выводит экземпляры Microsoft.PowerShell.Commands.MatchInfo, чья .Matches
коллекция содержит один или несколько экземпляров [System.Text.RegularExpressions.Match], т. е. экземпляров того же типа, что и возвращаемый [regex]::Match()
-AllMatches
также не передается, .Matches
имеет только одну запись, поэтому используется [0]
для нацеливания на эту запись выше.Как видите, работа с выходными объектами Select-Object
требует, чтобы вы в конечном итоге работали с тем же типом .NET, что и при непосредственном вызове [regex]::Match()
.
Однако вызовы методов не требуются, а обнаружение свойств выходных объектов в PowerShell упрощается с помощью командлета Get-Member.
Если вы хотите зафиксировать совпадения в зубчатом массиве:
$capturedStrings = @(
$packages |
Select-String '(?<packageId>[^\d]+)\.(?<version>[\w\d\.-]+)(?=.nupkg)' |
ForEach-Object {
# Output an array of all capture-group matches,
# *as a single object* (note the `, `)
, $_.Matches[0].Groups.Where({ $_.Name -ne '0' }).Value
}
)
Это возвращает массив массивов, каждый элемент которого является массивом совпадений группы захвата для данного пакета, так что $capturedStrings[0][0]
возвращает значение packageId
, например, для первого пакета.
Примечание:
$_.Matches[0].Groups.Where({ $_.Name -ne '0' }).Value
программно перечисляет все совпадения группы захвата и возвращает значения их .Value
свойств в виде массива, используя перечисление доступа к членам; обратите внимание, что имя '0'
должно быть исключено, так как оно представляет собой полное совпадение.
С группами захвата в вашем конкретном регулярном выражении приведенное выше эквивалентно следующему, как показано в закомментированной строке вашего вопроса:
@($_.Matches[0].Groups['packageId'].Value, $_.Matches[0].Groups['version'].Value)
, ...
, унарная форма оператора построения массива , используется в качестве ярлыка для вывода массива (обозначенного здесь ...
) в целом, как единый объект. По умолчанию будет происходить перечисление, и элементы будут испускаться один за другим. , ...
на самом деле является ярлыком для концептуально более ясного Write-Output -NoEnumerate ...
- см. этот ответ для объяснения техники.
Кроме того, @(...)
, оператор подвыражения массива необходим для того, чтобы гарантировать, что будет возвращен зубчатый массив (вложенный массив) даже в том случае, если во всех $packages
будет возвращен только один массив.
@Dbl, теперь я вижу, что пропустил закомментированные строки во фрагменте кода в вашем вопросе, которые действительно показывают, что унарная ,
была единственной отсутствующей частью. Я просто замалчивал их, полагая, что они не важны из-за того, что они закомментированы. В любом случае, я рад, что мы нашли решение.
Что вы имеете в виду под "без использования С#"? В вашем вопросе нет ни одной строки С#...