内容和 08.01.03 完全一样,Linux 侧只多一个新问题:换行符。
Windows 存过的文件行尾是 \r\n,而 wsdl2h 会把那个 \r 当成「值的一部分」。
上一节编好了工具,
这一节又要动 typemap.dat——因为 Windows 那份和 Linux 这份本该一样,实际却常常不一样。
这一节讲的就是那个「为什么不一样」。
一、同一个文件,两个文件系统
typemap.dat 是纯文本,而纯文本的「行尾」在不同系统里定义不同:
| 系统 | 行尾字节 | 名字 |
|---|---|---|
| Windows | 0D 0A |
CRLF |
| Linux/macOS | 0A |
LF |
| 老 Mac OS | 0D |
CR(已淘汰) |
这两列本身没问题,问题是「跨系统传文件」时没有转换:在 Windows 上编辑过、扔到 Linux 上,
文件里每一行末尾都多一个 0D。
而 wsdl2h 读配置文件是「按行切、去掉行尾」——
它按 0A 切,切完之后每行末尾还留着一个 0D。
于是 tt = "http://…/schema" 这一行的值变成了 "http://…/schema"\r——
URI 末尾多一个不可见字符,全表失配。
二、为什么它特别难查
三个原因叠在一起,让这个错几乎无法用眼睛发现:
| 原因 | 表现 | 后果 |
|---|---|---|
\r 不可见 |
打印出来一模一样 | 肉眼对比不出来 |
| URI 很长 | 78 个字符里多一个 | 看不出哪里不一样 |
| 报错不指向它 | 只报 unbound 或 Fault | 怀疑方向全错 |
第三行是最要命的:
你会去怀疑「鉴权算错了」「时钟不对」「密码错了」,唯独不会怀疑「配置文件里多了个回车」。
而验证办法其实很简单:把那一行读出来,看末尾的字符码——repr() 一打就出来了,
但前提是你想到「要去看这个」。
第二行那个「看不出哪里不一样」有个例外:如果你用 cmp 比两个文件,
它会明确告诉你「第几个字节不同」——这比肉眼看快得多,也是跨平台传文件后的标准动作。
三、归一化怎么做
| 工具 | 命令 | 说明 |
|---|---|---|
dos2unix |
dos2unix typemap.dat |
最直接 |
sed |
sed -i 's/\r$//' typemap.dat |
没有 dos2unix 时用 |
| 编辑器 | 「以 LF 保存」 | VS Code/Notepad++ 都有 |
| 版本库 | .gitattributes 里 * text=auto |
长期方案 |
第四行才是根本办法:在版本库里配好 text=auto,
检出时按平台转换、提交时统一成 LF——这样「谁在哪个系统上编辑过」就不再影响结果。
前三行是「已经出问题了怎么救」,第四行是「不让它出问题」。
工程上两者都要有:先救,再防。
第二行那个 sed 要注意:\r 在有些 sed 版本里不认,
更保险的写法是 sed -i 's/\x0d$//'——用十六进制码,绕开转义差异。
四、反过来也有问题
Linux 生成的文件拿到 Windows 上,也有坑:
| 文件 | 表现 | 严重吗 |
|---|---|---|
onvif.h |
VS 能读 LF | 一般不严重 |
| 带宏续行的代码 | \ 后面多一个 \r,宏断了 |
真会编不过 |
.nsmap |
看读取方式 | 可能解析失败 |
第二行是真问题:C 的宏续行靠行尾的 \,如果 \ 后面还有一个 \r,
编译器会认为「这不是续行」——于是宏定义被截断,报的错离真正原因十万八千里。
不过 gSOAP 生成的文件里宏续行不多,
所以这个问题在 ONVIF 项目里不常见——但如果你自己写了带长宏的文件跨平台传,一定要想到它。
第三行那个 .nsmap 视实现而定:它既有「读文件」也有「编进程序」两种用法,
前一种会被 \r 影响,后一种不受影响。
五、动手 Demo:Windows 改过的 typemap.dat 到 Linux 会坏
Demo 用同一份内容做 CRLF 与 LF 两个版本,量字节差、再看末尾字符。
重点是最后那两行 repr()——它是唯一能让 \r 现形的办法。
src = open("typemap.dat", encoding="utf-8").read()
crlf = src.replace("\n", "\r\n") # Windows 存法
lf = src.replace("\r\n", "\n") # Linux 存法
print(len(crlf.encode()) - len(lf.encode())) # 16
line = [x for x in crlf.split("\n") if x.startswith("tds ")][0]
print(repr(line[-6:])) # 'wsdl"\r'
实跑输出:
--------------------------------------------------------------------
Windows 改过的 typemap.dat 到 Linux 会坏
--------------------------------------------------------------------
**这一节只有一个知识点,但它是真实事故:换行符。**
**Windows 记事本存出来的文件行尾是 `\r\n`(CRLF)**,
**Linux 下很多解析器只认 `\n`(LF)**。
**`wsdl2h` 读 typemap.dat 时,如果行尾多一个 `\r`**,
**它会把这个 `\r` 当成「值的一部分」**——
**于是命名空间 URI 末尾多一个不可见字符,全表失配。**
**用同一份内容做两种换行,量一遍**:
版本 行数 字节 每行多
------------------------------------------------------
**LF(Linux)** 16 724 —
**CRLF(Windows)** 16 740 **16 字节**
**第三列差的就是行数**:16 行,每行多 1 个字节,正好 16。
**这 16 个字节在编辑器里看不见,在字符串比较里却是致命的。**
**验证办法:把一行读出来,看末尾是什么。**
CRLF 版那一行的末尾 6 个字符:'wsdl"\r'
转成 LF 之后:'/wsdl"'
**差的就是那个 `\r`**:CRLF 版末尾多一个它,所以同样取 6 个字符,窗口整体右移了一位。
**比较两个 URI 时,`.../wsdl\r` 和 `.../wsdl` 不相等**,
**但打印出来一模一样**——这就是它难查的原因。
**所以跨平台传文本文件,一定要做一次换行归一**:
**`dos2unix typemap.dat`,或者编辑器里选「以 LF 保存」。**
**反过来也一样**:**Linux 上生成的 `.h`/`.cpp` 拿到 VS 里打开**,
**VS 能认 LF,但如果混着 CRLF,某些版本会在宏续行处报错。**
那 16 个字节就是「16 行 × 每行多一个 \r」——
这个数字能对上,说明差异完全来自换行符,没有别的东西。
第二段的 repr() 是关键:直接 print(line[-6:]) 只会看到 wsdl",
\r 会把光标移回行首,把前面的内容覆盖掉——这是终端行为,不是文件内容丢了。
所以「用 print 看不可见字符」这件事本身就不可靠,必须用 repr()、hex()、或者 cmp。
输出里那个「窗口整体右移一位」也要理解:
CRLF 版末尾多一个字符,所以取末尾 6 个字符时,看到的内容比 LF 版少一个——
这正好说明「多了一个字节」。
30 秒实验
- 用
cmp -l 文件A 文件B比较两个版本,看它报哪些字节不同。 - 把
repr(line[-6:])改成line[-6:].encode().hex(),看十六进制码。 - 查一下你项目里有没有
.gitattributes,看有没有配text=auto。
常见误区
- 用
print看文件内容判断有没有\r。\r会把光标移回行首覆盖显示, 看起来和没有一样。要用repr()或hex()。 - 跨系统传文件不做换行归一。 在 Windows 编辑过、扔到 Linux, 每个 URI 末尾都会多一个不可见字符。
- 只救不防。
dos2unix是救急,长期要在版本库配text=auto。
自测三问(点开对答案)
1. Windows 编辑过的 `typemap.dat` 到 Linux 上会怎样? —— **每行末尾多一个 `\r`**,`wsdl2h` 会把它当成「值的一部分」,**于是命名空间 URI 全对不上。** 2. 为什么这个错特别难查? —— **`\r` 不可见、URI 很长、报错不指向它**。你会去怀疑鉴权、时钟、密码,不会怀疑配置文件。 3. 根治办法是什么? —— **在版本库配 `.gitattributes` 的 `text=auto`**,提交时统一成 LF,检出时按平台转换。下一节生成 onvif.h:Linux 侧的命令只差三处,但每一处都会让命令跑不起来。
- ☐ (本节完,下一节 08.08.05 生成onvif.h头文件)