复制具有方括号 [ ] 的文件名并使用 * 通配符

25

我正在使用Windows 7上的PowerShell,并编写一个脚本将一堆文件从一个文件夹结构复制到另一个文件夹结构中,类似于编译。PowerShell的Copy-Item cmdlet认为方括号[ ]是某种通配符,但不知何故我无法转义它们。

我无法使用-LiteralPath,因为我想使用星号*通配符,因为文件名中包含日期,而日期会更改。日期用作版本号。

这篇文章很有帮助,但无论我用多少个反引号(每个括号两个或四个),都无法转义方括号。

我没有收到错误消息;PowerShell的行为就像我输入了错误的文件名。

这是我正在处理的特定行:

#to Fusion Server
Copy-item -Path $FSG\$SW\0.RoomView.Notes\starter\"[RoomView] Versions explained*.pdf" -Destination $FSG\$containerFolder\$rootFolder\"Fusion Server"\

这就是全部内容:

# Compiles the Fusion packet for distribution

###############################
###########Variables###########
###############################

#folder structure
$FSG = "F:\FSG"
$containerFolder = "Packet.Fusion for IT and AV Professionals"
$rootFolder      = "Fusion for IT and AV pros $(Get-Date -format “MM-dd-yyyy”)"
$subRoot1        = "Fusion Server"
$subRoot2        = "Scheduling Enhancement and Panels"
$subRoot2sub1    = "Scheduling Panels"
$subRoot3        = "SQL Server"

#source folders
$HW      = "0.Hardware"
$3SMDoc  = "0.Hardware\TPMC-3SM.Documentation"
$4SMDoc  = "0.Hardware\TPMC-4SM.Documentation"
$4SMDDoc = "0.Hardware\TPMC-4SM-FD.Documentation"
$730Doc  = "0.Hardware\TSW-730.Documentation"
$730OLH  = "0.Hardware\TSW-730.OLH"
$CENRVS  = "0.Hardware\CEN-RVS.Notes"

$ProjMgmt = "0.Project Management"

$SW            = "0.Software"
$RVLicensing   = "0.Software\0.RoomView.License"
$RVNotes       = "0.Software\0.RoomView.Notes"
$SQLLicensing  = "0.Software\database.SQL.Licensing"
$SQLNotes      = "0.Software\database.SQL.Notes"
$FRVMarketing  = "0.Software\Fusion RV.Marketing"
$FRVNetworking = "0.Software\Fusion RV.Networking"
$FRVNotes      = "0.Software\Fusion RV.Notes"


###############################
#create the directory structure
###############################

md -Path $FSG\$containerFolder -Name $rootFolder

cd $FSG\$containerFolder\$rootFolder
md "eControl and xPanels"
md "Fusion Server" #$subRoot1
md "Getting Started as a User"
md "Project Management"
md "RoomView Connected Displays"
md "Scheduling Enhancement and Panels" #$subRoot2
md "SQL Server" #$subRoot3

cd $FSG\$containerFolder\$rootFolder\$subRoot1
md "CEN-RVS"
md "Licenseing Information"
md "Networking"
md "Official Documentation"
md "Prerequisites, including powerShell script"
md "Product Info"
md "Requirements"
md "Tech Info"
md "Windows Authentication to Fusion RV"

cd $FSG\$containerFolder\$rootFolder\$subRoot2
md "Outlook Add-in"
md "Scheduling Panels" #$subRoot2sub1

cd $FSG\$containerFolder\$rootFolder\$subRoot2\$subRoot2sub1
md "TPMC-3SM"
md "TPMC-4SM"
md "TPMC-4SM-FD"
md "TSW-730"

cd $FSG\$containerFolder\$rootFolder\$subRoot3
md "Multi-database model only"
md "SQL Licensing"

cd $FSG\$containerFolder
#reset current folder


###############################
#copy the files
###############################

#Copy-Item -Path C:\fso\20110314.log -Destination c:\fsox\mylog.log

#To the root
Copy-item -Path $FSG\$ProjMgmt\starter\"Fusion Support Group Contact info*.pdf" -Destination $FSG\$containerFolder\$rootFolder\
Copy-item -Path $FSG\$containerFolder\"Fusion for IT and AV professionals release notes.txt" -Destination $FSG\$containerFolder\$rootFolder\

#to eControl and xPanels
Copy-item -Path $FSG\$SW\xpanel.Notes\starter\*.* -Destination $FSG\$containerFolder\$rootFolder\"eControl and xPanels"\

