代码之家  ›  专栏  ›  技术社区  ›  Chris Oldwood

为什么此powershell函数的默认参数会根据的使用更改值。或者调用其中的命令?

  •  11
  • Chris Oldwood  · 技术社区  · 6 年前

    我遇到了一些奇怪的行为,其中一个默认的论点是 出现 改变价值( null 或空字符串)基于该函数是否使用 . & 调用内部的本机命令。

    下面是一个具有两个相同函数的示例脚本,其中唯一的区别是它们如何调用本机命令( cmd.exe ):

    function Format-Type($value)
    {
        if ($value -eq $null) { '(null)' } else { $value.GetType().FullName }
    }
    
    function Use-Dot
    {
        param(
            [string] $Arg = [System.Management.Automation.Language.NullString]::Value
        )
    
        Write-Host ".: $(Format-Type $Arg)"
    
        . cmd.exe /c exit 0
    }
    
    function Use-Ampersand
    {
        param(
            [string] $Arg = [System.Management.Automation.Language.NullString]::Value
        )
    
        Write-Host "&: $(Format-Type $Arg)"
    
        & cmd.exe /c exit 0
    }
    
    Use-Dot
    Use-Ampersand
    

    在powershell 5.1上,我得到了以下输出,这表明在这两种情况下参数的值是不同的:

    .: (null)
    &: System.String
    

    以这种方式将这种行为联系起来听起来很可笑,因此我肯定我在这里遗漏了一些明显的(或可能非常微妙的)东西,但这是什么?

    ——

    问题 What is the . shorthand for in a PowerShell pipeline? 谈范围的不同 . & 但这并没有说明为什么在命令调用中甚至没有引用的默认参数可能会受到其使用的影响。在我的例子中,来电者似乎受到了影响 之前 命令甚至被调用。

    0 回复  |  直到 5 年前
        1
  •  0
  •   sheldonhull    6 年前

    我花了点时间研究这个,这就是我观察到的。

    首先为了清楚起见,我不认为您应该在基本比较中将nullstring值视为与null相同的值。也不知道你为什么需要这个,因为这通常是我对C开发的期望。你应该可以使用 $null 在powershell的大部分工作。

    
    
    if($null -eq [System.Management.Automation.Language.NullString]::Value)
    {
        write-host "`$null -eq [System.Management.Automation.Language.NullString]::Value"
    }
    else
    {
        write-host "`$null -ne [System.Management.Automation.Language.NullString]::Value"
    }
    

    其次,这个问题不一定是因为接线员 & . 相反,我相信您正在处理底层参数绑定强制。强数据类型绝对是powershell的薄弱环节,甚至可以明确声明 [int]$val 在写入时,powershell可能会在下一行自动将其设置为字符串类型 Write-Host $Val .

    为了确定潜在的行为,我使用 Trace-Command 功能( Trace Command )

    我将use dot改为只调用函数,因为不需要写主机来输出字符串。

    
    function Use-Ampersand
    {
        param(
            [string]$NullString = [System.Management.Automation.Language.NullString]::Value
        )
        Format-Type $NullString
        &cmd.exe /c exit 0
    }
    
    

    我修改的格式类型也使用了 零值 在左边,同样由于类型推断。

    function Format-Type($v= [System.Management.Automation.Language.NullString]::Value)
    {
    
        if ($null  -eq $v)
        {       
         '(null)'
        }
        else {
            $v.GetType().FullName
         }
    }
    

    为了缩小数据类型的问题范围,我使用了以下命令,但这不是我发现问题的地方。两者直接调用时工作相同。

    Trace-Command -Name TypeConversion -Expression { Format-Type $NullString} -PSHost
    Trace-Command -Name TypeConversion -Expression { Format-Type ([System.Management.Automation.Language.NullString]$NullString) } -PSHost
    

    但是,当我使用typeconversion跟踪运行函数时,它显示了转换的差异,这可能解释了您观察到的一些行为。

    Trace-Command -Name TypeConversion  -Expression { Use-Dot} -PSHost
    Trace-Command -Name TypeConversion  -Expression { Use-Ampersand} -PSHost
    
    # USE DOT
    DEBUG: TypeConversion Information: 0 :  Converting "" to "System.String".
    DEBUG: TypeConversion Information: 0 :      Converting object to string.
    DEBUG: TypeConversion Information: 0 :  Converting "" to "System.Object". <<<<<<<<<<<
    DEBUG: TypeConversion Information: 0 :  Converting ".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.PY;.PYW;.CPL" to "System.String".
    DEBUG: TypeConversion Information: 0 :      Result type is assignable from value to convert's type
    

    OUTPUT: (null)

    # Use-Ampersand
    DEBUG: TypeConversion Information: 0 : Converting "" to "System.String".
    DEBUG: TypeConversion Information: 0 :     Converting object to string.
    DEBUG: TypeConversion Information: 0 : Converting "" to "System.String". <<<<<<<<<<<
    DEBUG: TypeConversion Information: 0 :     Converting null to "".        <<<<<<<<<<<
    DEBUG: TypeConversion Information: 0 : Converting ".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.PY;.PYW;.CPL" to "System.String".
    DEBUG: TypeConversion Information: 0 :     Result type is assignable from value to convert's type
    
    

    OUTPUT: System.String

    明显的区别在于 Use-Ampersand 它显示了 Converting null to "" VS Converting "" to "System.Object" . 在powershell中, $null <> [string]'' . 空字符串比较将通过空检查,从而成功输出 GetType() .

    关于powershell方法的几点思考

    为什么要这样做,我不确定,但在你投入更多时间研究之前,让我提供一个基于艰难学习的建议。

    如果开始处理由于尝试强制powershell中的数据类型而导致的问题,请首先考虑powershell是否适合该作业

    是的,您可以使用类型扩展。是的,您可以使用.NET数据类型,如 $List = [System.Collections.Generic.List[string]]::new() 一些.NET类型的规则可以强制执行。但是,powershell并不是设计为像c_这样的强类型语言。试图这样做会导致许多困难。虽然我是powershell的超级粉丝,但我已经认识到它的灵活性应该得到重视,它的局限性应该得到尊重。

    如果我真的有需要映射的问题 [System.Management.Automation.Language.NullString]::Value 所以,我会考虑我的方法。

    这就是说,这是一项具有挑战性的调查,我不得不接受,同时提供我的10美分之后。

    其他资源

    把我的答案贴出来后,我又找到了一个 answer 这似乎很相关,也支持了不使用 [NullString] 通常情况下,它在powershell中的使用并不是真正为之设计的。

        2
  •  0
  •   Matt McNabb    5 年前

    这是一个迷人的,我不能完全解释,但可以确认,它似乎是由点源在第一个功能。我已经简化了你的脚本,以便重新制作:

    function Use-Dot
    {
        param
        (
            [string]
            $Param1 = [System.Management.Automation.Language.NullString]::Value
        )
    
        $Param1.GetType()
    
        . {$null}
    }
    
    function Use-Invoke
    {
        param
        (
            [string]
            $Param2 = [System.Management.Automation.Language.NullString]::Value
        )
    
        $Param2.GetType()
    
        & {$null}
    }
    
    Use-Dot
    Use-Invoke
    

    这将返回use dot的空调用错误,并返回use invoke的字符串类型:

    PS C:\> C:\TestArgScope.ps1
    You cannot call a method on a null-valued expression.
    At c:\TestArgScope.ps1:10 char:5
    +     $Param1.GetType()
    +     ~~~~~~~~~~~~~~~~~
        + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
        + FullyQualifiedErrorId : InvokeMethodOnNull
    
    
    IsPublic IsSerial Name                                     BaseType                                                        
    -------- -------- ----                                     --------                                                        
    True     True     String                                   System.Object   
    

    现在考虑这样做:

    function Use-Dot
    {
        param
        (
            [string]
            $Param1
        )
    
        $Param1.GetType()
    
        . {$null}
    }
    
    function Use-Invoke
    {
        param
        (
            [string]
            $Param2
        )
    
        $Param2.GetType()
    
        & {$null}
    }
    
    Use-Dot -Param1 [System.Management.Automation.Language.NullString]::Value
    Use-Invoke -Param1 [System.Management.Automation.Language.NullString]::Value
    

    这将输出两个函数的字符串类型,我认为这表明在提供默认参数值时,在参数绑定期间出现问题。正如sheldonh所指出的,dot源代码似乎正在改变它后面的函数的类型转换行为,但是为什么呢?我不知道所有的答案,但有几点意见:

    1. 不应该有任何理由点源对cmd.exe的调用-点源有一个非常特定的用例,与调用命令不同。
    2. 我完全没有理由使用nullstring类型。我知道这实际上不是你的生产脚本,所以我不知道你为什么要用它来演示,但它确实有一个特定的用途,那就是用于.NET方法调用。
    3. 在函数中使用点源可能是个坏主意,可能会产生一些不可靠的结果。
    4. 字符串与$null的比较是一个坏主意-为什么在您可以这样做时使用它:

      if (!($Arg)) {<do something>}