次の方法で共有


$nullについて知りたいこと

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 値が使用されている場合、エラーが発生しない場合、結果は無効になります。 $null0に評価される場合もあれば、結果全体を$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値のプロパティにアクセスしようとすると、そのプロパティも$nullCount プロパティは、この規則の例外です。

PS> $value = $null
PS> $value.Count
0

$null値がある場合、Count0。 この特別なプロパティは、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 値を含み、その Count1されている配列を作成できます。 しかし、配列内に空の配列を配置すると、項目としてカウントされません。 カウントは 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 をよりよく理解し、あなたが行くかもしれないより不明瞭なシナリオの認識を持って、ここから離れて行くことを願っています。