我很难理解文件名编码的工作原理。在 unix.SE 上我发现了相互矛盾的解释。
文件名存储为字符
引用另一个答案: 关于linux上文件系统字符编码的几个问题
[...] 正如您在问题中提到的,UNIX 文件名只是一个字符序列;内核对编码一无所知,这完全是用户空间(即应用程序级)概念。
如果文件名存储为字符,则必须涉及某种编码,因为最终文件名必须以位或字节序列的形式出现在磁盘上。如果用户可以选择任何编码将字符映射到馈送到内核的字节序列,可以创建任何有效文件名的字节序列。
假设以下情况:用户使用随机编码X,它将文件转换foo
为字节序列α并将其保存到磁盘。另一个用户使用编码是。在这个编码中α转换为/
,不允许作为文件名。但是,对于第一个用户,该文件是有效的。
我认为这种情况不会发生。
文件名存储为二进制 blob
引用另一个答案: Linux 上的文件名和路径使用什么字符集编码?
正如其他人所指出的,对此没有真正的答案:文件名和路径没有编码;操作系统仅处理字节序列。各个应用程序可能会选择将它们解释为以某种方式编码,但这会有所不同。
如果系统不处理字符,如何禁止文件名中的特定字符(例如/
或)? 没有编码NULL
就没有 a 的概念。/
一个解释是文件系统可以存储包含以下内容的文件名任何
字符,只有考虑编码的用户程序才会因包含无效字符的文件名而阻塞。反过来,这意味着文件系统和内核可以毫无困难地处理包含/
.
我也认为这是错误的。
编码在哪里进行以及不允许特定字符的限制在哪里?
答案1
简短回答:Unix/Linux/BSD 内核、namei()
功能中施加的限制。编码发生在用户级程序中,例如xterm
,firefox
或ls
。
我认为你是从错误的前提开始的。 Unix 中的文件名是具有任意值的字节字符串。一些值,0x0 (ASCII Nul) 和 0x2f (ASCII '/') 是不允许的,不能作为多字节字符编码的一部分,不能作为任何东西。 “字节”可以包含表示字符的数字(采用 ASCII 和其他一些编码),但“字符”可能需要超过 1 个字节(例如,Unicode 的 UTF-8 表示形式中高于 0x7f 的代码点)。
这些限制源于文件名打印约定和 ASCII 字符集。最初的 Unix 使用 ASCII '/'(数字 0x2f)值字节来分隔部分或完全限定路径的片段(例如 '/usr/bin/cat' 具有“usr”、“bin”和“cat”片段) 。最初的 Unix 使用 ASCII Nul 来终止字符串。除了这两个值之外,文件名中的字节可以采用任何其他值。您可以在 Unicode 的 UTF-8 编码中看到这一点。可打印 ASCII 字符(包括“/”)在 UTF-8 中仅占用一个字节。上述代码点的 UTF-8 不包含任何零值字节,除了 Nul 控制字符。 UTF-8 是为 Plan-9(Unix 王位的觊觎者)而发明的。
较旧的 Unix(看起来像 Linux)有一个namei()
函数,每次仅查看一个字节的路径,并在 0x2F 值字节处将路径分成多个片段,在零值字节处停止。 namei()
是 Unix/Linux/BSD 内核的一部分,因此这是强制执行异常字节值的地方。
请注意,到目前为止,我讨论的是字节值,而不是字符。 namei()
不对字节强制执行任何字符语义。这取决于用户级程序,例如ls
,它可能根据字节值或字符值对文件名进行排序。xterm
根据字符编码决定文件名点亮哪些像素。如果你不告诉xterm
你有 UTF-8 编码的文件名,那么当你调用它时你会看到很多乱码。如果vim
未编译为检测 UTF-8(或任何 UTF-16、UTF-32)编码,则当您打开包含 UTF-8 编码字符的“文本文件”时,您会看到很多乱码。
答案2
问题是,内核一点也不关心应用程序如何解释作为文件名给出的数据。
假设我有一个专门处理 UTF-16 字符串的 C 应用程序。我通过正确配置的输入法将 ∯ 符号 (Unicode 0x222F) 输入到“另存为”提示/对话框中。
如果应用程序不进行任何形式的翻译并将其以普通的旧 C 字符串 ( char*
) 发送到(例如,fopen
在写入模式下),则内核将看不到 ∯,甚至尝试想象这一点。它将看到两个char
s,一个接一个,带有值0x22 0x2F
(假设 8 位字符和C 库中没有搞笑内容)。
也就是说,从内核的角度来看,一个有效的 char ( "
) 后跟/
(ASCII 0x2F)。fopen
将返回EISDIR
(即“看起来像一个目录,并且您请求了写入模式!”)。
如果我输入 ∮ (Unicode 0x222E
),内核将会看到两个很好的字符,并创建一个文件,通过 ASCII 语言应用程序看到,该文件将被命名为".
.
如果我在应用程序中输入a
文件名,并且应用程序将其以 UTF-16 形式传递给内核,则内核将读取0x00 0x61
,实际上甚至不会考虑0x61
,因为0x00
已经终止了该字符串,就其本身而言担心的。错误消息与空文件名相同(ENOENT
我相信)。
所以内核确实将数据视为 blob。这是一个s流char
。您选择的用户空间编码中的无效“字符”是那些在其 blob(传递到内核的二进制表示形式)中生成0x00
or 0x2F
(“null”和)的字符。/
答案3
字节与字符的分离是在 Unix 设计之后很久才出现的。当它被设计时,这些词的使用仅传达有关如何解释 8(或 6、或 9)位的信息,但该词编码没有提到。
文件名是字节序列。允许除 0x2f“/”之外的任何字节。由于用作字符串终止符,包含 0x00 的字节甚至无法到达内核。应用程序可以根据它选择的编码来解释字节序列。如果这听起来很混乱,我想确实如此。
有更多信息位于http://www.gtk.org/api/2.6/glib/glib-Character-Set-Conversion.html你可能会发现有用。