Извлечь строку из текстового файла через Powershell - PullRequest
0 голосов
/ 09 февраля 2019

Я пытался извлечь определенные значения из нескольких строк в файле .txt с помощью PowerShell.

Host
Class
INCLUDE vmware:/?filter=Displayname Equal "server01" OR Displayname Equal "server02" OR Displayname Equal "server03 test"

Это то, что я хочу:

server01
server02
server03 test

У меня есть код:

$Regex = [Regex]::new("(?<=Equal)(.*)(?=OR")           
$Match = $Regex.Match($String)

Ответы [ 4 ]

0 голосов
/ 09 февраля 2019

Другой вариант (PSv3 +), объединяющий [regex]::Matches() с оператором -replace для краткого решения:

$str = @'
Host
Class
INCLUDE vmware:/?filter=Displayname Equal "server01" OR Displayname Equal "server02" OR Displayname Equal "server03 test"
'@ 

[regex]::Matches($str, '".*?"').Value -replace '"'

Regex ".*?" соответствует всем "..." -замкнутым токенам;.Value извлекает их, а -replace '"' удаляет " символов.

Это может быть неочевидно, но это, пожалуй, самое быстрое решение среди ответов здесь, основываясь на моих тестах - см. Ниже.


В качестве отступления: приведенное выше было бы еще более PowerShell-идиоматическим, если бы у оператора -match - который ищет только a (одно) совпадение - был вариант с именемскажем, -matchall, чтобы можно было написать:

# WISHFUL THINKING (as of PowerShell Core 6.2)
$str -matchall '".*?"' -replace '"'

См. предложение этой функции на GitHub.


Необязательное чтение: сравнение производительности

Прагматически говоря, все решения здесь полезны и могут быть достаточно быстрыми, но могут быть ситуации, когда производительность должна быть оптимизирована.

Как правило, использование Select-String (и конвейер в целом) приходитс потерей производительности - предлагая элегантность и эффективность использования памяти потоковая обработка.

Кроме того, повторный вызов блоков скрипта (например, { $_.Value }) имеет тенденцию быть медленным - особенно в конвейерес ForEach-Objectили Where-Object, но также - в меньшей степени - с помощью методов сбора .ForEach() и .Where() (PSv4 +).

В области регулярных выражений вы платите штраф за производительность за просмотр переменной длины- выражения за (например, (?<=EQUAL\s*")) и использование групп захвата (например, (.*?)).

Вот сравнение производительности с использованием функции Time-Command , в среднем 1000 прогонов:

Time-Command -Count 1e3 { [regex]::Matches($str, '".*?"').Value -replace '"' },
   { [regex]::matches($String, '(?<=Equal\s*")[^"]+') | Foreach {$_.Value} },
   { [regex]::Matches($str, '\"(.*?)\"').Groups.Where({$_.name -eq '1'}).Value },
   { $str | Select-String -Pattern '(?<=Equal\s*")[^"]+' -AllMatches | ForEach-Object{$_.Matches.Value} } |
     Format-Table Factor, Command

Пример времени с моего MacBook Pro;точное время не имеет значения (вы можете удалить вызов Format-Table, чтобы увидеть их), но относительная производительность отражается в столбце Factor, от самого быстрого до самого медленного.

Factor Command
------ -------
1.00   [regex]::Matches($str, '".*?"').Value -replace '"' # this answer
2.85   [regex]::Matches($str, '\"(.*?)\"').Groups.Where({$_.name -eq '1'}).Value # AdminOfThings'
6.07   [regex]::matches($String, '(?<=Equal\s*")[^"]+') | Foreach {$_.Value} # Wiktor's
8.35   $str | Select-String -Pattern '(?<=Equal\s*")[^"]+' -AllMatches | ForEach-Object{$_.Matches.Value} # LotPings'
0 голосов
/ 09 февраля 2019

Вы можете изменить свое регулярное выражение, чтобы использовать группу захвата, которая указана в скобках.Обратная косая черта просто избежать кавычек.Это позволяет вам просто захватить то, что вы ищете, а затем отфильтровать это дальше.Группе захвата здесь автоматически присваивается имя 1, поскольку я не предоставил имя.Группа захвата 0 - это полное совпадение, включая кавычки.Я переключился на метод Matches, потому что он охватывает все совпадения для строки, тогда как Match только захватывает первое совпадение.

$regex = [regex]'\"(.*?)\"'    
$regex.matches($string).groups.where{$_.name -eq 1}.value

Если вы хотите экспортировать результаты, вы можете сделать следующее:

$regex = [regex]'\"(.*?)\"'    
$regex.matches($string).groups.where{$_.name -eq 1}.value | sc "c:\temp\export.txt"
0 голосов
/ 09 февраля 2019

Альтернативное чтение файла напрямую с помощью Select-String с использованием хорошего регулярного выражения Wiktor:

Select-String -Path .\file.txt -Pattern '(?<=Equal\s*")[^"]+' -AllMatches|
    ForEach-Object{$_.Matches.Value} | Set-Content NewFile.txt

Пример вывода:

> Get-Content .\NewFile.txt
server01
server02
server03 test
0 голосов
/ 09 февраля 2019

Вы можете использовать

[regex]::matches($String, '(?<=Equal\s*")[^"]+')

См. Демоверсию regex .

См. другие способы извлечения нескольких совпадений здесь .Тем не менее, ваша главная проблема - шаблон регулярных выражений.Шаблон (?<=Equal\s*")[^"]+ соответствует:

  • (?<=Equal\s*") - местоположение, которому предшествуют пробелы Equal и 0+, а затем "
  • [^"]+ - потребляет 1+символы, отличные от двойной кавычки.

Демонстрация:

$String = "Host`nClass`nINCLUDE vmware:/?filter=Displayname Equal ""server01"" OR Displayname Equal ""server02"" OR Displayname Equal ""server03 test"""
[regex]::matches($String, '(?<=Equal\s*")[^"]+') | Foreach {$_.Value}

Вывод:

server01
server02
server03 test

Вот полный фрагмент, читающий файл и получающий всесовпадения и сохранение в файл:

$newfile = 'file.txt'
$file = 'newtext.txt'
$regex = '(?<=Equal\s*")[^"]+'
Get-Content $file | 
     Select-String $regex -AllMatches | 
     Select-Object -Expand Matches | 
     ForEach-Object { $_.Value } |
     Set-Content $newfile
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...