Windows 和 Linux 目录名称中禁止使用哪些字符?

What characters are forbidden in Windows and Linux directory names?

提问人:Jeff 提问时间:12/30/2009 最后编辑:Mateen UlhaqJeff 更新时间:10/15/2023 访问量:699940

问:

我知道这在 Linux 中是非法的,在 Windows 中也是非法的。/*"/\<>:|?

我还错过了什么?我需要一个全面的指南,其中也考虑了双字节字符。

Linux Windows 目录 文件名

评论

2赞 Jim Michaels 1/18/2017
仅仅因为 win32 API 通过并不意味着它是允许的。先阅读 NTFS 规范和 FAT32 规范,然后再在 Windows 上使用 RCS 和 CVS。
4赞 eckes 6/11/2017
^在 FAT 上是被禁止的
7赞 Soren Bjornstad 9/4/2018
@DavidC.Bishop:这篇 SO 文章断言 Linux 内核会阻止你使用包含斜杠的文件名。你能够让它工作吗?
54赞 Jim Balter 10/9/2018
“/ 在 Linux 中并不违法。你只需要在输入它时用 \ 转义它“——这种说法是完全错误的。文件名组件不能包含 /,转义它不起作用。
5赞 naskew 10/10/2018
我只在 NTFS 上测试,可以说.[ ] = : ;和 ,似乎没问题。我没有测试 FAT32

答:

268赞 Dour High Arch 12/30/2009 #1

禁止文件名字符的“综合指南”在 Windows 上不起作用,因为它保留了文件名和字符。是的,像 and 这样的字符是被禁止的,但有无数个名称仅由被禁止的有效字符组成。例如,空格和点是有效的文件名字符,但禁止仅由这些字符组成的名称。*"?

Windows 不区分大写字符和小写字符,因此如果已命名文件夹,则无法创建命名的文件夹。更糟糕的是,像 和 这样的看似允许的名称,以及许多其他名称,是保留的,不允许的。Windows 也有几个长度限制;如果移动到另一个文件夹,在一个文件夹中有效的文件名可能会失效。命名文件和文件夹的规则位于 Microsoft 文档中。AaPRNCON

通常,不能使用用户生成的文本来创建 Windows 目录名称。如果要允许用户命名他们想要的任何内容,则必须创建安全名称(如 、 等),将用户生成的名称及其路径等效项存储在应用程序数据文件中,并在应用程序中执行路径映射。AABA2

如果绝对必须允许用户生成的文件夹名称,则判断它们是否无效的唯一方法是捕获异常并假定名称无效。即使这样也充满了危险,因为为拒绝访问、脱机驱动器和驱动器空间不足而引发的异常与可能为无效名称引发的异常重叠。你正在打开一个巨大的伤害罐头。

评论

12赞 Adrian McCarthy 12/30/2009
MSDN 链接中的关键词是“目标文件系统不允许的其他字符”。Windows 上可能有不同的文件系统。有些可能允许 Unicode,有些可能不允许。通常,验证名称的唯一安全方法是在目标设备上尝试。
130赞 Borodin 1/28/2016
有一些准则,“有无数个仅由被禁止的有效字符组成的名称”是没有建设性的。同样,“Windows 不区分大写和小写字符”是一个愚蠢的例外——OP 询问的是语法而不是语义,没有一个正直的人会说像这样的文件名是无效的,因为可能存在。A.txta.TXT
12赞 AntonPiatek 4/11/2016
COPY CON PRN表示从键盘输入或可能的 stdin 读取,并将其复制到打印机设备。不确定它在现代窗户上是否仍然有效,但肯定是很长一段时间。在过去,您可以使用它来键入文本,并让点阵打印机简单地输出它。
7赞 Casey 10/17/2017
“一般来说,你不能使用用户生成的文本来创建Windows目录名称。 <-- 如果你想这样做,你可以有一个字符白名单,如果你可以忽略已经存在的问题,它在很大程度上会起作用。
17赞 JBentley 4/19/2020
@JimBalter 除非我误解了,否则它不是建设性的,因为如果文件名的规则定义明确且本身不是无限的,那么“仅由被禁止的有效字符组成的无限数量的名称”是毫无意义的。这个答案中没有任何理由以对读者有帮助或有用的方式将可能性描述为无限。例如,对比以下内容:(1) 在 Linux 中,不允许使用“/”。(2) Linux的综合指南是不可能的,因为有无数个不允许的名称,例如“/”、“//”、“//”、“a/a”、“b/b”等。
29赞 Leonardo Herrera 12/30/2009 #2

好吧,如果只是出于研究目的,那么你最好的选择是看看这个关于文件名的维基百科条目

如果你想编写一个可移植函数来验证用户输入并基于此创建文件名,简短的回答是不要。看看像 Perl 的 File::Spec 这样的可移植模块,以了解完成这样一个“简单”任务所需的所有跃点。

91赞 Jonathan Leffler 12/30/2009 #3

在Linux和其他Unix相关系统下,传统上只有两个字符不能出现在文件或目录的名称中,它们是NUL和斜杠。当然,斜杠可以出现在路径名中,分隔目录组件。'\0''/'

谣言1 说 Steven Bourne(以“贝壳”而闻名)有一个包含 254 个文件的目录,每个文件对应一个文件名中可能出现的单个字母(字符代码)(不包括 ,当然,该名称是当前目录)。它被用来测试 Bourne shell,并经常对备份程序等粗心的程序造成严重破坏。/'\0'.

其他人已经介绍了Windows文件名的规则,并提供了有关该主题的MicrosoftWikipedia的链接。

请注意,MacOS X 具有不区分大小写的文件系统。它的当前版本似乎允许在文件名中使用冒号,尽管从历史上看,情况并非总是如此::

$ echo a:b > a:b
$ ls -l a:b
-rw-r--r--  1 jonathanleffler  staff  4 Nov 12 07:38 a:b
$

但是,至少在 macOS Big Sur 11.7 中,文件系统不允许文件名不是有效的 UTF-8 字符串。这意味着文件名不能包含在 UTF-8 中始终无效的字节(0xC0、0xC1、0xF5-0xFF),并且您不能使用0x80的延续字节。0xBF作为文件名中的唯一字节。给出的错误是 92 非法字节序列。

POSIX 定义了一个可移植文件名字符集,包括:

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
a b c d e f g h i j k l m n o p q r s t u v w x y z
0 1 2 3 4 5 6 7 8 9 . _ -

坚持使用仅由这些字符组成的名称可以避免大多数问题,尽管 Windows 仍然增加了一些复杂性。


1 Kernighan & Pike 在 ['The Practice of Programming'](https://www.cs.princeton.edu/~bwk/tpop.webpage/) 中,在第 6 章 Testing, §6.5 Stress Tests 中也说了同样的话:

当史蒂夫·伯恩(Steve Bourne)编写他的Unix shell(后来被称为Bourne shell)时,他创建了一个包含254个文件的目录,其中包含一个字符名称,每个字节值一个,除了和斜杠,这两个字符不能出现在Unix文件名中。他使用该目录进行模式匹配和标记化的各种测试。(当然,测试目录是由程序创建的。在那之后的几年里,该目录一直是文件树遍历程序的祸根;它考验了他们毁灭。'\0'

请注意,该目录必须包含条目 . 和 ..,因此可以说是 253 个文件(和 2 个目录),或 255 个名称条目,而不是 254 个文件。这不会影响轶事的有效性,也不会影响它所描述的仔细测试。

TPOP之前是 http://plan9.bell-labs.com/cm/cs/tpophttp://cm.bell-labs.com/cm/cs/tpop,但现在(2021-11-12)都坏了。 另请参阅有关TPOP的维基百科。

评论

1赞 j_kubik 9/9/2012
254 个文件?那么utf8呢?
28赞 Jonathan Leffler 9/9/2012
这 254 个文件都是单字符文件名,每个字符允许在文件名中使用一个。当 Steve Bourne 编写 Bourne shell 时,UTF-8 甚至连一盏灯都没有。UTF-8 强加了有关有效字节序列的规则(并完全不允许字节 0xC0、0xC1、0xF5-0xFF)。否则,在我正在讨论的细节层面上,它没有太大的不同。
2赞 Dan Pritts 12/10/2013
MacOS HFS+ 文件系统的磁盘目录分隔符实际上是 ':“' 而不是 '/'。当您使用 *nix API 时,操作系统通常(可能总是)做正确的事情。但是,如果您要迁移到 OSX 世界,例如使用 applescript,请不要指望这种情况会可靠地发生。看起来 Cocoa API 可能也使用 / 并隐藏 : ,但我很确定旧的 Carbon API 不会。
1赞 Jonathan Leffler 4/24/2020
请注意,如果目录的名称中包含冒号,则无法将该目录添加到 Unix 变量中,因为冒号用作分隔符(在 Windows 上为分号)。因此,此类目录中的程序必须使用指定其位置的路径名(可以是相对的或绝对的)运行,或者您必须位于该目录中并且 dot (,当前目录) ,这被广泛认为是不安全的。PATH.PATH
2赞 Jonathan Leffler 10/24/2022
FWIW:在运行 macOS Big Sur 11.7 的 MacBook Pro 上,我可以创建名称为 的文件,但无法创建全名为0xC0、0xC1 0xF5的任何单个字节的文件。0xFF,也不使用任何 UTF-8 延续字节0x80。0xBF.错误号为92“非法字节序列”。由此,我推断 macOS 坚持文件名是不包含或 null 字节的有效 UTF-8 字符串。我没有检查非字符(如 U+FFFF)、代理项范围、PUA(专用区域)范围以及未分配代码块中的字符,例如 U+80000..U+8FFFF。:/
48赞 AeonOfTime 4/16/2015 #4

您可以使用白名单,而不是创建字符黑名单。考虑到所有因素,在文件或目录名称上下文中有意义的字符范围非常短,除非您有一些非常具体的命名要求,否则如果用户无法使用整个 ASCII 表,则他们不会将其与您的应用程序对立。

它不能解决目标文件系统中的保留名称问题,但使用白名单可以更容易地从源头上降低风险。

本着这种精神,这是一系列可以被认为是安全的角色:

  • 字母 (a-z A-Z) - Unicode 字符(如果需要)
  • 数字 (0-9)
  • 下划线 (_)
  • 连字符 (-)
  • 空间
  • 点 (.)

以及您希望允许的任何其他安全字符。除此之外,你只需要强制执行一些关于空格和点的额外规则。这通常就足够了:

  • 名称必须至少包含一个字母或数字(以避免仅包含点/空格)
  • 名称必须以字母或数字开头(以避免前导点/空格)
  • 名称不能以点或空格结尾(如果存在,只需修剪它们,就像 Explorer 一样)

这已经允许相当复杂和荒谬的名称。例如,这些名称可以通过以下规则实现,并且在 Windows/Linux 中是有效的文件名:

  • A...........ext
  • B -.- .ext

从本质上讲,即使白名单字符如此之少,您仍然应该决定真正有意义的字符,并相应地验证/调整名称。在我的一个应用程序中,我使用了与上述相同的规则,但删除了任何重复的点和空格。

评论

42赞 pkh 5/14/2016
那么我的非英语用户呢,他们都会被这个搞砸吗?
5赞 tahoar 10/13/2016
我们使用白名单方法,但不要忘记在 Windows 上,您必须管理保留的、与大小写的无关的字符串,例如设备名称(prn、lpt1、con)和 .和。。
6赞 LarsH 6/4/2019
“考虑到所有因素,在文件或目录名称上下文中有意义的字符范围非常短。”也许对于某些用例。我现在正在处理一个涉及 20 种语言的媒体文件的项目,文件名需要反映媒体项的标题,因为最终用户将以这种方式查找内容。许多名称使用标点符号。对文件名字符的任何限制都是有代价的,因此在这种情况下,我们必须尽量减少限制。在此用例中,在文件名中没有意义的字符范围比有意义的字符范围要短得多,也简单得多。
7赞 LarsH 6/12/2019
如今,许多程序的现实是,您不知道客户是谁,或者他们将使用什么语言。例如,如果您要在应用商店、Windows 或 Apple 商店中向公众发布。默认情况下,您可以使您的软件仅提供英语(或仅欧洲),这是一种常见的方法......对于其他语言的使用者来说,这是一个令人沮丧的问题,他们正在寻找满足他们需求的软件。对于开发人员来说,这也可能是可以避免的收入损失。将程序设计为在很大程度上与脚本无关并不需要付出太多努力。
6赞 atimholt 5/1/2020
我想说的是,任何好的代码都会说出它的含义。在这种情况下,白名单感觉很像一种“货物崇拜”解决方案,在数百万“未知未知”的情况下会被打破。你不是不允许不可能的值,而是不允许你不敢测试的值。
1030赞 Christopher Oezbek 8/13/2015 #5
  1. 禁止打印的 ASCII 字符是:

    • Linux/Unix:

      / (forward slash)
      
    • 窗户:

      < (less than)
      > (greater than)
      : (colon - sometimes works, but is actually NTFS Alternate Data Streams)
      " (double quote)
      / (forward slash)
      \ (backslash)
      | (vertical bar or pipe)
      ? (question mark)
      * (asterisk)
      
  2. 不可打印的字符

    如果您的数据来自允许不可打印字符的来源,则需要检查更多内容。

    • Linux/Unix:

      0 (NULL byte)
      
    • 窗户:

      0-31 (ASCII control characters)
      

    注意:虽然在 Linux/Unix 文件系统下创建文件名中带有控制字符的文件是合法的,但对于用户来说,处理此类文件可能是一场噩梦

  3. 保留的文件名

    保留以下文件名:

    • 窗户:

      CON, PRN, AUX, NUL 
      COM1, COM2, COM3, COM4, COM5, COM6, COM7, COM8, COM9
      LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, LPT9
      

      (既可以单独使用,也可以使用任意文件扩展名,例如 )。LPT1.txt

  4. 其他规则

    • 窗户:

      文件名不能以空格或点结尾。

    • macOS:

      你没有要求它,但以防万一:根据上下文,冒号和正斜杠是不允许的(例如,Finder 支持斜杠,终端支持冒号)。(更多详情):/

评论

6赞 Eryk Sun 5/18/2018
请注意,在将 DOS 路径转换为本机 NT 路径时,运行时库将应用保留的 DOS 设备名称和有关以点或空格结尾的文件名的规则。如果路径以“\\?\”本地设备前缀开头,则跳过此规范化步骤,但将“\\?\”替换为 NT 的“\??\“ 设备前缀。此前缀指示对象管理器在登录会话和全局 DOS 设备目录中搜索指向本机 NT 设备的符号链接,该设备通常是“\Device”目录中的设备对象。
4赞 Eryk Sun 5/18/2018
OTOH,保留字符不仅仅是 DOS 命名空间的一个函数。它们在内核和文件系统中保留在较低级别。“\”字符是 NT 的路径分隔符,由对象管理器保留。对象名称中允许使用其他所有内容,其中包括 DOS 设备名称,例如“C:”。其他保留字符(包括 ASCII 控制字符)是由于内核的文件系统运行时库,该库由 Microsoft 的文件系统使用。这些字符保留在主文件名中,而不是保留在流名称中。
7赞 Eryk Sun 5/18/2018
这些字符保留为通配符。这是由于一个特殊的设计决策,即让文件系统在实现系统调用时实现低级别的目录列表过滤。在POSIX系统中,这是在应用程序级别实现的。*?<>"NtQueryDirectoryFile
8赞 Jim Balter 10/9/2018
“你可以在大多数 Linux 发行版上用正斜杠命名一个文件就好了。”“/”始终被内核视为目录分隔符,而不仅仅是 shell。没有办法用 C 程序或 Python 脚本或任何其他方式解决这个问题。
4赞 Lutz Prechelt 3/18/2019
有趣的事实:使用 Cygwin,您可以轻松创建 和 .然后尝试在 Windows 资源管理器中删除它们:你不能。或者在 : 你不能。不过,Cygwin可以。这似乎是 1980 年代的限制,是人为帮助的。lpt1lpt1.txtcmd.exe
42赞 chrisjej 9/14/2015 #6

让 Windows 告诉您答案的简单方法是尝试通过资源管理器重命名文件,并在新名称中键入任何非法字符,例如反斜杠。Windows 将弹出一个消息框,告诉您非法字符列表:\

文件名不能包含以下任何字符:
\ / : * ?" < > |

以下是 Windows 10 专业版弹出窗口的屏幕截图:

enter image description here

请参阅:Microsoft 文档 - 命名文件、路径和命名空间 - 命名约定

评论

3赞 Zsolti 1/26/2021
我记得以前是这样。我刚刚在 Windows 10 中尝试过,但该消息框不再显示,而是正在播放声音。
1赞 Cadoiz 7/13/2023
这就是错误消息的样子 - 添加它的编辑被拒绝了。由于最后一位编辑,不再需要存档链接。在 Win11 中怎么样?消息/声音/???
-8赞 Meng Lu 10/4/2015 #7

我有同样的需求,正在寻找推荐或标准参考,并遇到了这个线程。我目前在文件和目录名称中应避免使用的字符黑名单是:

$CharactersInvalidForFileName = {
    "pound" -> "#",
    "left angle bracket" -> "<",
    "dollar sign" -> "$",
    "plus sign" -> "+",
    "percent" -> "%",
    "right angle bracket" -> ">",
    "exclamation point" -> "!",
    "backtick" -> "`",
    "ampersand" -> "&",
    "asterisk" -> "*",
    "single quotes" -> "“",
    "pipe" -> "|",
    "left bracket" -> "{",
    "question mark" -> "?",
    "double quotes" -> "”",
    "equal sign" -> "=",
    "right bracket" -> "}",
    "forward slash" -> "/",
    "colon" -> ":",
    "back slash" -> "\\",
    "lank spaces" -> "b",
    "at sign" -> "@"
};

评论

5赞 PypeBros 10/25/2016
你介意评论一下列表中有吗?@
13赞 Nigel Alderton 1/11/2017
问题是哪些字符是非法的。列表中的大多数字符都是合法的。
9赞 ashleedawg 3/3/2018
这封信 ?哈哈,我假设那是来自......好吧,这仍然留下了一些......我重命名了一张图片,但不得不把它改回来,因为它看起来很生气......blank spaces(),-.;[]^_~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ.jpg
3赞 Dogg Bookins 4/20/2016 #8

虽然唯一非法的 Unix 字符可能是 和 ,尽管应该包括一些命令行解释的考虑。/NULL

例如,虽然在 Unix 中命名文件可能是合法的,但在命令行上使用此类文件名时可能会被误解。1>&22>&1

同样,也可以命名一个文件 ,但是当尝试从命令行访问它时,shell 将转换为其变量值。$PATH$PATH

评论

2赞 ThorSummoner 7/8/2017
对于 BASH 中的文字,我发现在没有插值的情况下声明文字的最佳方法是 ,例如: , “hi”$'myvalueis'$ echo 'hi' > $'2>&1'cat 2\>\&1
-2赞 forthy42 8/15/2016 #9

在Unix shell中,您几乎可以用单引号引用几乎每个字符。除了单引号本身,并且不能表示控制字符,因为没有展开。从带引号的字符串中访问单引号本身是可能的,因为您可以将字符串与单引号和双引号连接起来,例如可用于访问名为(此处也可以使用双引号)的文件。'\'I'"'"'m'"I'm"

因此,您应该避免所有控制字符,因为它们太难在 shell 中输入。其余的仍然很有趣,尤其是以破折号开头的文件,因为大多数命令将它们读取为选项,除非您之前有两个破折号,或者您用 指定它们,这也隐藏了开始 .--./-

如果你想好听,不要使用 shell 和典型命令使用的任何字符作为语法元素,有时取决于位置,所以例如,你仍然可以使用 ,但不能作为第一个字符;与 相同,您只能在意思(“隐藏文件”)时将其用作第一个字符。当你是卑鄙的时,你的文件名是 VT100 转义序列 ;-),因此 ls 会使输出出现乱码。-.

评论

0赞 Jim Balter 8/5/2017
问题不在于贝壳。
0赞 FCastro 4/19/2017 #10

截至 2017 年 4 月 18 日,在该主题的答案中,没有简单的字符和文件名的黑白列表 - 并且有很多回复。

我能想到的最好的建议是让用户按照他喜欢的方式命名文件。当应用程序尝试保存文件时使用错误处理程序,捕获任何异常,假设文件名是罪魁祸首(显然在确保保存路径也正常之后),并提示用户输入新文件名。为了获得最佳结果,请将此检查过程置于一个循环中,该循环一直持续到用户正确或放弃为止。最适合我(至少在 VBA 中)。

评论

3赞 Mike 9/22/2017
从技术角度来看,您的答案@FCastro是正确的。然而,从用户体验的角度来看,这是一场噩梦——用户被迫一次又一次地玩“输入一些东西,如果你成功,我会告诉你”的游戏。我宁愿看到一条消息(警告样式)告诉用户他们输入了一个非法字符,稍后将进行转换。
2赞 Jim Balter 10/9/2018
克里斯托弗·奥兹贝克(Christopher Oezbek)在2015年提供了这样的黑名单。
-3赞 Matthias Ronge 4/25/2017 #11

在 Windows 中创建 Internet 快捷方式时,为了创建文件名,它会跳过非法字符,但正斜杠除外,正斜杠将转换为减号。

评论

2赞 Cees Timmerman 8/19/2021
好的,那么哪些字符是非法的?
14赞 Wojciech Sciesinski 6/26/2017 #12

对于 Windows,可以使用 PowerShell 进行检查

$PathInvalidChars = [System.IO.Path]::GetInvalidPathChars() #36 chars

要显示 UTF-8 代码,您可以转换

$enc = [system.Text.Encoding]::UTF8
$PathInvalidChars | foreach { $enc.GetBytes($_) }

$FileNameInvalidChars = [System.IO.Path]::GetInvalidFileNameChars() #41 chars

$FileOnlyInvalidChars = @(':', '*', '?', '\', '/') #5 chars - as a difference

评论

2赞 Robin Davies 5/11/2020
对于那些不会说 PowershelI 的人来说,$FileNameInvalidChars是通过0x1F 0x00的,并且:“ < > |* ?\ /
0赞 Cadoiz 9/17/2021
(" < > |对路径和文件都无效)
7赞 Bret Cameron 8/15/2019 #13

在 Windows 10 (2019) 中,当您尝试键入以下字符时,错误会禁止它们:

文件名不能包含以下任何字符:

\ / : * ? " < > | enter image description here

评论

3赞 Cadoiz 9/21/2020
可能重复到 stackoverflow.com/a/32565700/4575793
4赞 Charlie Rix 12/29/2019 #14

下面是基于 Christopher Oezbek 的答案的 Windows c# 实现

containsFolder 布尔值使它变得更加复杂,但希望涵盖所有内容

/// <summary>
/// This will replace invalid chars with underscores, there are also some reserved words that it adds underscore to
/// </summary>
/// <remarks>
/// https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names
/// </remarks>
/// <param name="containsFolder">Pass in true if filename represents a folder\file (passing true will allow slash)</param>
public static string EscapeFilename_Windows(string filename, bool containsFolder = false)
{
    StringBuilder builder = new StringBuilder(filename.Length + 12);

    int index = 0;

    // Allow colon if it's part of the drive letter
    if (containsFolder)
    {
        Match match = Regex.Match(filename, @"^\s*[A-Z]:\\", RegexOptions.IgnoreCase);
        if (match.Success)
        {
            builder.Append(match.Value);
            index = match.Length;
        }
    }

    // Character substitutions
    for (int cntr = index; cntr < filename.Length; cntr++)
    {
        char c = filename[cntr];

        switch (c)
        {
            case '\u0000':
            case '\u0001':
            case '\u0002':
            case '\u0003':
            case '\u0004':
            case '\u0005':
            case '\u0006':
            case '\u0007':
            case '\u0008':
            case '\u0009':
            case '\u000A':
            case '\u000B':
            case '\u000C':
            case '\u000D':
            case '\u000E':
            case '\u000F':
            case '\u0010':
            case '\u0011':
            case '\u0012':
            case '\u0013':
            case '\u0014':
            case '\u0015':
            case '\u0016':
            case '\u0017':
            case '\u0018':
            case '\u0019':
            case '\u001A':
            case '\u001B':
            case '\u001C':
            case '\u001D':
            case '\u001E':
            case '\u001F':

            case '<':
            case '>':
            case ':':
            case '"':
            case '/':
            case '|':
            case '?':
            case '*':
                builder.Append('_');
                break;

            case '\\':
                builder.Append(containsFolder ? c : '_');
                break;

            default:
                builder.Append(c);
                break;
        }
    }

    string built = builder.ToString();

    if (built == "")
    {
        return "_";
    }

    if (built.EndsWith(" ") || built.EndsWith("."))
    {
        built = built.Substring(0, built.Length - 1) + "_";
    }

    // These are reserved names, in either the folder or file name, but they are fine if following a dot
    // CON, PRN, AUX, NUL, COM0 .. COM9, LPT0 .. LPT9
    builder = new StringBuilder(built.Length + 12);
    index = 0;
    foreach (Match match in Regex.Matches(built, @"(^|\\)\s*(?<bad>CON|PRN|AUX|NUL|COM\d|LPT\d)\s*(\.|\\|$)", RegexOptions.IgnoreCase))
    {
        Group group = match.Groups["bad"];
        if (group.Index > index)
        {
            builder.Append(built.Substring(index, match.Index - index + 1));
        }

        builder.Append(group.Value);
        builder.Append("_");        // putting an underscore after this keyword is enough to make it acceptable

        index = group.Index + group.Length;
    }

    if (index == 0)
    {
        return built;
    }

    if (index < built.Length - 1)
    {
        builder.Append(built.Substring(index));
    }

    return builder.ToString();
}

评论

0赞 iiminov 5/20/2020
我有三个问题:1.为什么使用初始容量值进行初始化?2. 为什么要在长度上加 12?3. 12 是任意选择的,还是这个数字背后有什么想法?StringBuilderfilename
1赞 Charlie Rix 4/2/2021
抱歉耽搁了,我刚刚注意到这个问题 1) 用长度初始化 stringbuilder 有点微优化。我记不清了,但它从一个小缓冲区开始,每次超过缓冲区大小时都会加倍。2)增加一点额外的保证长度不会减少一个。3)如果我们使用十二进制而不是十进制,世界会更好。12 相当于将 10 加起来(我只需要将长度填充一小部分任意量)。
26赞 Cadoiz 4/27/2020 #15

讨论不同的可能方法

定义方面的困难,什么是合法的,什么是不合法的已经解决了,并提出了白名单。但不仅是 Windows,许多 Unixoid 操作系统都支持 8 位以上的字符,例如 Unicode。您还可以在这里讨论诸如 UTF-8 之类的编码。您可以考虑 Jonathan Leffler 的评论,他在评论中提供了有关现代 Linux 的信息并描述了 MacOS 的详细信息维基百科指出,(例如),

修饰符字母冒号 [(见下文 7.) ]有时用于 Windows 文件名,因为它与用于文件名的 Segoe UI 字体中的冒号相同。不允许使用 [继承的 ASCII] 冒号本身。

因此,我想提出一种更自由的方法,使用 Unicode 同形字符来替换“非法”字符。我发现在我的类似用例中的结果更具可读性,并且它仅受所用字体的限制,该字体非常广泛,Windows 默认值为 3903 个字符。此外,您甚至可以从替换中恢复原始内容。

使用整个 Unicode 块(例如“fullwidth”)作为替换

为了保持井井有条,我总是会给角色、它的名字和十六进制数字表示。在评论中,i30817 谈到了仅为“滥用非法字符的愚蠢操作系统”保留范围的想法,这基本上是 Bill Sellers 显然所做的:“它不那么漂亮,但它总是有效,而且更容易记住。在候选块中,有全角、小格式变体、组合/修饰符/覆盖(见下文 4.)或半字符。请考虑下表的概述:

角色名称 原始代码 原始字符 全角代码 全角字符 小型变体 小型变体代码
1. 星号 U+2A * U+FF0A U+FE61型
2. 句号 U+2E . U+FF0E U+FE52型
3. 引号 U+22型 " U+FF02型 没有
4. 反向固相线 U+5C \ U+FF3C U+FE68型
5. 固相线 U+2楼 / U+FF0F 没有
6.1. 左方括号 U+5B [ U+FF3B (仅限) U+FE5D
6.2. 右方括号 U+5D ] U+FF3D (仅限) U+FE5E
7. 结肠 U+3A型 : U+FF3A U+FE55型
8. 分号 U+3B型 ; U+FF1B U+FE54型
9. 垂直线 U+7C | U+FF5C 没有
10. 逗号 U+2C , U+FF0C U+FE50型
11. 问号 U+3楼 ? U+FF1F U+FE56型
12.1. 大于符号 U+3E > U+FF1E U+FE65型
12.2. 小于符号 U+3C < U+FF1C U+FE64型
13. Circumflex 口音 U+5E ^ U+FF3E 没有

一些全角字符(1、6.1、6.2 和 11)也包含在下面的“更多可能的选择和研究说明”中。

如何键入非标准字符

假设您要键入 .要获取其所有信息,您始终可以在合适的平台上搜索此字符 (),例如此 Unicode 查找该 Unicode 表(仅允许搜索名称,在本例中为“Tifinagh Letter Yan”)。您应该获取其 Unicode 编号和 HTML 代码(请注意,2D4F11599 的十六进制)。有了这些知识,您就有多种选择来生成这些特殊字符,包括使用ⵏ (Tifinagh Letter Yan)U+2D4F&#11599;

  • 代码点到 Unicode 转换器或再次 Unicode 查找(请在搜索十六进制时添加)以将数字表示反向转换为 Unicode 字符(请记住将下面的代码点基数分别设置为十进制或十六进制)0x
  • Autohotkey 中的单行 makro:键入而不是字符串 - 这是我输入这些特殊字符的方式,如果有共同的兴趣,可以共享我的 Autohotkey 脚本:?*:altpipe::{U+2D4F}altpipe
  • Alt字符或替代代码,按住 ,后跟所需字符的十进制数(更多信息,例如,此处,查看此处那里的表格)。对于示例,这将是 +。请注意,许多程序并不完全支持所有 unicode 的 Windows 功能(截至撰写本文时)。Microsoft Office 是一个例外,它通常可以工作,其他一些操作系统提供类似的功能。在 MS Word 中输入这些带有 Alt 组合的字符也是 Wally Brockway 在他前面提到的答案中建议的方式¹⁴ - 如果您不想将所有十六进制值转换为它们的十进制 asc,您可以在那里找到其中的一些¹⁴altAlt11599
  • 在 MS Office 中,您还可以使用本 MS 文章中所述的 + 来生成字符ALTX
  • 大多数操作系统都提供字符映射附件,您可以在其中找到特殊字符,通常它们还包括按名称搜索的选项
  • 如果你很少需要它,你当然仍然可以复制粘贴你选择的特殊字符,而不是输入它

更多可能的选择和研究笔记

所以你对更广泛的角色的外观不满意吗?有很多选择。注意:十六进制数表示不区分大小写,前导零可以自由添加或省略,例如,并且是等效的。如果可用,我会尝试指出更多信息或替代方案 - 请随时向我展示更多或更好的信息。U+002Au+2a

  1. 您可以使用列出的众多选项之一来代替 (),例如,或 . 将变音符号组合起来也可能是一个有效的选择。您可以阅读 4.有关组合字符的详细信息。*U+2A * ASTERISKU+2217 ∗ (ASTERISK OPERATOR)Full Width Asterisk U+FF0A *u+20f0 ⃰ combining asterisk above

  2. 而不是 (),其中一个可能是一个不错的选择,例如。.U+2E . full stop⋅ U+22C5 dot operator

  3. 代替 (),您可以使用 ,更多替代项请参阅此处。在这种情况下,我还包括 Wally Brockway 回答的一些好建议,并且 - 从现在开始,我将用 ¹⁴ 表示来自该来源的想法。"U+22 " quotation mark“ U+201C english leftdoublequotemarku+2036 ‶ reversed double primeu+2033 ″ double prime

  4. 您可以使用 (other here) 或 ¹⁴ 代替 (),而不是 ()。您也可以尝试 或 但请注意某些字符的间距,包括 or 字符。它们本身没有宽度,可以产生类似 --> ̸th̷is 的东西,即(为澄清这 6 个字符而添加下划线)。添加空格后,你会得到 --> ̸ th ̷ is,即(加上两个空格,为 8 个字符)。第二个 () 在 stackoverflow-font 中看起来很糟糕。/U+2F / SOLIDUS∕ DIVISION SLASH U+2215u+2044 ⁄ fraction slash̸ U+0338 COMBINING LONG SOLIDUS OVERLAY̷ COMBINING SHORT SOLIDUS OVERLAY U+0337combiningoverlay̸_th̷_is̸ _th ̷ _isCOMBINING SHORT SOLIDUS OVERLAY

  5. 您可以使用 (more) 或 ¹⁴ 代替 (),而不是 ()。\U+5C Reverse solidus⧵ U+29F5 Reverse solidus operator u+20E5 ⃥ combining reverse solidus overlay

  6. 要替换 () 和 (),您可以使用例如 和 (从这里开始,更多可能性在这里)。[U+5B [ Left square bracket]U+005D ] Right square bracketU+FF3B[ FULLWIDTH LEFT SQUARE BRACKETU+FF3D ]FULLWIDTH RIGHT SQUARE BRACKET

  7. 您可以使用 或 代替 (),(请参阅冒号(字母),有时用于 Windows 文件名,因为它与用于文件名的 Segoe UI 字体中的冒号相同。冒号本身是不允许的......来源和更多替代品见这里)。另一种选择是:¹⁴:u+3a : colonU+2236 ∶ RATIO (for mathematical usage)U+A789 ꞉ MODIFIER LETTER COLONu+1361 ፡ ethiopic wordspace

  8. 您可以使用 () 代替 (),而不是 ()。;u+3b ; semicolonU+037E ; GREEK QUESTION MARK

  9. 对于 (),有一些很好的替代品,例如:、、(维基百科中的最后一个)或 。此外,框绘图字符还包含各种其他选项。|u+7c | vertical lineU+2223 ∣ DIVIDESU+0964 । DEVANAGARI DANDAU+01C0 ǀ LATIN LETTER DENTAL CLICKU+2D4F ⵏ Tifinagh Letter Yan

  10. 例如,您可以使用 () 代替 ()。,, U+002C COMMA‚ U+201A SINGLE LOW-9 QUOTATION MARK

  11. 对于 (),这些是很好的候选者:或(从这里和这里)。还有两个来自 Dingbats Block(搜索“问题”)和 ¹⁴?U+003F ? QUESTION MARKU+FF1F ? FULLWIDTH QUESTION MARKU+FE56 ﹖ SMALL QUESTION MARKu+203d ‽ interrobang

  12. 虽然我的机器似乎接受它不变,但为了完整起见,我仍然想包括 () 和 ()。这里最好的替换可能也来自报价块,例如 和 分别。tifinagh 块仅包含 ¹⁴ 来替换 .最后一个概念是 和 。>u+3e greater-than sign<u+3c less-than signu+203a › single right-pointing angle quotation marku+2039 ‹ single left-pointing angle quotation markⵦ (u+2D66)<⋖ less-than with dot u+22D6⋗ greater-than with dot u+22D7

如需更多想法,您还可以查看此块。你还想有更多想法吗?您可以尝试绘制您想要的角色并查看此处的建议。如果您发现有价值的东西,请发表评论。

评论

1赞 DDR 9/19/2020
我制作了一个程序来应用这些更改 github.com/DDR0/fuseblk-filename-fixer。如果我错过了任何角色(或图案),请告诉我!
2赞 i30817 1/19/2022
如果 unicode 联盟中的“某人”只为“滥用非法字符的愚蠢操作系统”保留一个范围,其字体映射将映射到“非法字符字形”,但有所不同,那就太好了。甚至替代?具有不同的宽度和特性,导致我想更换!即使这样,当高度与“”不一致时,也会感到恼火。(例如)。
0赞 Heriberto Lugo 11/10/2022
应该注意的是,虽然文件系统会接受这些“替代方案”,但它们可能会在其他地方引起问题。我将 U+FF3B 添加到文件路径中,Windows 没有问题。但是,当我尝试在 c# 中使用 System.IO.File.ReadAllBytes 时,它崩溃了。因此,这些绝对不应用作文件系统限制的解决方法。
1赞 Bill Sellers 7/21/2023
这就是我所做的,但我只是对所有这些字符使用全角字符选项。它不那么漂亮,但它总是有效,更容易记住。我只是在 Windows 字符映射表附件中搜索“全角”。半角也是一种选择,但全角选项对我来说看起来更好一些。但我同意 Unicode 中 7 位 ASCII 重复范围的建议,或者 Windows 可以只使用其中一个专用范围......
1赞 Cadoiz 9/15/2023
@DDR,如果需要,您可以考虑最近的编辑。
11赞 Kartik Soneji 8/5/2020 #16

对于任何寻找正则表达式的人:

const BLACKLIST = /[<>:"\/\\|?*]/g;
4赞 gridtrak 11/12/2020 #17

.NET Framework 为无效的文件系统字符提供以下函数:System.IO

这些函数返回相应的结果,具体取决于运行 .NET 运行时的平台。也就是说,这些函数的文档页面中的备注说:

从此方法返回的数组不保证包含 文件和目录中无效的完整字符集 名字。完整的无效字符集可能因文件系统而异。

评论

0赞 Cees Timmerman 8/19/2021
stackoverflow.com/a/44750843/819417 的副本
3赞 Wally Brockway 1/1/2021 #18

我一直认为 Windows 文件名中禁止的字符意味着所有外来字符也将被取缔。无法使用 ,尤其让我恼火。有一天,我发现几乎只有那些字符被禁止。可以使用其他 Unicode 字符。因此,确定了与我能找到的被禁止字符最接近的 Unicode 字符,并为它们制作了 MS Word 宏,如 +、+ 等。现在,我使用替换字符在 Word 中形成文件名,并将其复制到 Windows 文件名中。到目前为止,我还没有遇到任何问题。? / :Alt?Alt:

以下是替换字符 ( + 十进制 Unicode) :Alt

  • ⃰ ⇔ 8432Alt
  • ⁄ ⇔ 8260Alt
  • ⃥ ⇔ 8421Alt
  • ∣ ⇔ 8739Alt
  • ⵦ ⇔ 11622Alt
  • ⮚ ⇔ 11162Alt
  • ‽ ⇔ 8253Alt
  • ፡ ⇔ 4961Alt
  • ‵‵ ⇔ 8246Alt
  • “ ⇔ 8243Alt

作为测试,我使用所有这些字符形成了一个文件名,Windows 接受了它。

评论

0赞 Cadoiz 4/22/2021
我自由地改进了您的格式以提高可读性。我还在上面解释了相同的基本思想,现在纳入了您的一些建议,如果可以的话。谢谢!stackoverflow.com/a/61448658/4575793
1赞 Cees Timmerman 8/17/2021 #19

这在 Python 中对我来说已经足够了:

def fix_filename(name, max_length=255):
    """
    Replace invalid characters on Linux/Windows/MacOS with underscores.
    List from https://stackoverflow.com/a/31976060/819417
    Trailing spaces & periods are ignored on Windows.
    >>> fix_filename("  COM1  ")
    '_ COM1 _'
    >>> fix_filename("COM10")
    'COM10'
    >>> fix_filename("COM1,")
    'COM1,'
    >>> fix_filename("COM1.txt")
    '_.txt'
    >>> all('_' == fix_filename(chr(i)) for i in list(range(32)))
    True
    """
    return re.sub(r'[/\\:|<>"?*\0-\x1f]|^(AUX|COM[1-9]|CON|LPT[1-9]|NUL|PRN)(?![^.])|^\s|[\s.]$', "_", name[:max_length], flags=re.IGNORECASE)

另请参阅此过时的列表,了解其他遗留内容,例如 FAT32。=

1赞 Gabriel Staples 7/30/2023 #20

例如,OP的问题已经在这里这里得到了充分的回答。在这里,我只是通过展示如何在 Linux 上修复它来扩展这些答案:

在 Linux 中,查找所有带有 Windows 中禁止的字符的文件和文件夹名称

如果您使用的是 Linux,并且只想查找所有带有 Windows 中禁止的字符的文件和文件夹名称,则可以运行以下命令:

# Find all files and folders with any of these Windows-illegal characters in
# their name:  \ : * ? " < > |
find . -name '*[\\:\*?\"<\>|]*'

例如,这非常有用,因此您可以手动清理或“修复”在 Linux 上编写的 git 代码存储库,您现在需要在 Windows 上克隆和使用。如果您不首先在文件和文件夹名称中找到并清除并修复所有与 Windows 不兼容的字符,则存储库将无法在 Windows 上克隆,并且您会看到如下错误,例如:

$ git clone https://github.com/ElectricRCAircraftGuy/eRCaGuy_hello_world.git
Cloning into 'eRCaGuy_hello_world'...
remote: Enumerating objects: 4342, done.
remote: Counting objects: 100% (1184/1184), done.
remote: Compressing objects: 100% (366/366), done.
remote: Total 4342 (delta 819), reused 1149 (delta 799), pack-reused 3158Receiving objects: 100% (4342/4342), 6.50 Mi
Receiving objects: 100% (4342/4342), 7.02 MiB | 6.50 MiB/s, done.

Resolving deltas: 100% (2725/2725), done.
error: invalid path 'cpp/class_copy_constructor_and_assignment_operator/Link to Copy constructor vs assignment operat
or in C++ - GeeksforGeeks%%%%% [see `t2 = t1;  -- calls assignment operator, same as "t2.operator=(t1);" `].desktop'
fatal: unable to checkout working tree
warning: Clone succeeded, but checkout failed.
You can inspect what was checked out with 'git status'
and retry with 'git restore --source=HEAD :/'

在上面,您可以看到导致失败的原因,因为我的文件名在 Windows 中路径无效,导致存储库无法在 Windows 上克隆,因为它包含双引号 () 字符。因此,我将在 Linux 上手动重命名该文件,删除字符,并将更改推送到我的 git 存储库,以便我可以在 Windows 上克隆它。error: invalid pathgit clonecpp/class_copy_constructor_and_assignment_operator/Link to Copy constructor vs assignment operat or in C++ - GeeksforGeeks%%%%% [see `t2 = t1; -- calls assignment operator, same as "t2.operator=(t1);" `].desktop""

保留 Windows 文件路径 <= 259 个字符,文件夹路径 <= 248 个字符 ( 错误:git cloneFilename too long)

即使您通过使用上述命令查找从文件夹和文件名中删除了禁止的字符,请记住,Windows 限制仍然存在,将文件的总路径长度限制为 <= 259 个字符,或文件夹的 <= 248 个字符。请参阅此处:NTFS(Windows XP 和 Windows Vista)中的最大文件名长度?find . -name '*[\\:\*?\"<\>|]*'MAX_PATH

如果违反此路径限制,然后尝试在 Windows 上访问存储库,则会收到以下错误:git cloneFilename too long

$ git clone https://github.com/ElectricRCAircraftGuy/eRCaGuy_hello_world.git
Cloning into 'eRCaGuy_hello_world'...
remote: Enumerating objects: 4347, done.
remote: Counting objects: 100% (1189/1189), done.
remote: Compressing objects: 100% (370/370), done.
remote: Total 4347 (delta 823), reused 1152 (delta 800), pack-reused 3158
Receiving objects: 100% (4347/4347), 7.03 MiB | 5.82 MiB/s, done.
Resolving deltas: 100% (2729/2729), done.
error: unable to create file cpp/class_copy_constructor_and_assignment_operator/Link to Copy constructor vs assignmen
t operator in C++ - GeeksforGeeks%%%%% [see `t2 = t1;  -- calls assignment operator, same as ''t2.operator=(t1);'' `]
.desktop: Filename too long
Updating files: 100% (596/596), done.
Filtering content: 100% (8/8), 2.30 MiB | 2.21 MiB/s, done.
fatal: unable to checkout working tree
warning: Clone succeeded, but checkout failed.
You can inspect what was checked out with 'git status'
and retry with 'git restore --source=HEAD :/'

请注意这部分,因为我的文件名长得离谱:

error: unable to create file cpp/class_copy_constructor_and_assignment_operator/Link to Copy constructor vs assignment operator in C++ - GeeksforGeeks%%%%% [see `t2 = t1; -- calls assignment operator, same as ''t2.operator=(t1);'' `].desktop: Filename too long

缩短长文件名以减少路径长度,提交并推送更改,然后再次尝试克隆。

引用:

  1. 在 Windows 10 Pro 上,我尝试在文件夹名称中键入 a,但出现以下弹出窗口错误:"

    enter image description here

  2. 我使用 https://regex101.com/(参见:https://regex101.com/r/lI5Lg9/1)来构建和测试正则表达式,以了解要转义的字符,方法是查看右侧的“解释”部分:[\\:\*?\"<\>|]

    enter image description here