PowerShell-删除文本文件中定界符之间的多行文本

时间:2019-12-03 16:46:13

标签: regex xml powershell replace

我编辑XML文件,并使用PowerShell在记事本中打开它们并替换文本字符串。给定两个不同的分隔符(开始和结束),它们在XML文件中出现多次,我想完全删除分隔符之间的文本(是否同时删除分隔符也没关系给我)。

在下面的示例文本中,我想完全删除起始定界符和结束定界符之间的文本,但保留之前和之后的所有文本。

我面临的问题是每行文本的末尾都有换行符,这使我无法执行简单操作:

-replace "<!--A6-->.*?<!--A6 end-->", "KEVIN"

开始分隔符:

<!--A6-->

停止定界符:

<!--A6 end-->

示例文字:

<listItem>
<para>Apple iPhone 6</para>
</listItem>
<listItem>
<para>Apple iPhone 8</para>
</listItem>
<!--A6-->
<listItem>
<para>Apple iPhone X</para>
</listItem>
<!--A6 end-->
</randomList></para>
</levelledPara>
<levelledPara>
<!--A6-->
<title>Available Apple iPhone Colors</title>
<para>The current iPhone model is available in
the follow colors.  You can purchase this model
in store, or online.</para>
<!--A6 end-->
<para>If the color option that you want is out
of stock, you can find them at the following
website link.</para>

当前代码:

$Directory = "C:\Users\hellokevin\Desktop\PSTest"

$FindBook = "Book"

$ReplaceBook = "Novel"

$FindBike = "Bike"

$ReplaceBike = "Bicycle"

Get-ChildItem -Path $Directory -Recurse |
    Select-Object -Expand FullName|
        ForEach-Object {
            (Get-Content $_) -replace $FindBook,$ReplaceBook -replace "<!--A6-->.*?<!--A6 end-->", "KEVIN" |
            Set-Content ($_ + "_new.xml")
        }

任何帮助将不胜感激。作为PowerShell的新手,我不知道如何在代码的每一行末尾添加换行符。感谢您的光临!

2 个答案:

答案 0 :(得分:1)

极端是不建议在XML文件上使用搜索和替换的,因此应不惜一切代价避免,因为这样很容易损坏XML。

有更好的修改XML的方法,它们都遵循以下模式:

  • 加载XML文档
  • 修改文档树
  • 将XML文档写回到文件中。

对于您的情况(“删除标记之间的节点”),可能如下:

  • 加载XML文档
  • 按文档顺序查看所有XML节点
  • 当我们看到一条注释显示为“ A6”时,设置一个标志以从现在开始删除节点
  • 当我们看到一条注释显示为“ A6 end”时,请取消设置该标志
  • 收集所有应删除的节点(在标记打开时出现)
  • 最后一步,将其删除
  • 将XML文档写回到文件中。

以下程序将完全做到这一点(并同时删除“ A6”注释本身):

$doc = New-Object xml
$doc.Load("C:\path\to\your.xml")

$toRemove = @()
$A6flag = $false
foreach ($node in $doc.SelectNodes('//node()')) {
    if ($node.NodeType -eq "Comment") {
        if ($node.Value -eq 'A6') {
            $A6flag = $true
            $toRemove += $node
        } elseif ($node.Value -eq 'A6 end') {
            $A6flag = $false
            $toRemove += $node
        }
    } elseif ($A6flag) {
        $toRemove += $node
    }
}
foreach ($node in $toRemove) {
    [void]$node.ParentNode.RemoveChild($node)
}

$doc.Save("C:\path\to\your_modified.xml")

您也可以在foreach循环内进行字符串替换:

if ($node.NodeType -eq "Text") {
    $node.Value = $node.Value -replace "Apple","APPLE"
}

在单个-replace上执行$node.Value是安全的。不在整个XML上执行-replace

答案 1 :(得分:0)

注意:

  • 通常,为了进行稳健的处理,应使用专用XML解析器 来解析XML文本。

  • 在特定情况下,使用 regex 便捷快捷方式 ,但需要注意的是,仅因为删除的线段是自包含元素或元素序列 而起作用;如果此假设不成立,则修改将使XML文档无效。

    • 此外,可能存在字符编码问题,因为将XML文件读取为文本并不符合显式要求。文件的XML声明中可能存在encoding属性-有关详细信息,请参见底部。

    • 也就是说,以下技术适用于修改没有特定形式结构的纯文本文件


  • 您需要使用sSingleLineregex option来确保.也匹配换行符-这样的选项,如果是内联使用,则必须放在正则表达式开始处的(?...)内;即'(?s)...'

    • 临时,您可以按照x15的建议使用替代方法[\s\S]代替.;此表达式匹配任何为空白char的字符。或非空格字符,因此匹配任何字符,包括换行符。
  • 要完全删除感兴趣的行,您还必须匹配前面和后面的换行符

