在 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>

请注意,此设置还会使 stdoutUTF-8-SIG 一起使用,因此您的 &lt;outfile&gt; 将保持原始编码。

对于您的-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.stdoutsys.stdin

python中sys.stdoutsys.stdin

如何中止在 python3 中等待 sys.stdin?

[Python]sys.stdin.readline(), sys.stdout.write(), sys.stdin.write()

sys.stdin.readlines() 挂起 Python 脚本

如何完成 sys.stdin.readlines() 输入?