在 Python 3 中使用 sys.stdin 进行文本处理时,我应该如何处理 BOM?
Posted
技术标签:
【中文标题】在 Python 3 中使用 sys.stdin 进行文本处理时,我应该如何处理 BOM?【英文标题】:How am I suppposed to handle the BOM while text processing using sys.stdin in Python 3? 【发布时间】:2018-12-31 00:02:45 【问题描述】:注意:possible duplicate 涉及较旧版本的 Python,这个问题已经产生了独特的答案。
我一直在编写一个脚本,将Project Gutenberg Texts 文本处理为我正在开发的应用程序的内部文件格式。在脚本中,我使用re
模块处理章节标题。除了在一种情况下:第一行之外,这非常有效。如果我的正则表达式包含 ^ 插入符号以要求正则表达式匹配位于行首,我的正则表达式将始终在第一行的第一个章节标记上失败,因为 BOM 被用作第一个字符。 (示例正则表达式:^Chapter
)。
我发现,如果我不包含插入符号,它不会在第一行失败,然后在我处理之后,<feff>
会包含在标题中。一个例子:
<h1><feff>Chapter I</h1>
根据this SO question(我从中了解到 BOM)的建议是修复您的脚本以不消耗/损坏 BOM。 Other SO questions 讨论使用编解码器解码文件,但讨论我从未遇到过的错误,并且不讨论使用模板解码器打开文件的语法。
要明确:
我一般使用以下格式的管道:
cat -s <filename> | <other scripts> | python <scriptname> [options] > <outfile>
我正在使用以下语法打开文件:
import sys
fin = sys.stdin
if '-i' in sys.argv: # For command line option "-i <infile>"
fin = open(sys.argv[sys.argv.index('-i') + 1], 'rt')
for line in fin:
...Processing here...
我的问题是处理这个问题的正确方法是什么?在处理文本之前我是否删除了 BOM?如果是这样,怎么做?或者我是否在处理文件之前对文件使用解码器(我正在从标准输入读取,所以 我将如何完成此操作?)
文件以 UTF-8 编码存储,以 DOS 结尾 (\r\n
)。在使用 set ff=unix
处理之前,我将它们以 vim 转换为 UNIX 文件格式(在运行脚本之前我必须执行几个手动预处理任务)。
【问题讨论】:
嗯,fin = sys.argv[sys.argv.index('-i') + 1]
应该给你一个fin
中的文件名。然后应该使用您未显示的open
调用打开它,您可以在此处声明要过滤掉 BOM。能否请您出示您的open
指令?
@Serge 我很抱歉。我是从记忆中输入的,忘记包括打开的。但是,我主要使用sys.stdin
,因为我一直在管道中使用它。我特别想知道如何用sys.stdin
声明它。
Python 3 应该用文本文件透明地规范化行尾(Python 2 有 'Ur'
用于打开文件以使用行尾规范化读取)。建议副本的要点是在打开文件时使用utf-8-sig
编码以透明地忽略BOM。
如果您无论如何都要对文件进行预处理,那么在该过程中将其删除可能是最简单的方法。检查第一个字符,如果是“零宽度不间断空格”,则将其删除。
参见例如***.com/questions/45240387/…
【参考方案1】:
作为对现有答案的补充,可以使用编解码器模块从标准输入中过滤 UTF8 BOM。只需使用sys.stdin.buffer
访问底层字节流并使用StreamReader
对其进行解码
import sys
import codecs
# trick to process sys.stdin with a custom encoding
fin = codecs.getreader('utf_8_sig')(sys.stdin.buffer, errors='replace')
if '-i' in sys.argv: # For command line option "-i <infile>"
fin = open(sys.argv[sys.argv.index('-i') + 1], 'rt',
encoding='utf_8_sig', errors='replace')
for line in fin:
...Processing here...
【讨论】:
这实际上似乎是最优雅的解决方案,因为它(在我看来)比其他解决方案更便携。它将如何处理非 utf-8 编码的脚本?会窒息吗? 我的评论似乎是由于对 BOM 和字符编码的误解。在仔细阅读 this Unix.SE discussion 和 this quora question 之后,我得出的结论是,除了删除它之外,我可能永远不需要担心 BOM,因此将此答案作为最终、最优雅和便携的解决方案。【参考方案2】:在 Python 3 中,stdin
应该正确地自动解码,但如果它不适合您(以及 Python 2),您需要在调用脚本之前指定 PythonIOEncoding,例如
PYTHONIOENCODING="UTF-8-SIG" python <scriptname> [options] > <outfile>
请注意,此设置还会使 stdout
与 UTF-8-SIG
一起使用,因此您的 <outfile>
将保持原始编码。
对于您的-i
参数,只需执行open(path, 'rt', encoding="UTF-8-SIG")
【讨论】:
我可以只导出一个环境变量,还是您的解决方案需要在我运行脚本时声明PYTHONIOENCODING="UTF-8-SIG"
?
是的,将其声明为环境变量应该会影响所有 python 脚本
如果您在脚本的早期设置它,它将在脚本持续时间内保持设置(当然,除非您明确取消设置或更改它)。您需要 export
它以便它对 Python 等子进程可见。
@tripleee:当您说“在脚本中较早设置”时,您指的是我的管道命令的 python 脚本吗?我很好奇,因为如果我可以把它写到我的 python 脚本中,这可能是最简单的解决方案。
包含管道的 shell 脚本。如果 Python 脚本很简单,您可以将其嵌入到 shell 脚本中,但我可能也会考虑在 Python 中进行预处理。【参考方案3】:
你真的不需要导入codecs
或任何东西来处理这个问题。正如 lenz 在 cmets 中建议的那样,只需检查 BOM 并将其丢弃即可。
for line in input:
if line[0] == "\ufeff":
line = line[1:] # trim the BOM away
# the rest of your code goes here as usual
【讨论】:
【参考方案4】:在 Python 3.9 中,标准输入的默认编码似乎是 utf-8
,至少在 Linux 上是这样:
In [2]: import sys
In [3]: sys.stdin
Out[3]: <_io.TextIOWrapper name='<stdin>' mode='r' encoding='utf-8'>
sys.stdin
有方法reconfigure()
:
sys.stdin.reconfigure("utf-8-sig")
在尝试读取标准输入之前应该调用它。这将解码 BOM,读取 sys.stdin
时将不再出现。
【讨论】:
以上是关于在 Python 3 中使用 sys.stdin 进行文本处理时,我应该如何处理 BOM?的主要内容,如果未能解决你的问题,请参考以下文章
[Python]sys.stdin.readline(), sys.stdout.write(), sys.stdin.write()