【问题标题】:Find and Replace in a Large File在大文件中查找和替换
【发布时间】:2011-02-16 13:12:57
【问题描述】:

我想在一个大的 xml 文件中找到一段文本,并想用一些其他文本替换。文件大小约为(50GB)。我想在命令行中执行此操作。我正在看 Powershell,想知道它是否可以处理大尺寸。另外我想知道在powershell中转义关键运算符的语法。我是 PowerShell 新手

目前我正在尝试这样的事情,但它不喜欢它

    Get-Content C:\File1.xml | Foreach-Object {$_ -replace "xmlns:xsi=\"http:\/\/www\.w3\.org\/2001\/XMLSchema-instance\"", ""} | Set-Content C:\File1.xml

我要替换的文本是 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 带有空字符串 ""。

问题

  1. powerShell 可以大号处理吗 文件
  2. 如何调用 命令行中的 powershell 脚本
  3. 转义键的语法 powerShell 和列表中的运算符 powerShell 中的关键操作符。
  4. 我不希望替换发生在 内存和更喜欢流式假设 不会将服务器带到 它的膝盖。
  5. 我可以采取其他方法吗(不同 工具/策略?)

谢谢

【问题讨论】:

    标签: powershell


    【解决方案1】:

    它不喜欢它,因为您不能使用 Get-Content/Set-Content 同时读取文件并写回文件。我建议使用临时文件,然后在最后将 file1.xml 重命名为 file1.xml.bak 并将临时文件重命名为 file1.xml。

    1. 可以,只要您不尝试一次加载整个文件。逐行将起作用,但会有点慢。使用 -ReadCount 参数并将其设置为 1000 以提高性能。
    2. 哪个命令行?电源外壳?如果是这样,那么您可以像 .\myscript.ps1 那样调用您的脚本,如果它需要参数,那么 c:\users\joe\myscript.ps1 c:\temp\file1.xml
    3. 如果您不需要引用 PowerShell 变量,通常对于正则表达式我会使用单引号。然后你只需要担心正则表达式转义而不是 PowerShell 转义。如果您需要使用双引号,则反引号字符是双引号中的转义字符,例如“`$p1 设置为 $ps1”。在您的示例中,单引号将您的正则表达式简化为(注意:正斜杠不是正则表达式中的元字符):

      'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'

    4. 您绝对希望流式传输此内容,因为 50GB 不适合内存。但是,如果您逐行处理,这会带来问题。如果您要替换的文本被分成多行怎么办?

    5. 如果您没有分割线问题,那么我认为 PowerShell 可以处理此问题。

    【讨论】:

    • @Keith,你真的信任 PowerShell ;) 我可能会担心 OutOfMemoryException 因为 50gb 足够大,可以收集少量内存泄漏......只是一个猜测。就我个人而言,我会直接使用 File.Open 并使用流并手动比较(无正则表达式)。
    • 难道不应该使用某种 XML API 来做到这一点吗?只是一个想法。不知道 SAX 或 StAX 在 .NET 中是否可用;我很少使用 XML,但是做一个字符串替换听起来不对。
    • .NET 有一个只进的光标样式阅读器 (XmlReader/XmlTextReader) - 一种与 SAX 推送方法有点不同的拉机制。这有点乏味,但当整个 Xml 文档无法放入内存时,这是一个很好的方法。
    • @stej,关于正则表达式的好点 - 看起来它不是必需的,可以用 String.Replace() 替换。
    【解决方案2】:

    powershell 字符串中的转义字符是反引号 ( ` ),而不是反斜杠 ( \ )。我会举一个例子,但反引号也被 wiki 标记使用。 :(

    您唯一应该逃避的是引号 - 没有句号等应该没问题。

    【讨论】:

      【解决方案3】:

      我有类似的需求(并且缺乏类似的 powershell 经验),但我从本页的其他答案中拼凑出一个完整的答案以及更多的研究。

      我还想避免正则表达式处理,因为我也不需要它——只是一个简单的字符串替换——但是在一个大文件上,所以我不希望它加载到内存中。

      这是我使用的命令(添加换行符以提高可读性):

      Get-Content sourcefile.txt
          | Foreach-Object {$_.Replace('http://example.com', 'http://another.example.com')}
          | Set-Content result.txt
      

      完美运行!从来没有占用太多内存(很明显它没有将整个文件加载到内存中),只是运行了几分钟然后就完成了。

      【讨论】:

      • 在 200 MB 文件上,PS 占用了 3.5 GB RAM。当我杀死它时 30%CPU。
      • 也许检查 -ReadCount-RAW happysysadm.com/2014/10/…
      【解决方案4】:

      这是我的看法,基于此处的其他一些答案:

      Function ReplaceTextIn-File{
        Param(
          $infile,
          $outfile,
          $find,
          $replace
        )
      
        if( -Not $outfile)
        {
          $outfile = $infile
        }
      
        $temp_out_file = "$outfile.temp"
      
        Get-Content $infile | Foreach-Object {$_.Replace($find, $replace)} | Set-Content $temp_out_file
      
        if( Test-Path $outfile)
        {
          Remove-Item $outfile
        }
      
        Move-Item $temp_out_file $outfile
      }
      

      这样称呼:

      ReplaceTextIn-File -infile "c:\input.txt" -find 'http://example.com' -replace 'http://another.example.com' 
      

      【讨论】:

        【解决方案5】:

        除了担心以块的形式读取文件以避免将其加载到内存中之外,您还需要经常转储到磁盘,以免将结果文件的全部内容存储在内存中。

        Get-Content sourcefile.txt -ReadCount 10000 | 
            Foreach-Object {
                $line = $_.Replace('http://example.com', 'http://another.example.com')
                Add-Content -Path result.txt -Value $line
            }
        

        -ReadCount <number> 设置一次读取的行数。然后ForEach-Object 在读取每一行时写入。对于一个填充了 SQL 插入的 30GB 文件,我使用了大约 200MB 的内存和 8% 的 CPU。同时,在我杀死它之前,在达到 3GB 内存时将其全部传输到 Set-Content

        【讨论】:

        • 鉴于 readcount 的默认值为 1,这实际上是一种改进吗?某些文件在一行中包含所有内容(例如最小化的 json)。
        • @duct_tape_coder 我觉得这取决于上行速度有多快。如果删除/更改读取计数有更好的效果,我会做一些测试并更新答案。我还将研究自发布此答案以来我已经意识到的其他一些选项。
        猜你喜欢
        • 2017-01-10
        • 1970-01-01
        • 2011-07-18
        • 2011-10-20
        • 1970-01-01
        • 1970-01-01
        • 2014-08-17
        • 1970-01-01
        相关资源
        最近更新 更多