はじめに
Powershellにて。配列が入っているであろう「 \$x 」があるとして、その要素をどうやって参照したらエラーなく取れるのか検討してみました。
出来れば簡潔な表現を使いたいものですが、簡単なものほど時々意図しない動きをしたりします。それぞれどんなリスクがあるのかについて確認しました。
\$[0] や $[-1] の様に必ず存在する添え字を指定しているのに時々 "インデックスが配列の境界外です。" というエラーに悩まされている方は、配列がNull になっている可能性や Set-StrictMode の厳密モードの設定を確認してみましょう。
なお最後を取る場合は添え字を -1 、Select-Objectは First を Last にします。
結論
- 配列であることが確実な場合 → \$x[0]
- 配列かどう不明だがNullではないことがわかっている場合 → @(\$x)[0]
- 厳密モードやNullかもしれない場合 → Select-Object -First 1
- NULLのときは別処理したい → if や Switch 構文で分岐
- 配列の Count がゼロにならない、条件分岐の先で値が取れない → 長さゼロの文字列や $null を持つ配列を除外するため [String]::isNullOrEmpty() を試す
厳密モード
PowerShellの 厳密モード(Strict Mode) を有効にすると、初期化されていない変数の使用や、プロパティをメソッドのように誤って記述した場合(例:「$x.property()」)にエラーを返すようになります。これにより、潜在的なバグを事前に発見しやすくなります。
厳密モードは、スクリプトの冒頭に以下のように記述することで有効になります。
Set-StrictMode -Version Latest
この設定を活用することで、PowerShellスクリプトの品質と信頼性を向上させることができます。
ただしインポートしたモジュールの行儀が悪いとsエラーになることもある点は注意。
$x[0] を使った場合
一番シンプルなケースです。
$x = @(1,2,3)
の様にあらかじめ配列であることが確定していれば問題ないのですが、変数に入るものが配列以外のものがあると意図しない動きをすることも。
function Get-Fruits($num)
{
$bascket = @("りんご","みかん","アボカド")
for($i=0;$i -lt $num;$i ++)
{
$bascket[$i]
}
}
Set-StrictMode -Version Latest
Write-Host "■ 2個、「りんご」が返る"
$x = Get-Fruits 2
Write-Host " とってきたフルーツは $($x.length) 個"
Write-Host " 最初のフルーツは $($x[0]) "
Write-Host "■ 3個、「り」が返る"
$x = Get-Fruits 1
Write-Host " とってきたフルーツは $($x.length) 個"
Write-Host " 最初のフルーツは $($x[0]) "
個数は Length ではなく Count を使えば配列の個数が返ります。
でも、厳密モード下ではCountもうまく動きません。
Set-StrictMode -off
Write-Host "■ Count を使えば配列の個数が返る"
$x = Get-Fruits 0
Write-Host " とってきたフルーツは $($x.count) 個"
Write-Host "■ しかし添え字は使えず、エラーになる"
Write-Host " 最初のフルーツは $($x[0]) "
Write-Host '■ $Null.Count はエラーになる'
Write-Host " Null のCountは $($null.count)"
write-host '■ 厳密モードでなければ $Null が入った変数に Count は使える'
$x = $Null
Write-Host " Null の入った変数のCountは $($x.count)"
Set-StrictMode -Version Latest
write-host '■ 厳密モードだとエラー'
Write-Host " Null の入った変数のCountは $($x.count)"
@($x)[0] を使った場合
そんな時は添え字をつける前に @ を付けて配列にするとうまくいくようになります。
Set-StrictMode -Off
Write-Host "■ 厳密モードでなければ「りんご」も取れるし、ゼロ個のケースも動く"
$x = Get-Fruits 2
Write-Host " とってきたフルーツは $(@($x).length) 個"
Write-Host " 最初のフルーツは $(@($x)[0]) "
$x = Get-Fruits 0
Write-Host " とってきたフルーツは $(@($x).count) 個"
Write-Host " 最初のフルーツは $(@($x)[0]) "
Set-StrictMode -Version Latest
Write-Host '■ 厳密モードだと添え字はエラー'
$x = Get-Fruits 0
Write-Host " とってきたフルーツは $(@($x).count) 個"
Write-Host " 最初のフルーツは $(@($x)[0]) "
Select-Object -First 1
どちらも動くようにするにはこちらを使います。
Set-StrictMode -Version Latest
$x = Get-Fruits 0
Write-Host "■ どちらも動くようにするなら個数は配列にキャスト、最初の取り出しはSelect-Object"
3..0 | Foreach-Object {
Write-Host "■ $_ 個、りんご(または空)"
$x = Get-Fruits $_
$FruitsCount = @($x).Count
$firstFruits = $x | Select-object -First 1
Write-Host " とってきたフルーツは $FruitsCount 個"
Write-Host " 最初のフルーツは '$firstFruits' "
}
@($x).Count を調べて 0なら 'ほげほげ' 、 1なら $x、2以上なら$x[0] の様に分岐
コードは冗長になりますが、こうしてもOKです。
3..0 | Foreach-Object {
Write-Host "■ $_ 個、りんご(または空)"
$x = Get-Fruits $_
$FruitsCount = @($x).Count
$FirstFruits = if(0 -eq $FruitsCount) {
'なにもない'
} elseif (1 -eq $FruitsCount) {
$x
} else{
@($x)[0]
}
Write-Host " とってきたフルーツは $FruitsCount 個"
Write-Host " 最初のフルーツは '$firstFruits' "
}
これでもカウントがおかしいのであれば \$x に入れる際に
$x = Get-Fruits $_ | where-object {$false -eq [string]::isNullOrEmpty($_)}
のようにするとうまくいくかもしれません。
関数が \$Null や''(長さゼロの文字列) を返すと、@($x).Count 関数は1以上の値を返してしまうためです。isNullOrEmpty はこういったゴミを除外するのに便利です。
おすすめの記事
Powershell の \$null は、同じ \$null に見えても異なる挙動を示すことがあります。こちらの公式ドキュメントを一読することをお勧めします。
- 文字列が空かどうかは[String]::IsNullOrEmpty() を使うべし
- \$null の比較時、 \$null は必ず左辺に置くべし
- @(@()).count や @((値を返さない関数の戻り値)).Count は 0だが @(\$null).Count は 1 になる
などのコツや例が書かれています。