通过类型分配添加成员怪异



在下面的代码中,为什么name1的[int]类型赋值被忽略,而实际上被视为值的一部分?程序错误

$name1 = 4
$name2 = 4
$name3 = 4
$myObject = New-Object System.Object
$myObject | Add-Member -type NoteProperty -name name1 -Value [int]$name1
$myObject | Add-Member -type NoteProperty -name name2 -Value $($name2 -as [int])
$myObject | Add-Member -type NoteProperty -name name3 -Value $([int]$name3)
$myObject

输出:

name1  name2 name3
-----  ----- -----
[int]4     4     4

Powershell版本:

get-host | select-object version
Version       
-------       
5.1.19041.1023

现有答案中有很好的信息,但让我尝试一个系统的概述:

tl;dr

为了表达式(例如[int] $name1
$env:HOME + 'foo')、或(嵌套的)命令(如Get-Date -Format yyyy)的输出作为参数传递给命令[/strong>(例Add-Member),请将其封装在(...)中,分组运算符:

# Note the required (...) around expression [int] $name1
Add-Member -type NoteProperty -name name1 -Value ([int] $name1)

相比之下,$(...),在这种情况下通常不需要子表达式运算符,使用它可能会产生副作用-请参阅此答案。

  • 简而言之:(在可扩展字符串"..."之外),您只需要$(...)来包含一个语言语句(如if语句或foreach循环)或多个语句(用;分隔的命令、表达式和语言语句的任何组合)

PowerShell的解析模式:

PowerShell具有两种基本解析模式

  • 参数模式,其工作方式类似于shell

    • 在参数模式中,第一个令牌被解释为命令名(cmdlet、函数、别名或外部可执行文件或.ps1脚本的路径名),后接参数的空白分隔列表,其中字符串可以是未加引号的[1],并且由文字部分和变量引用的混合组成的参数通常被视为可扩展字符串(就好像它们被包含在"..."中一样)
  • 表达式模式,其工作方式类似于编程语言(其中必须引用字符串),以及运算符语言语句(如赋值、foreachwhile循环),可以使用强制转换

概念about_Parsing介绍了这些模式;简而言之,是给定上下文中的第一个标记,它决定了应用哪种模式

给定的语句可能由以任何一种模式解析的部分组成,这确实是上面发生的情况:

  • 因为您的语句以命令名(Add-Member)开头,所以它是在参数模式下解析的。

  • (...)强制一个新的解析上下文,在当前的情况下([int] $name1)是在表达式模式下解析的,因为从[开始)。

什么是元字符(一个具有特殊句法意义的字符)在解析模式之间不同

  • [=仅在表达式模式下是特殊的,而在argument方式下则不是特殊的,因为它们是逐字使用的。

  • 相反,令牌初始@后跟变量名称仅在参数模式下是特殊的,在该模式下用于参数飞溅。

因此,将复合参数[int]$name1视为可扩展字符串,并生成逐字逐句的字符串[int]4

某些表达式在用作命令参数时不需要封装在(...)中(假设$var = 'Foo'):

  • 独立变量引用(例如Write-Output $varWrite-Output $env:OS)
  • 对此类引用的属性访问(例如Write-Output $var.Length)
  • 方法对此类引用调用(例如Write-Output $var.ToUpper())

请注意,这些参数是与它们的原始数据类型一起传递的,而不是字符串化的(尽管字符串化可以由接收命令执行)。

陷阱

  • 有时需要显式使用"..."来抑制属性访问解释,并逐字解释变量引用后面的.(例如,为了获得逐字foo.txtWrite-Output "$var.txt")。

  • 如果使用$(...)作为复合参数的一部分而不使用显式"..."引用,则如果$(...)子表达式开始该参数,则该参数将被分解为多个参数(例如,Write-Output $('a' + 'b')/c传递两个

  • 类似地,只有当参数以未加引号的标记开始时,混合带引号和未加引号字符串以形成单个参数才有效(例如,Write-Output One"$var"'$Two'按预期工作并产生逐字OneFoo$Two,但Write-Output 'One'"$var"'$Two'作为三个参数传递,逐字OneFoo$Two)。

简而言之:

  • 解析参数的精确规则很复杂:

    • 此答案总结了未引用参数的规则
    • 这个答案总结了在一个参数中混合使用带引号和不带引号的字符串
    • 这个答案)(底部部分)概括了PowerShell的字符串文字
  • 为了安全起见,请避免在"..."之外使用$(...),并避免在单个字符串参数中混合引用样式;在表达式中使用(单个)"..."字符串(例如Write-Output "$(Split-Path $PROFILE)/foo.txt"或)或字符串串接(Write-Output ('One' + $var + '$Two')


[1]假设它们既不包含空格也不包含PowerShell的任何元字符(请参阅此答案)。虽然引用通常采用将整个参数括在单引号或双引号中的形式(如'foo bar'"foo $var"),但也可以使用PowerShell的转义符backtick(`)引用(转义)字符(例如foo` bar)

来自about_Parsing帮助文件:

参数模式解析时,PowerShell首先将输入解释为表达式。但是当遇到命令调用时,解析在参数模式下继续。

参数模式是为解析的参数和参数而设计的shell环境中的命令所有输入都被视为可扩展字符串,除非它使用以下语法之一:

您可以将代码包含在子表达式($(...))中,以避免PowerShell将[int]$name1视为可扩展字符串,正如您已经发现的那样:-)

这是经过设计的。在将表达式传递到函数之前,需要将其括在括号中以便对其求值。

如果你试图在那里放置一个函数调用,这是一样的,例如:

$myObject | Add-Member -type NoteProperty -name name1 -Value Write-Host

返回为:

name1
-----
Write-Host

并且Write-Host [string]2返回[string]2而不是2

最新更新