PowerShell $null
は、多くの場合、単純に見えますが、多くの微妙な違いがあります。 予期せず$null
値に遭遇したときに何が起こるかを知るために、$null
を詳しく見てみましょう。
注
この記事の 元のバージョン は、@KevinMarquetteによって書かれたブログに登場しました. PowerShell チームは、このコンテンツを Microsoft と共有してくれた Kevin に感謝します。 PowerShellExplained.comで彼のブログをチェックしてください.
NULL とは
NULL は、不明または空の値と考えることができます。 変数は、値またはオブジェクトを割り当てるまで NULL です。 これは、値を必要とし、値が NULL の場合にエラーを生成するいくつかのコマンドがあるため、重要な場合があります。
PowerShell の$null
$null
は、NULL を表すために使用される PowerShell の自動変数です。 これを変数に割り当て、比較で使用し、コレクション内の NULL のプレース ホルダーとして使用できます。
PowerShell は、 $null
を NULL 値を持つオブジェクトとして扱います。 これは、別の言語から来た場合に予想される内容とは異なります。
$nullの例
初期化していない変数を使用しようとすると、値は $null
。 これは、 $null
値がコードに侵入する最も一般的な方法の 1 つです。
PS> $null -eq $undefinedVariable
True
変数名の入力ミスが発生した場合、PowerShell はそれを別の変数として見なし、値は $null
。
$null
値を見つけるもう 1 つの方法は、結果が得られない他のコマンドから取得される場合です。
PS> function Get-Nothing {}
PS> $value = Get-Nothing
PS> $null -eq $value
True
$null の影響
$null
値は、表示される場所によってコードに異なる影響を与えます。
文字列の場合
文字列で $null
を使用する場合は、空白の値 (または空の文字列) になります。
PS> $value = $null
PS> Write-Output "'The value is $value'"
'The value is '
これは、ログメッセージで変数を使用するときに角かっこを置くのが好きな理由の1つです。 値が文字列の末尾にある場合は、変数値の端を識別することがさらに重要です。
PS> $value = $null
PS> Write-Output "The value is [$value]"
The value is []
これにより、空の文字列と $null
値を見つけやすくなります。
数値式の場合
数値式で $null
値が使用されている場合、エラーが発生しない場合、結果は無効になります。
$null
が0
に評価される場合もあれば、結果全体を$null
する場合もあります。
値の順序に応じて 0 または $null
を与える乗算の例を次に示します。
PS> $null * 5
PS> $null -eq ( $null * 5 )
True
PS> 5 * $null
0
PS> $null -eq ( 5 * $null )
False
コレクション内の場合
コレクションを使用すると、インデックスを使用して値にアクセスできます。 実際に null
されているコレクションにインデックスを作成しようとすると、次のエラーが表示されます: Cannot index into a null array
。
PS> $value = $null
PS> $value[10]
Cannot index into a null array.
At line:1 char:1
+ $value[10]
+ ~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : NullArray
コレクションがあるが、コレクション内にない要素にアクセスしようとすると、 $null
結果が得られます。
$array = @( 'one','two','three' )
$null -eq $array[100]
True
オブジェクトの代わりに
指定したプロパティを持たないオブジェクトのプロパティまたはサブプロパティにアクセスしようとすると、未定義の変数の場合と同様に $null
値が取得されます。 この場合、変数が $null
であるか、実際のオブジェクトであるかは関係ありません。
PS> $null -eq $undefined.Some.Fake.Property
True
PS> $date = Get-Date
PS> $null -eq $date.Some.Fake.Property
True
null 値式のメソッド
$null
オブジェクトでメソッドを呼び出すと、RuntimeException
がスローされます。
PS> $value = $null
PS> $value.ToString()
You cannot call a method on a null-valued expression.
At line:1 char:1
+ $value.ToString()
+ ~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
私が You cannot call a method on a null-valued expression
フレーズを見るたびに、私が最初に探しているのは、最初に $null
をチェックせずに変数のメソッドを呼び出している場所です。
$null の確認
私の例で$null
をチェックするときに、常に$null
を左側に配置していることに気付いたかもしれません。 これは意図的であり、PowerShell のベスト プラクティスとして受け入れられます。 右側に配置しても期待どおりの結果が得られないシナリオがいくつかあります。
次の例を見て、結果を予測してみてください。
if ( $value -eq $null )
{
'The array is $null'
}
if ( $value -ne $null )
{
'The array is not $null'
}
私が $value
を定義しない場合、最初の1つは $true
に評価され、メッセージは The array is $null
。 落とし穴になるのは、どちらも $value
となるような $false
を作成できる点です。
$value = @( $null )
この場合、 $value
は $null
を含む配列です。
-eq
は、配列内のすべての値をチェックし、一致する$null
を返します。 これは $false
に評価されます。
-ne
は$null
と一致しないすべてのものを返し、この場合は結果がありません (これは$false
にも評価されます)。 どちらも$true
ではありませんが、どちらかはそうであるべきように見えます。
両方が $false
に評価される値を作成できるだけでなく、両方が $true
評価される値を作成することもできます。 Mathias Jessen (@IISResetMe) には、そのシナリオについて詳しく説明した 投稿 があります。
PSScriptAnalyzer と VS Code
PSScriptAnalyzer モジュールには、PSPossibleIncorrectComparisonWithNull
と呼ばれるこの問題をチェックするルールがあります。
PS> Invoke-ScriptAnalyzer ./myscript.ps1
RuleName Message
-------- -------
PSPossibleIncorrectComparisonWithNull $null should be on the left side of equality comparisons.
VS Code では PSScriptAnalyser ルールも使用されるため、スクリプトでこれを問題として強調表示または識別します。
シンプルなif文のチェック
$null以外の値を確認する一般的な方法は、比較せずに単純な if()
ステートメントを使用することです。
if ( $value )
{
Do-Something
}
値が $null
の場合、これは $false
に評価されます。 簡単そうに見えますが、それによって調べられることが、それで調べようとしていることと完全に一致していることに注意してください。 私はそのコード行を次のように読みました。
$value
に値がある場合。
しかし、それは全体の話ではありません。 その行は実際に言っています:
$value
が$null
または0
または$false
されていない場合、または空の文字列または空の配列。
そのステートメントのより完全なサンプルを次に示します。
if ( $null -ne $value -and
$value -ne 0 -and
$value -ne '' -and
($value -isnot [array] -or $value.Length -ne 0) -and
$value -ne $false )
{
Do-Something
}
変数に値があるだけでなく、他の値がif
としてカウントされることを覚えている限り、基本的な$false
チェックを使用しても問題ありません。
数日前にコードをリファクタリングするときに、この問題が発生しました。 これは、このような基本的なプロパティチェックを行っていました。
if ( $object.Property )
{
$object.Property = $value
}
オブジェクトプロパティに値が存在する場合にのみ値を割り当てたいと思っていました。 ほとんどの場合、元のオブジェクトには、$true
ステートメントでif
評価される値がありました。 しかし、私は値が時々設定されないという問題に遭遇しました。 コードをデバッグしたところ、オブジェクトにプロパティがありますが、空白の文字列値であることが判明しました。 これにより、以前のロジックで更新されるのを防ぐことができます。 だから私は適切な $null
チェックを追加し、すべてがうまくいった。
if ( $null -ne $object.Property )
{
$object.Property = $value
}
このような小さなバグは見つけにくく、 $null
の値を積極的にチェックします。
$null.数える
$null
値のプロパティにアクセスしようとすると、そのプロパティも$null
。
Count
プロパティは、この規則の例外です。
PS> $value = $null
PS> $value.Count
0
$null
値がある場合、Count
は0
。 この特別なプロパティは、PowerShell によって追加されます。
[PSCustomオブジェクト]数える
PowerShell のほぼすべてのオブジェクトには、その Count
プロパティがあります。 重要な例外の 1 つは、Windows PowerShell 5.1 の [pscustomobject]
です (これは PowerShell 6.0 で修正されています)。
Count
プロパティがないため、使用しようとすると$null
値が取得されます。
Count
チェックの代わりに $null
を使おうとしないよう注意してください。
Windows PowerShell 5.1 と PowerShell 6.0 でこの例を実行すると、さまざまな結果が得られます。
$value = [pscustomobject]@{Name='MyObject'}
if ( $value.Count -eq 1 )
{
"We have a value"
}
列挙可能な null
他とは異なる動作をする特殊な種類の $null
が 1 つあります。 私はそれを列挙可能なnullと呼ぶつもりですが、実際には System.Management.Automation.Internal.AutomationNullです。
この列挙可能な null は、何も返されない関数またはスクリプト ブロックの結果として取得される値です (void の結果)。
PS> function Get-Nothing {}
PS> $nothing = Get-Nothing
PS> $null -eq $nothing
True
$null
と比較すると、$null
値が得られます。 値が必要な評価で使用する場合、値は常に $null
されます。 しかし、配列内に配置すると、空の配列と同じように扱われます。
PS> $containEmpty = @( @() )
PS> $containNothing = @($nothing)
PS> $containNull = @($null)
PS> $containEmpty.Count
0
PS> $containNothing.Count
0
PS> $containNull.Count
1
1 つの $null
値を含み、その Count
が 1
されている配列を作成できます。 しかし、配列内に空の配列を配置すると、項目としてカウントされません。 カウントは 0
。
列挙可能な null をコレクションのように扱う場合は、空です。
厳密に型指定されていない関数パラメーターに列挙可能な null を渡すと、PowerShell は既定で列挙可能な null を $null
値に強制します。 つまり、関数内では、値は $null
型ではなくとして扱われます。
パイプライン
違いを確認する主な場所は、パイプラインを使用する場合です。
$null
値はパイプできますが、列挙可能な null 値はパイプできません。
PS> $null | ForEach-Object{ Write-Output 'NULL Value' }
'NULL Value'
PS> $nothing | ForEach-Object{ Write-Output 'No Value' }
コードによっては、ロジック内の $null
を考慮する必要があります。
最初に $null
をチェックするか、または次のようにします
- パイプラインで null を除外する (
... | where {$null -ne $_} | ...
) - パイプライン関数で処理する
foreach
foreach
の私のお気に入りの機能の1つは、$null
コレクションを列挙しないという点です。
foreach ( $node in $null )
{
#skipped
}
これにより、コレクションを列挙する前に $null
チェックする必要ができなくなります。
$null
値のコレクションがある場合でも、$node
を$null
できます。
foreach
は、PowerShell 3.0 でこの方法で作業を開始しました。 以前のバージョンを使用している場合は、そうではありません。 これは、2.0 互換性のためにコードをバック移植する際に注意する必要がある重要な変更の 1 つです。
値の型
技術的には、参照型のみを $null
できます。 しかし、PowerShell は非常に寛大であり、変数を任意の型にすることができます。 値型を強く型付けすることを決めた場合、それを$null
にすることはできません。
PowerShell は、 $null
を多くの型の既定値に変換します。
PS> [int]$number = $null
PS> $number
0
PS> [bool]$boolean = $null
PS> $boolean
False
PS> [string]$string = $null
PS> $string -eq ''
True
$null
からの有効な変換がない型がいくつかあります。 これらの型では、 Cannot convert null to type
エラーが発生します。
PS> [datetime]$date = $null
Cannot convert null to type "System.DateTime".
At line:1 char:1
+ [datetime]$date = $null
+ ~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : MetadataError: (:) [], ArgumentTransformationMetadataException
+ FullyQualifiedErrorId : RuntimeException
関数パラメーター
関数パラメーターで厳密に型指定された値を使用することは非常に一般的です。 一般に、スクリプト内の他の変数の型を定義しない傾向がある場合でも、パラメーターの型を定義する方法を学習します。 関数には既に厳密に型指定された変数があり、それを認識していない場合もあります。
function Do-Something
{
param(
[string] $Value
)
}
パラメーターの型を string
として設定するとすぐに、値を $null
することはできません。 値が $null
されているかどうかを確認して、ユーザーが値を指定したかどうかを確認するのが一般的です。
if ( $null -ne $Value ){...}
$Value
は、値が指定されていない場合に ''
空の文字列です。 代わりに自動変数 $PSBoundParameters.Value
を使用してください。
if ( $null -ne $PSBoundParameters.Value ){...}
$PSBoundParameters
には、関数が呼び出されたときに指定されたパラメーターのみが含まれます。
ContainsKey
メソッドを使用して、プロパティを確認することもできます。
if ( $PSBoundParameters.ContainsKey('Value') ){...}
IsNotNullまたはEmpty
値が文字列の場合は、静的な文字列関数を使用して、値が $null
されているか、空の文字列であるかどうかを同時に確認できます。
if ( -not [string]::IsNullOrEmpty( $value ) ){...}
私は値型が文字列でなければならないことがわかっているときに、これを頻繁に使用しています。
$null チェックをする場合
私は防御系のプログラマーです。 関数を呼び出して変数に代入するたびに、 $null
をチェックします。
$userList = Get-ADUser kevmar
if ($null -ne $userList){...}
私はif
を使用するよりも、foreach
またはtry/catch
を使用することをはるかに好みます。 誤解しないでください。私はまだtry/catch
をたくさん使っています。 しかし、エラー条件または空の結果セットをテストできる場合は、真の例外に対する例外処理を許可できます。
また、オブジェクトの値にインデックスを付けたりメソッドを呼び出したりする前に、 $null
をチェックする傾向があります。 この 2 つのアクションは、 $null
オブジェクトに対して失敗するため、最初に検証することが重要です。 この記事では、これらのシナリオについて既に説明しました。
結果なしシナリオ
異なる関数とコマンドが結果なしのシナリオを異なる方法で処理することを理解することが重要です。 多くの PowerShell コマンドは、列挙可能な null とエラー ストリーム内のエラーを返します。 しかし、例外をスローするものや、状態オブジェクトを返すものもあります。 使用するコマンドによって、結果が存在しないシナリオやエラー シナリオがどのように処理されるかを、理解しておく必要があります。
$nullへの初期化
私が拾った習慣の1つは、変数を使用する前にすべての変数を初期化することです。 これは他の言語で行う必要があります。 関数の上部または foreach
ループに入る際に、使用しているすべての値を定義します。
ここでは、詳しく見ていただきたいシナリオを示します。 次に示すのは、以前に見つけ出す必要があったバグの例です。
function Do-Something
{
foreach ( $node in 1..6 )
{
try
{
$result = Get-Something -Id $node
}
catch
{
Write-Verbose "[$result] not valid"
}
if ( $null -ne $result )
{
Update-Something $result
}
}
}
ここでの予想は、 Get-Something
が結果または列挙可能な null を返すということです。 エラーが発生した場合は、ログに記録されます。 次に、処理する前に有効な結果が得られたかどうかを確認します。
このコードに隠れているバグは、 Get-Something
が例外をスローし、 $result
に値を割り当てない場合です。 割り当ての前に失敗するため、$null
変数に$result
を割り当てることはできません。
$result
には、他のイテレーションからの以前の有効な $result
が含まれています。
Update-Something
この例では、同じオブジェクトに対して複数回実行します。
この問題を軽減するために使用する前に、$result
を $null
ループ内でforeach
するように設定しました。
foreach ( $node in 1..6 )
{
$result = $null
try
{
...
スコープの問題
これは、スコープの問題を軽減するのにも役立ちます。 この例では、ループ内の $result
を繰り返し値を割り当てます。 ただし、PowerShell を使用すると、関数の外部からの変数値が現在の関数のスコープに取り込まれるので、関数内でそれらを初期化すると、その方法で導入できるバグが軽減されます。
関数の初期化されていない変数は、親スコープの値に設定されている場合は $null
されません。
親スコープは、関数を呼び出し、同じ変数名を使用する別の関数である可能性があります。
同じ Do-something
例を取ってループを削除すると、次の例のようになります。
function Invoke-Something
{
$result = 'ParentScope'
Do-Something
}
function Do-Something
{
try
{
$result = Get-Something -Id $node
}
catch
{
Write-Verbose "[$result] not valid"
}
if ( $null -ne $result )
{
Update-Something $result
}
}
Get-Something
を呼び出すと例外がスローされる場合、$null
チェックで $result
からの Invoke-Something
が見つかります。 関数内で値を初期化すると、この問題が軽減されます。
変数の名前付けは難しく、作成者が複数の関数で同じ変数名を使用するのが一般的です。 私は常に $node
、$result
、$data
を使用しています。 したがって、異なるスコープの値が、それらがすべきでない場所に表示されるのは非常に簡単です。
出力を$nullにリダイレクトする
私はこの記事全体の $null
値について話してきましたが、出力を $null
にリダイレクトすることに言及しなかった場合、トピックは完全ではありません。 情報または非表示にするオブジェクトを出力するコマンドがある場合があります。 出力を $null
にリダイレクトすると、その処理が行います。
Out-Null
Out-Null コマンドは、パイプライン データを $null
にリダイレクトする組み込みの方法です。
New-Item -Type Directory -Path $path | Out-Null
$nullに割り当てる
$null
を使用する場合と同じ効果を得るために、コマンドの結果をOut-Null
に割り当てることができます。
$null = New-Item -Type Directory -Path $path
$null
は定数値であるため、上書きすることはできません。 私は自分のコードの見た目が気に入りませんが、多くの場合、 Out-Null
よりも高速に動作します。
$nullにリダイレクトする
リダイレクト演算子を使用して、出力を $null
に送信することもできます。
New-Item -Type Directory -Path $path > $null
異なるストリームで出力するコマンドライン実行可能ファイルを処理している場合。 すべての出力ストリームを次のように $null
にリダイレクトできます。
git status *> $null
概要
私はこの記事で多くの話題をカバーしましたが、この記事が私の深い掘り下げのほとんどよりも断片化されていることを知っています。 これは、 $null
値が PowerShell のさまざまな場所にポップアップ表示される可能性があり、すべての微妙な違いが見つかる場所に固有であるためです。 私はあなたが $null
をよりよく理解し、あなたが行くかもしれないより不明瞭なシナリオの認識を持って、ここから離れて行くことを願っています。
PowerShell