Skip to content

Vim 打开中文文件乱码:encoding、fileencodings 与 fileencoding ​

从 Windows 同事那里拿到一个配置文件,用 Vim 打开,中文全变成了 ÖÐÎIJâÊÔ。网上搜到一行配置加进 vimrc,这个文件好了,另一个 UTF-8 的文件却开始显示成 浣犲ソ。

Vim 处理文件编码时涉及三个选项,名字非常相似,作用完全不同:

  • encoding:Vim 内部使用的编码,也是界面显示用的编码。现代系统上应该是 utf-8。
  • fileencodings(复数):打开文件时依次尝试的编码列表,第一个能成功转换的就被采用。
  • fileencoding(单数):这个文件被识别成的编码,也是保存时使用的编码。

乱码几乎都出在第二个:探测列表的内容和顺序。本文在 Vim 9.1 上用 GBK、UTF-8、带 BOM 的 UTF-8 文件验证几种配置的结果。实验见文末。

打开 gbk.txt(d6 d0 ce c4 …),默认 fileencodingsucs-bom无 BOM,跳过utf-8不是合法 UTF-8default环境也是 utf-8latin1:接受任何字节fileencoding=latin1 → ÖÐÎIJâÊÔucs-bom,utf-8,gbk,latin1(严格的在前)gbk.txt → cp936:中文测试utf8.txt → utf-8:中文测试ucs-bom,gbk,utf-8,latin1(顺序反了)UTF-8 的「中文测试」→ 碰巧仍是 utf-8UTF-8 的「你好」→ cp936:浣犲ソ乱码时不要修改中文:按 latin1 打开再保存,原有字节不变;用 :e ++enc=gbk 重新打开转换:正确打开后 :set fileencoding=utf-8 再保存;去掉 BOM::set nobomb
图 1 · 实测(Vim 9.1):默认列表里没有 gbk,GBK 文件一路失败到最后的 latin1,显示成 ÖÐÎÄ。把 gbk 加在 utf-8 之后,两种文件都正确。顺序反过来时,UTF-8 的「你好」恰好也是合法的 GBK,被识别成 cp936,显示为 浣犲ソ;而「中文测试」碰巧没有被误认,是否乱码取决于内容。

一、默认配置把 GBK 识别成了 latin1 ​

text
utf8.txt 的字节:e4 b8 ad e6 96 87 e6 b5 8b e8 af 95 0a
gbk.txt  的字节:d6 d0 ce c4 b2 e2 ca d4 0a

默认 fileencodings = ucs-bom,utf-8,default,latin1
  打开 gbk.txt  → fileencoding=latin1,第一行:ÖÐÎIJâÊÔ
  打开 utf8.txt → fileencoding=utf-8,第一行:中文测试

Vim 按顺序尝试:没有 BOM,ucs-bom 不成立;GBK 的字节不是合法的 UTF-8,utf-8 失败;default 是当前语言环境的编码,在 UTF-8 环境下也失败;最后的 latin1 能接受任何字节序列,于是它总会「成功」。每个 GBK 字节被当成一个西欧字符显示出来,就是 ÖÐÎÄ。

latin1 放在最后是有意的:它保证任何文件都能打开,并且保存时不改变原有的字节。

text
按 latin1 打开 GBK 文件、加一个字符后保存:d6 d0 ce c4 b2 e2 ca d4 21 0a(原有字节未变)

所以看到乱码时不要急着保存修改,乱码只是显示的问题,原样保存不会损坏文件;但如果在乱码状态下修改了中文内容,写进去的就真的是错误的字节了。

二、把 gbk 加进列表:顺序决定一切 ​

text
fileencodings = ucs-bom,utf-8,gbk,latin1
  打开 gbk.txt  → fileencoding=cp936,第一行:中文测试
  打开 utf8.txt → fileencoding=utf-8,第一行:中文测试

在 utf-8 之后、latin1 之前加上 gbk,两种文件都能正确识别(Vim 内部把 gbk 记为 cp936)。网上常见的配置也是这个顺序。

顺序反过来就会出问题:

text
fileencodings = ucs-bom,gbk,utf-8,latin1
  打开 UTF-8 的「中文测试」 → fileencoding=utf-8,第一行:中文测试
  打开 UTF-8 的「你好」     → fileencoding=cp936,第一行:浣犲ソ

GBK 是双字节编码,很多 UTF-8 字节序列恰好也能被当成合法的 GBK 解读。「你好」的 UTF-8 字节被 GBK 成功「转换」了,于是 Vim 采用了 GBK,显示出 浣犲ソ;「中文测试」碰巧包含 GBK 里不存在的组合,转换失败,才轮到 UTF-8。同样的配置,是否乱码取决于文件内容,这正是开头那个「这个好了、那个坏了」的原因。

规则:严格的编码放在前面,宽松的放在后面。UTF-8 的结构有很强的校验性,随机字节几乎不可能是合法的 UTF-8,所以它应该排在 GBK 之前;latin1 什么都接受,永远放在最后。

三、手动指定与转换保存 ​

探测失败时,不必改配置,用 ++enc 重新打开当前文件:

text
:e ++enc=gbk gbk.txt → fileencoding=cp936,第一行:中文测试

要把文件转换成 UTF-8,以正确的编码打开后修改 fileencoding 再保存:

text
以 gbk 打开后 set fileencoding=utf-8 再保存:e4 b8 ad e6 96 87 e6 b5 8b e8 af 95 0a

带 BOM 的 UTF-8 文件由 ucs-bom 识别,保存时默认保留 BOM;set nobomb 后保存可以去掉它:

text
带 BOM 的文件 set nobomb 后保存:e4 b8 ad e6 96 87 0a

BOM 在 Windows 的一些工具里是识别 UTF-8 的依据,但在 Shell 脚本(第一行的 #! 前多了三个字节)、CSV 的第一个字段名、JSON 解析器里常常造成问题。

四、推荐配置 ​

vim
set encoding=utf-8
set fileencodings=ucs-bom,utf-8,gbk,latin1

新建的文件 fileencoding 为空,保存时使用 encoding 的值,也就是 UTF-8、不带 BOM,不需要再在 vimrc 里设置 fileencoding(它是缓冲区选项,在 vimrc 里设置只影响启动时的那个缓冲区)。

encoding 要在 vimrc 的开头设置,修改它会影响已经加载的内容。更根本的做法是让团队的文件统一使用 UTF-8,并在仓库的 .editorconfig 里写明 charset = utf-8。字节、码点和字形的区别,见一个表情是几个字符。

五、检查清单 ​

  • 先用 :set fileencoding? 看 Vim 把文件识别成了什么。
  • fileencodings 里 UTF-8 在 GBK 之前,latin1 在最后。
  • 识别错误时用 :e ++enc=编码 重新打开,不要在乱码状态下修改中文。
  • 转换编码:正确打开后 :set fileencoding=utf-8,再保存。
  • 不需要 BOM 的文件 :set nobomb。

配套实验

参考资料

  • Vim 帮助::help 'encoding'、:help 'fileencodings'、:help 'fileencoding'、:help ++enc、:help 'bomb'
  • EditorConfig 文档

文章以 CC BY-NC-SA 4.0 授权 · 代码片段以 MIT 授权