#to Fusion Server
Copy-item -Path $FSG\$SW\0.RoomView.Notes\starter\"[RoomView] Versions explained*.pdf" -Destination $FSG\$containerFolder\$rootFolder\"Fusion Server"\

我该怎么做才能避免使用方括号并仍然使用 Copy-Item cmdlet的通配符文件名部分?

10个回答

34
在这种情况下,您需要使用单引号的双反引号来转义括号。如果您使用双引号字符串,也可以使用四个反引号。
因此,修复后的代码行为:
Copy-item -Path $FSG\$SW\0.RoomView.Notes\starter\'``[RoomView``] Versions explained*.pdf' -Destination $FSG\$containerFolder\$rootFolder\'Fusion Server'\

另一个关于文件路径和特殊字符等的好资源是阅读这篇文章:Taking Things (Like File Paths) Literally


编辑

感谢@mklement0指出,这个不一致的真正原因是因为目前PowerShell中存在的一个漏洞1。这个漏洞会导致通配符字符以及默认-Path参数下的反引号在转义时与其他参数(如-Include-Filter参数)的行为不同。

进一步解释了@mklement0的优秀回答,以及评论和下面其他的回答:

为了更好地理解为什么我们在这种情况下需要使用单引号、两个反引号以及突出显示bug和不一致性,让我们通过一些示例来演示正在发生的事情:Get-Item和相关的cmdlet(Get-ChildItemCopy-Item等)在同时处理转义通配符字符未转义通配符字符*时,会以不同的方式处理-Path参数!简而言之:我们需要单引号和双反引号的组合是因为基础PowerShell提供程序如何解析带有通配符的-Path参数字符串。它似乎会先解析转义字符,然后再次解析通配符进行评估。让我们通过一些示例来演示这种奇怪的结果:

首先,让我们创建两个用于测试的文件,分别命名为File[1]a.txtFile[1]b.txt

"MyFile" | Set-Content '.\File`[1`]a.txt'
"MyFriend" | Set-Content '.\File`[1`]b.txt'

我们将尝试不同的方法来获取文件。我们知道方括号[ ]通配符,所以我们需要使用反引号字符对它们进行转义。

我们将尝试显式地获取一个文件。

让我们从使用单引号文字字符串开始:

PS C:\> Get-Item 'File[1]a.txt'
PS C:\> Get-Item 'File`[1`]a.txt'

    Directory: C:\

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       2019-09-06   5:42 PM              8 File[1]a.txt

PS C:\> Get-Item 'File``[1``]a.txt'

    Directory: C:\

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       2019-09-06   5:42 PM              8 File[1]a.txt

对于单引号字符串,只需要一个反引号就可以检索文件,但是两个反引号也可以。

使用双引号字符串,我们得到:

PS C:\> Get-Item "File[1]a.txt"
PS C:\> Get-Item "File`[1`]a.txt"
PS C:\> Get-Item "File``[1``]a.txt"

    Directory: C:\  

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       2019-09-06   5:42 PM              8 File[1]a.txt

对于双引号字符串,正如预期的那样,我们可以看到需要两个反引号才能使其正常工作。

现在,我们想要检索两个文件,并使用通配符。

让我们从单引号开始:

PS C:\> Get-Item 'File[1]*.txt'
PS C:\> Get-Item 'File`[1`]*.txt'
PS C:\> Get-Item 'File``[1``]*.txt'

    Directory: C:\

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       2019-09-06   5:42 PM              8 File[1]a.txt
-a----       2019-09-06   5:49 PM             10 File[1]b.txt

使用单引号时,如果有通配符字符,我们需要两组反引号。一组用于转义括号,第二个反引号用于转义通配符在评估通配符时使用的反引号所转义的括号。

双引号同理:

PS C:\> Get-Item "File[1]*.txt"
PS C:\> Get-Item "File`[1`]*.txt"
PS C:\> Get-Item "File``[1``]*.txt"
PS C:\> Get-Item "File```[1```]*.txt"
PS C:\> Get-Item "File````[1````]*.txt"

    Directory: C:\

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       2019-09-06   5:42 PM              8 File[1]a.txt
-a----       2019-09-06   5:49 PM             10 File[1]b.txt

使用双引号时,使用通配符进行评估需要更多的文字。在这种情况下,我们需要组反引号。对于双引号,我们需要两个反引号来转义括号,另外两个反引号来转义星号通配符的评估中的转义字符。

编辑

