PowerShell命令與指令碼(13)——處理XML
阿新 • • 發佈:2021-06-26
之前原始資訊儲存在逗號分隔的記錄檔案或者.ini檔案中,但是近幾年XML標準佔了上風。XML是”可擴充套件標記語言“的縮寫,是一種對於任意結構化的資訊的可描述性語言。過去處理XML還是相當麻煩的,但是現在PowerShell中,對XML有了非常優秀的支援。通過它的幫助,你既可以非常容易的在XML中包裝資料,也可以非常舒服的訪問已有的XML檔案。
XML 結構
XML使用標籤來唯一標識資訊片段,一個標籤像網站中的HTML文件使用的一樣,是一對尖括號。通常,一條資訊被一個起始和結束標記所分割。結束標記前面使用“/”,其結果謂之結點,在下面的例子就應當叫做Name結點。另外,結點擁有它自身相關資訊的屬性(attributes)。這些資訊位於開始標籤。<Name>Tobias Weltner</Name>
<staff branch="Hanover" Type="sales">...</staff>
如果一個結點為空,它的開始結點和結束結點可以摺疊起來。結束符號“/”指向標籤結束。例如,在Hanover的子公司沒有任何員工從事銷售工作,那這個標籤可以像這樣描述。
<staff branch="Hanover" Type="sales"/>
通常,如果一個標籤不空,包含更多資訊,這些資訊應當包含在標籤中。這就允許按照你喜歡的深度產生資訊結構。下面XML結構描述Hanover子公司銷售部門工作的兩位員工。
為了讓XML檔案被識別,通常在它們的開始有一個非常簡單類似下面例子中的一個頭宣告。<staff branch="Hanover" Type="sales"> <employee> <Name>Tobias Weltner</Name> <function>management</function> <age>39</age> </employee> <employee> <Name>Cofi Heidecke</Name> <function>security</function> <age>4</age> </employee> </staff>
<?xml version="1.0" ?>
頭宣告聲明瞭緊跟其後的XML符合1.0 版本規範。被稱為”schema”的東西也可以在這裡被指定。具體來說,Schema有個XSD檔案的形式,它用來描述XML檔案結構應當遵循確定的目的。在上一個例子中,Schema可能會指定必須包含“staff”結點作為員工的資訊,進而指定多個命名為“staff”的子結點為必須的。Schema也可以指定相關的資訊,例如每個員工的名稱和定義他們具體的職能。
因為XML檔案有純文字構成,你可以使用編輯器建立他們,也可以直接使用PowerShell。讓我們將前面定義的員工資訊儲存為一個XML檔案吧:
注意:XML檔案大小寫敏感的!$xml = @' <?xml version="1.0" standalone="yes"?> <staff branch="Hanover" Type="sales"> <employee> <Name>Tobias Weltner</Name> <function>management</function> <age>39</age> </employee> <employee> <Name>Cofi Heidecke</Name> <function>security</function> <age>4</age> </employee> </staff> '@ | Out-File employee.xml
載入和處理XML檔案
如果你想將XML檔案按照實際的XML來處理,而不是純文字。檔案的內容必須轉換成XML型別。型別轉換已經提到,只須一行。$xmldata = [xml](Get-Content employee.xml)
Get-Content從之前儲存的xml檔案中讀取xml內容,然後使用[xml]將xml內容轉換成真正的XML。你可以將xml文字直接轉換成XML,然後儲存在變數$xml中:
$xmldata = [xml]$xml
然而,轉換隻會在指定的xml文字合法並不包含解析錯誤時有效。當你嘗試轉換的xml檔案結構不合法,會報錯。
用來描述XML的結構和資訊現在被存放在變數$xmldata。從現在開始,獲取一小段資訊將會變得非常容易,因為XML物件將每個結點轉換成了屬性。你可以像這樣獲取員工資訊:
訪問和更新單個結點
如果一個結點在xml中是唯一的,你可以像前面的例子一樣輸入一個句號來訪問它。然而,多數情況下XML文件包含了許多類似的結點(被稱為兄弟結點),像前面最後一個例子中一樣,包含了多個獨立的員工。比如,你可以使用管道獲得特定的員工,然後來更新它的資料。
$xmldata.staff.employee |
Where-Object { $_.Name -match "Tobias Weltner" }
$employee = $xmldata.staff.employee |
Where-Object { $_.Name -match "Tobias Weltner" }
$employee.function = "vacation"
$xmldata.staff.employee | ft -autosize
使用SelectNodes()來選擇Nodes
SelectNodes()方法是Xpath查詢語言支援的方法,也允許你選擇結點。XPath指的是一個結點‘路徑名稱’:
述XML的結構和資訊現在被存放在變數$xmldata。從現在開始,獲取一小段資訊將會變得非常容易,因為XML物件將每個結點轉換成了屬性。你可以像這樣獲取員工資訊:
如果你想,你還可以獲取一個年齡小於18歲的員工列表:
類似的方式,查詢語言也支援獲取列表中的最後一位員工資訊,所以也可以指定位置:
$xmldata.SelectNodes("staff/employee[last()]")
$xmldata.SelectNodes("staff/employee[position()>1]")
或者,你可以使用所謂的XpathNavigator,從中獲取許多從XML文字的型別轉換。
# 建立一個 XML定位:
$xpath = [System.XML.XPath.XPathDocument]`
[System.IO.TextReader][System.IO.StringReader]`
(Get-Content employee.xml | out-string)
$navigator = $xpath.CreateNavigator()
# 輸出Hanover子公司的最後一位員工
$query = "/staff[@branch='Hanover']/employee[last()]/Name"
$navigator.Select($query) | Format-Table Value
Value
-----
Cofi Heidecke
# 輸出Hanover子公司的除了Tobias Weltner之外的所有員工,
$query = "/staff[@branch='Hanover']/employee[Name!='Tobias
Weltner']"
$navigator.Select($query) | Format-Table Value
Value
-----
Cofi Heideckesecurity 4
注:如果你的XML文件包含名稱空間,SelectNodes時稍有不同
訪問屬性
屬性是定義在一個XML標籤中的資訊,如果你想檢視結點的屬性,可以使用get_Attributes()方法:
使用GetAttribute()方法來查詢一個特定的屬性:
使用SetAttribute()方法來指定新的屬性,或者更新(重寫)已有的屬性。
新增新結點
如果你想在員工結點列表中新增新的員工。首先,使用CreateElement()建立一個員工元素,然後定製員工的內部結構。最後,就可以在XML結構中你期望的位置插入這個元素。
# 載入XML文字檔案:
$xmldata = [xml](Get-Content employee.xml)
# 建立新的結點:
$newemployee = $xmldata.CreateElement("employee")
$newemployee.set_InnerXML( `
"<Name>Bernd Seiler</Name><function>expert</function>")
# 插入新結點:
$xmldata.staff.AppendChild($newemployee)
# 驗證結果:
$xmldata.staff.employee
# 輸出為純文字:
$xmldata.get_InnerXml()
儲存XML檔案
瀏覽擴充套件型別
PowerShell擴充套件型別系統(ETS)確保了物件可以被轉換成有意義的文字。此外,它還可以傳遞額外的屬性和方法給物件。這些操作的精確定義被存放在副檔名為.ps1xml的檔案中。 一句話ETS就是定義物件那些屬性應當顯示,那些屬性應當隱藏。 比如: 為什麼預設只顯示Mode,LastWriteTime,Length,Name這幾個屬性? 誰規定的,就是ETS(擴充套件型別系統)配置的。 擴充套件型別系統的XML資料 所有的這些檔案包含了眾多的檢視,你可以使用PowerShell 支援的XML方法來檢驗它。[xml]$file = Get-Content "$pshome\dotnettypes.format.ps1xml"
$file.Configuration.ViewDefinitions.View
查詢預定義的檢視
預定義的檢視非常有意思,因為你可以在像format-table和format-list這樣的格式化命令中使用-View引數來對指定的結果做出豐富的調整和更改。
Get-Process | Format-Table -view Priority
Get-Process | Format-Table -view StartTime
很遺憾,沒人告訴你像Priority 和 StartTime這樣的檢視或者其它檢視已經存在。你可以檢視相關的XML檔案,檢視檔案顯示每一個檢視結點包含了子結點:Name,ViewSelectedBy,TableControl。但是開始,檢視的原始XML資料可能看起來費解,不清楚。
使用一行命令即可重新格式化文字,讓它方便閱讀:
$file.get_OuterXML().Replace("<", "`t<").Replace(">", ">`t").Replace(">`t`t<", ">`t<").Split("`t") |
ForEach-Object {$x=0}{ If ($_.StartsWith("</")) {$x--} `
ElseIf($_.StartsWith("<")) { $x++}; (" " * ($x)) + $_; `
if ($_.StartsWith("</")) { $x--} elseif `
($_.StartsWith("<")) {$x++} }
每一個檢視由一個Name,一個ViewSelectedBy中的.NET型別組成,作為檢視合格的條件,TableControl結點也一樣,指定物件被支援轉換成文字。如果你想在一列中輸出定義在XML檔案中的所有的檢視,Format-Table命令足矣,然後選擇你想在摘要中顯示的屬性。
[xml]$file = Get-Content "$pshome\dotnettypes.format.ps1xml"
$file.Configuration.ViewDefinitions.View |
Format-Table Name, {$_.ViewSelectedBy.TypeName}
你所看到是XML檔案中定義的所有的檢視了,第二列顯示的就是檢視定義的物件型別。其中的Priority和StartTime就是我們之前在上一個例子中使用的兩個檢視。看了第二列應當就會非常清楚,該檢視是針對System.Diagnostics.Process物件,恰恰就是命令Get-Process獲取的物件。
(Get-Process | Select-Object -first 1).GetType().FullName
你可能會驚訝有些Name是成對出現的,例如System.TimeSpan。其原因正是上一個例子中提到的TableControl結點,一起的還有其它型別轉換的結點ListControl, WideControl 和 CustomControl。這些結點在第一次概覽時,不會被顯示。原因是每一個檢視只允許出現一個這樣的結點。TableControl被輸出時或多或少帶有隨機性,因為在轉換第一條記錄時,PowerShell是基於未知物件的轉換。
接下來我們會提取出XML檔案中所有的必須的資訊。首先對檢視按照ViewSelectedBy.TypeName排序,接著根據criterion(標準)來分組。你也可以按照只匹配出一次確定的物件型別來排序。你只需要那些值得在-view引數中指定的,存在多個物件型別的檢視。
[xml]$file = Get-Content "$pshome\dotnettypes.format.ps1xml"
$file.Configuration.ViewDefinitions.View |
Sort-Object {$_.ViewSelectedBy.TypeName} |
Group-Object {$_.ViewSelectedBy.TypeName} |
Where-Object { $_.Count -gt 1} |
ForEach-Object { $_.Group} |
Format-Table Name, {$_.ViewSelectedBy.TypeName}, `
@{expression={if ($_.TableControl) { "Table" } elseif `
($_.ListControl) { "List" } elseif ($_.WideControl) { "Wide" } `
elseif ($_.CustomControl) { "Custom" }};label="Type"} -wrap
如果對於這幾行的格式化命令有任何疑問,可以參考第五節,主要講格式化。關於像Format-Table與其它這樣的格式化命令,其重要性在於可以讓你在表格的列中顯示特定的物件,屬性,或者指令碼塊。如果你想在表格列中不直接顯示屬性,而是顯示屬性的子屬性,那麼子表示式是必須的。因為你對ViewSelectedBy屬性不感興趣,但是對它的子屬性TypeName感興趣,所以列必須定義在指令碼塊中。第三列也是指令碼塊。因為它的長度和列頭衝突,一個用於格式化的雜湊表應當應用到這裡,允許你能選擇列的標題。
提供給你的結果是一個可編輯的列表。第一列顯示的是所有檢視的名稱;檢視被適用的物件型別位於第二列;第三列展示這些格式化命令Format-Table, Format-List, Format-Wide 或者 Format-Custom那個會應用到它。
記住這些包含格式化資訊的XML檔案。你只有在生成了所有格式化XML檔案列表時,才可以得到一個完整的概覽。