为什么弯引号会破坏你的代码、JSON 和 CSV

报错指向的那一行看起来毫无问题,因为差别在肉眼看来几乎不可见。

弯引号(“ ” ‘ ’)与编程语言和数据格式要求的直引号(" ')是不同的 Unicode 字符。解析器按精确的码位匹配,所以弯引号对它们来说只是普通字符,字符串永远无法开始。把弯引号替换为直引号即可立即修复。

它们是真正不同的字符

一张表就能说明全部问题:

字符 名称 码位
" 直双引号 U+0022
左双引号 U+201C
右双引号 U+201D
' 直撇号 U+0027
左单引号 U+2018
右单引号 U+2019

编程语言、JSON、CSV 和 shell 解释器只认其中两个作为字符串定界符:U+0022 和 U+0027。另外四个只是普通可打印字符,和字母或逗号属于同一类别。

所以当解析器遇到 “name” 时,它看到的不是引号稍微花哨了一点的字符串,而是一串以意外符号开头的无引号字符。报错信息会写 unexpected token,并指向一行看起来完全正确的代码——因为在正常字号下," 和 “ 之间的差别只有几个像素的弧度。

从对话粘贴{ name: Ada }SyntaxError: Unexpected token '“'规范化后{ "name": "Ada" }
弯引号与解析器期望的直引号是不同的字符。

它们从哪里来

几乎总是来自软件的“好心帮忙”。

  • Word 和 Google Docs 会在你输入时把直引号转换成弯引号。这项功能默认开启,大多数人从未注意到,因为在正文中这正是你想要的效果。
  • 聊天模型 会生成弯引号,因为它们从排版规范的出版文本中学习,而那些文本用的是标准引号。
  • macOS 在许多文本输入框中系统级地应用智能替换。
  • CMS 编辑器和评论框 经常对提交的文本执行排版过滤。
  • PDF 复制粘贴 会带上排版者使用的原始字符,而任何专业排版的文档用的都是弯引号。

共同点在于:文本在到达为解析而设计的工具之前,先经过了为阅读而设计的东西。正文需要排版引号,代码需要精确的字节,麻烦就发生在这个边界上。

还有几个同类字符值得注意:省略号字符(…)代替三个句点、连接号和破折号代替连字符、不间断空格代替普通空格。每一个都是与其相似的 ASCII 字符不同的码位,也都会以同样隐蔽的方式让解析器出错。

具体会破坏什么

JSON。 JSON 中的每个字符串都必须使用 U+0022。一个弯引号就会让整个文档无效,而且由于解析器只报告第一个错误,一个包含几十处弯引号的文件需要手动修好几轮。粘贴到 JSON 格式化与校验工具 可以显示第一个问题字符的准确位置。

CSV。 引号规则的存在是为了保护包含逗号的字段。弯引号不是引号字符,所以像 “Smith, John” 这样的字段会在逗号处被拆分,之后的每一列都会错位一位。这比语法错误更麻烦,因为它是静默失败的——你会得到一个导入时毫无报错但内容错误的文件。

源代码。 在大多数语言中这会导致编译或解析错误,至少错误很明显。在 shell 脚本中可能更糟:用连接号代替连字符的 rm –rf 并不是你想要的那个参数,而带弯引号的参数会被当作字面文本直接传递。

配置文件。 YAML、TOML 和 .env 文件会静默地把弯引号当作值的一部分接受。你的 API 密钥末尾多了一个装饰性字符,认证失败,而日志里没有任何线索告诉你原因。

正则表达式和搜索。 包含直引号的模式无法匹配包含弯引号的文本,所以查找替换会悄无声息地返回零个结果。

如何正确修复

统一文本格式。 把文本粘贴到弯引号转直引号,它会将四种弯引号全部替换为对应的 ASCII 字符,并报告替换数量。针对 AI 生成的文本,清理 ChatGPT 和 AI 文本工具可以一次性同时处理引号、破折号、不可见字符和表情符号。

关闭问题源头。 如果这种情况反复发生,请禁用替换功能:

  • Word:文件 → 选项 → 校对 → 自动更正选项 → 键入时自动套用格式 → 取消勾选“直引号替换为弯引号”。
  • Google Docs:工具 → 偏好设置 → 取消勾选“使用智能引号”。
  • macOS:系统设置 → 键盘 → 文本输入 → 编辑 → 关闭智能引号和破折号。

不要在文字处理软件中编写代码。 真正的修复是结构性的。任何最终要交给解析器的内容都应该在纯文本编辑器中编写,纯文本编辑器永远不会改写你的字符。

注意避免过度纠正。 不要在正文中无差别地替换弯引号。在文章或邮件中,排版引号才是正确的,直引号反而更难看。只在边界处统一格式——即文本从文档进入代码或数据的时候——而不是默认对所有内容都处理。

如果统一引号后文件仍然报错,下一个嫌疑对象是不可见字符,参见不可见 Unicode 字符参考

常见问题

为什么我的 JSON 明明看起来正确却报 unexpected token?

几乎总是本该用直引号的地方出现了弯引号。JSON 严格要求 U+0022,而弯引号 U+201C 和 U+201D 对解析器来说只是普通字符。两者在屏幕上只差几个像素,所以那一行看起来毫无问题。

弯引号和直引号有什么区别?

它们是不同的 Unicode 字符。直引号是 U+0022 和 U+0027,是所有解析器都期望的 ASCII 字符。弯引号是 U+2018、U+2019、U+201C 和 U+201D——朝向文本弯曲的排版符号,在正文中是正确的,但作为字符串定界符则无效。

如何把弯引号转换为直引号?

把文本粘贴到弯引号转直引号之类的规范化工具中,它会替换全部四种弯引号并报告数量。用查找替换来做需要分四步、每个字符一步,这正是它常常做不彻底的原因。

为什么我的 CSV 导入后列错位了?

包含逗号的字段只有用直引号包裹才会受到保护。弯引号不是引号字符,所以字段会在逗号处被拆分,之后的每一列都会错位。这种错误是静默失败的,会生成一个导入顺利但内容错误的文件。

我应该总是删除弯引号吗?

不应该。在正文中,弯引号是正确的排版,看起来比直引号更好。只有当文本进入代码、JSON、CSV、配置文件或其他将被解析而非阅读的内容时,才需要统一为直引号。

本指南提到的工具

相关指南