案例 BOM 的消费端:shell 报 Exec format error、Go 报 illegal byte order mark、JSON 解析停在第 0 位

2026-10-22 00:03:42

UTF-8 BOM 在消费端被读取时:shell、Go 源码、JSON 各自的报错与字节层确认

BOM 的问题分两侧。导出侧是 Excel 打开 UTF-8 CSV 乱码,需要主动加 BOM 才认(EF BB BF,即 U+FEFF)。消费侧相反:文件被脚本解释器、编译器、JSON 解析器读进去的那一刻,多出的三个字节让工具链直接拒绝。这篇只记录后者。

现象:三类报错

1. shell 脚本无法执行

$ bash myscript
myscript: line 1: #!/bin/sh: No such file or directory
$ dash myscript
myscript: 1: myscript: #!/bin/sh: not found

cat -v file 会显示 M-oM-;M-?#!/bin/bash。shell 不认 BOM,把它和 shebang 一起当成要执行的命令名。

和 CRLF 要分开看:#!/bin/sh\r 的报错是 exec failed: 2 No such file or directory,而 BOM 在前的报错是 exec failed: 8 Exec format error,errno 8 即 ENOEXEC。内核 execve 直接拒绝,shell 才回退到自己解释,于是 #!/bin/sh 被当作命令。

ShellCheck 对应规则 SC1082:

This file has a UTF-8 BOM. Remove it with: `LC_CTYPE=C sed '1s/^...//' 。

2. Go 源码

Go 规范允许编译器忽略作为文本第一个码点的 BOM;go/scanner 里 bom = 0xFEFF,注释是 "only permitted as very first character"。BOM 出现在 offset > 0 时报 illegal byte order mark;文件开头是 UTF-16 的 FF FE / FE FF 时报 illegal UTF-8 encoding (got UTF-16)(go.dev/issue/71950)。go/build 的 importReader 会 Peek(3) 后 Discard 开头 BOM。

历史 issue 里,文件中间出现 BOM 报 ./x.go:4: Unicode (UTF-8) BOM in middle of file;go test 生成的 _testmain.go 曾因导出数据里的 U+FEFF 报错(issue 5260)。

参考:。

3. JSON 与配置

RFC 8259 规定实现不得在网络传输的 JSON 文本前加 BOM,但解析实现可以忽略 BOM 而不是报错()。实际各家行为:

  • V8:SyntaxError: Unexpected token '', "{ ""a"": 1 }" is not valid JSON
  • Firefox:JSON.parse: unexpected character at line 1 column 1
  • Safari:JSON Parse error: Unrecognized token ''
  • Python:json.decoder.JSONDecodeError: Unexpected UTF-8 BOM (decode using utf-8-sig): line 1 column 1 (char 0),改用 open(path, encoding="utf-8-sig")
  • Jackson 读 byte[] 时自动跳过 BOM,readTree(byte[]) 正常;若 BOM 留在自己解码出的 String 里,报 Unexpected character ('' (code 65279 / 0xfeff)): expected a valid value。Spring 的 converter 同样读字节,正常。
  • .NET 的 Encoding.UTF8 默认写 BOM,StreamWriter 默认不写;NServiceBus + Json.NET 把 BOM 写进消息体后,Azure Functions 绑定报 Unexpected character encountered while parsing value: ?. Path '', line 0, position 0.,修复是 new StreamWriter(s, new UTF8Encoding(false))。

PHP 是另一条路径:被 include 的文件开头有 BOM,这三个字节先被输出,导致 headers already sent,页面顶部多一行空白或 。PHP 5.3.6 之后内置服务器等场景有修正,输出侧问题仍在。

字节层确认

先看前三字节:

$ head -c 3 file.json | xxd
00000000: efbb bf                                  ...
$ od -c file | head -1

Python 里用字节模式,不要走文本模式:

from pathlib import Path
Path(f).read_bytes().split(b"\n", 1)[0]
# repr → b'\xef\xbb\xbf#!/bin/sh'

read_text().splitlines() 会改动换行,判断 BOM 时不要用。

各工具链行为对照

读取方遇到 BOM 的行为结果
内核 execve不识别ENOEXEC(8),shell 回退后报 shebang 找不到
Go go/scanner首个码点忽略,中间拒绝illegal byte order mark / illegal UTF-8 encoding (got UTF-16)
Python 文本 openutf-8 报错用 utf-8-sig 才去 BOM
JS JSON.parse不忽略Unexpected token
Node fs.readFileSync(path,'utf8')保留 BOM(node issue #1918,按设计)后续解析失败
Node TextDecoder默认去 BOM{ignoreBOM: true} 才保留
Jackson byte[]自动跳过正常
Jackson String不跳过code 65279 / 0xfeff
PHP include原样输出headers already sent

Node 这一条最容易被误判:同一个 JSON 文件浏览器能解析,Node 脚本里却失败,差别就在 fs.readFileSync 和 TextDecoder 的默认值。

修复与预防

去掉脚本 BOM,按 SC1082 给出的写法:

$ LC_CTYPE=C sed '1s/^...//'  与 。
复制全文 生成海报 BOM UTF-8 字节序标记 Shell Go JSON 字符编码

推荐文章

程序员茄子在线接单