@mklement0提到,使用-Path参数的这种行为不一致,并且与-Include参数的行为不同,后者只需要一个反引号就可以正确转义括号。这个问题可能会在以后的PowerShell版本中被“修复”。


1 截至Windows PowerShell v5.1 / PowerShell Core 6.2.0-preview.3版本


1
我想点赞,但是我的声望不够:( - YetAnotherRandomUser
3
如果这解决了您的问题,您可以接受这个答案。 - HAL9256
3
同意hal9256的观点。不好的是,楼主把你的回答本质复制到了他自己的回答中,并将其标记为正确答案。这样做一点都不好。 - user66001
正如@HAL9256的链接文章所建议的那样,我使用了-LiteralPath参数而不是-Path参数,以确保没有字符被解释为通配符(请参见https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/copy-item)。 - David McClelland
1
@mklement0 您是正确的,使用单引号,一个转义字符就足以转义括号。在这种情况下,OP 还想 应用正常的非转义通配符字符。在这种情况下,您需要两组转义字符才能使其工作。我已经编辑了我的答案,以更好地说明正在发生的事情。 - HAL9256
2
@HAL9256:你是正确的(+1):由于一个_bug_ - 参见这个GitHub问题,混合(未转义的)?*与转义的[]需要后者被双重转义(由目标cmdlet /提供程序看到)。 这种模糊行为绝对没有理由(实际上我曾经遇到过这种情况)。Bug必须在提供程序代码中,因为通配符本身的功能是正确的: 'a[b' -like 'a\['$true,而 'a[b' -like 'a``['` - 正确地 - 抱怨了一个_无效的模式_。 - mklement0

13

简而言之

鉴于在您的情况下,-LiteralPath 不是一个选项,所以在您的通配符表达式中,[] 字符[1]被认为是字面量,必须进行有选择地转义,具体取决于路径的指定方式,可以使用```

# Due to use of "...", ``, i.e. *two* backticks must be used.
# The same applies if your path is *unquoted*
# With a '...'-quoted path, use just *one* `
Copy-item -Path "$FSG\...\``[RoomView``] Versions explained*.pdf" -Destination ...

概述和一些背景信息:
为了有效地转义您希望作为通配符表达式的一部分被解释为字面值的字符,它必须被`转义,就像目标cmdlet(其基础PowerShell驱动程序)所看到的那样。
确保这一点可能会变得棘手,因为`(反引号)也用作双引号字符串"...")和未引用的命令参数的转义字符(在很大程度上与双引号字符串的行为相似)。
注意:问题中的情景不允许使用-LiteralPath,但是在你知道路径是一个具体的、字面的路径的情况下,使用-LiteralPath(在PowerShell Core中可以缩写为-lp)是最好的选择-参见this answer
当将参数传递给支持通配符的PowerShell驱动程序相关的cmdlet的-Path参数(Get-ChildItemCopy-ItemGet-Content等),并且你希望[]被视为字面值而不是字符集/范围表达式时:
  • 字符串-字面表示

    • 'file`[1`].txt'

      • ` 字符在 '...' 内保持不变,因此目标 cmdlet 可以按预期接收到它们。
    • "file``[1``].txt"

      • ``,即在 "..." 内部需要加倍,以保留结果字符串中的一个 `(第一个 ` 是(双引号)字符串内部的转义字符,第二个 ` 是它转义的字符,要传递给后续处理)。
    • file``[1``].txt(仅作为命令参数有效,不适用于表达式)

      • 对于未引用的命令参数,情况类似于 "..."
    • 注意由于错误 - 参见 GitHub 问题 #7999 - 混合(未转义的)?* 与转义的 [] 需要后者被双重转义(使用 ``,目标 cmdlet / 提供程序看到的)

      • 如果您想要使用通配符模式匹配字面文件名 file[1].txt,该模式要求字面匹配 [],同时还包含特殊字符 *(匹配任意字符的运行),您需要使用 'file``[1``]*'(注意);对于双引号或未转义的参数,您需要使用四个反引号: "file````[1````]*" / file````[1````]* - 有关更多信息,请参见 this answer

      • 请注意,-like 运算符的直接使用通配符不受影响:

        • 'a[b' -like 'a`[*' 是 - 正确的 - $true
        • 'a[b' -like 'a``[*' - 正确地 - 报告了一个无效的模式
      • 同样,参数 -Include-Exclude 不受影响

      • -Filter 本来就遵循不同的规则:根本不支持 [...] 结构,并且始终将 [] 字符视为字面量(再次参见 this answer)。

  • 要以编程方式转义(路径)字符串(或其中的一部分),请使用 [WildcardPattern]::Escape()

    # 要将其作为单独的命令参数使用,请将其括在(...)中
    # 要将其作为较大字符串的一部分使用,请将其括在 $(...) 中
    [WildcardPattern]::Escape('file[1].txt') # -> 'file`[1`].txt'
    

