正如经常发生的那样,在尝试尽可能准确地提出我的问题时,我找到了一个解决方案。对于如何更优雅地解决这个问题,我仍然很喜欢任何其他答案,但由于我没有找到任何其他资源与此信息,我在这里提供我的解决方案,希望它可以帮助其他人解决同样的问题。
无效字符映射到特定代码点
注意:我是从我所做的观察中推断出所有这些。我很高兴有人发表评论或提供更完整或更正确的替代答案。
有一组字符对 Windows 文件名无效,但这是操作系统的限制,而不是文件系统的限制。这意味着可以在 SMB 共享上设置在另一个操作系统(例如 MacOS)上有效但在 Windows 上无效的文件名。当 Windows 遇到这样的文件时,无效字符会被一组代理 unicode 代码点遮蔽,这允许 Windows 与文件交互而无需重命名它们。这些代码点位于覆盖0xE000-0xF8FF 的unicode Private Use Area。由于这些代码点未映射到可打印字符,Powershell 将它们全部显示为 ▯ (U+25AF)。在我的特定用例中,我需要报告文件名中存在哪些无效字符,因此这个通用字符消息没有帮助。
通过实验,我能够确定每个可打印受限字符的代理代码点。我已将它们包含在下面以供参考(注意:关于此的 YMMV,我尚未在多个系统上对其进行测试,但我怀疑它在版本之间是一致的)。
| Character |
Unicode |
| " |
0xF020 |
| * |
0xF021 |
| / |
0xF022 |
| < |
0xF023 |
| > |
0xF024 |
| ? |
0xF025 |
| \ |
0xF026 |
| | |
0xF027 |
| (trailing space) |
0xF028 |
: 不允许在我可以轻松访问的任何系统上的文件名中使用,因此我无法测试那个。
在 Powershell 中测试名称
既然我们知道了这一点,那么在 powershell 中解决这个问题就很简单了。我创建了一个哈希表,其中所有代理 unicode 点作为键,“真实”字符作为值,然后我们可以将其用作查找表。在测试名称之前,我选择替换文件名字符串中的字符。这使得调试更容易。
#Set up regex for invalid characters
$invalid = [Regex]::new('^\s|[\"\*\:<>?\/\\\|]|\s$')
#Create lookup table for unicode values
$charmap = @{
[char]0xF020 = '"'
[char]0xF021 = '*'
[char]0xF022 = '/'
[char]0xF023 = '<'
[char]0xF024 = '>'
[char]0xF025 = '?'
[char]0xF026 = '\'
[char]0xF027 = '|'
[char]0xF028 = ' '
}
Get-ChildItem -Path "\\path\to\folder" -Recurse | Foreach-Object {
# Get the filename
$fixedname = split-path -path $_.FullName -leaf
#Iterate through the hashtable and replace all the proxy characters with printable versions
foreach($key in $charmap.getEnumerator()){
$fixedname = $fixedname.Replace($key.Name,$key.Value)
}
#Build a list of invalid characters to include in report (not shown here)
$invalidmatches = $invalid.Matches($fixedname)
if ($invalidmatches.count -gt 0) {
$invalidchars = $($invalidmatches | foreach-object {
if ($_.value -eq ' '){"Leading or trailing space"} else {$_.value}}) -join ", "
}
}
扩展解决方案
理论上,您还可以扩展它以涵盖其他禁止的字符,例如 ASCII 控制字符。由于这些代理 unicode 点位于 PUA 中,并且没有关于如何处理的文档(据我所知),因此发现这些关联取决于实验。我很乐意在这里停下来,因为我已经遍历了 MacOS 系统上的用户可以轻松放入文件名的所有字符。