为什么我不能在写主机中使用 $_ ? [英] Why can't I use $_ in write-host?

查看:65
本文介绍了为什么我不能在写主机中使用 $_ ?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试通过管道将字符串数组传递给 write-host 并显式使用 $_ 来编写这些字符串:

'foo', 'bar', 'baz' |写主机 $_

但是,它失败了:

<块引用>

输入对象不能绑定到命令的任何参数,因为命令不接受管道输入,或者输入及其属性与接受管道输入的任何参数都不匹配.

这个错误信息对我来说毫无意义,因为我完全可以写

'foo', 'bar', 'baz' |写主机

我原以为两条管道都是等价的.显然,他们不是.那么,有什么区别呢?

解决方案

我原以为两条管道都是等价的.

他们不是:

<块引用>

'foo', 'bar', 'baz' |写主机

它是以下基于管道的等效项(等效于最终效果,而不是技术上):

foreach ($str in 'foo', 'bar', 'baz') { Write-Host -Object $str }

也就是说,在您的命令中,Write-Host 接收来自管道的输入,该管道隐式绑定到它的 -Object 参数 每个输入对象,通过参数-Object被声明为通过属性[Parameter(ValueFromPipeline=$true)]

接受管道输入
<块引用>

'foo', 'bar', 'baz' |写主机 $_

管道处理开始之前,参数 - $_ 在您的情况下 - 绑定到参数first:

由于 $_ 前面没有参数名称,它位置绑定到 - 隐含 - -Object 参数.

然后,当管道处理开始时,pipeline 参数绑定找不到管道绑定 Write-Host 参数再绑定到,因为只有这样的参数,-Object 已经绑定,即通过一个参数 $_.

换句话说:您的命令错误地尝试绑定 -Object 参数 twice; 不幸的是,错误消息并没有完全使很清楚.

更重要的一点是,使用 $_ 只在脚本块内有意义 ({ ... }) 对每个输入对象进行评估.
在该上下文之外,$_(或其别名,$PSItem)通常没有价值,不应使用.

虽然 $_ 最常用于传递给 ForEach-ObjectWhere-Object cmdlet 的脚本块,但还有其他一些有用的应用程序,最常见的是使用 Rename-Item cmdlet:一个 delay-bind 脚本块参数:

# 示例:使用延迟绑定脚本块将 *.txt 文件重命名为 *.dat 文件:获取项目 *.txt |Rename-Item -NewName { $_.BaseName + '.dat' }

也就是说,不是将 static 新名称传递给 Rename-Item,而是传递一个为每个输入对象求值的 脚本块 - 输入对象绑定到 $_,像往常一样 - 启用动态行为.

然而,如链接答案中所述,此技术仅适用于 (a) 管道绑定和 (b) not [object][scriptblock] 输入;因此,鉴于 Write-Object-Object 参数 [object] 类型,该技术不会工作:

 # 尝试在输出时将所有输入包含在 [...] 中.#!!不工作.'foo', 'bar', 'baz' |写主机-对象{[$_]";}

因此,在这种情况下,基于管道的解决方案需要使用 ForEach-Object:

# -Object 是可选的PS>'foo', 'bar', 'baz' |ForEach-Object { write-host -Object "[$_]";}[foo][酒吧][巴兹]

I am trying to pipe an array of strings to write-host and explicitly use $_ to write those strings:

'foo', 'bar', 'baz' | write-host $_

However, it fails with:

The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.

This error message makes no sense to me because I am perfectly able to write

'foo', 'bar', 'baz' | write-host

I would have expected both pipelines to be equivalent. Apparently, they're not. So, what's the difference?

I would have expected both pipelines to be equivalent.

They're not:

'foo', 'bar', 'baz' | write-host

It is the pipeline-based equivalent of the following (equivalent in ultimate effect, not technically):

foreach ($str in 'foo', 'bar', 'baz') { Write-Host -Object $str }

That is, in your command Write-Host receives input from the pipeline that implicitly binds to its -Object parameter for each input object, by virtue of parameter -Object being declared as accepting pipeline input via attribute [Parameter(ValueFromPipeline=$true)]


'foo', 'bar', 'baz' | write-host $_

Before pipeline processing begins, arguments - $_ in your case - are bound to parameters first:

Since $_ isn't preceded by a parameter name, it binds positionally to the - implied - -Object parameter.

Then, when pipeline processing begins, pipeline parameter binding finds no pipeline-binding Write-Host parameter to bind to anymore, given that the only such parameter, -Object has already been bound, namely by an argument $_.

In other words: your command mistakenly tries to bind the -Object parameter twice; unfortunately, the error message doesn't exactly make that clear.

The larger point is that using $_ only ever makes sense inside a script block ({ ... }) that is evaluated for each input object.
Outside that context, $_ (or its alias, $PSItem) typically has no value and shouldn't be used.

While $_ is most typically used in the script blocks passed to the ForEach-Object and Where-Object cmdlets, there are other useful applications, most typically seen with the Rename-Item cmdlet: a delay-bind script-block argument:

# Example: rename *.txt files to *.dat files using a delay-bind script block:
Get-Item *.txt | Rename-Item -NewName { $_.BaseName + '.dat' }

That is, instead of passing a static new name to Rename-Item, you pass a script block that is evaluated for each input object - with the input object bound to $_, as usual - which enables dynamic behavior.

As explained in the linked answer, however, this technique only works with parameters that are both (a) pipeline-binding and (b) not [object] or [scriptblock] typed; therefore, given that Write-Object's -Object parameter is [object] typed, the technique does not work:

 # Try to enclose all inputs in [...] on output.
 # !! DOES NOT WORK.
 'foo', 'bar', 'baz' | write-host -Object { "[$_]" }

Therefore, a pipeline-based solution requires the use of ForEach-Object in this case:

# -Object is optional
PS> 'foo', 'bar', 'baz' | ForEach-Object { write-host -Object "[$_]" }
[foo]
[bar]
[baz]

这篇关于为什么我不能在写主机中使用 $_ ?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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