如何让 PowerShell 很好地处理文件名中的 [ 或 ]?
How can I make PowerShell handle [ or ] in file name well?
我修改了 PowerShell - Batch change files encoding To UTF-8 的 PowerShell 脚本。
# Modified version of
[Threading.Thread]::CurrentThread.CurrentUICulture = 'en-US'
$Encoding = New-Object System.Text.UTF8Encoding($True) # If UTF8Encoding($False), It will be UTF-8 without BOM
$source = "C:\Users\AKULA\Desktop\SRC" # source directory
$destination = "C:\Users\AKULA\Desktop\DST" # destination directory
if (!(Test-Path $destination)) {
New-Item -Path $destination -ItemType Directory | Out-Null
}
# Delete all previously generated file
Get-ChildItem -Path $destination -Include * -File -Recurse | ForEach-Object {$_.Delete()}
# Recursively convert all files into UTF-8
foreach ($i in Get-ChildItem $source -Force -Recurse -Exclude "desktop.ini") {
if ($i.PSIsContainer) {
continue
}
$name = $i.Fullname.Replace($source, $destination)
$content = Get-Content $i.Fullname
if ($null -ne $content) {
[System.IO.File]::WriteAllLines($name, $content, $Encoding)
} else {
Write-Host "No content from: $i"
}
}
但是在使用之后,我发现 PS 无法很好地处理 [
或 ]
。
我制作了一些在 name/content.
中具有多样性的测试文件
Get-Content : An object at the specified path C:\Users\AKULA\Desktop\SRC\FILENAME[[[[[[]]]]]]]].txt does not exist, or
has been filtered by the -Include or -Exclude parameter.
At C:\Users\AKULA\Desktop\Convert_to_UTF-8.ps1:24 char:16
+ $content = Get-Content $i.Fullname
+ ~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (System.String[]:String[]) [Get-Content], Exception
+ FullyQualifiedErrorId : ItemNotFound,Microsoft.PowerShell.Commands.GetContentCommand
由于我无法嵌入有问题的图片,这里是 IMGUR 相册的link。
完整图像列表:https://imgur.com/a/aN1RG2L
这些是我测试过的:
- 测试文件有不同的名称。他们的名字包含 space、
'
、
[]
。还组成了不同的语言(日语,韩语)。
- 这些文件具有相同的内容,使用 UCS-2 BE BOM(UTF-16 BE) 编码,所以
我可以检查它是否已重新编码为 UTF-8。
如何让我的脚本很好地处理文件名中的 [
或 ]
?
确实,使用-LiteralPath
参数是最好的解决方案(在PowerShell [Core] v6+中,您可以缩短为-lp
):
$content = Get-Content -LiteralPath $i.Fullname
-LiteralPath
确保 $i.Fullname
被采用 verbatim (字面意思);也就是说,路径中的 [
和 ]
被解释为它们自身而不具有特殊含义(见下文)。
至于你试过的:
$content = Get-Content $i.Fullname
实际上等同于:
$content = Get-Content -Path $i.Fullname
也就是说,传递给 Get-Content
的(第一个)位置 参数隐式绑定到
-Path
参数.
-Path
参数接受wildcard expressions以允许通过模式匹配路径;除了支持 *
(任何 运行 个字符)和 ?
(恰好 1 个字符),通配符模式 [...]
表示字符集或范围(例如,[12]
或[0-9]
)。
因此,包含 [...]
,例如 foo[10].txt
的实际路径是 而不是 ,因为 [10]
被解释为作为匹配 单个 字符的字符集,即 1
或 0
;即 foo[10].txt
将匹配 foo0.txt
和 foo1.txt
,但不匹配字面上名为 foo[10].txt
.
的文件
当(隐含地)使用 -Path
时,它 是 可以 escape [
和 ]
应逐字解释的实例,即通过反引号 (`
),但请注意,在引用 and/or 变量引用时,这可能会变得很棘手。
如果您知道路径是文字路径,最好养成使用 -LiteralPath
的习惯(在 PowerShell Core 中 你可以缩短为 -lp
).
但是,如果您的路径包含 文字 [
和 ]
而您 也需要通配符匹配,必须使用`
-escaping——参见this answer.
不幸的是,至少在两种情况下解决方案的好建议不成立。
选择性错误处理
Get-Content -LiteralPath "nobox[]"
给出错误消息和异常类型,就好像涉及通配符一样:
Get-Content : An object at the specified path box[] does not exist, or has been filtered by the -Include or -Exclude parameter.
At line:1 char:1
+ Get-Content -Path "nobox[]"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (System.String[]:String[]) [Get-Content], Exception
+ FullyQualifiedErrorId : ItemNotFound,Microsoft.PowerShell.Commands.GetContentCommand
没有括号,我们得到:
Get-Content : Cannot find path 'nobox' because it does not exist.
At line:1 char:1
+ Get-Content -LiteralPath "nobox"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (nobox:String) [Get-Content], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand
因此,静静地处理一个可选文件,比如:
try {
$lines = Get-Content -LiteralPath $path -ErrorAction Stop
}
catch [System.Management.Automation.ItemNotFoundException] {
$lines = @()
}
在带括号的路径上阻塞。
正在创建硬或符号 link
一个次要和一个主要警告:
-
Path
参数,新项目的名称,“像其他 cmdlet 的 LiteralPath 参数一样工作”,清楚地说明了 documentation of New-Item,这似乎是正确的并且有道理。虽然我希望我们可以通过写 -LiteralPath
. 来澄清这一点
Value
参数,link 的目标(在 v5 中秘密地称为 Target
,后来公开),根据相同的文档不接受通配符,但那是个谎言。命令:
New-Item -ItemType "HardLink" -Path "whatever" -Target "*"
使 Powershell 尖叫“无法设置位置,因为路径‘*’解析为多个容器。”。
所以你总是需要目标的转义。如果你有一个名为“f[]”的文件,那么这将显示一个错误:
New-Item -ItemType "HardLink" -Path "whatever" -Target "f[]"
这将创建一个 link:
New-Item -ItemType "HardLink" -Path "f[2]" -Target ([WildcardPattern]::Escape("f[]"))
ItemType“SymbolicLink”相同。
我修改了 PowerShell - Batch change files encoding To UTF-8 的 PowerShell 脚本。
# Modified version of
[Threading.Thread]::CurrentThread.CurrentUICulture = 'en-US'
$Encoding = New-Object System.Text.UTF8Encoding($True) # If UTF8Encoding($False), It will be UTF-8 without BOM
$source = "C:\Users\AKULA\Desktop\SRC" # source directory
$destination = "C:\Users\AKULA\Desktop\DST" # destination directory
if (!(Test-Path $destination)) {
New-Item -Path $destination -ItemType Directory | Out-Null
}
# Delete all previously generated file
Get-ChildItem -Path $destination -Include * -File -Recurse | ForEach-Object {$_.Delete()}
# Recursively convert all files into UTF-8
foreach ($i in Get-ChildItem $source -Force -Recurse -Exclude "desktop.ini") {
if ($i.PSIsContainer) {
continue
}
$name = $i.Fullname.Replace($source, $destination)
$content = Get-Content $i.Fullname
if ($null -ne $content) {
[System.IO.File]::WriteAllLines($name, $content, $Encoding)
} else {
Write-Host "No content from: $i"
}
}
但是在使用之后,我发现 PS 无法很好地处理 [
或 ]
。
我制作了一些在 name/content.
Get-Content : An object at the specified path C:\Users\AKULA\Desktop\SRC\FILENAME[[[[[[]]]]]]]].txt does not exist, or
has been filtered by the -Include or -Exclude parameter.
At C:\Users\AKULA\Desktop\Convert_to_UTF-8.ps1:24 char:16
+ $content = Get-Content $i.Fullname
+ ~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (System.String[]:String[]) [Get-Content], Exception
+ FullyQualifiedErrorId : ItemNotFound,Microsoft.PowerShell.Commands.GetContentCommand
由于我无法嵌入有问题的图片,这里是 IMGUR 相册的link。
完整图像列表:https://imgur.com/a/aN1RG2L
这些是我测试过的:
- 测试文件有不同的名称。他们的名字包含 space、
'
、[]
。还组成了不同的语言(日语,韩语)。 - 这些文件具有相同的内容,使用 UCS-2 BE BOM(UTF-16 BE) 编码,所以 我可以检查它是否已重新编码为 UTF-8。
如何让我的脚本很好地处理文件名中的 [
或 ]
?
确实,使用-LiteralPath
参数是最好的解决方案(在PowerShell [Core] v6+中,您可以缩短为-lp
):
$content = Get-Content -LiteralPath $i.Fullname
-LiteralPath
确保 $i.Fullname
被采用 verbatim (字面意思);也就是说,路径中的 [
和 ]
被解释为它们自身而不具有特殊含义(见下文)。
至于你试过的:
$content = Get-Content $i.Fullname
实际上等同于:
$content = Get-Content -Path $i.Fullname
也就是说,传递给 Get-Content
的(第一个)位置 参数隐式绑定到
-Path
参数.
-Path
参数接受wildcard expressions以允许通过模式匹配路径;除了支持 *
(任何 运行 个字符)和 ?
(恰好 1 个字符),通配符模式 [...]
表示字符集或范围(例如,[12]
或[0-9]
)。
因此,包含 [...]
,例如 foo[10].txt
的实际路径是 而不是 ,因为 [10]
被解释为作为匹配 单个 字符的字符集,即 1
或 0
;即 foo[10].txt
将匹配 foo0.txt
和 foo1.txt
,但不匹配字面上名为 foo[10].txt
.
当(隐含地)使用 -Path
时,它 是 可以 escape [
和 ]
应逐字解释的实例,即通过反引号 (`
),但请注意,在引用 and/or 变量引用时,这可能会变得很棘手。
如果您知道路径是文字路径,最好养成使用 -LiteralPath
的习惯(在 PowerShell Core 中 你可以缩短为 -lp
).
但是,如果您的路径包含 文字 [
和 ]
而您 也需要通配符匹配,必须使用`
-escaping——参见this answer.
不幸的是,至少在两种情况下解决方案的好建议不成立。
选择性错误处理
Get-Content -LiteralPath "nobox[]"
给出错误消息和异常类型,就好像涉及通配符一样:
Get-Content : An object at the specified path box[] does not exist, or has been filtered by the -Include or -Exclude parameter.
At line:1 char:1
+ Get-Content -Path "nobox[]"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (System.String[]:String[]) [Get-Content], Exception
+ FullyQualifiedErrorId : ItemNotFound,Microsoft.PowerShell.Commands.GetContentCommand
没有括号,我们得到:
Get-Content : Cannot find path 'nobox' because it does not exist.
At line:1 char:1
+ Get-Content -LiteralPath "nobox"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (nobox:String) [Get-Content], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand
因此,静静地处理一个可选文件,比如:
try {
$lines = Get-Content -LiteralPath $path -ErrorAction Stop
}
catch [System.Management.Automation.ItemNotFoundException] {
$lines = @()
}
在带括号的路径上阻塞。
正在创建硬或符号 link
一个次要和一个主要警告:
-
Path
参数,新项目的名称,“像其他 cmdlet 的 LiteralPath 参数一样工作”,清楚地说明了 documentation of New-Item,这似乎是正确的并且有道理。虽然我希望我们可以通过写-LiteralPath
. 来澄清这一点
Value
参数,link 的目标(在 v5 中秘密地称为Target
,后来公开),根据相同的文档不接受通配符,但那是个谎言。命令:
New-Item -ItemType "HardLink" -Path "whatever" -Target "*"
使 Powershell 尖叫“无法设置位置,因为路径‘*’解析为多个容器。”。
所以你总是需要目标的转义。如果你有一个名为“f[]”的文件,那么这将显示一个错误:
New-Item -ItemType "HardLink" -Path "whatever" -Target "f[]"
这将创建一个 link:
New-Item -ItemType "HardLink" -Path "f[2]" -Target ([WildcardPattern]::Escape("f[]"))
ItemType“SymbolicLink”相同。