Эффективность замены нескольких строк PowerShell
Я пытаюсь заменить 600 различных строк в очень большом текстовом файле 30Mb+. Я создаю сценарий, который делает это; следуя этому вопрос:
сценарий:
$string = gc $filePath
$string | % {
$_ -replace 'something0','somethingelse0' `
-replace 'something1','somethingelse1' `
-replace 'something2','somethingelse2' `
-replace 'something3','somethingelse3' `
-replace 'something4','somethingelse4' `
-replace 'something5','somethingelse5' `
...
(600 More Lines...)
...
}
$string | ac "C:log.txt"
но так как это будет проверять каждую строку 600 раз, и в текстовом файле есть более 150 000 строк, это означает, что есть много времени обработки.
есть ли лучшая альтернатива этому, которая более эффективна?
любой совет будет оценили, спасибо.
4 ответов
Итак, вы говорите, что хотите заменить любую из 600 строк в каждой из 150 000 строк, и вы хотите запустить одну операцию замены в строке?
Да, есть способ сделать это, но не в PowerShell, по крайней мере, я не могу придумать. Это можно сделать в Perl.
Методика:
- построить хэш, где ключи являются somethings и значения somethingelses.
- соедините ключи хэша с | символ и используйте его как группу совпадений в регулярном выражении.
- в замене интерполируйте выражение, которое извлекает значение из хэша, используя переменную match для группы захвата
Проблема:
к сожалению, PowerShell не предоставляет переменные соответствия вне вызова regex replace. Это не работает с -заменить оператор и он не работает с [регулярное выражение]:: заменить.
в Perl, вы можете сделать это, например:
$string =~ s/(1|2|3)/@{[ + 5]}/g;
это добавит 5 к цифрам 1, 2 и 3 по всей строке, поэтому, если строка "1224526123 [2] [6]", он превращается в "6774576678 [7] [6]".
однако, в PowerShell, оба из них терпят крах:
$string -replace '(1|2|3)',"$( + 5)"
[regex]::replace($string,'(1|2|3)',"$( + 5)")
в обоих случаях 1$ принимает значение null, а выражение принимает значение plain old 5. Переменные соответствия в заменах только значение в результирующей строке, т. е. строка с одной кавычкой или независимо от того, что оценивает строка с двумя кавычками. Они в основном просто обратные ссылки, которые выглядят как переменные соответствия. Конечно, вы можете процитировать $ перед числом в строке с двойными кавычками, поэтому он будет оценивать соответствующую группу совпадений, но это побеждает цель - он не может участвовать в выражении.
Решение:
[этот ответ был изменен от оригинала. Он был отформатирован, чтобы соответствовать строкам соответствия с метасимволами regex. И экран телевизора, конечно.]
если использование другого языка приемлемо для вас, следующий скрипт Perl работает как шарм:
$filePath = $ARGV[0]; # Or hard-code it or whatever
open INPUT, "< $filePath";
open OUTPUT, '> C:\log.txt';
%replacements = (
'something0' => 'somethingelse0',
'something1' => 'somethingelse1',
'something2' => 'somethingelse2',
'something3' => 'somethingelse3',
'something4' => 'somethingelse4',
'something5' => 'somethingelse5',
'X:\Group_14\DACU' => '\DACU$',
'.*[^xyz]' => 'oO{xyz}',
'moresomethings' => 'moresomethingelses'
);
foreach (keys %replacements) {
push @strings, qr/\Q$_\E/;
$replacements{$_} =~ s/\/\\/g;
}
$pattern = join '|', @strings;
while (<INPUT>) {
s/($pattern)/$replacements{}/g;
print OUTPUT;
}
close INPUT;
close OUTPUT;
он ищет ключи хэша (слева от =>), и заменяет их соответствующими значениями. Вот что происходит:
- на foreach цикл проходит через все элементы хэша и создайте массив под названием @strings, который содержит ключи %замены хэш, с метасимволами, цитируемыми с помощью \Q и \E, и результат этого цитируется для использования в качестве шаблона регулярных выражений ( qr = регулярное выражение цитаты). В том же проходе он избегает всех обратных косых черт в строках замены, удваивая их.
- далее элементы массива соединяются с |'s, чтобы сформировать шаблон поиска. Вы можете включить скобки группировки в $ pattern если хотите, но я думаю, что таким образом становится яснее, что происходит.
- на пока loop считывает каждую строку из входного файла, заменяет любую из строк в шаблоне поиска соответствующими строками замены в хэше и записывает строку в выходной файл.
кстати, вы могли заметить несколько других модификаций оригинального сценария. Мой Perl собрал пыль во время моего недавнего удара PowerShell, и на втором взгляде я заметил несколько вещей, которые можно было бы сделать лучше.
-
while (<INPUT>)
чтение файла по одной строке за раз. Гораздо разумнее, чем читать все 150 000 строк в массив, особенно когда ваша цель-эффективность. - я упростил
@{[$replacements{}]}
до$replacements{}
. Perl не имеет встроенного способа вычисления выражения например, PowerShell $(), так что @{[ ]} используется в качестве обходного пути - он создает буквальный массив из одного элемента, содержащего выражение. Но я понял, что это не обязательно, если выражение является всего лишь одной скалярной переменной (у меня было это как пережиток моего первоначального тестирования, где я применял вычисления к 1$ переменная матч). - на закрыть заявления не являются строго необходимыми, но это считается хорошая практика, чтобы явно закрыть свои работы.
- я изменил на аббревиатура foreach, чтобы сделать его более ясным и знакомым программистам PowerShell.
объединение метода хэша из ответ Ади Инбара, и оценщик матча от ответ кита Хилла к другому недавнему вопросу, вот как вы можете выполнить замену в PowerShell:
# Build hashtable of search and replace values.
$replacements = @{
'something0' = 'somethingelse0'
'something1' = 'somethingelse1'
'something2' = 'somethingelse2'
'something3' = 'somethingelse3'
'something4' = 'somethingelse4'
'something5' = 'somethingelse5'
'X:\Group_14\DACU' = '\DACU$'
'.*[^xyz]' = 'oO{xyz}'
'moresomethings' = 'moresomethingelses'
}
# Join all (escaped) keys from the hashtable into one regular expression.
[regex]$r = @($replacements.Keys | foreach { [regex]::Escape( $_ ) }) -join '|'
[scriptblock]$matchEval = { param( [Text.RegularExpressions.Match]$matchInfo )
# Return replacement value for each matched value.
$matchedValue = $matchInfo.Groups[0].Value
$replacements[$matchedValue]
}
# Perform replace over every line in the file and append to log.
Get-Content $filePath |
foreach { $r.Replace( $_, $matchEval ) } |
Add-Content 'C:\log.txt'
Я также не знаю, как решить это в powershell, но я знаю, как решить это в Bash, и это с помощью инструмента под названием sed. К счастью, есть также Sed для Windows. Если все, что вы хотите сделать, это заменить" что-то# "на" somethingelse# " везде, то эта команда сделает трюк для вас
sed -i "s/something([0-9]+)/somethingelse/g" c:\log.txt
в Bash вам действительно нужно будет избежать нескольких этих символов с обратными косыми чертами, но я не уверен, что вам нужно в windows. Если первая команда жалуется, что вы можете попробовать
sed -i "s/something\([0-9]\+\)/somethingelse/g" c:\log.txt
Я бы использовал оператор powershell switch:
$string = gc $filePath
$string | % {
switch -regex ($_) {
'something0' { 'somethingelse0' }
'something1' { 'somethingelse1' }
'something2' { 'somethingelse2' }
'something3' { 'somethingelse3' }
'something4' { 'somethingelse4' }
'something5' { 'somethingelse5' }
'pattern(?<a>\d+)' { $matches['a'] } # sample of more complex logic
...
(600 More Lines...)
...
default { $_ }
}
} | ac "C:\log.txt"