簡體   English   中英

PowerShell - 刪除文本文件中分隔符之間的多行文本

[英]PowerShell - Removing multiple lines of text between delimiters in a text file

我編輯 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 的新手,我不知道如何在代碼中的每一行末尾考慮換行符。 感謝您的關注!

在 XML 文件上使用搜索和替換是非常不可取的,應該不惜一切代價避免,因為這樣很容易損壞 XML。

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

  • 加載 XML 文檔
  • 修改文檔樹
  • 將 XML 文檔寫回文件。

對於您的情況(“刪除標記之間的節點”),這可能如下所示:

  • 加載 XML 文檔
  • 按文檔順序查看所有 XML 節點
  • 當我們看到一條寫着“A6”的評論時,設置一個標志以從現在開始刪除節點
  • 當我們看到“A6 結束”的評論時,取消設置該標志
  • 收集所有應該刪除的節點(在標志打開時出現)
  • 在最后一步,刪除它們
  • 將 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"
}

在單個$node.Value上執行-replace是安全的。 對整個 XML 執行-replace不是。

筆記:

  • 通常,為了進行穩健的處理,您應該使用專用的 XML 解析器來解析 XML 文本。

  • 在手頭的特定情況下,使用正則表達式是一種方便的快捷方式,但需要注意的是,它僅適用於被刪除的行塊是自包含元素或元素序列 如果此假設不成立,則修改將使 XML 文檔無效。

    • 此外,可能存在字符編碼問題,因為將 XML 文件作為文本讀取並不會遵守文件的 XML 聲明中可能存在的顯式encoding屬性 - 有關詳細信息,請參閱底部部分。

    • 也就是說,下面的技術適用於修改沒有特定正式結構的純文本文件


  • 您需要使用s ( SingleLine ) regex 選項來確保. 也匹配換行符- 如果使用內聯,此類選項必須放在正則表達式開頭的(?...)內; 也就是說, '(?s)...'在這種情況下。

    • 臨時,您也可以使用解決方法[\\s\\S]而不是. ,正如x15所建議的那樣; 此表達式匹配任何作為空白字符的字符。 或非空白字符,因此匹配任何字符,包括換行符。
  • 要完全刪除感興趣的行,您還必須匹配前面和后面的換行符

(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 中默認為 ANSI,在無 BOM 的 UTF-8 PowerShell [Core] v6+ 中默認為 ANSI。
    • 如有疑問,請在Get-ContentSet-Content使用-Encoding參數
    • 有關更多信息,請參閱底部部分。
  • \\r?\\n匹配 Windows 風格的 CRLF 換行符和 Unix 風格的 LF-only 換行符。

  • 使用(?:\\r?\\n)? 而不是\\r?\\n如果換行符不能保證在感興趣的行之前/之后。

要驗證結果字符串是否仍然是有效的 XML 文檔,只需將命令(或其捕獲的結果) [xml][xml] : [xml] ((Get-Content ...) -replace ...)

如果您發現文檔已損壞,請使用Tomalak 的完全健壯但更復雜的 XML 解析答案


XML 文件和字符編碼:

如果您使用Get-Content將 XML 文件讀取為 text ,並且該文件既沒有 UTF-8 BOM 也沒有 UTF-16 / UTF-32 BOM,則Get-Content做出一個假設:它假設 ANSI 編碼(例如,Windows -1252) 在 Windows PowerShell 中,更明智的是,在 PowerShell [Core] v6+ 中使用 UTF-8 編碼。 由於Get-Content是通用的文本文件讀取 cmdlet,因此它知道 XML 輸入文件的 XML 聲明中的潛在encoding屬性

  • 如果您知道實際編碼,請使用-Encoding參數來指定它。

  • 使用具有相同值的-Encoding稍后使用Set-Content保存文件:與 PowerShell 中的通常情況一樣,一旦數據被文件讀取 cmdlet 加載到內存中,就不會保留有關其原始編碼的信息,並且使用諸如Set-Content類的文件寫入 cmdlet 稍后使用其固定的默認編碼,在 Windows PowerShell 中再次使用 ANSI,在 PowerShell [Core] v6+ 中使用無 BOM 的 UTF-8。 請注意,不幸的是,不同的 cmdlet 在 Windows PowerShell 中具有不同的默認值,而 PowerShell [Core] v6+ 值得稱贊的是始終默認為 UTF-8。

System.Xml.XmlDocument .NET 類型(其 PowerShell 類型加速器是[xml] )提供強大的 XML 解析如果文檔的 XML 聲明包含顯式encoding ,則使用其.Load().Save()方法提供更好的編碼支持命名使用的編碼的屬性

  • 如果存在這樣的屬性(例如, <?xml version="1.0" encoding="ISO-8859-1"?> ), .Load().Save()都會遵守它。

    • 即具有encoding屬性的輸入文件將被正確讀取,並以相同的編碼保存。
    • 當然,這假設在encoding屬性中命名的encoding反映了輸入文件的實際編碼。
  • 否則,如果文件沒有 BOM ,則假定(BOM-less) UTF-8,就像 PowerShell [Core] v6+ 的Get-Content / Set-Content - 這是明智的,因為 XML 文檔既沒有encoding屬性也沒有根據W3C XML 建議,UTF-8 或 UTF-16 BOM 應默認為 UTF-8; 如果文件確實有 BOM,則只允許使用 UTF-8 和 UTF-16,而無需在encoding屬性中命名encoding ,盡管實際上XmlDocument也可以正確讀取帶有 BOM 的 UTF-32 文件。

    • 這意味着.Save()不會保留沒有encoding屬性的(帶 BOM 的)UTF-16 或 UTF-32 文件encoding ,而是創建一個無 BOM 的 UTF-8 文件。

    • 如果您想檢測文件的實際編碼 - 從其 BOM 推斷/不存在,或者,如果存在, encoding屬性,請通過XmlTextReader實例讀取您的文件:

       # 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屬性相矛盾,如果有的話),您可以明確指定編碼(相當於使用-EncodingGet-Content / Set-Content ),使用.Load() / .Save()方法重載接受Stream實例,通過使用給定編碼構造的StreamReader / StreamWriter實例; 例如:

       # Get the encoding to use, matching the input file's. # Eg, if the input file is ISO-8859-1-encoded, but lacks # an `encoding` attribute in the XML declaration. $enc = [System.Text.Encoding]::GetEncoding('ISO-8859-1') # Create a System.Xml.XmlDocument instance. $xmlDoc = [xml]::new() # Create a stream reader for the input XML file # with explicit encoding. $streamIn = [System.IO.StreamReader]::new( "$pwd/some.xml", # IMPORTANT: use a FULL PATH $enc ) # Read and parse the file. $xmlDoc.Load($streamIn) # Close the stream $streamIn.Close() # ... process the XML DOM. # Create a stream *writer* for saving back to the file # with the same encoding. $streamOut = [System.IO.StreamWriter]::new( "$pwd/t.xml", # IMPORTANT: use a FULL PATH $false, # don't append $enc # same encoding as above in this case. ) # Save the XML DOM to the file. $xmlDoc.Save($streamOut) # Close the stream $streamOut.Close()

將文件路徑傳遞給 .NET 方法一般警告:始終使用完整路徑,因為 .NET 對當前目錄的理解通常與 PowerShell 不同。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM