您想要瞭解$null

PowerShell $null 通常看起來很簡單,但有許多細微差別。 讓我們仔細看看 $null ,讓您知道當您意外遇到 $null 值時會發生什麼事。

注意

本文的原始版本出現在@KevinMarquette撰寫的部落格上。 PowerShell 小組感謝 Kevin 與我們分享此內容。 請查看他在 PowerShellExplained.com部落格。

什麼是 NULL?

您可以將 NULL 視為未知或空白值。 變數是 NULL,直到您將值或物件指派給它為止。 這很重要,因為有些命令需要值,如果值為 NULL,就會產生錯誤。

PowerShell $null

$null 是 PowerShell 中用來表示 NULL 的自動變數。 您可以將它指派給變數、在比較中使用它,並將它當做集合中 NULL 的位置持有者使用。

PowerShell 會將 $null 視為值為 NULL 的物件。 如果您來自另一種語言,這與您預期的情況不同。

$null範例

每當您嘗試使用尚未初始化的變數時,值會是 $null。 這是值潛入程式代碼的最常見方式 $null 之一。

PS> $null -eq $undefinedVariable
True

如果您碰巧誤寫變數名稱,則 PowerShell 會將它視為不同的變數,且值為 $null

另一種方式是 $null ,當這些值來自未提供任何結果的其他命令時。

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

這是我在記錄訊息中使用括弧時,將括弧放在變數周圍的原因之一。 當值位於字串結尾時,識別變數值的邊緣就更加重要。

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,則第一個評估為 $true ,而我們的訊息為 The array is $null。 這裡的陷阱是,可以建立 $value 允許兩者成為的 $false

$value = @( $null )

在此情況下, $value 是包含的 $null陣列。 會 -eq 檢查陣列中的每個值,並傳 $null 回相符的 。 這會評估為 $false。 會 -ne 傳回不符合 $null 的所有專案,在此情況下沒有任何結果(這也會評估為 $false)。 即使兩者 $true 看起來都不應該如此。

我們不僅能夠建立一個值,讓兩者都評估為 $false,而且可以建立兩者都評估為 $true的值。 馬蒂亞斯·傑森(@IISResetMe)有一個很好的 帖子 ,深入探討這個案例。

PSScriptAnalyzer 和 VSCode

PSScriptAnalyzer 模組有一個規則,會檢查此問題是否稱為 PSPossibleIncorrectComparisonWithNull

PS> Invoke-ScriptAnalyzer ./myscript.ps1

RuleName                              Message
--------                              -------
PSPossibleIncorrectComparisonWithNull $null should be on the left side of equality comparisons.

由於 VS Code 也會使用 PSScriptAnalyser 規則,因此也會醒目提示或將它識別為腳本中的問題。

簡單檢查

人們檢查非$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
}

只要您記得其他值算作 ,$false而不只是變數具有值,使用基本if檢查就完全沒問題了。

幾天前重構一些程序代碼時,我遇到此問題。 它有一個基本的屬性檢查,如下所示。

if ( $object.property )
{
    $object.property = $value
}

只有當物件屬性存在時,我才想要將值指派給物件屬性。 在大部分情況下,原始物件有一個值,會在 語句中評估 $trueif 。 但我遇到值偶爾未設定的問題。 我偵錯了程式代碼,發現物件具有 屬性,但它是空白字串值。 這可防止其使用先前的邏輯進行更新。 所以我增加了一個適當的 $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 會新增這個特殊屬性。

[PSCustomObject]計數

PowerShell 中幾乎所有的物件都有該 count 屬性。 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 不同。 我要將其稱為空$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

您可以有包含一個 $null 值的陣列,其 count1。 但是,如果您在陣列內放置空的結果,則不會將其計算為專案。 計數為 0

如果您將空白 $null 視為集合,則它是空的。

如果您將空值傳遞至未強型別的函式參數,PowerShell 預設會將無值強制轉換成 $null 值。 這表示在函式內,值會視為 $nullSystem.Management.Automation.Internal.AutomationNull 類型。

