VS Code 中 Markdown 文件内容变成问号后如何恢复?
结论
如果原字符在保存时已经被替换成普通问号 ?,切换编码也无法恢复。文件里保存的就是问号,原字符的字节信息已经丢失。
恢复内容还有两种可能:
- 文件只是显示乱码,原始字节仍然存在。这种情况可以用正确的编码重新打开或转换。
- 从 VS Code 撤销记录、本地历史、Git 提交、GitHub 历史、系统备份或云盘版本中找回旧副本。
查明情况之前,不要再对原文件执行 Save with Encoding。
切换编码为什么会造成数据丢失
VS Code 中的两个编码命令用途不同:
Reopen with Encoding:使用指定编码重新解释现有字节,通常不会立即改写文件。Save with Encoding:把编辑器中的字符转换成指定编码,然后写回文件。
如果目标编码无法表示某些中文、阿拉伯文或其他字符,编码器可能会用 ? 代替它们。保存后,多个不同的字符都会变成同一个字节 0x3F,无法再判断每个问号原来是什么。
例如,原内容可能是:
中文 / العربية
错误保存后变成:
?? / ???????
这时再选择 UTF-8,只会把这些问号按 UTF-8 保存,原字符不会重新出现。
另一种常见结果是替换字符 �,也就是 Unicode 字符 U+FFFD。如果这个字符已经写入文件,通常说明原始字符信息在解码或保存过程中丢失了。
第一步:保留当前文件
先复制文件,后续恢复操作都在副本上进行:
cp "document.md" "document.md.damaged-backup"
Windows PowerShell 使用:
Copy-Item "document.md" "document.md.damaged-backup"
如果文件还在 VS Code 中打开,先尝试 Ctrl+Z,macOS 使用 Cmd+Z。即使文件已经保存,当前编辑器会话的撤销栈有时仍能回到保存前。暂时不要关闭窗口,关闭后可能无法继续使用撤销记录。
判断是显示乱码还是字符已被替换
先查看文件的原始字节:
xxd "document.md" | less
如果出问题的位置连续出现 3f,说明文件实际保存的是 ASCII 问号:
3f 3f 3f 3f
如果出现以下 UTF-8 字节序列,说明文件中保存的是 �:
ef bf bd
这两种情况都不能靠重新选择编码还原。
也可以用 Python 统计问号和替换字符的数量:
from pathlib import Path
data = Path("document.md").read_bytes()
print("0x3F question marks:", data.count(b"\x3f"))
print("UTF-8 replacement characters:", data.count(b"\xef\xbf\xbd"))
0x3F 也可能是原文正常使用的英文问号,需要结合它出现的位置和连续数量来判断。
如果十六进制数据中还有大量非 ASCII 字节,而 VS Code 只是显示乱码,原始内容可能仍在。此时应在副本上使用 Reopen with Encoding,逐一尝试文件可能采用的编码,如 UTF-8、GB18030、Big5 或 Windows-1256。尝试期间不要保存文件。
从 VS Code 本地历史恢复
在 VS Code 的资源管理器中选中文件,打开 Timeline(时间线)视图,查看是否有 Local History 条目。找到编码操作之前的版本后,先打开并核对内容,再恢复或另存为新文件。
能否找到历史版本,取决于当时的 VS Code 配置、文件类型,以及历史记录是否已被清理。
如果当前窗口还没有关闭,可以继续检查:
- 使用
Undo回退到编码转换之前。 - 查看当前文件所在目录中是否有未保存的副本、临时文件或名称相近的备份。
- 如果 VS Code 曾异常退出,重新启动后检查是否恢复了未保存的编辑器内容。
临时文件和崩溃恢复数据并不可靠。一旦找到可用内容,应立即复制到单独的位置。
从 Git 恢复
如果文件曾提交到本地 Git 仓库,可以查看它的完整历史:
git log --all -- "path/to/document.md"
查看某次提交中的文件内容:
git show <commit>:path/to/document.md
确认内容无误后,将其输出到新文件,以免覆盖当前版本:
git show <commit>:path/to/document.md > document.recovered.md
还可以检查所有分支和引用:
git branch --all
git reflog
如果提交已经删除或分支被重写,可以查找尚未被 Git 清理的对象:
git fsck --full --no-reflogs --unreachable
找到疑似 blob 后查看内容:
git show <object-id>
git fsck 只能找到曾进入 Git 对象数据库、目前无法通过正常引用访问的对象。如果文件从未执行过 git add,也没有提交,通常无法通过 Git 找回。
从 GitHub 查找旧副本
如果文件曾上传到 GitHub,可以检查以下位置:
- 仓库的提交历史和文件历史;
- 其他分支、标签和发布附件;
- 已删除分支对应的 Pull Request;
- 仓库的 Fork;
- 本机以前克隆过的仓库;
- 浏览器下载目录、最近访问记录和本地缓存;
- GitHub 账户中的其他仓库或 Gist。
如果仓库还在,但当前版本已经损坏,可以在文件页面打开 History,查找编码操作之前的提交。没有提交到 GitHub 的本地修改无法从 GitHub 恢复。
使用正确的编码转换
只有确认原始字节仍然存在,编码转换才有意义。例如,文件实际采用 GB18030,只是被当成其他编码显示,可以先生成一个 UTF-8 副本:
iconv -f GB18030 -t UTF-8 "document.md" > "document.utf8.md"
转换前必须保留原文件。如果 iconv 报告非法字节,不要使用忽略错误的参数直接覆盖源文件。跳过无法转换的字节可能造成更多数据丢失。
无法确定编码时,可以分别生成多个候选文件:
iconv -f GB18030 -t UTF-8 "document.md" > "candidate-gb18030.md"
iconv -f BIG5 -t UTF-8 "document.md" > "candidate-big5.md"
再人工检查哪个版本的内容正确。自动编码检测只能给出猜测,处理短文本或多语言文本时尤其容易出错,不能根据检测结果直接覆盖文件。
其他备份来源
如果 VS Code、Git 和 GitHub 中都找不到旧版本,还可以检查:
- Windows 文件历史记录或”以前的版本”;
- macOS Time Machine;
- OneDrive、Dropbox、iCloud Drive 等同步服务的版本历史;
- NAS 快照和公司终端备份;
- 邮件、聊天附件、压缩包或其他设备中的副本;
- 构建产物、已发布网页、搜索引擎缓存等只读副本。
有时可以从已发布的 HTML 或构建结果中复制回正文,但 Markdown 标记、注释和未发布内容可能已经缺失。
避免再次发生
多语言 Markdown 文件通常应统一保存为 UTF-8。可以在 VS Code 设置中指定默认编码:
{
"files.encoding": "utf8",
"files.autoGuessEncoding": false
}
如果经常处理来源不明的旧文件,可以启用自动猜测,但保存前仍要确认识别结果:
{
"files.encoding": "utf8",
"files.autoGuessEncoding": true
}
自动猜测并不总是准确。比较稳妥的操作顺序是先复制文件,再使用 Reopen with Encoding 检查显示。确认所有文字都正常后,另存为 UTF-8。重要文件还应及时提交到 Git,或使用带版本历史的备份服务。
备注:内容仅供参考。
