问题
I'm using PowerShell on Windows 7, and writing a script to copy a bunch of files from one folder structure to another. Kind of like compiling. The PowerShell Copy-Item
cmdlet thinks that square brackets, [ ], are wildcards of some kind, and I am not able to escape them for some reason.
I can't use -LiteralPath
, because I want to use an asterisk * wildcard since the filename has a date as part of the filename, and the date changes. The date is used as a version number.
This post was helpful, but no amount of ticks (2x or 4x per bracket) escapes the square brackets.
I am not receiving an error; PowerShell behaves the same as if I entered in the wrong filename.
This is the specific line I'm working on:
#to Fusion Server
Copy-item -Path $FSG\$SW\0.RoomView.Notes\starter\"[RoomView] Versions explained*.pdf" -Destination $FSG\$containerFolder\$rootFolder\"Fusion Server"\
And this is the whole thing:
# 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"\
What can I do to escape the square brackets and still use a wildcard filename part of the Copy-Item
cmdlet?
回答1:
In this situation, you have to use double-backticks with single quotes in order to escape the brackets. You can also use quadruple backticks when you use double quoted strings.
So the fixed line of code is:
Copy-item -Path $FSG\$SW\0.RoomView.Notes\starter\'``[RoomView``] Versions explained*.pdf' -Destination $FSG\$containerFolder\$rootFolder\'Fusion Server'\
Another good resource on file paths and wired characters etc. is to read this article: Taking Things (Like File Paths) Literally
EDIT
Thanks to @mklement0 for highlighting that the true cause of this inconsistency is because of a bug currently in PowerShell1. This bug causes escaping of wildcard characters, as well as backticks with the default
-Path
parameter to behave differently than other parameters e.g. the-Include
and-Filter
parameters.
To expand on @mklement0's excellent answer, and comments, and other answers below:
To better understand why we need single quotes and two back ticks in this situation; (and to highlight the bug and inconsistencies) let's run through some examples to demonstrate what is going on:
Get-Item
, and associated cmdlets (Get-ChildItem
, Copy-Item
, etc.), handle the -Path
parameter differently when dealing with a combination of escaped wildcard characters and unescaped wildcard characters *at the same time***!
TLDR: The underlying reason that we need a combination of single quotes and double backticks is how the underlying PowerShell provider parses the -Path
parameter string for wildcards. It appears to parse it once for the escape characters, and a second time for the evaluation of the wildcard.
Let's go through some examples to demonstrate this odd outcome:
First, let's create two files to test with called File[1]a.txt
and File[1]b.txt
"MyFile" | Set-Content '.\File`[1`]a.txt'
"MyFriend" | Set-Content '.\File`[1`]b.txt'
We'll try different ways to get the file. We know that Square brackets [ ]
are wildcards, and so we need to escaped them with the backtick character.
We will try to get one file explicitly.
Let's start by using single quoted literal strings:
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
For single quoted strings, one backtick is all that is required to retrieve the file, but two backticks also work.
Using Double quoted strings we get:
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
For double quoted strings, as expected, we can see that we need two backticks to make it work.
Now, we want to retrieve both files and use a wildcard.
Let's start with single quotes:
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
With the single quotes, when we have a wildcard character, we need two sets of backticks. One to escape the bracket, and a second backtick to escape the backtick that we used to escape the bracket when the wildcard is evaluated.
Similarly for double quotes:
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
With double quotes it's a little more verbose to evaluate with a wildcard. In this case, we need four sets of back ticks. For double quotes we need two backticks to escape the bracket, and another two backticks to escape the escape characters once it comes to evaluation of the star wildcard.
EDIT
As @mklement0 mentions, this behavior with the -Path
parameter is inconsistent, and behaves differently than the -Include
parameter, where only a single backtick is required to properly escape the brackets. This may be "fixed" in a later version of PowerShell.
1 As of Windows PowerShell v5.1 / PowerShell Core 6.2.0-preview.3
回答2:
The way that Powershell automatically tab-completes the filename is usually the best way,
Example:
copy-item '.\file`[test`].txt'
回答3:
On PowerShell v 2.0 and up the escape character to use is the backslash. For example, if we want to remove the brackets from this string "[Servername: QA01]" which is the sort of output we get from the Exchange Admin PowerShell cmdlet activity in System Center Orchestrator, we use the following logic:
$string -replace '\[','' -replace '\]',''
>Servername: QA01
This is pretty weird. See, you have to use a single-quote (which normally implies in PowerShell 'evaluate this precisely as written', so this is very odd syntax).
Don't feel bad for not figuring this out on your own, this is very odd syntax.
回答4:
Apparently, square brackets need double-backticks to escape, which is unusual. Reference here.
You're sure that doesn't work? I've seen it referred to a few times.
Edit: Yes, it works, you used double quotes instead of backticks.
Double quote is above the apostrophe character, next to the Enter key. Backtick is right underneath the Escape key, sharing the key with the tilde, ~.
回答5:
I use this:
Copy-Item $file.fullname.replace("[", "``[").replace("]", "``]") $DestDir
回答6:
An overview and some background information:
In order to effectively escape a character that you want to be interpreted verbatim as part of a wildcard expression, it must be
`
-escaped as seen by the target cmdlet (its underlying PowerShell drive provider).Ensuring that can get tricky, because
`
(backtick) is also used as the escape character in double-quoted strings ("..."
) and unquoted command arguments (which for the most part behave like double-quoted strings).
Note: The scenario in the question doesn't allow use of -LiteralPath
, but in cases where you know a path to be a concrete, literal path, use of the -LiteralPath
(which can be shorted to -lp
in PowerShell Core) is the best choice - see this answer.
When passing an argument to the wildcard-supporting -Path
parameter of a PowerShell drive provider-related cmdlet (Get-ChildItem
, Copy-Item
, Get-Content
, ...) and you want [
and ]
to be treated verbatim rather than as a character set/range expression:
String-literal representations:
'file`[1`].txt'
`
chars. are preserved as-is inside'...'
, so the target cmdlet sees them, as intended.
"file``[1``].txt"
``
, i.e. doubling is needed inside"..."
in order to preserve a single`
in the resulting string (the first`
is the (double-quoted) string-internal escape character, and the second`
is the character it escapes, to be passed through).
file``[1``].txt
- Ditto for unquoted command arguments, which (for the most part) act like
"..."
- Ditto for unquoted command arguments, which (for the most part) act like
Caveat: Due to a bug - see this GitHub issue - mixing (unescaped)
?
or*
with escaped[
and]
requires the latter to be doubly escaped (with``
, as seen by the target cmdlet / provider):If you wanted to match literal filename
file[1].txt
with a wildcard pattern that matches[
and]
literally while also containing special character*
(to match any run of characters), instead of the expected'file`[1`]*'
, you'll have to use'file``[1``]*'
(sic); with a double-quoted or unescaped argument you then have to effectively use quadruple backticks:"file````[1````]*"
/file````[1````]*
- see this answer for more.Note that direct use of wildcards with the
-like
operator is not affected:'a[b' -like 'a`[*'
is - correctly -$true
,- whereas
'a[b' -like 'a``[*'
- rightfully - complains about an invalid pattern.
Similarly, parameters
-Include
and-Exclude
are not affected.-Filter
plays by different rules to begin with:[...]
as a construct isn't supported at all, and[
and]
chars. are always considered literals (again, see this answer).
To escape a path string programmatically, via a variable, use:
$literalName = 'file[1].txt' $escapedName = [WildcardPattern]::Escape($literalName) # -> 'file`[1`].txt'
回答7:
One option is to get the filenames using the legacy dir, which will let you use the * wildcard character, but doesn't try to "blob" the square brackets. Then feed that list to move-item using -literalpath
cmd /c dir *]* /b |
foreach { Move-Item -LiteralPath $_ -Destination <destination path> }
回答8:
Assuming nothing else matches, you can use ? instead of the brackets. A file named "a[h-j]", copying to directory "foo":
copy-item a?h-j? foo
回答9:
There's a difference between '
and `
:
- The first is the single quote that is the non-shift character on the " key.
- The second is the backtick that I thought I was using but actually wasn't. It's the nonshift character on the ~ key.
This works:
# to Fusion Server
Copy-item -Path $FSG\$SW\0.RoomView.Notes\starter\'``[RoomView``] Versions explained*.pdf' -Destination $FSG\$containerFolder\$rootFolder\"Fusion Server"\
来源:https://stackoverflow.com/questions/21008180/copy-file-with-square-brackets-in-the-filename-and-use-wildcard