[1] 当使用PowerShell通配符模式与-like运算符一起使用时,严格来说只有[需要转义以进行字面解释 - 例如,'[a]' -like '`[a]'$true,就像'[a]' -like '`[a`]'一样。然而,使用[WildcardPattern]::Escape()进行编程转义时,]也会被转义,而且最重要的是,在传递给provider cmdlets(例如Get-ChildItem)路径参数中,]也必须转义。


1
只是补充一下,使用单引号时,一个转义字符就足以转义括号。如果您想要应用正常的非转义通配符字符,您需要两组转义字符才能使其生效。请参见我的答案编辑以获取更详细的解释。 - HAL9256
谢谢,@HAL9256;你是正确的。正如我在你的答案评论中所述,那个要求是一个“bug”;我已经相应地更新了我的答案。 - mklement0
很好的答案,不过不需要一个变量... $([WildcardPattern]::Escape($literalName)) 也能正常工作;不需要变量。反正谁需要变量呢! - Bitcoin Murderous Maniac
1
@BitcoinMurderousManiac: 的确,不需要变量 - 答案使用了一个来通过变量名说明该函数调用的返回结果。请注意,当将表达式和(单个)命令作为参数传递时,应使用(...)而不是$(...); $(...)可能会产生副作用 - 请参见此答案。 (https://stackoverflow.com/a/58248195/45375) - mklement0
1
为了一切神圣的爱...我正在使用Copy-Item复制数以万计的文件(别问我为什么,是的,我熟悉robocopy和xcopy),大部分情况下都按预期工作,但大约有100个文件默默地失败了,没有任何错误或警告。原来这些文件名中都含有方括号,而我却不知道在PowerShell中方括号是通配符。将-Path更改为-LiteralPath解决了这个问题。浪费了数小时,让我的理智受到了挑战。感谢您的帖子! - undefined
1
很高兴听到它对你有所帮助,@NightOwl。在文件路径的上下文中,[] 是通配符元字符,真是一个不断给予的礼物。关于这个问题的摘要,请参阅PowerShell的GitHub存储库中的此评论 - undefined

6
我使用这个:
 Copy-Item  $file.fullname.replace("[", "``[").replace("]", "``]") $DestDir

3
在 PowerShell v 2.0 及以上版本中,要使用的转义字符是反斜杠。例如,如果我们想从该字符串“[Servername: QA01]”中删除括号,这是我们从 System Center Orchestrator 中的 Exchange 管理 PowerShell 命令行活动获得的输出类型,我们可以使用以下逻辑:
$string -replace '\[','' -replace '\]',''
>Servername: QA01

这很奇怪。你需要使用单引号(通常在PowerShell中表示“精确评估此内容”,因此这是非常奇怪的语法)。

不要因为没有自己想出来而感到难过,这是非常奇怪的语法。


2
实际上,我最终自己解决了这个问题,并在下面发布了答案。但是,由于我的声望不够或系统不允许我将其提升,因此其他人正在推广其他答案,尽管我已经回答了自己的问题。系统似乎不喜欢像我这样没有积分的人回答自己的问题,哈哈。 - YetAnotherRandomUser
1
是的,在这里自己回答自己的问题很容易获得负评。通常最好选择一个最接近你自己的答案并且结束问题。 - FoxDeploy
1
实际上,yetanotherrandomuser,你并没有回答自己的问题。hal9256将带有方括号的文件名复制并使用通配符 这个问题上提供了一个几乎22小时前就包含了你回答要点的答案。在我看来,社区不喜欢这样做,并利用其投票权力来说明这一点。 - user66001
感谢foxdeploy的回答。这对我来到这个页面的原因有所帮助。 - user66001
\(反斜杠)是正则表达式中的转义字符,与PowerShell无关 - 您需要在此处使用它来转义[]正则表达式元字符。PowerShell的转义字符是\``(反引号),在多个上下文中使用:双引号字符串和未引用的命令参数,行继续和通配符模式。问题是如何在将文件系统路径解释为通配符模式的情况下使用[]`字面上。您正在回答不同的问题。 - mklement0

3
通常情况下,Powershell自动补全文件名的方式是最佳方式。
示例:
copy-item '.\file`[test`].txt'

1
显然,方括号需要双反引号进行转义,这是不寻常的。此处有参考资料。你确定那不起作用吗?我已经看到过几次提到它了。 编辑:是的,它有效,你使用了双引号而不是反引号。 双引号在撇号字符上方,在Enter键旁边。反引号就在Escape键下面,与波浪线~共享键位。

2x和4x的字符都无法为我转义[或]。我用错了吗?我已经尝试在“”内和“”外使用源路径。 - YetAnotherRandomUser
有超过一种类型的tick吗?据我所知,我只有一个....“不工作”是指文件没有被复制。 - YetAnotherRandomUser
顺便提一下(无关紧要):正如问题发布者自己的答案所证明的那样,错误的是单引号而不是反引号。 - mklement0
1
需要使用引号和反引号的组合。@mklement0首先并且正确地回答了这个问题。 - Jeter-work

0
假设没有其他匹配项,您可以使用“?”代替括号。将名为“a[h-j]”的文件复制到目录“foo”中:
 copy-item a?h-j? foo 

0

一种选择是使用传统的dir获取文件名,这将允许您使用*通配符字符,但不会尝试“blob”方括号。然后使用-literalpath将该列表提供给move-item

cmd /c dir *]* /b |
  foreach { Move-Item -LiteralPath $_ -Destination <destination path> }

0

我认为可以使用Get-ChildItem来进行通配符匹配,然后将结果传递给Copy-Item并使用LiteralPath。

Get-ChildItem $FSG\$ProjMgmt\starter\"Fusion Support Group Contact info*.pdf" | %{Copy-Item -LiteralPath $_.FullName -Destination "$FSG\$containerFolder\$rootFolder\"}

我经常这样做,但路径中没有变量-可能需要做些事情来使变量在脚本块内正确范围。通常,采用此方法可以使您在Copy-Item或Move-Item路径中不使用通配符,因此LiteralPath可以工作。没有必要使用转义字符,特别是当您不知道文件路径中这些特殊字符会出现在哪里或什么时候。

-3

单引号'和反引号`是有区别的:

  • 第一个是单引号,它是"键上的非Shift字符。
  • 第二个是反引号,我以为我在使用它,但实际上并不是。它是~键上的非Shift字符。

这样可以正常工作:

# to Fusion Server
Copy-item -Path $FSG\$SW\0.RoomView.Notes\starter\'``[RoomView``] Versions explained*.pdf' -Destination $FSG\$containerFolder\$rootFolder\"Fusion Server"\

2
-1 hal9256 在本页面的其他位置提供了完全相同的答案(https://dev59.com/rGEi5IYBdhLWcg3wueR0),只是格式略有不同。请奖励那些回答他人问题的人,将其答案标记为答案,而不是您复制的答案。 - user66001
真的吗?@user66001是在挑拨3年前的帖子的事情吗?继续阅读所有的文本和时间戳,因为你错过了一些东西。从我现在能看到的情况来看,这个答案比你最喜欢的答案的评论早了1分钟,这让我相信在解决了我的问题之后才回到这篇文章。你可以清楚地在其他评论和这个答案中看到我不知道撇号和单引号的区别。你还会注意到,这是唯一一个解释撇号和单引号有所不同的答案。 - YetAnotherRandomUser
1
“试图挖掘一个三年前的帖子上的戏剧?” - 不是的 - 我昨天通过谷歌才发现这篇帖子,因为那时我有一个问题需要解决,由于 Stack Exchange 多年前做出的决定,它仍然可以被查看和添加。 - user66001
你还会注意到,这是唯一一个解释单引号和双引号不同的答案。从hal9256的回答中,我只能推断出社区认为应该是正确答案(尽管我不能否认你的答案更明显)。但问题在于你使用了双引号而不是单引号来包含你的字符串。双反引号解决方案似乎只适用于单引号。这意味着存在差异,并且可以在hal9256和你各自提供的完全相同的示例中看到。 - user66001
1
你是要说@hal9256抄袭了其他网站,因为他的回答发布在这些网站之前吗?- 很好,我们可以达成一致。我们不是在谈论互联网的其他部分,而是关于这个页面,在这个问答网站上。再次根据哪个答案获得了最多的投票,这是社区排名答案的方式,在这里应该接受第一个提供正确答案的人,并在子点上放置一个评论,说明你不理解“`”和“''”之间的区别。这有助于促进人们回答问题... - user66001
显示剩余4条评论

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