当前位置:网站首页 > 更多 > 玩电脑 > 正文

[玩转系统] 关于 $null 你想知道的一切

作者:精品下载站 日期:2024-12-14 03:04:07 浏览:16 分类:玩电脑

关于 $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 的集合建立索引,则会收到此错误:无法对 null 数组建立索引

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 对象上调用方法会引发 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 can not call a method on a null-valued expression 时,我首先要查找的是在没有先检查变量 $的情况下调用变量方法的位置空

检查 $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 的值。 Mathias Jessen (@IISResetMe) 有一篇很好的文章深入探讨了这种情况。

PSScriptAnalyzer 和 VSCode

PSScriptAnalyzer 模块有一个名为 PSPossibleIn CorrectComparisonWithNull 的规则来检查此问题。

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 不是 $null0$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
}

我只想在对象属性存在时为其赋值。在大多数情况下,原始对象的值在 if 语句中计算结果为 $true。但我遇到了一个问题,即该值有时无法设置。我调试了代码,发现该对象具有该属性,但它是一个空字符串值。这阻止了它用以前的逻辑进行更新。因此,我添加了适当的 $null 检查,一切正常。

if ( $null -ne $object.property )
{
    $object.property = $value
}

像这样的小错误很难发现,因此我需要积极检查 $null 的值。

$null.Count

如果您尝试访问 $null 值的属性,则该属性也是 $nullcount 属性是此规则的例外。

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

当您有 $null 值时,count0。此特殊属性是由 PowerShell 添加的。

[PS自定义对象] 计数

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,但它实际上是一个 System.Management.Automation.Internal.AutomationNull。这个可枚举 null 是作为不返回任何内容的函数或脚本块的结果而获得的值(空结果)。

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 视为集合,那么它就是空的。

如果将可枚举 null 传递给非强类型的函数参数,PowerShell 默认情况下会将可枚举 null 强制转换为 $null 值。这意味着在函数内部,该值被视为 $null 而不是 System.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 (... | 其中 {$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 进行有效的转换。这些类型会生成无法将 null 转换为类型 错误。

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') ){...}

不为空或为空

如果该值为字符串,则可以使用静态字符串函数同时检查该值为 $null 或空字符串。

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

当我知道值类型应该是字符串时,我发现自己经常使用它。

当我 $null 检查时

我是一名防守型脚本编写者。每当我调用函数并将其分配给变量时,我都会检查它是否有 $null

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

与使用 try/catch 相比,我更喜欢使用 ifforeach。不要误会我的意思,我仍然经常使用 try/catch。但是,如果我可以测试错误条件或空结果集,我就可以允许我的异常处理针对真正的异常。

我还倾向于在索引值或调用对象的方法之前检查 $null。对于 $null 对象,这两个操作会失败,因此我发现首先验证它们很重要。我已经在本文前面介绍了这些场景。

无结果场景

重要的是要知道不同的函数和命令以不同的方式处理无结果场景。许多 PowerShell 命令在错误流中返回可枚举的 null 和错误。但其他人会抛出异常或给你一个状态对象。您仍然需要了解您使用的命令如何处理无结果和错误情况。

初始化为$null

我养成的一个习惯是在使用所有变量之前初始化它们。您需要使用其他语言执行此操作。在函数的顶部或当我进入 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 对同一对象执行多次。

在使用 foreach 循环来缓解此问题之前,我将 $result 设置为 $null

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 检查会从 Invoke-Something 找到 $result代码>.初始化函数内的值可以缓解这个问题。

命名变量很困难,作者在多个函数中使用相同的变量名是很常见的。我知道我一直使用 $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 并了解您可能遇到的更晦涩的场景。

您需要 登录账户 后才能发表评论

取消回复欢迎 发表评论:

关灯