HOWTO · PowerShell
如何使用 PowerShell 解析 XML 文件
本文将讨论如何使用 Windows PowerShell 正确解析 XML 文件。
本页内容
在 PowerShell 中提取 XML 值时,可以使用带 XPath 表达式的 Select-Xml 快速查询;需要浏览已加载的文档时,可以使用 System.Xml.XmlDocument;需要以流的方式向前读取时,可以使用 System.Xml.XmlTextReader。以下示例只读取 XML,使用一份简单的计算机清单,并展示元素文本与属性值的区别。
使用 Select-Xml 解析 XML 元素和属性
Select-Xml 会计算 XPath 表达式并返回匹配对象。每个匹配对象都通过其 Node 属性提供选中的 XML 节点。使用 InnerText 读取元素文本;InnerXml 返回元素内部包含的标记,适用于需要将这部分内容作为 XML 使用的情况。
运行此示例:
$path = Join-Path ([System.IO.Path]::GetTempPath()) "computers-$([guid]::NewGuid()).xml"
@'
<Computers>
<Computer name="WINPC-01" ip="127.0.0.1"><Name>WINPC-01</Name></Computer>
<Computer name="WINPC-02" ip="192.168.1.105"><Name>WINPC-02</Name></Computer>
</Computers>
'@ | Set-Content -Path $path -Encoding utf8
Write-Output 'Element names:'
Select-Xml -Path $path -XPath '/Computers/Computer/Name' |
ForEach-Object { $_.Node.InnerText }
Write-Output 'Attribute names:'
Select-Xml -Path $path -XPath '/Computers/Computer' |
ForEach-Object { $_.Node.GetAttribute('name') }
Remove-Item -Path $path
Element names:
WINPC-01
WINPC-02
Attribute names:
WINPC-01
WINPC-02
XPath /Computers/Computer/Name 按照从根节点到各个 Name 元素的层级进行匹配。要选择属性,请选择 Computer 元素,并从每个匹配节点读取 name 属性。请根据 XML 的结构调整 XPath;元素名称及其大小写必须与文档一致。如果 XML 内容已在字符串中,Select-Xml 也可以通过 -Content 参数接收它。
输出分为两组,标签 “Element names” 和 “Attribute names” 保持英文:“Element names” 下方列出元素文本,“Attribute names” 下方列出 name 属性值。
使用 System.Xml.XmlDocument 加载并查询 XML
System.Xml.XmlDocument 会将文档加载为可供浏览的树结构。当脚本需要进行多个相关查询,或需要在父节点与子节点之间移动时,这种方式很方便。它的取舍是文档会保留在内存中,因此对于非常大的文件,流式读取器可能更合适。
此示例会加载一个临时 XML 文件,选择每个计算机节点,并在读取前检查是否存在 Name 子节点。如果 XML 架构允许省略该元素,这项检查就很重要;找不到匹配项时,SelectSingleNode() 会返回 $null。
$path = Join-Path ([System.IO.Path]::GetTempPath()) "computers-$([guid]::NewGuid()).xml"
@'
<Computers>
<Computer><Name>WINPC-01</Name></Computer>
<Computer><Ip>192.168.1.105</Ip></Computer>
</Computers>
'@ | Set-Content -Path $path -Encoding utf8
$document = [System.Xml.XmlDocument]::new()
$document.Load($path)
foreach ($computer in $document.SelectNodes('/Computers/Computer')) {
$nameNode = $computer.SelectSingleNode('Name')
if ($null -ne $nameNode) {
Write-Output $nameNode.InnerText
}
else {
Write-Output 'No Name element'
}
}
Remove-Item -Path $path
读取文本值时使用 InnerText,而不是 InnerXml;后者会返回元素内部嵌套的标记。如果文档使用默认 XML 命名空间,不带前缀的 XPath 表达式可能无法匹配其中的元素;请创建 XmlNamespaceManager,并在查询中使用前缀。
使用 System.Xml.XmlTextReader 以流的方式读取 XML
System.Xml.XmlTextReader 通过只读、只能向前移动的读取器,按照文档顺序提供 XML 节点。它不会构建完整的文档树,因此处理大型输入时可以减少内存使用。由于读取器不能向后浏览,应在相关节点经过时提取所需值。请在 finally 块中关闭读取器,以便即使处理提前停止也能释放文件句柄。
以下示例会读取每个 Computer 起始元素的 name 属性。它会自行创建 XML 文件,并在处理完成后释放读取器。
$path = Join-Path ([System.IO.Path]::GetTempPath()) "computers-$([guid]::NewGuid()).xml"
@'
<Computers>
<Computer name="WINPC-01" ip="127.0.0.1" />
<Computer name="WINPC-02" ip="192.168.1.105" />
</Computers>
'@ | Set-Content -Path $path -Encoding utf8
$reader = [System.Xml.XmlTextReader]::new($path)
try {
while ($reader.Read()) {
if ($reader.NodeType -eq [System.Xml.XmlNodeType]::Element -and
$reader.Name -eq 'Computer') {
Write-Output $reader.GetAttribute('name')
}
}
}
finally {
$reader.Dispose()
Remove-Item -Path $path
}
如果要读取元素文本而不是属性,请检查匹配的元素名称,并在读取器定位到该元素时读取其文本。ReadString() 会读取文本内容并推进读取器,因此在更复杂的循环中要考虑这项移动。使用完毕后请释放读取器;如果一直打开,输入文件可能会保持锁定。
选择适合的 PowerShell XML 解析方法
如果用简短的 XPath 查询从文件或字符串中提取少量节点最清晰,请选择 Select-Xml。如果需要方便地浏览树结构、执行多个查询或访问相关节点,请选择 System.Xml.XmlDocument。如果可以按顺序处理节点,并希望避免将完整的树加载到内存中,请选择 System.Xml.XmlTextReader。
这三种方法都可以读取元素和属性。主要区别在于它们提供文档的方式:Select-Xml 返回 XPath 匹配结果,XmlDocument 支持浏览已加载的树结构,而 XmlTextReader 则以流的方式向前移动。请根据文档大小和查询模式选择合适的方法;如果输入可能省略某些节点或属性,也要检查它们是否存在。