管線

您看到差異的主要位置是使用管線時。 您可以使用管線傳送值,但不能使用 $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 之一 $null 是它不會列舉集合。

foreach ( $node in $null )
{
    #skipped
}

這樣可省去我先 $null 檢查集合,再列舉集合。 如果您有值的集合 $null$node 仍然可以是 $null

foreach 開始使用 PowerShell 3.0。 如果您碰巧是舊版,則情況並非如此。 這是 2.0 相容性後端移植程式代碼時要注意的重要變更之一。

值類型

在技術上,只有參考型別可以是 $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') ){...}

IsNotNullOrEmpty

如果值是字串,您可以使用靜態字串函式來檢查值是否為 $null 或同時為空字串。

if ( -not [string]::IsNullOrEmpty( $value ) ){...}

當我知道實值類型應該是字串時,我經常會使用這個 。

當我$null檢查時

我是一個防禦腳本手。 每當我呼叫函式並將它指派給變數時,我就會檢查它是否有 $null

$userList = Get-ADUser kevmar
if ($null -ne $userList){...}

我更喜歡使用 if 或使用 foreachtry/catch。 別搞錯了,我仍然用 try/catch 了很多。 但是,如果我可以測試錯誤條件或空的結果集,我可以允許我的例外狀況處理是針對真正的例外狀況。

我也會在索引到物件上的值或呼叫方法之前先檢查 $null 。 這兩個物件動作失敗, $null 因此我發現必須先驗證它們很重要。 我已在本文稍早涵蓋這些案例。

沒有結果案例

請務必知道不同的函式和命令會以不同的方式處理無結果案例。 許多 PowerShell 命令都會傳回空白 $null 和錯誤數據流中的錯誤。 但其他人會擲回例外狀況或提供狀態物件。 您必須知道您使用的指令如何處理無結果和錯誤案例。

初始化為$null

我所挑選的一個習慣是先初始化所有變數,然後再使用這些變數。 您必須以其他語言執行這項操作。 在函式頂端,或當我輸入 foreach 循環時,我定義我正在使用的所有值。

以下是我想仔細看看的案例。 這是我以前必須追趕的 Bug 的範例。

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。 如果發生錯誤,我們會將其記錄。 然後,我們會檢查以確定我們在處理之前取得有效的結果。

隱藏在此程序代碼中的 Bug 是 Get-Something 擲回例外狀況,且不會將值指派給 $result時。 它會在指派之前失敗,因此我們甚至不會指派 $null$result 變數。 $result 仍然包含來自其他反覆專案的上一個有效 $result 專案。 Update-Something 表示在此範例中對相同物件執行多次。

我在 foreach 循環內設定 $result$null 右方,然後再使用它來減輕此問題。

foreach ( $node in 1..6 )
{
    $result = $null
    try
    {
        ...

範圍問題

這也有助於減輕範圍問題。 在此範例中,我們會在迴圈中將值指派給 $result 來回。 但是,因為PowerShell允許函式外部的變數值流入目前函式的範圍,因此在您的函式內初始化它們可減輕可透過這種方式引入的Bug。

如果您的函式中未初始化的變數設定為父範圍中的值,則不是 $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 檢查會 $resultInvoke-Something找到 。 初始化函式內的值可減輕此問題。

命名變數很難,而且作者通常會在多個函式中使用相同的變數名稱。 我知道我一直使用 $node$result$data 因此,來自不同範圍的值很容易出現在不應該出現的地方。

將輸出重新導向至$null

我一直在討論 $null 本文的值,但如果我未提及將輸出重新導向至 $null,主題就不會完成。 有時候,您有命令會輸出您想要隱藏的資訊或物件。 將輸出重新導向至 $null 該作業。

Out-Null

Out-Null 命令是將管線數據重新導向至 $null的內建方式。

New-Item -Type Directory -Path $path | Out-Null

指派給$null

您可以針對與 使用 Out-Null相同的效果,將命令的結果指派給 $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 和意識到你可能會遇到的更模糊的案例。