• アキレウスのかかと

Microsoftがシェルやシステムスクリプト用として開発したPowerShellにも「弱点」がある。たとえば、PowerShellは、XMLの扱いが苦手だ。いや、簡単に読み込んでアクセスできるだろう? と言われるかもしれない。PowerShellには、JSONやCSVとPowerShellのPSCustomObjectオブジェクト間で変換が行えるのに、XMLは変換が行えず、.NET側のオブジェクトをそのまま扱うことしかできない。

PowerShellは、プロパティなどの「名前」で大文字小文字を区別しない。これに対して、XMLでは、大文字小文字が区別される。このため、XMLをPowerShellのオブジェクトに変換すると、大文字と小文字の区別が失われてしまう。

大文字小文字を区別しないので、変数やプロパティ名で大文字小文字を間違えてもエラーにはならない。Windowsのファイル名と同じく、ユーザーが入力した大文字、小文字の区別は残る。このため、変数を定義するときに「$MyVar」としても、「$myvar」でアクセスできてしまう。プロパティ名なども同様で、大文字小文字が区別されない。エラーを減らすという点では効果はあると思われる。定義時には、大文字小文字を使ったキャメルケースなどで変数を定義しても、あとでアクセスするときには、それを忘れて小文字だけで入力してしまうことがある。シェルという使い方を考えると「便利」といえば「便利」である。

しかし、大文字小文字を区別すると不便で使いにくいのか? というとそうでもない。たとえば、Linuxでよく使われるbashなどUnix/Linux系では、昔から大文字小文字は区別されてきた。その上で、環境変数は大文字だけ使いましょう、ローカルの変数は小文字だけにしましょう、みたいな慣習があった。大文字小文字の区別があることは、それほど不便な感じはなかったし、大文字小文字を区別する言語も少なくないので、違和感も感じない。

具体的な例を見てみることにしよう。以下のリストは、テスト用のXMLをヒアドキュメントで入力して、アクセスするもの。


[xml]$xml=@"
<?xml version='1.0' encoding='UTF-8'?>
<Main name="Test" id="1">
<sub name="s1" id="2" />
<SUB Name="S2" ID="3" />
</Main>
"@

簡易な方法でアクセスしてみる。「$xml.main.sub」と入力すると、subタグとSUBタグは同一視されて2つの結果が出力される(写真01)。また、この方法では、タグと属性はともにプロパティとしてアクセスする。たとえば、subタグのid属性値は「$xml.main.sub.id」でアクセスする。

  • 写真01: $xmlにXMLをヒアドキュメントで入力し、.NETのXMLオブジェクトを作る。これは、通常のPowerShellオブジェクトのようにプロパティで、タグ要素や属性値にアクセスができる。しかし、大文字と小文字が区別されない。これに対してSelect-Xmlコマンドを使うと、XPathで大文字小文字を区別したタグと属性にアクセスができる

この状態では、XMLはPowerShellの通常のオブジェクトのように扱えるため、


$xml.main.sub | Where-Object name -eq 's1'

のようにPowerShellのコマンドでオブジェクトのように扱える。ただし、subとSUBタグは同一視されている。

正しい方法はSelect-Xmlコマンドを使うものだ。これは、XMLオブジェクト(System.Xml.XmlDocument)からXPathを使って、正しく要素を取り出すコマンドだ。subタグのname属性を見たいなら、


(Select-Xml -Xml $xml -XPath "//sub/@name").node

とする。Select-Xmlコマンドの出力は、SelectXmlInfoというオブジェクトなので、そのnodeプロパティを展開することで中身が得られる。XPathオプションの引数を"//SUB/@Name"にすると、SUBタグのName属性を取り出せる。このようにSelect-Xmlを使うことで、大文字小文字を区別したタグや属性値のアクセスが行える。

Select-Xmlコマンドでは、XPathを使う必要があり、コマンド記述が結構面倒である。文字ケースが違うだけのタグがなければ、簡易な方法が利用できる。パイプラインを使って、XMLファイルをまとめてPowerShellのオブジェクトに変換するのも簡単だ。PSCustomObjectに必要な情報のみを入れてしまえば、あとは、ConvertTo系のコマンドが利用できる。

以下のコマンドは、フォントのソースコード(Windows Terminal付属のCascadiaフォント)から、glifファイル(中身はXML)にあるユニコードのコードポイント定義(<unicode hex="E0C9"/>のようなタグ)を取り出すもる。XMLの要素からPowerShellのオブジェクトを生成、これをCSVに変換する。ここまでできると、あとの処理や分析は、エクセル(PowerQuery利用)などで簡単に行える。


$myxml=[Xml]::new()
Get-ChildItem -Recurse -Filter *.glif | %{ $myxml.Load($_.FullName); $u=$myxml.glyph.unicode.hex;if($u -ne $null){foreach($uc in $u){[pscustomobject]@{Unicode=$uc;Name=$myxml.glyph.name}}}} | sort Unicode | ConvertTo-Csv

XMLファイルの中に、文字ケースだけが異なるタグがあるかどうかを検出するのは結構面倒だ。しかし、同じ綴りのタグ名を大文字、小文字で使い分けるのは混乱を生むし、わざわざ行うことは少ないと考えると、ギャンブルにはなるが、大文字、小文字だけが異なるタグは存在しないと仮定することもできる。とはいえ、誤りが含まれる可能性がある方法であり不安は残る。

今回のタイトルネタは、ホメロスの「イーリアス」に登場するアキレウス(アキレス)から。アキレウスの母は息子を冥界のステュクス川に浸し不死の体にした。そのとき、かかとを掴んでいたため、そこは水に浸からずかかとは不死にならなかった。このため「Achilles’ heel」は「弱点」という意味を持つようになった。弁慶の泣き所的な感じがあるが、同じく伝説の勇者ジークフリートも背中に弱点があった。不死になる竜の血を浴びたとき、菩提樹の葉があって、そこだけ血が付かなかったからと言われている。PowerShellもユーザーに使いやすくと大文字小文字の区別をしなかったのだろうが、それがXMLを自らのオブジェクトでは、完全に扱えない取り込めないという弱点になってしまった。