我正在编写代码,以获取所有符合条件的文件。对于找到的每个文件,我创建一个仅包含几个文件($ Obj)值的自定义对象,然后将其添加到ArrayList ($Files)
中。然后将此ArrayList
返回到调用函数并存储在其($OldFiles)
的变量中。
当找不到文件或多个文件时,此方法工作正常。但是,如果仅找到一个文件,则ArrayList
会将其自身转换为PSCustomObject
,然后将引发异常,因为返回值无法将PSCustomObject
转换为ArrayList
。
奇怪的是,如果我在return语句之前添加简单的行"$Files.getType()"
,就没有错误,并且ArrayList
仍然是ArrayList
。
基本上,这将不起作用: 在第一个函数中进行了简化:
$Files = [System.Collections.ArrayList]@()
$Files.Add($Obj) > $null
return $Files
这是顶部函数的外观:
$OldFiles = [System.Collections.ArrayList](Check-OldFiles -Path $Directory -Age $Age -FilesOnly:$FilesOnly -Recurse:$Recurse -UserCreationTime:$UserCreationTime)
但这很好用: 在第一个函数中进行了简化:
$Files = [System.Collections.ArrayList]@()
$Files.Add($Obj) > $null
$Files.getType()
return $Files
这是顶部函数的外观:
$OldFiles = [System.Collections.ArrayList](Check-OldFiles -Path $Directory -Age $Age -FilesOnly:$FilesOnly -Recurse:$Recurse -UserCreationTime:$UserCreationTime)
为什么会发生这种情况?
PowerShell
的版本是5.0
答案 0 :(得分:2)
澄清,注意(@Bender最大)和解释(@Phragos):
@()不会“包装”其内容。它将其内容转换为Array
。如果其内容已经是Array
,则它按原样返回。如果它是一个值或单个对象,则将其复制到一个元素Object[]
中。如果是其他某种形式的集合(例如ArrayList
),则会将每个元素复制到Object[]
中。 (注意:“ @()”为空的Object[]
。)
请勿使用,(...)
强制...成为Array
。输出对象(显示,文件,打印机等)时,自动展开行为可能会引起误解。
$myCollection = ,(get-process)
除非只有一个进程(例如来自$myCollection
的进程),否则不会将Array
留给get-process -name
个进程。相反,$myCollection
将是包含整个输出(单个对象或输出对象的Array
)的单个元素的Array
。格式输出cmdlet(Format-Table
,Format-List
和Format-Wide
)掩盖了这一事实,该命令检查接收到的对象,如果有Arrays
,则将它们解包。 (更多详细信息How PowerShell Formatting and Outputting REALLY works)。
您说“但是,如果我这样做可以正常工作”:
$myCollection | foreach { $_.starttime }
我说,是的,您会获得启动时间列表,但但是不是您的想法。在上面的示例中,$myCollection
被解包,每个元素都被发送到foreach
的管道中。只有一个元素是Array
,因此foreach
循环了一次,并且$_
设置为Array
个进程。由于Array
是一个集合,但是没有启动时成员,因此PowerShell(V3 +)执行成员枚举,创建一个Array
,该{由每个{的元素的启动时成员的值组成{1}}。然后将此$_
展开并发送到管道中。
您说“那,效果是一样的”,我回答,是的,直到您尝试更复杂的方法为止:
Array
预期的输出是文件名和长度的列表。实际输出是名称,空白行和文件数的列表。为什么? $myFiles = ,(gci -file *)
$myFiles | foreach { $_.name+' '+$_.length }
是包含文件列表的一个元素的$myFiles
,因此Array
循环一次,其中foreach
是此列表。 $_
没有 name 成员,因此PowerShell执行成员枚举产生一个Array
的名称。接下来,将一个空格的Array
添加(连接)到该String
。最后,添加Array
。由于$_.length
确实具有长度成员,因此此值(Array
)被添加到名称的Int32
和Array
中。然后将此' '
拆开并发送出去。
摘要,如果您希望某个可能是Array
的东西肯定是Array
,请不要使用,(...)。始终使用@(...),就是这样。
但是,在其他情况下,使用((...)是适当的,但仍可能造成混淆。
解析命令行时,请使用空格分隔令牌而不是参数。因此,以下是等效的:
Array
使用前置的,创建一个由一项组成的数组时,这可能使您绊倒。假设您有一个接受1,2,3
1 , 2, 3
作为参数的cmdlet,但只想提供一个本身就是Array
的元素。例如,
Array
由于解析器实际上忽略了之前的空格(因为是单个字符标记,所以不需要分隔),因此将其解析为:
new-object collections.arraylist ,(1,2,3)
New-Object : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'ComObject'. Specified method is not supported.
At line:1 char:12
+ new-object collections.arraylist ,(1,2,3)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [New-Object], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgument,Microsoft.PowerShell.Commands.NewObjectCommand
因此在这种情况下应该是:
new-object ("collections.arraylist",(1,2,3))
注意:@(1,2,3)无效,因为1,2,3已经是 new-object collections.arraylist (,(1,2,3))
。
Powershell中的return语句与功能语言不同,它不是产生输出的唯一方法。每个产生除Array
†之外的值的表达式都被发送到管道中,即被收集以返回(数组的值被展开并且元素被单独收集)。 return 使函数在生成值后返回(它不会“返回”值)。我假设函数的看不见的部分没有生成任何值。但是,将$ Files.gettype()放入函数将导致生成一个值(void
)。因此,该函数总是返回比以前多1个对象。我必须假定未测试0个匹配文件的情况(该功能已经在这种情况下起作用了,对吗?),因为现在我们回到一个对象,因此该功能再次返回“值”(System.RuntimeType
) 。我不知道如何使用返回的对象,但是没有提到由System.RuntimeType
的第一个元素是ArrayList
而不是System.RuntimeType
引起的问题。大概,用法并不介意为不存在的属性获取$ null(仅PSCustomObject
和System.RuntimeType
都使用[String] Name和[String] Fullname)和{{1 }}不会引起类型冲突(System.IO.FileInfo
和Attributes
都是System.RuntimeType.Attributes
,但是System.IO.FileInfo.Attributes
的值不是有效的System.Enum
值,或者PowerShell会这样说)
†赋值语句的类型为[Collections.ArrayList].Attributes
,而赋值表达式(表达式中的赋值)具有非空类型。因此,
IO.FileInfo.Attributes
答案 1 :(得分:0)
当单个元素沿管道向下传递时,集合会在PowerShell中自动展开。如果您想保证即使只有一个元素,变量也仍然是一个集合,可以使用一些技巧(以下示例使用Get-Process
,但是可以使用任何返回集合的集合变量或cmdlet) ):
$myCollection = @(Get-Process)
),
(例如$myCollection = , (Get-Process)
@()
指定了一个数组,您可以为它提供一个元素列表或使用一个可能返回集合的cmdlet。
, (Get-Process)
语法看起来很奇怪,但这与为数组指定元素的硬编码列表的语法相同。请考虑以下内容:
$myArray = 1, 2, 3, 4, 5, "Bender"
这将创建一个包含元素1
,2
,3
,4
,5
和"Bender"
的数组。不过,在上面的简写中,仅将,
作为表达式中的第一个字符表示这是一个数组,请照此处理。由于PowerShell展开集合的性质,在该初始逗号之后返回的所有后续数组/集合都将添加到最终集合中。