【问题标题】:Parse XML File with variable types using Powershell使用 Powershell 解析具有变量类型的 XML 文件
【发布时间】:2018-04-06 13:39:59
【问题描述】:

我正在尝试从脚本中删除变量的定义,并从类似于下面的 XML 配置文件中读取它们:

XML 文件

<?xml version="1.0" encoding="utf-8" ?>
<settings>
    <process>FALSE</process>
    <xmlDir>\\serv1\dev</xmlDir>
    <scanDir>\\serv1\dev</scanDir>
    <processedDir>\\serv1\dev\done</processedDir>
    <errorDir>\\serv1\dev\err</errorDir>
    <log>\\serv1\dev\log\dev-Log##DATE##.log</log>
    <retryDelay>5</retryDelay>
    <retryLimit>3</retryLimit>
</settings>

然后用下面的代码解析脚本中的 XML:

[xml]$configFile = Get-Content $PSScriptRoot\$confFile
$settings = $configFile.settings.ChildNodes
foreach ($setting in $settings) {  
    New-Variable -Name $setting.LocalName -Value ($setting.InnerText -replace '##DATE##',(get-date -f yyyy-MM-dd)) -Force
}

这很好用,但问题是它们都被读取为字符串,但我需要一些整数。为了解决这个问题,我必须在创建变量后将它们更改为整数,如下所示:

$retryDelay = ([int]$retryDelay)
$retryLimit = ([int]$retryLimit)

虽然这可行,但我希望在 XML 中包含其他变量,例如 boolean $true / $false(并作为布尔值读入),并且希望 foreach 能够处理它们的类型而不是附加行在脚本中。任何线索表示赞赏。

【问题讨论】:

  • XML 没有类型信息,除非您添加它。什么都不知道,每个元素和属性值都是一个字符串。也许你想使用 JSON 作为你的配置文件格式?

标签: xml powershell


【解决方案1】:

首先,永远不要像这样读取 XML 文件。这会破坏内置于 XML 解析器中的编码检测,迟早会导致您的数据遭到破坏。

# BAD, DO NOT USE
[xml]$configFile = Get-Content $PSScriptRoot\$confFile

正确地读取 XML 文件是这样工作的 - 创建一个新的 XML 对象并让它处理文件加载:

$configFile = New-Object xml
$configFile.Load("$PSScriptRoot\$confFile")

其次,我强烈建议不要从文件中创建全局变量。这是一种不好的风格,因为它很容易通过盲目地覆盖现有变量来破坏您的程序。使用哈希来存储文件中的值,或者直接使用 XML 文件作为您的配置。

$config = @{}

foreach ($setting in $configFile.SelectNodes("/settings/*") ) {
    $config[$setting.Name] = $setting.InnerText
}

第三,XML 没有固有的数据类型信息。在您添加有关它的更多信息之前,一切都是字符串。一种方法可能是type 属性(type="string" 可以视为默认值):

<settings>
    <process type="boolean">FALSE</process>
    <xmlDir type="string">\\serv1\dev</xmlDir>
    <scanDir type="string">\\serv1\dev</scanDir>
    <processedDir type="string">\\serv1\dev\done</processedDir>
    <errorDir type="string">\\serv1\dev\err</errorDir>
    <log type="string">\\serv1\dev\log\dev-Log##DATE##.log</log>
    <retryDelay type="int">5</retryDelay>
    <retryLimit type="int">3</retryLimit>
</settings>

当然,type 属性本身没有任何意义。您需要编写关注这些属性并进行必要类型转换的代码(if ($setting.type -eq "boolean") { ... } 等)。

第四,我相信只要使用 JSON 作为配置文件格式,你会很多变得更好。它更易于编辑,并且具有固有的数据类型信息。

{
    "settings": {
        "process": false,
        "xmlDir": "\\\\serv1\\dev",
        "scanDir": "\\\\serv1\\dev",
        "processedDir": "\\\\serv1\\dev\\done",
        "errorDir": "\\\\serv1\\dev\\err",
        "log": "\\\\serv1\dev\\log\\dev-Log##DATE##.log",
        "retryDelay": 5,
        "retryLimit": 3
    }
}

