Python fileinput 换行编码问题
昨晚抽了个时间想修一下 anvil 的这个 issue。
根据之前的代码实现,我有 99.99% 的把握换行符被替换成 CRLF
是使用了 fileinput
原地修改了文件导致的。
那段修改的代码的一个等价实现如下:
1 | import shutil |
orig.txt
是一个模板文件,它会被拷贝到指定目录变成一个新文件 new.txt
,并修改文件内容。
上述代码使用 fileinput
并且通过指定 inplace=True
来完成原地编辑;print()
会直接往 stdio 输出 fileinput
内部做了重定向将 stdout 的数据写到磁盘上。
end=''
是避免默认地往每行加一个换行。
一切看起来是那么的标准,因为实现基本是直接从网上抄的,比如 Python fileinput 使用总结。
但是实际中发现个问题:原文件的 EOL 是 LF
,但是新文件的却是 CRLF
。系统是 Windows,环境是 Python 3.6。
百思不得其解,拿 fileinput force line ending 作为关键字在网上搜了好久也没有解决问题;比如最基本的用 mode=rb
以二进制打开文件,最后问题依旧。
睡了一觉后早上起来一琢磨这问题是不是由 print()
导致的?
经过一番搜索发现问题果然是 print()
导致的….
SO 上有两个类似的问题:
- Prevent Python print()’s automatic newline conversion to CRLF on Windows
- Print LF with Python 3 to Windows stdout
原因
print()
内部使用的 Text I/O Wrapper 卡死了 newline 跟着系统默认,导致最后往 stdout 输出的时候自动把 LF
转为了 CRLF
解决方案
(1) 中的解决方案虽然多种多样并且允许你直接使用 print()
来输出 newline='\n'
但是大体上还是太不清真了。
一个看起来比较干净的方案是:
1 | with fileinput.FileInput(new_name, inplace=True, mode='rb') as f: |
绕开 print()
直接网 stdout.buffer
写。
因为这里已经是二进制数据了,所以需要用 rb
打开文件并且字符串操作都要转换为 bytes。
所以总结一下就是:Stop Using Print() for fileinput data modification!
Rant 1
实际上我解决这个 issue 的 PR 没有用这个方法,而是直接放弃了 fileinput 用最传统的文件 I/O 解决了。
谁知道 fileinput
还有哪些不知道的坑呢…
Rant 2
fileinput in-place rewrite 依赖 stdout 这个真是坑啊 🙄