字符集和编码II: fat/msdos/vfat (文件名乱码的问题)
具体到文件名乱码的问题,需要明确两点
- 第一,文件名作为一个字符串,需要被编码后存入文件系统;
- 第二,Linux内核无非是个特殊的应用程序,它读取文件名,再把文件名以编码后的形式传递出去。
但Linux内核只能逐字节处理编码流(而Windows NT内核是UCS-2的,逐2字节处理编码流),因此必须采用某种单字节编码(这包括所有的不定长编码)进行输出——这就是Linux内核所谓的NLS,对应于Windows的codepage。
在对文件名的处理上,fat和vfat的区别在于:fat/msdos只支持短文件名(8.3命名法),而vfat加入了对长文件名和UNICODE的支持。
为了保持与fat的兼容性,在vfat中,每个文件同时拥有“长”文件名和短文件名,其中短文件名不区分大小写(实际上是不允许小写字母出现在文件 名中)。可以这么理解,对于vfat,“长文件名”是文件真正的名字,“短文件名”则是提供兼容性的名字。举例来说,文件“真名”为abc.txt,它的 短文件名是ABC.TXT;文件“真名”为alongname.txt,它的短文件名则是ALONGN~1.TXT。
无论是fat还是vfat,短文件名按codepage编码存储,长文件名按UNICODE编码存储。因此,如果文件的真名(也就是长文件名)是
为了访问fat/vfat文件系统,我们需要用内核的msdos或vfat模块。它们有三个跟字符集有关的内核参数:codepage,iocharset,utf8。我们来确定它们对应着什么样的编码或解码函数。
-
codepage=enc:用来转换短文件名中字节码为128~255的代码页。这一选项指定的是短文件名的解码函数
φ−1enc (因为短文件名被codepage编码了),解码的结果是短文件名被用unicode表示; -
iocharset=enc:用来转换长文件名和解码后的短文件名。也就是说,这一选项指定编码函数
φenc ,使得内核最终的输出是经过φenc 编码的; - utf8:几乎等于iocharset=utf8
这样,参数有如下的具体选择:
-
codepage:这一选项只跟短文件名有关。短文件名可以通过mount -t msdos看到,也可以用windows命令行看到。不在乎短文件名的同学,这个选项完全无所谓;在乎短文件名并且使用简体中文的同学,请写
codepage=936
-
iocharset:分情况讨论。
- 若locale不是utf8,则选择相应的locale,比如GB系就应该选
iocharset=cp936
- 若locale是utf8的,可以选择
iocharset=utf8
但这样做的缺点是会导致vfat模块将允许短文件名使用小写字母,这与windows是不兼容的(使用iocharset=utf8时,内核会出一条警告信息的——这比较没道理,估计是个历史遗留问题);
- 若locale不是utf8,则选择相应的locale,比如GB系就应该选
- utf8:一个解决办法是使用utf8参数,只要iocharset!=utf8,vfat就会处理大小写的问题;而utf8参数则会最终处理字 符集的转换。utf8参数只能显式地通过mount调用(不能在编译内核时预先指定,当然也可以写在/etc/fstab里),我的意思是这个选项不能偷 懒不写。
最后是结论:
- codepage=936,这一选项可以在内核默认设置;
-
本地locale是gb系的,参数应为
mount -t vfat -o iocharset=cp936
-
本地locale是utf8的,如果能忍受内核每次都要警告,并且不跟windows交互,可以
mount -t vfat -o iocharset=utf8
-
要是不能忍3或者交互时出现奇怪的问题,那就
mount -t vfat -o iocharset=cp936,utf8
实际上只要内核默认的iocharset不是utf8,直接写
mount -o utf8
就可以,这里iocharset=xxx的作用仅仅是处理大小写,所以怎么填都没关系。
我们来看一下这些选项究竟做了些什么事情,假设一个FAT文件系统在简体中文的windows上用过,而Linux程序的locale设置为UTF-8,文件名为
-
在文件系统上(也就是在硬盘里),文件名被存为
s ,短文件名被存为φcp936(s′) ; -
内核通过codepage=936选项,将映射
φ−1cp936 作用于φcp936(s′) ,得到φ−1cp936∘φcp936(s′)=s′; -
内核通过utf8或iocharset=utf8选项,将映射
φutf8 作用于s,s′ ,最终向应用程序输出φutf8(s′),φutf8(s); -
应用程序(如ls、nautilus)通过locale的设置,将
φ−1utf8 作用于内核输出,得到我们所看见的字符串s,s′ .