PowerShell 和 XML
介绍 PowerShell 如何轻松地以编程方式处理 XML
引言
XML无处不在。因此,许多人发现需要处理XML,而传统的文本编辑器无法胜任。有些编辑器提供不错的格式化功能(如 Notepad++),但它们不提供以编程方式检查XML底层数据的机制。
PowerShell使处理XML变得非常简单。它将XML元素转换为.NET对象的属性,无需编写任何解析代码。所以您只需要在计算机上安装PowerShell即可开始!
快速示例
这是一个快速示例,展示PowerShell如何将XML元素和属性映射到对象属性。它假设file.xml文件存在并包含以下文本:
<!-- file.xml -->
<employees>
<employee id="101">
<name>Frankie Johnny</name>
<age>36</age>
</employee>
<employee id="102">
<name>Elvis Presley</name>
<age>79</age>
</employee>
<employee id="301">
<name>Ella Fitzgerald</name>
<age>102</age>
</employee>
</employees>
加载file.xml到XmlDocument
对象并访问节点/属性的示例。
PS C:\> $xml = [xml](get-content file.xml)
PS C:\> $xml
#comment employees
-------- ---------
file.xml employees
PS C:\> $xml.employees
employee
--------
{Frankie Johnny, Elvis Presley, Ella Fitzgerald}
PS C:\> $xml.employees.employee
id name age
-- ---- ---
101 Frankie Johnny 36
102 Elvis Presley 79
301 Ella Fitzgerald 102
PS C:\> $xml.employees.employee[0].name
Frankie Johnny
PS C:\> $xml.employees.employee[1].age
79
PS C:\>
cmdlet `get-content`相当于UNIX中的`cat`,它以文本行的形式返回文件内容。
`get-content` cmdlet前面的方括号`[xml]`表示一个类型对象。在这种情况下,它将从`get-content file.xml`返回的文本转换为XmlDocument
对象。一旦您拥有一个XmlDocument
对象,PowerShell内置的XML支持就会生效。单个XMLElement
对象将其子节点作为属性呈现。这意味着元素名称就是属性。在这种情况下,根元素是<employees>
,因此它作为属性在`$xml`变量上进行访问。试试这个:输入'`$xml.em`'然后按TAB键。是的,元素名称支持Tab自动完成。这让您更容易一些。
等等,还有更多
您仍然可以访问底层XmlElement
对象的各种方法。执行命令'`$xml | gm`'以获取任何XML节点可用的所有方法和属性列表。这意味着您可以使用XPath
查询语法调用SelectNodes()
和SelectSingleNode()
。以下是使用XmlElement
上的SelectNodes()
方法的示例。
PS C:\> $xml = (Get-Content file.xml)
PS C:\> $xml = [xml](Get-Content file.xml)
PS C:\> $xml.SelectNodes("/employees/employee")
id name age
-- ---- ---
101 Frankie Johnny 36
102 Elvis Presley 79
301 Ella Fitzgerald 102
通过将这些结果通过PowerShell管道传递,您可以将这些结果传递给其他命令,如select
-object、where
-object和foreach
-object,以修剪值或基于值执行命令,从而获得非常富有表现力和强大的脚本体验。**注意**:我使用的是`get-content` (gc) 的别名。大多数PowerShell cmdlet都有其全名的缩写别名。
在下面的4个示例中,我们使用where
-object和foreach
-object来检查employee节点的属性,以便我们只返回符合特定条件的结果或以某种方式处理它们。第一个示例看起来应该返回年龄大于50的2个员工,但它并没有。原因是XML对象的属性始终是字符串。因此,它进行的是字典序比较。这可以通过使用类型转换运算符`[int
]`将值转换为int
来轻松解决,这与我们将`get-content`的字符串输出转换为xml文档`[xml
]`的方式非常相似。
第2个命令显示,转换为int
后可以正确返回年龄大于50的员工。第3个示例显示您可以调用返回的string
对象上的方法。因此,假设您的员工ID的第一个数字表示部门,您可以使用String
对象上的startsWith()
方法轻松实现。`$_`是什么?`$_`是一个代表当前管道对象的变量。使用它是为了让您能够操作传递给函数/脚本块的当前对象。where
-object cmdlet接受一个脚本块(由大括号`{ }`表示),并执行其中的powershell脚本。如果表达式评估为true
,则对象将通过管道传递到下一个命令(在本例中是默认的输出命令);当where
-object脚本块评估为false
时,对象将被“丢弃”。
如果您想以某种方式处理XML节点的值,可以使用foreach
-object,它也接受一个脚本块。事实上,第4个命令使用foreach
来创建所有员工节点值的连接字符串。
PS C:\> $xml = [xml](gc file.xml)
PS C:\> $xml.employees.employee | where { $_.age -gt 50 }
id name age
-- ---- ---
102 Elvis Presley 79
PS C:\> $xml.employees.employee | where { [int]$_.age -gt 50 }
id name age
-- ---- ---
102 Elvis Presley 79
301 Ella Fitzgerald 102
PS C:\> $xml.employees.employee | where { $_.id.startsWith("1") }
id name age
-- ---- ---
101 Frankie Johnny 36
102 Elvis Presley 79
PS C:\> $xml.employees.employee | foreach { $_.id + ":" + $_.name + ":" + $_.age }
101:Frankie Johnny:36
102:Elvis Presley:79
301:Ella Fitzgerald:102
一个非常简单愚蠢的例子
RSS feed易于访问,它们基本上就是XML。所以让我们看看PowerShell如何处理RSS feed。互联网上已经有很多文章展示了这一点有多么容易,但我认为我应该也包含进来。从您喜欢的网站抓取一个RSS feed,看看是否有任何文章符合某些条件。我将抓取TMZ的RSS feed,并检查是否有包含我的名字“scott”的文章。首先,我想展示如何抓取XML RSS文档并显示文章,使用其一个或多个属性。这可以通过使用.NET类System.Net.WebClient
的DownloadString(String url)
方法来实现。非常容易。
接下来,我将选择标题中包含“scott”的文章。我将使用-imatch Regex Powershell运算符。它对目标字符串在输入字符串中的任何位置进行不区分大小写的搜索。**注意**:您的结果可能不同,因为TMZ网站上的文章每天都在变化。另外,我使用的是'ft',它是'format-table'的别名,它允许我选择要显示的属性。
PS C:\powershell> $url = "http://www.tmz.com/rss.xml"
PS C:\powershell> $feed=[xml](new-object system.net.webclient).downloadstring($url)
PS C:\powershell> $feed.rss.channel.item | format-table title,link
title link
----- ----
Report: Marie Osmond's Son Commits Suicide http://www.tmz.com/2010/02/27/marie-osmond-son-commits-s...
Scotty Lago Conspiracy Theory -- Up in Smoke http://www.tmz.com/2010/02/27/scotty-lago-michael-phelps...
Brittany Murphy -- 109 Mystery Pills http://www.tmz.com/2010/02/27/brittany-murphy-prescripti...
Liev Schreiber to the Rescue! http://www.tmz.com/2010/02/27/liev-schreiber-broadway-au...
Scotty Lago's Olympic Conspiracy Theory http://www.tmz.com/2010/02/27/scotty-lago-olympics-vanco...
Britney Spears -- The Blonde Is Back http://www.tmz.com/2010/02/27/britney-spears-blonde-hair...
Former 'Idol' Elliott Yamin in Chile During Quake http://www.tmz.com/2010/02/27/american-idol-elliott-yami...
'Pants on the Ground' Guy -- King of Vegas http://www.tmz.com/2010/02/27/pants-on-the-ground-guy-la...
Reality to Nas -- 'Memba Me? http://www.tmz.com/2010/02/27/nas-federal-tax-bill-lien-...
Nic Cage's Manager -- Get Me Outta This Suit! http://www.tmz.com/2010/02/27/nic-cage-manager-sam-levin...
Audrina Patridge: Bad Acting Got Me Towed http://www.tmz.com/2010/02/27/audrina-patridge-tow-car-t...
TMZ's Bangin' Backside Contest -- Bootyfull! http://www.tmz.com/2010/02/27/tmzs-bangin-backside-conte...
Conan the Barbarian -- One Hairy Situation http://www.tmz.com/2010/02/27/conan-the-barbarian-in-ano...
Carol Brady vs. Mrs. C: Who'd You Rather? http://www.tmz.com/2010/02/27/carol-brady-vs-mrs-c-whod-...
Nicole Richie & the Chocolate Factory http://www.tmz.com/2010/02/27/nicole-richie-and-the-choc...
Guess Who This Guy Turned Into! http://www.tmz.com/2010/02/27/guess-who-this-guy-turned-...
Jon Cryer Alleged Hit - Mexico Gang Connection? http://www.tmz.com/2010/02/27/jon-cryer-alleged-hit-mexi...
Avril & Deryck's Divorce Takes a Backseat http://www.tmz.com/2010/02/27/avril-lavigne-and-derycks-...
World to Joanna Krupa's Mom -- Thank You! http://www.tmz.com/2010/02/27/joanna-krupa-dancing-with-...
K-Fed Confused by Green Mystery Substance http://www.tmz.com/2010/02/27/k-fed-kevin-federline-shop...
PS C:\powershell> $feed.rss.channel.item | where { $_.title -imatch "scott" } | ft title,link
title link
----- ----
Scotty Lago Conspiracy Theory -- Up in Smoke http://www.tmz.com/2010/02/27/scotty-lago-michael-phelps...
Scotty Lago's Olympic Conspiracy Theory http://www.tmz.com/2010/02/27/scotty-lago-olympics-vanco...
细则和注意事项
“item”属性问题
Powershell会自动为每个XmlElement
添加一个“Item
”属性。这使得可以像哈希表一样访问其属性,如'`$feed["rss"]`'。由于RSS文档包含'Item'节点,PowerShell在尝试访问它们时会报错。所以您必须通过更改名称(我知道这很烦人)或编写代码来绕过“item”节点,就像上面的示例一样。下面是一个在尝试访问底层“Item”节点时看到的错误的示例。
PS C:\powershell> $feed.rss.channel
format-default : The member "Item" is already present.
+ CategoryInfo : NotSpecified: (:) [format-default], ExtendedTypeSystemException
+ FullyQualifiedErrorId :
AlreadyPresentPSMemberInfoInternalCollectionAdd,Microsoft.PowerShell.Commands.FormatDefa
ultCommand
隐藏方法
XmlDocument
和XmlElement
的一些底层方法并未通过`get-member`公开。但您可以从.NET API文档中轻松找到它们。并且类定义的任何属性都需要使用它们的get方法形式(`get_Property()`)来访问。建议了解底层XmlElement
实例的方法或随身携带API文档。
更新XML
PowerShell中没有设置XmlElements
值的本机支持。您必须像在C#中一样调用底层XmlElement
/XmlDocument
对象的get方法。但请记住,当您执行`'$xml | gm'`时它们不会显示。了解XML .NET类(XmlElement
、XmlDocument
)的方法在这里很有帮助。
其他资源
描述“item”属性问题的文章,以及一些关于如何在PowerShell中修改XML文档并将其保存回来的示例
后续步骤
在我目前的工作中,我正在使用Lucene为我们的音乐目录(包括专辑、曲目和艺术家)构建搜索索引。Lucene是一个开源的信息检索库。它被广泛使用,并且有Java版本和.NET端口。我们在这里工作时使用Java,但我一直在PowerShell中编写一些围绕Lucene.NET的脚本。不幸的是,Lucene.NET端口比Java版本落后几个版本,但仍然非常有用(最新版本是2.4)。在我的下一篇文章中,我计划结合PowerShell强大的XML处理功能,无缝集成Lucene.NET,以便可以非常轻松地从网上获取XML文档/RSS feed,并对它们进行索引以实现快速检索。
结论
希望这篇文章展示了使用Windows PowerShell处理XML的简便性。除了强大的XML处理能力之外,我希望我还展示了如何将管道架构与XML结合起来,创建非常灵活且有用的PowerShell脚本/工具,从而提高表达能力。
这是我第一次在CodeProject发帖。我希望您觉得它有用,并欢迎任何反馈。谢谢!
历史
- 2010年2月27日:初始帖子