Edited at

powershellの@()の謎を解く

More than 3 years have passed since last update.

powershell で配列の配列は以下のように書くことができる

$ary = @(@(1, 2), @(3, 4))

しかし、以下は、配列の配列にはならない @(1, 2)と同じになってしまう。

$ary = @(@(1, 2))

powershellを使っていて必ず引っかかる理解不能な挙動である。この記事ではこの挙動について実験でわかったことを説明する。

その前に配列を可視化するために簡単な関数を定義しよう。

この関数を使ってどのような配列が定義されているかを確認しながら説明する。


prettyprint.ps1

function p {

Param($obj)

Write-Host (prettyprint $obj)
}

function prettyprint {
Param($obj)

$result = New-Object System.Text.StringBuilder;

. {
switch ($obj.GetType()) {
{ $_.BaseType -eq [System.Array] } {
$sep = ", "
$result.Append("[")
for ($i = 0; $i -lt $obj.Length; $i++) {
if ($i -eq $obj.Length - 1) {
$sep = ""
}
$result.Append((prettyprint $obj[$i]))
$result.Append($sep)
}
$result.Append("]")
}
{ $_.Name -eq "String" } {
$result.Append("""$obj""")
}
{ $_.BaseType -eq [System.ValueType] } {
$result.Append($obj)
}
default {
$result.Append("[$($_.Name)]").Append($obj.ToString())
}
}
} | Out-Null

$result.ToString()
}


例えば、最初の例は以下のように表示される

p @(@(1, 2), @(3, 4))

# => [[1, 2], [3, 4]]

p @(@(1, 2))

# => [1, 2]

確かに2番目の例 @(@(1, 2)) は、二重の配列になっていない。


配列リテラルの基本

まず、配列表記の基本は以下である。

$ary = 1,2,3     # 3要素の配列

$ary = @(1,2,3) # 3要素の配列
$ary = ,1 # 1要素の配列
$ary = @(1) # 1要素の配列
$ary = @() # 0要素の配列


@() の規則1

@()の中の配列は常に一重の配列展開が行われる

まず、わかりにくいが@(1,2)は、そもそも1,2が配列なのでこれが一重だけ展開されていると見る。

この過程を以下のように図示する。

(ここで、[] は便宜上配列表現であると見なしてほしい。1,2[1,2]と表記する)

@(1,2) => @([1,2]) => [1,2]

従って、@(@(1,2))は、

@(@(1,2)) => @(@([1,2])) => @([1,2]) => [1,2]

@(@(@(1,2)))でも同じである。結局 [1,2]になる。

これで、@(@(1,2),@(3,4))が配列の配列になることの説明もつく。@(1,2),@(3,4)[[1,2],[3,4]]であるから外側の配列だけが一重展開される。

@(@(1,2),@(3,4))

=> @([@([1,2]),@([3,4])])
=> @([[1,2],[3,4]])
=> [[1,2],[3,4]]

@()という記述自体が不要なだけのようにも思えるが、@()は1要素、0要素の配列表現に必要である。

@(1) => [1]

@() => []


@() の規則2

@()の中では「式」を配列の要素とみなし、複数の式は複数の要素になる。配列の一重展開は「式」単位に行われる。

(例)

p @(1; 2) # => [1, 2]

@()の中で複数要素を行単位に書くことができるのはこの規則からである。

p @(

1
2
)
# => [1, 2]

そして、@(@(1,2),@(3,4))が二重配列になるのに以下が一重配列になるのはこの規則からである。

(「式」単位に一重展開されるため、[1,2] は、1,2 に、[3,4]は、3,4になる)

p @(

@(1,2)
@(3,4)
)
# => [1,2,3,4]

以下が配列の配列になるのは "," で終わる行は「式」が終わっていないからである(2行でひとつの式になっている)

p @(

@(1,2),
@(3,4)
)
# => [[1,2],[3,4]]

また、以下が二重配列になるのは,により各式が二重配列([[1,2]])になっており、それが展開されるからである。

p @(

,@(1,2) # [[1,2]]
,@(3,4) # [[3,4]]
)
# => [[1,2],[3,4]]

以下など直感に反した結果になるが上記で説明が付く(最初の要素[1,2]は展開され2要素になる。次の要素[[3,4]]は展開され[3,4]になる)

p @(

@(1,2)
,@(3,4)
)
# => [1, 2, [3, 4]]

これらの規則は関数の出力でも同じである

function foo {

@(1, 2)
,@(3, 4)
}

p (foo)
# => [1, 2, [3, 4]]

以上、実験と予想を基に記事をまとめた。これらについて公式に説明しているドキュメントがあれば教えてほしい。