Reputation: 473
I modified PowerShell script from PowerShell - Batch change files encoding To UTF-8.
# Modified version of https://stackoverflow.com/q/18684793
[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"
}
}
But after using it, I've found that PS cannot handle [
or ]
well.
I made some test files that has diversity in 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
Since I cannot embed images in question, here is link of IMGUR album.
Full image list: https://i.sstatic.net/LtSBS.jpg
These are what I've tested:
'
,
[]
. Also made up different language(Japanese, Korean).How can I make my script handle [
or ]
in file name well?
Upvotes: 2
Views: 3073
Reputation: 439787
tl;dr
Indeed, use of the -LiteralPath
parameter is the best solution (in PowerShell (Core) v6+, you can shorten to -lp
):
$content = Get-Content -LiteralPath $i.Fullname
-LiteralPath
ensures that $i.Fullname
is taken verbatim (literally); that is, [
and ]
in the path are interpreted as themselves rather than having special meaning, as they would have as a -Path
argument, due to being interpreted as a wildcard expression - note that -Path
is positionally implied if you only pass a value (a string) as the first argument, as you did (Get-Content $i.FullName
)
Note: This answer analogously applies to all cmdlets that have both -Path
and -LiteralPath
parameters, such as Set-Content
, Out-File
, and Set-Location
.
As for what you tried:
$content = Get-Content $i.Fullname
is effectively the same as:
$content = Get-Content -Path $i.Fullname
That is, the (first) positional argument passed to Get-Content
is implicitly bound to the
-Path
parameter.
The -Path
parameter accepts wildcard expressions to allow matching paths by patterns; in addition to support for *
(any run of characters) and ?
(exactly 1 character), [...]
inside a wildcard pattern denotes a character set or range (e.g., [12]
or [0-9]
).
Therefore an actual path that contains [...]
, e.g., foo[10].txt
, is not recognized as such, because the [10]
is interpreted as a character set matching a single character that is either 1
or 0
; that is foo[10].txt
would match foo0.txt
and foo1.txt
, but not a file literally named foo[10].txt
.
When (implicitly) using -Path
, it is possible to escape [
and ]
instances that should be interpreted verbatim, namely via the backtick (`
), but note that this can get tricky to get right when quoting and/or variable references are involved.
If you know a path to be a literal path, it is best to form a habit of using -LiteralPath
(which in PowerShell (Core) 7 you can shorten to -lp
).
However, if your path contains literal [
and ]
and you also need wildcard matching, you must use `
-escaping - see this answer.
Upvotes: 3
Reputation: 1639
There are at least two situations where the solution's good advice doesn't hold, unfortunately.
Get-Content -LiteralPath "nobox[]"
gives an error message and exception type as if wildcards are involved:
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
whereas without the brackets, we get:
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
Therefore, to silently deal with an optional file, but not bluntly suppress every exception, something like:
try {
$lines = Get-Content -LiteralPath $path -ErrorAction Stop
}
catch [System.Management.Automation.ItemNotFoundException] {
$lines = @()
}
chokes on paths with brackets.
A minor and a major caveat:
Path
parameter, the name of the new item, "works like the LiteralPath parameter of other cmdlets", says the documentation of New-Item clearly, and that seems true and makes sense. Though I wish we could clarify that by writing -LiteralPath
.Value
parameter, the target of the link (also known as Target
secretly in v5 and openly later), does not accept wildcard characters according to the same documentation, but that's a lie. The command:New-Item -ItemType "HardLink" -Path "whatever" -Target "*"
makes Powershell squeal "Cannot set the location because path '*' resolved to multiple containers.".
So you always need the escapes for the target. If you have a file named "f[]", then this will display an error:
New-Item -ItemType "HardLink" -Path "whatever" -Target "f[]"
and this will create a link:
New-Item -ItemType "HardLink" -Path "f[2]" -Target ([WildcardPattern]::Escape("f[]"))
Same for ItemType "SymbolicLink".
Upvotes: 1