(Get-Content -Raw file.xml) -replace '(?s)\r?\n<!--A6-->.*?<!--A6 end-->\r?\n'
  • Get-Content -Raw file.xml将文件作为一个整体读入内存 (单个字符串)。

    • Get-Content在没有BOM的情况下对文件的字符编码进行了假设:Windows PowerShell假定为ANSI编码,而PowerShell [Core] v6 +现在明智地假定为UTF-8。由于Get-Content是读取cmdlet的通用文本文件,因此 不会意识到XML输入文件的XML声明中潜在的encoding属性(例如,
      <?xml version="1.0" encoding="ISO-8859-1"?>
    • 同样,Set-Content在Windows PowerShell和无BOM的UTF-8 PowerShell [Core] v6 +中默认为ANSI。
    • 如有疑问,请同时使用-EncodingGet-Content的{​​{1}}参数
    • 有关详细信息,请参见底部。
  • Set-Content匹配Windows风格的CRLF换行符和Unix风格的仅LF换行符。

  • 如果不能保证换行符在感兴趣的行之前/之后,请使用\r?\n而不是(?:\r?\n)?

要验证结果字符串仍然是有效的XML文档,只需将命令(或其捕获的结果)强制转换为\r?\n[xml]

如果发现文档已损坏,请使用Tomalak's fully robust, but more complex XML-parsing answer


XML文件和字符编码:

如果您使用[xml] ((Get-Content ...) -replace ...)来读取XML文件作为文本,并且该文件既没有UTF-8 BOM也没有UTF-16 / UTF-32 BOM,{{1 }}进行假设:在Windows PowerShell中采用ANSI编码(例如Windows-1252),在PowerShell [Core] v6 +中更合理地采用UTF-8编码。由于 Get-Content是读取通用文本文件的cmdlet,因此不知道XML输入文件的XML声明中潜在的Get-Content属性强>。

  • 如果您知道实际的编码,请使用Get-Content参数进行指定。

  • 使用具有相同值的encoding,以后再用-Encoding保存文件:通常在PowerShell中,一旦文件读取cmdlet将数据加载到内存中, 不会保留有关其原始编码的信息 ,后来使用文件写入cmdlet(例如-Encoding)会使用其固定的默认编码,该编码也是ANSI Windows PowerShell和PowerShell [Core] v6 +中的无BOM的UTF-8。请注意,不幸的是,不同的cmdlet在Windows PowerShell中具有不同的默认值,而PowerShell [Core] v6 +应该一致地默认为UTF-8。

System.Xml.XmlDocument .NET类型(其PowerShell类型加速器为Set-Content)提供强大的XML解析,并使用其Set-Content[xml]方法提供更好的编码支持 if 如果文档的XML声明包含显式的.Load()属性,则为使用的编码命名:

  • 如果存在这样的属性(例如.Save()),则encoding<?xml version="1.0" encoding="ISO-8859-1"?>都将使用它。

    • 这是具有.Load()属性的输入文件,将被正确读取,并以相同的编码保存。
    • 当然,这假定.Save()属性中命名的编码反映的是输入文件的实际编码。
  • 否则,如果文件中没有BOM ,则,假定假定为 (无BOM)UTF-8,与PowerShell [Core] v6 +的{{1 }} / encoding-这很明智,因为根据{{3},既没有encoding属性也没有UTF-8或UTF-16 BOM的XML文档应默认为UTF-8。 };如果文件确实具有BOM表,则只允许使用UTF-8和UTF-16,而无需在Get-Content属性中命名编码,尽管实际上{ {1}}还可以正确读取带有BOM的UTF-32文件。

    • 这意味着Set-Content保留(带有BOM的)UTF-16或UTF-32文件的编码。 encoding属性,并将创建一个无BOM的UTF-8文件。

    • 如果您要检测文件的实际编码-根据文件的BOM /缺少文件的编码或encoding属性(如果存在)来推断,请通过XmlDocument实例:

      .Save()
    • 如果给定文件是不兼容,并且您知道实际使用的编码和/或您想保存给定文件(请确保它与encoding属性并不矛盾(如果有),您可以显式指定编码(相当于将encodingXmlTextReader / # Create an XML reader. $xmlReader = [System.Xml.XmlTextReader]::new( "$pwd/some.xml" # IMPORTANT: use a FULL PATH ) # Read past the declaration, which detects the encoding, # whether via the presence / absence of a BOM or an explicit # `encoding` attribute. $null = $xmlReader.MoveToContent() # Report the detected encoding. $xmlReader.Encoding # You can now pass the reader to .Load(), if needed # See next section for how to *save* with the detected encoding. $xmlDoc = [xml]::new() $xmlDoc.Load($xmlReader) $xmlReader.Close() 一起使用),使用encoding / -Encoding方法重载通过使用给定编码构造的Get-Content / Set-Content实例接受.Load()实例;例如:

      .Save()

将文件路径传递给.NET方法的一般警告:始终使用完整路径 ,因为.NET对当前目录的理解通常不同于PowerShell的思想。