使用ConvertFrom-JSON cmdlet 解析数据。使用Get-Content -Encoding UTF8 阅读。

在处理文本文件时使用Encoding 参数很重要,在使用Set-ContentOut-File 编写文件时也是如此。这里没有隐藏的魔法可以做正确的事情,你必须明确编码。

这里有一些关于Out-FileSet-Content 行为的更深入的信息。 Powershell set-content and out-file what is the difference?

【讨论】:

  • 感谢@Tomalak 提供的信息,非常感谢!我认为按照您的建议使用 JSON 可能更容易。我想我可以使用 foreach 遍历 JSON,类似于我解析 XML 的方式?
  • 好吧。如果您像所示那样构建 JSON,则迭代必须基于 $config.settings | Get-Member -Type NoteProperty | ...。另请参阅this earlier answer of mine 以获得更多解释。
  • 但是,正如我所说,我不喜欢使用循环从配置文件中填充变量的整个想法。您的配置文件包含您的设置。您的程序知道需要什么设置、它们有什么名称以及它们有什么默认值。您可以在程序中按原样使用$config 变量,无需将其内容转移到其他任何地方。需要时直接使用$retryLimit = $config.settings.retryLimit或直接使用$config.settings.retryLimit即可。
  • 现在,如果您想处理$config.settings.retryLimit 可能未设置的情况,也有办法做到这一点。在 JavaScript 中,你会说 config.settings.retryLimit || 3 使其默认为 3,以防它未定义。只是在 Javascript 中,访问在 Powershell 中未设置的属性并不是错误。你只会得到$null。在 Powershell 中将其设置为默认值 3 的构造是 ($config.settings.retryLimit, 3 -ne $null)[0] 并解释为什么会这样:stackoverflow.com/a/17647824/18771
  • 希望有人发现这一点 - 如果您尝试检查 Windows 包过滤平台防火墙配置导出 - 由 netsh wfp show filters 生成 - 您需要使用此技术而不是get-content
【解决方案2】:

我同意 Tomalak 的回答,JSON 可能更适合您的用例。这是一个实际示例,向您展示如何使用它。这是使用从哈希表创建的自定义对象来生成 JSON 并将其保存到文件中:

$Config = [pscustomobject]@{
    Process = $false
    xmldir = '\\serv1\dev'
    scanDir = '\\serv1\dev'
    processedDir = '\\serv1\dev\done'
    errorDir = '\\serv1\dev\err'
    log = '\\serv1\dev\log\dev-Log##DATE##.log'
    retryDelay = 5
    retryLimit = 3
}

$Config | ConvertTo-Json | Out-File .\config.txt -Encoding UTF8

这会创建如下所示的 JSON:

{
    "Process":  false,
    "xmldir":  "\\\\serv1\\dev",
    "scanDir":  "\\\\serv1\\dev",
    "processedDir":  "\\\\serv1\\dev\\done",
    "errorDir":  "\\\\serv1\\dev\\err",
    "log":  "\\\\serv1\\dev\\log\\dev-Log##DATE##.log",
    "retryDelay":  5,
    "retryLimit":  3
}

可以这样读:

$Settings = Get-Content .\config.txt -Encoding UTF8 | ConvertFrom-Json

由于您可以看到 JSON 存储变量的方式,PowerShell 在读回变量时可以更好地正确键入它们。

【讨论】:

  • Get-Content 既不默认为 UTF-8 也不具有任何文件编码检测魔法,但这是许多人的基本假设。读取/写入文本文件时,始终使用显式 Encoding 设置。
  • 请参阅此处:stackoverflow.com/questions/10655788/…,了解有关 Out-FileSet-Content 如何表现和处理文件编码的详细信息。
  • @Mark Wragg 非常感谢。非常感谢您的帮助。
猜你喜欢
  • 2022-11-16
  • 2021-01-02
  • 2020-12-30
  • 2020-10-22
  • 2018-03-06
  • 2017-02-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多