如何让 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

这些是我测试过的:

如何让我的脚本很好地处理文件名中的 []

确实,使用-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] 被解释为作为匹配 单个 字符的字符集,即 10;即 foo[10].txt 将匹配 foo0.txtfoo1.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”相同。