对于 PowerShell cmdlet,我可以始终将脚本块传递给字符串参数吗?

Jus*_*ner 2 parameters powershell cmdlets scriptblock delay-bind

我正在查看 PowerShell 的Rename-Itemcmdlet的文档,有一个这样的例子。

Get-ChildItem *.txt | Rename-Item -NewName { $_.name -Replace '\.txt','.log' }
Run Code Online (Sandbox Code Playgroud)

此示例说明如何使用 Replace 运算符重命名多个文件,即使 NewName 参数不接受通配符也是如此。

此命令将当前目录中的所有 .txt 文件重命名为 .log。

该命令使用 Get-ChildItem cmdlet 获取当前文件夹中具有 .txt 文件扩展名的所有文件。然后,它使用管道运算符 (|) 将这些文件发送到 Rename-Item 。

NewName 的值是在将值提交给 NewName 参数之前运行的脚本块。

注意最后一句话:

NewName 的值是在将值提交给 NewName 参数之前运行的脚本块

实际上NewName是一个字符串:

[-NewName] <String>
Run Code Online (Sandbox Code Playgroud)

那么这是否意味着当所需的参数类型是字符串时,我总是可以使用脚本块?

mkl*_*nt0 7

# Delay-bind script-block argument:
# The code inside { ... } is executed for each input object ($_) and
# the output is passed to the -NewName parameter.
... | Rename-Item -NewName { $_.Name -replace '\.txt$','.log' }
Run Code Online (Sandbox Code Playgroud)

上面的调用显示了延迟绑定脚本块( { ... }) 参数的应用,这是一个隐式特性

  • 适用于旨在接受管道输入的参数

    • 任何类型的不同之处如下,在这种情况下经常参数绑定发生[1]

      • [scriptblock]
      • [object][psobject]然而,确实有效,因此[pscustomobject]也是如此)
      • (未指定类型),实际上与 [object]
    • 这些参数是否接受按( ValueFromPipelineBy) 或按属性名称( ValueFromPipelineByPropertyName)输入的管道是无关紧要的。

  • 通过传递的脚本块而不是类型适当的参数启用 每个输入对象的转换;脚本块针对每个管道对象进行评估,该对象可以$_像往常一样在脚本块内部访问,并且脚本块的输出(假定为适合参数的类型)用作参数。

    • 由于此类临时脚本块的定义与您的目标参数类型不匹配,因此在传递它们时必须始终明确使用参数名称

    • 延迟绑定脚本块无条件提供接入管道输入对象,即使该参数将通常不会被给定的管道对象的约束,如果它被定义为ValueFromPipelineByPropertyName和对象没有该名称的属性。

    • 这启用了诸如以下调用 的技术Rename-Item,其中来自的管道输入Get-Item- 像往常一样 - 绑定到-LiteralPath参数,但将脚本块传递到-NewName- 通常仅绑定到具有.NewName属性的输入对象- 允许访问相同的管道对象,从而从输入文件名派生目标文件名:

      • Get-Item file | Rename-Item -NewName { $_.Name + '1' } # renames 'file' to 'file1'; 输入结合于两者 -LiteralPath(隐式地)所述-NewName脚本块。
    • 注意:与传递给ForEach-Objector 的脚本块不同Where-Object,例如,延迟绑定脚本块在变量作用域[2] 中运行,这意味着您不能直接修改调用者的变量,例如跨输入对象递增计数器。
      作为一种解决方法,使用[ref]在调用者的范围内声明的-typed 变量并.Value在脚本块内访问其属性 - 请参阅此答案以获取示例。


[1] 错误条件:

  • 如果错误地试图脚本块传递给一个参数要么是没有管道结合或者是[scriptblock]-或[object]-typed(无类型)定期装订参数发生

    • 脚本块被传递一次,在管道输入处理开始之前,如果有的话。
      也就是说,脚本块作为(可能转换的)value传递,并且不会发生任何评估
      • 对于可转换为脚本块的类型[object][scriptblock]/委托类型的参数System.Func,脚本块将按原样绑定。
      • 在(非管道绑定)[string]类型参数的情况下,脚本块的文字内容作为字符串值传递。
      • 对于所有其他类型,参数绑定 - 以及整个命令 - 将简单地失败,因为从脚本块转换是不可能的。
  • 如果你忽略了提供管道输入,同时通过延迟绑定脚本块到管道结合参数支持他们,你会得到以下错误

    • Cannot evaluate parameter '<name>' because its argument is specified as a script block and there is no input. A script block cannot be evaluated without input.

[2] 此GitHub 问题正在讨论此差异。


Pra*_*n V 5

那么这是否意味着当所需参数类型是字符串时我始终可以使用脚本块?:

这里的技术称为延迟绑定,在这种情况下非常有用。

当您延迟绑定时会发生什么?

PowerShell ParameteBinder 将理解延迟绑定的用法,并首先执行 ScriptBlock,然后将输出转换为各个参数的预期类型,这里是字符串。

下面是一个例子。

#Working one
'Path'|Join-Path -Path {$_} -ChildPath 'File'  

#Not working one
Join-Path -Path {'path'} -ChildPath 'File'
Join-Path : Cannot evaluate parameter 'Path' because its argument is specified as a script block and there is no input. A script block cannot be evaluated without input.
Run Code Online (Sandbox Code Playgroud)

要了解有关 ParameterBinding 的更多信息,您可以执行Trace-Command以下操作。

Trace-Command ParameterBinding -Expression {'Path'|Join-Path -Path {$_} -ChildPath 'File'} -PSHost
Run Code Online (Sandbox Code Playgroud)