对于 PowerShell cmdlet,我可以始终将脚本块传递给字符串参数吗? [英] For PowerShell cmdlets, can I always pass a script block to a string parameter?

查看:32
本文介绍了对于 PowerShell cmdlet,我可以始终将脚本块传递给字符串参数吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在查看 PowerShell 的 Rename-Item cmdlet 的文档,有一个这样的例子.

Get-ChildItem *.txt |Rename-Item -NewName { $_.name -Replace '.txt','.log' }

<块引用>

这个例子展示了如何使用替换操作符来重命名多个文件,即使 NewName 参数不接受通配符字符.

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

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

NewName 的值是一个脚本块,在该值之前运行提交给 NewName 参数.

注意最后一句:

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

实际上 NewName 是一个字符串:

[-NewName] <字符串>

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

解决方案

# Delay-bind script-block argument:# { ... } 中的代码为每个输入对象($_)和# 输出传递给 -NewName 参数.... |Rename-Item -NewName { $_.Name -replace '.txt$','.log' }

上面的调用显示了一个 delay-bind 脚本块 ({ ... }) 参数,这是一个隐式特性:

  • 适用于设计用于接收管道输入的参数

    • 任何类型除了以下,在这种情况下常规参数绑定发生[1]:

      • [脚本块]
      • [object]([psobject],但是,确实有效,因此等效的[pscustomobject] 也是)
      • (未指定类型),实际上与 [object]
      • 相同
    • 这些参数是否接受通过value(ValueFromPipelineBy)或属性名称(ValueFromPipelineByPropertyName)输入的管道>),无关紧要.

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

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

    • 延迟绑定脚本块无条件提供对管道输入对象的访问,即使参数通常被给定的管道对象绑定,如果它被定义为 ValueFromPipelineByPropertyName 并且该对象缺少该名称的属性.

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

      • Get-Item 文件 |Rename-Item -NewName { $_.Name + '1' } # 将 'file' 重命名为 'file1';输入绑定到两者 -LiteralPath(隐式) -NewName 脚本块.
    • 注意:与传递给 ForEach-ObjectWhere-Object 的脚本块不同,例如,delay-bind 脚本块在 child 变量作用域[2],这意味着您不能直接修改调用者的变量,例如跨输入对象递增计数器.
      作为解决方法,请使用 Get-Variable 访问调用者的变量并访问其脚本块内的 .Value 属性 - 请参阅 这个答案举个例子.


[1] 错误条件:

  • 如果您错误地尝试将脚本块传递给非管道绑定或[scriptblock]- 或[object]-typed (untyped), regular 参数绑定发生:

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

    • 无法评估参数"因为它的参数被指定为一个脚本块并且没有输入.没有输入就无法评估脚本块.

[2] 正在 GitHub 问题 #7157.

I'm looking at the documentation of PowerShell's Rename-Item cmdlet and there is an example like this.

Get-ChildItem *.txt | Rename-Item -NewName { $_.name -Replace '.txt','.log' }

This example shows how to use the Replace operator to rename multiple files, even though the NewName parameter does not accept wildcard characters.

This command renames all of the .txt files in the current directory to .log.

The command uses the Get-ChildItem cmdlet to get all of the files in the current folder that have a .txt file name extension. Then, it uses the pipeline operator (|) to send those files to Rename-Item .

The value of NewName is a script block that runs before the value is submitted to the NewName parameter.

Note the last sentence:

The value of NewName is a script block that runs before the value is submitted to the NewName parameter.

Actually NewName is a string:

[-NewName] <String>

So does that means I can always use a script block when the required parameter type is a string?

解决方案

# 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' }

The call above shows an application of a delay-bind script-block ({ ... }) argument, which is an implicit feature that:

  • only works with parameters that are designed to take pipeline input,

    • of any type except the following, in which case regular parameter binding happens[1]:

      • [scriptblock]
      • [object] ([psobject], however, does work, and therefore the equivalent [pscustomobject] too)
      • (no type specified), which is effectively the same as [object]
    • whether such parameters accept pipeline input by value (ValueFromPipelineBy) or by property name (ValueFromPipelineByPropertyName), is irrelevant.

  • enables per-input-object transformations via a script block passed instead of a type-appropriate argument; the script block is evaluated for each pipeline object, which is accessible inside the script block as $_, as usual, and the script block's output - which is assumed to be type-appropriate for the parameter - is used as the argument.

    • Since such ad-hoc script blocks by definition do not match the type of the parameter you're targeting, you must always use the parameter name explicitly when passing them.

    • Delay-bind script blocks unconditionally provide access to the pipeline input objects, even if the parameter would ordinarily not be bound by a given pipeline object, if it is defined as ValueFromPipelineByPropertyName and the object lacks a property by that name.

    • This enables techniques such as the following call to Rename-Item, where the pipeline input from Get-Item is - as usual - bound to the -LiteralPath parameter, but passing a script block to -NewName - which would ordinarily only bind to input objects with a .NewName property - enables access to the same pipeline object and thus deriving the destination filename from the input filename:

      • Get-Item file | Rename-Item -NewName { $_.Name + '1' } # renames 'file' to 'file1'; input binds to both -LiteralPath (implicitly) and the -NewName script block.
    • Note: Unlike script blocks passed to ForEach-Object or Where-Object, for example, delay-bind script blocks run in a child variable scope[2], which means that you cannot directly modify the caller's variables, such as incrementing a counter across input objects.
      As a workaround, use Get-Variable to gain access to a caller's variable and access its .Value property inside the script block - see this answer for an example.


[1] Error conditions:

  • If you mistakenly attempt to pass a script block to a parameter that is either not pipeline-binding or is [scriptblock]- or [object]-typed (untyped), regular parameter-binding occurs:

    • The script block is passed once, before pipeline-input processing begins, if any.
      That is, the script block is passed as a (possibly converted) value, and no evaluation happens.
      • For parameters of type [object] or [scriptblock] / a delegate type such as System.Func that is convertible to a script block, the script block will bind as-is.
      • In the case of a (non-pipeline-binding) [string]-typed parameter, the script block's literal contents is passed as the string value.
      • For all other types, parameter binding - and therefore the command as a whole - will simply fail, since conversion from a script block is not possible.
  • If you neglect to provide pipeline input while passing a delay-bind script block to a pipeline-binding parameter that does support them, you'll get the following error:

    • 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] This discrepancy is being discussed in GitHub issue #7157.

这篇关于对于 PowerShell cmdlet,我可以始终将脚本块传递给字符串参数吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