PowerShell多字符串替换效率

时间:2013-07-17 14:08:19

标签: regex perl powershell replace text-files

我试图在一个非常大的文本文件30Mb +中替换600个不同的字符串。我正在构建一个执行此操作的脚本;遵循此Question

脚本:

$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 个答案:

答案 0 :(得分:6)

Adi Inbar's answer中的哈希技术和Keith Hill's answer中的匹配评估器结合到另一个最近的问题,以下是在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'

答案 1 :(得分:4)

那么,你所说的是你想要替换150,000行中的600个字符串中的任何一个,并且你想要每行运行一次替换操作?

是的,有一种方法可以做到,但不是在PowerShell中,至少我想不到一个。它可以在Perl中完成。


方法:

  1. 构造一个哈希,其中键是多数,值是多数。
  2. 使用 | 符号加入哈希的键,并将其用作正则表达式中的匹配组。
  3. 在替换中,插入一个表达式,该表达式使用捕获组的匹配变量从哈希值中检索值

  4. 问题:

    令人沮丧的是,PowerShell不会在正则表达式替换调用之外公开匹配变量。它不适用于 -replace 运算符,并且不适用于 [regex] :: replace

    在Perl中,您可以这样做,例如:

    $string =~ s/(1|2|3)/@{[$1 + 5]}/g;
    

    这将在整个字符串中将数字1,2和3加5,所以如果字符串是“1224526123 [2] [6]”,则变为“6774576678 [7] [6]”。

    但是,在PowerShell中,这两个都失败了:

    $string -replace '(1|2|3)',"$($1 + 5)"
    
    [regex]::replace($string,'(1|2|3)',"$($1 + 5)")
    

    在这两种情况下, $ 1 计算为null,并且表达式求值为plain old 5.替换中的匹配变量仅在结果字符串中有意义,即单引号字符串或其他任何字符串双引号字符串求值为。它们基本上只是看起来像匹配变量的反向引用。当然,您可以在双引号字符串中引用数字之前的 $ ,因此它将评估到相应的匹配组,但这会失败目的 - 它无法参与表达式。< / p>


    解决方案:

    [此答案已从原始版本修改过来。它已被格式化为适合匹配字符串与正则表达式元字符。当然还有你的电视屏幕。]

    如果您可以使用其他语言,则以下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{$1}/g;
      print OUTPUT;
    }
    close INPUT;
    close OUTPUT;
    

    它搜索哈希的键( =&gt; 的左侧),并用相应的值替换它们。这是正在发生的事情:

    • foreach 循环遍历哈希的所有元素,并创建一个名为 @strings 的数组,其中包含%替换的键hash,使用 \ Q \ E 引用的元字符,以及用作正则表达式模式的引用结果( qr =引用正则表达式)。在同一个传递中,它通过加倍来消除替换字符串中的所有反斜杠。
    • 接下来,数组的元素与 | 连接以形成搜索模式。如果需要,您可以在 $ pattern 中包含分组括号,但我认为这样可以更清楚地了解正在发生的事情。
    • while 循环从输入文件中读取每一行,用散列中相应的替换字符串替换搜索模式中的任何字符串,并将该行写入输出文件。

    顺便说一下,您可能已经注意到原始脚本中的其他一些修改。在我最近的PowerShell踢中,我的Perl收集了一些灰尘,第二次看,我发现了一些可以做得更好的事情。

    • while (<INPUT>)一次读取一行文件。比将整个150,000行读入阵列更加明智,特别是当你的目标是效率时。
    • 我将@{[$replacements{$1}]}简化为$replacements{$1}。 Perl没有内置的方法来插入像PowerShell的 $()这样的表达式,所以 @ {[]} 用作解决方法 - 它创建了一个文字数组包含表达式的一个元素。但是我意识到如果表达式只是一个标量变量就没有必要了(我把它作为我初始测试的保留,我将计算应用于 $ 1 匹配变量)。 / LI>
    • 关闭语句并非绝对必要,但明确关闭文件句柄被认为是一种好习惯。
    • 我将 for 缩写更改为 foreach ,以使PowerShell程序员更清楚,更熟悉。

答案 2 :(得分:2)

我也不知道如何在powershell中解决这个问题,但我确实知道如何在Bash中解决这个问题,那就是使用一个名为sed的工具。幸运的是,还有Sed for Windows。如果您想要做的就是将“something#”替换为“somethingelse#”,那么此命令将为您提供帮助

sed -i "s/something([0-9]+)/somethingelse\1/g" c:\log.txt

在Bash中,你实际上需要用反斜杠来逃避这些角色,但我不确定你是否需要在windows中。如果第一个命令抱怨你可以尝试

sed -i "s/something\([0-9]\+\)/somethingelse\1/g" c:\log.txt

答案 3 :(得分:1)

我会使用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"