现有答案中有很好的信息;让我总结和补充它们:
您的命令的简化和强大的重新表述:
$latestReleaseNotesFile =
Get-ChildItem -LiteralPath $releaseNotesPath -Filter *.txt |
Select-Object -First 1
$releaseNote = $latestReleaseNotesFile | Get-Content
-
Get-ChildItem -LiteralPath 参数确保其参数被逐字处理,而不是作为通配符表达式,这是-Path 所期望的。
-
Get-ChildItem 的输出已经按名称排序(虽然这个事实没有正式记录,但它是用户开始依赖的行为,并且不会改变)。
-
通过不使用Select-Object FullName, Name将Get-ChildItem输出的System.IO.FileInfo实例转换为仅具有指定属性的[pscustomobject]实例,生成的对象可以作为一个整体 被传送到Get-Content,它由其.PSPath 属性值隐式绑定到-LiteralPath(别名为-PSPath),其中包含完整路径(带有PowerShell provider 前缀)。
至于你尝试了什么:
Get-Content $latestReleaseNotesFile
这个位置将变量$latestReleaseNotesFile的值绑定到Get-Content的-Path参数。
由于-Path 是[string[]] 类型的(即,它接受一个或多个字符串;使用Get-Help Get-Content 来查看),$latestReleaseNotesFile 的值是字符串化的 如有必要,通过其.ToString() 方法。
Select-Object FullName, Name
这将创建具有.FullName 和.Name 属性的[pscustomobject] 实例,其值取自Get-ChildItem 输出的System.IO.FileInfo 实例。
对[pscustomobject] 实例进行字符串化会产生一个非正式、哈希表-类似 的表示,仅适用于人类观察者;例如:
# -> '@{FullName=/path/to/foo; Name=foo})'
"$([pscustomobject] @{ FullName = '/path/to/foo'; Name = 'foo' }))"
注意:我使用 可扩展字符串 ("...") 进行字符串化,因为直接调用 .ToString() 会意外产生 空字符串,因为GitHub issue #6163 中描述的一个长期存在的错误。
不出所料,传递内容为 @{FullName=/path/to/foo; Name=foo}) 的字符串不是有效的文件系统路径,并导致您看到的错误。
如 Shayki 的回答所示,改为传递 .FullName 属性值可以解决该问题:
Get-Content -LiteralPath $latestReleaseNotesFile.FullName
如顶部所示,坚持使用System.IO.FileInfo 实例并通过管道 隐式绑定到-LiteralPath:
# Assumes that $latestReleaseNotesFile is of type [System.IO.FileInfo]
# This is the equivalent of:
# Get-Content -LiteralPath $latestReleaseNotesFile.PSPath
$latestReleaseNotesFile | Get-Content
陷阱:因此人们会期望将相同类型的对象作为参数传递会导致 same 绑定,但那是 不是正确的:
# !! NOT the same as:
# $latestReleaseNotesFile | Get-Content
# !! Instead, it is the same as:
# Get-Content -Path $latestReleaseNotesFile.ToString()
Get-Content $latestReleaseNotesFile
-
也就是说,参数不被它的.PSPath属性值绑定到-LiteralPath;相反,字符串化值绑定到-Path。
-
在 PowerShell (Core) 7+ 中,这通常不是问题,因为System.IO.FileInfo(和System.IO.DirectoryInfo)实例始终 > 字符串化到它们的 完整 路径(.FullName 属性值) - 但是,对于包含 [ 或 ] 的文字路径,它仍然会出现故障。
-
在 Windows PowerShell 中,此类实例在情境上 仅字符串化为文件 name (.Name),从而导致 出现故障和微妙可能存在错误 - 请参阅this answer。
GitHub issue #6057 讨论了这种有问题的不对称。
以下是对上述内容的总结,并附有具体指导:
将文件系统路径稳健地传递给文件处理 cmdlet:
注意:以下内容不仅适用于Get-Content,还适用于所有文件处理标准cmdlet——不幸的是Windows PowerShell中的Import-Csv例外,由于一个错误。
-
作为一个论据:
-
明确地使用 -LiteralPath,因为使用 -Path(如果 没有 参数被命名,这也是隐含的)将其参数解释为 wildcard expression,特别是导致包含[ 或] 的文字文件路径被误解。
# $pathString is assumed to be a string ([string])
# OK: -LiteralPath ensures interpretation as a literal path.
Get-Content -LiteralPath $pathString
# Same as:
# Get-Content -Path $pathString
# !! Path is treated as a *wildcard expression*.
# !! This will often not matter, but breaks with paths with [ or ]
Get-Content $pathString
-
另外,在 Windows PowerShell 中,当传递 System.IO.FileInfo 或 System.IO.DirectoryInfo 实例时,显式使用 .FullName(文件-system-native 路径)或 .PSPath 属性(包括 PowerShell provider 前缀;路径可能基于 PowerShell-specific drive)以确保其完整路径用来;这在 PowerShell (Core) 7+ 中不再需要,其中此类实例始终 字符串化为它们的 .FullName 属性 - 请参阅 this answer。
# $fileSysInfo is assumed to be of type
# [System.IO.FileInfo] or [System.IO.DirectoryInfo].
# Required for robustness in *Windows PowerShell*, works in both editions.
Get-Content -LiteralPath $fileSysInfo.FullName
# Sufficient in *PowerShell (Core) 7+*:
Get-Content -LiteralPath $fileSysInfo
-
通过管道:
-
System.IO.FileInfo 和System.IO.DirectoryInfo 实例,例如Get-ChildItem 和Get-Item 发出的实例,可以作为一个整体传递,并通过它们的-LiteralPath 属性值稳健地绑定到-LiteralPath - 在两个 PowerShell 版本,因此您可以在跨版本 脚本中安全地使用这种方法。
# Same as:
# Get-Content -LiteralPath $fileSysInfo.PSPath
$fileSysInfo | Get-Content
-
这种机制 - 在this answer 中有更详细的解释 - 依赖于与 参数名称 匹配的 属性名称,包括参数的 别名名字。因此,any 类型的输入对象具有 .LiteralPath、.PSPath 或仅在 PowerShell (Core) 7+ 中具有 .LP 属性(-LiteralPath 参数的所有别名)受该属性值的约束。[1]
# Same as:
# Get-Content -LiteralPath C:\Windows\win.ini
[pscustomobject] @{ LiteralPath = 'C:\Windows\win.ini' } | Get-Content
-
相比之下,任何具有.Path 属性的对象都通过该属性的值绑定到支持通配符的-Path 参数。
# Same as:
# Get-Content -Path C:\Windows\win.ini
# !! Path is treated as a *wildcard expression*.
[pscustomobject] @{ Path = 'C:\Windows\win.ini' } | Get-ChildItem
-
直接字符串输入和任何其他对象的字符串化表示也绑定到-Path。
# Same as:
# Get-Content -Path C:\Windows\win.ini
# !! Path is treated as a *wildcard expression*.
'C:\Windows\win.ini' | Get-Content
[1] 该逻辑仅适用于管道输入,而不适用于按参数输入的同一参数,这是一个不幸的不对称,在GitHub issue #6057.