弯引号(“ ” ‘ ’)与编程语言和数据格式要求的直引号(" ')是不同的 Unicode 字符。解析器按精确的码位匹配,所以弯引号对它们来说只是普通字符,字符串永远无法开始。把弯引号替换为直引号即可立即修复。
它们是真正不同的字符
一张表就能说明全部问题:
| 字符 | 名称 | 码位 |
|---|---|---|
" |
直双引号 | U+0022 |
| “ | 左双引号 | U+201C |
| ” | 右双引号 | U+201D |
' |
直撇号 | U+0027 |
| ‘ | 左单引号 | U+2018 |
| ’ | 右单引号 | U+2019 |
编程语言、JSON、CSV 和 shell 解释器只认其中两个作为字符串定界符:U+0022 和 U+0027。另外四个只是普通可打印字符,和字母或逗号属于同一类别。
所以当解析器遇到 “name” 时,它看到的不是引号稍微花哨了一点的字符串,而是一串以意外符号开头的无引号字符。报错信息会写 unexpected token,并指向一行看起来完全正确的代码——因为在正常字号下," 和 “ 之间的差别只有几个像素的弧度。
它们从哪里来
几乎总是来自软件的“好心帮忙”。
- 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、配置文件或其他将被解析而非阅读的内容时,才需要统一为直引号。