如何使Select-Object返回原始类型(例如String),而不是PSCustomObject?

48
以下代码给出了一个PSCustomObjects类型的数组,如何将其转换为一个字符串类型的数组?
$files = Get-ChildItem $directory -Recurse | Select-Object FullName | Where-Object {!($_.psiscontainer)}

作为一个次要问题,psiscontainer部分是什么?我从网上的一个例子中复制了它。
后接编辑:两个很好的答案,希望我能标记它们两个。已授予最初的答案。
4个回答

42
你只需要从对象中挑选出你想要的属性。在这种情况下,选择FullName
$files = Get-ChildItem $directory -Recurse | Select-Object FullName | Where-Object {!($_.psiscontainer)} | foreach {$_.FullName}
编辑:对于马克的问题:“foreach是做什么的?它在枚举什么?”我来解释一下。
宋大师的解释非常好,但我会在这里加上一个步骤说明,因为这可能会有所帮助。
关键概念是管道。想象一下一系列乒乓球依次滚动在一个狭窄的管道中。这些就是管道中的对象。管道的每个阶段 - 由管道符(|)分隔的代码段 - 都有一个进入和一个离开的管道。一个阶段的输出连接到下一个阶段的输入。每个阶段接收到对象后,对其进行处理,并将其发送到输出管道或发送新的、替换的对象。
Get-ChildItem $directory -Recurse

Get-ChildItem遍历文件系统,创建代表每个文件和目录的FileSystemInfo对象,并将它们放入管道中。
Select-Object FullName

Select-Object 在每个 FileSystemInfo 对象到达时,从中获取 FullName 属性(在这种情况下是一个路径),将该属性放入它创建的全新自定义对象中,并将该自定义对象输出到管道中。

Where-Object {!($_.psiscontainer)}

这是一个过滤器。它接收每个对象,检查它,并根据某些条件将其发送回或丢弃它。顺便说一下,你的代码有一个错误。到达这里的自定义对象没有一个psiscontainer属性。这个阶段实际上什么都不做。Sung Meister的代码更好。
foreach {$_.FullName}
foreach,其完整名称为ForEach-Object,在每个对象到达时抓取它,并从中获取FullName属性,一个字符串。现在,这里有一个微妙的部分:任何未被使用的值,也就是没有被变量捕获或以某种方式被抑制的值,都会被放入输出管道中。作为一个实验,试着用以下内容替换该阶段:
foreach {'hello'; $_.FullName; 1; 2; 3}

实际上试一下并检查输出。该代码块中有四个值。它们都没有被使用。请注意它们都出现在输出中。现在试试这个:
foreach {'hello'; $_.FullName; $x = 1; 2; 3}

注意到其中一个值被一个变量捕获。它不会出现在输出管道中。

19
更好的解决方案是使用"select-object -expand FullName"。该命令能够使输出结果只包含FullName的值,并去除其它属性信息,让结果更加清晰易读。请注意,这并不会改变原始数据的含义。 - Kevin Berridge
顺便提一下,您可以使用 tee-object 将变量分配并发出到输出。例如:ls | select -first 1 -property fullname | %{$_.FullName} | tee -variable firstpath - Richard Szalay

37

要获取文件名的字符串,您可以使用

$files = Get-ChildItem $directory -Recurse | Where-Object {!($_.psiscontainer)} | Select-Object -ExpandProperty FullName

-ExpandProperty参数允许您根据指定的属性类型返回对象。

进一步的测试表明,这在V1中不起作用,但该功能已在V2 CTP3中修复。


10

问题1:

我已经删除了“select-object”部分 - 它是多余的,并将“where”过滤器移动到“foreach”之前,不像dangph的回答 - 尽早过滤,以便在下一个管道线中只处理你需要处理的子集。

$files = Get-ChildItem $directory -Recurse | Where-Object {!$_.PsIsContainer} | foreach {$_.FullName}

这段代码片段基本上读取:

  • 递归获取所有文件的完整路径(Get-ChildItem $directory -Recurse)
  • 过滤掉目录(Where-Object {!$_.PsIsContainer})
  • 仅返回完整文件名(foreach {$_.FullName})
  • 将所有文件名保存到 $files 中

请注意,在 powershell 中,对于 foreach {$_.FullName},脚本块({ ... })中的最后一条语句会被返回,此处为类型为字符串的 $_.FullName。

如果您真的需要获取原始对象,则在去除 "select-object" 后无需进行任何操作。如果您要使用 Select-Object 但想要访问原始对象,请使用 "PsBase",这是一个完全不同的问题(主题) - 参考 "PSBASE、PSEXTENDED、PSADAPTED和PSOBJECT有何区别?" 获取更多关于该主题的信息。

对于问题 #2

并且按照!$_.PsIsContainer 进行过滤意味着您正在排除容器级别的对象 - 在您的情况下,您正在对FileSystem提供程序执行Get-ChildItem(您可以通过Get-PsProvider查看PowerShell提供程序),因此容器是一个DirectoryInfo(文件夹) PsIsContainer 在不同的PowerShell提供程序下具有不同的含义; 例如) 对于Registry提供程序,PsIsContainer的类型为Microsoft.Win32.RegistryKey 尝试这个:
>pushd HKLM:\SOFTWARE
>ls | gm

[更新] 对于以下问题: foreach是做什么的?它在枚举什么? 需要澄清的是,"foreach"是"Foreach-Object"的别名。 您可以通过以下方式找到答案,

get-help foreach

- 或 -

get-alias foreach

现在在我的答案中,“foreach”正在枚举前一个管道(已过滤掉目录)返回的每个FileInfo类型的对象实例。 {{em}}FileInfo{{/ em}}有一个名为{{em}}FullName{{/ em}}的属性,这就是“foreach”正在枚举的内容。
您可以通过特殊的管道变量{{dollar}}_{{/ dollar}}来引用通过管道传递的对象,该变量在“foreach”的脚本块上下文中为{{em}}FileInfo{{/ em}}类型。

foreach是什么意思?它正在枚举什么? - Mark Ingram

3

对于 V1 版本,请在您的配置文件中添加以下过滤器:

filter Get-PropertyValue([string]$name) { $_.$name }

然后您可以执行以下操作:
gci . -r | ?{!$_.psiscontainer} | Get-PropertyName fullname

顺便提一下,如果您正在使用PowerShell社区扩展,那么您已经拥有了这个功能。

关于在V2中使用Select-Object -Expand的能力,这是一个巧妙的技巧,但并不明显,而且也不是Select-Object或-Expand的本意。 -Expand是关于像LINQ的SelectMany和Select-Object是关于将多个属性投影到自定义对象上的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接