PowerShellのCompare-Object(diff)の罠
PowerShellでdiff
相当のCompare-Object
を使って、差分だけ処理するスクリプトを書こうとしたら、PowerShellの仕様による罠にハマった。
要旨
Compare-Object
で比較した結果は、差異の個数によって型が変わる。
- 差異がなかった(0個)の場合、
$null
が返る - 差異が1個の場合、単一の
PSCustomObject
が返る - 差異が2個以上の場合、
PSCustomObject
の配列が返る
この挙動の違いにより、結果を変数に保存してプロパティにアクセスしたり、パイプにつないだりする時には注意が必要になる。
必ず配列を返して欲しい場合は@()
で囲むのが安全。
実際の挙動
環境はWindows 10でPowerShellのバージョンは5.1。
まずは差異の個数が0、1、2となるよう、Compare-Object
で比較する。
PS T:\> $diff0 = Compare-Object @(1,2,3) @(1,2,3)
PS T:\> $diff1 = Compare-Object @(1,2,3) @(1,2)
PS T:\> $diff2 = Compare-Object @(1,2,3) @(1)
上で説明したように、$diff0
は$null
が、$diff1
はPSCustomObject
が、$diff2
は配列が返ってくる。
PS T:\> $diff0
PS T:\> $diff1
InputObject SideIndicator
----------- -------------
3 <=
PS T:\> $diff2
InputObject SideIndicator
----------- -------------
2 <=
3 <=
$diff1
と$diff2
の違いはコンソールに出力するとわかりにくいので、getType()
を使って確認。
PS T:\> $diff0.getType()
null 値の式ではメソッドを呼び出せません。
発生場所 行:1 文字:1
+ $diff0.getType()
+ ~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) []、RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
PS T:\> $diff1.getType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False PSCustomObject System.Object
PS T:\> $diff2.getType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
このため、Length
プロパティを使って差異があるかどうかを判定するのは上手くいかない(もちろん、Length
プロパティを使わず、$diff0 -neq $null
のようにすれば、差分があるかどうか自体は判定可能)。
# 差分が1個のときだけLengthプロパティが無い(なぜか$nullにはLengthプロパティがある)
PS T:\> $diff0.Length
0
PS T:\> $diff1.Length
PS T:\> $diff2.Length
2
# 差分があるかどうかを調べたいときは-ne 0を使えば一応正しく判定可能
PS T:\> $diff0.Length -ne 0
False
PS T:\> $diff1.Length -ne 0
True
PS T:\> $diff2.Length -ne 0
True
# 同じように見えて-gt 0は使えない
PS T:\> $diff0.Length -gt 0
False
PS T:\> $diff1.Length -gt 0
False
PS T:\> $diff2.Length -gt 0
True
対処法としては、Compare-Object
の結果を@()
で囲んで、必ず配列が返ってくるようにするのが安全。
PS T:\> $diff0_2 = @(Compare-Object @(1,2,3) @(1,2,3))
PS T:\> $diff1_2 = @(Compare-Object @(1,2,3) @(1,2))
PS T:\> $diff2_2 = @(Compare-Object @(1,2,3) @(1))
# GetType()で必ず配列が返る
PS T:\> $diff0_2.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
PS T:\> $diff1_2.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
PS T:\> $diff2_2.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
# Lengthプロパティも必ず存在する
PS T:\> $diff0_2.Length
0
PS T:\> $diff1_2.Length
1
PS T:\> $diff2_2.Length
2