【问题标题】:How can I detect if a file is binary (non-text) in Python?如何在 Python 中检测文件是否为二进制(非文本)文件?
【发布时间】:2010-10-28 06:33:40
【问题描述】:

如何在 Python 中判断文件是否为二进制(非文本)文件?

我在 Python 中搜索大量文件,并不断在二进制文件中找到匹配项。这使得输出看起来非常混乱。

我知道我可以使用 grep -I,但我对数据的处理超出了 grep 所允许的范围。

在过去,我只会搜索大于0x7f 的字符,但utf8 之类的字符在现代系统上是不可能的。理想情况下,解决方案会很快。

【问题讨论】:

  • IF “过去我只搜索大于 0x7f 的字符” THEN 您曾经使用纯 ASCII 文本 THEN 仍然没有问题,因为编码为 UTF-8 的 ASCII 文本仍然是 ASCII(即没有字节 > 127)。
  • @ΤZΩΤZΙΟΥ:是的,但我碰巧知道我正在处理的一些文件是 utf8。我的意思是一般意义上的习惯,而不是这些文件的特定意义。 :)
  • 只有概率。您可以检查:1)文件包含 \n 2)\n 之间的字节数相对较小(这不可靠)l 3)文件不包含值小于 ASCCI“空格”字符('' ) - 除了 "\n" "\r" "\t" 和零。
  • grep 本身用于识别二进制文件的策略类似于 Jorge Orpinel below 发布的策略。除非您设置 -z 选项,否则它只会扫描文件中的空字符 ("\000")。使用-z,它会扫描"\200"。有兴趣和/或怀疑的人可以查看grep.c 的第 1126 行。抱歉,我找不到包含源代码的网页,但您当然可以从gnu.org 或通过distro 获取。
  • P.S.正如 Jorge 帖子的 cmets 线程中所提到的,这种策略会对包含例如 UTF-16 文本的文件产生误报。尽管如此,git diff 和 GNU diff 也使用相同的策略。我不确定它是否如此流行是因为它比替代方案更快、更容易,还是仅仅是因为 UTF-16 文件在倾向于安装这些实用程序的系统上相对稀有。

标签: python file binary


【解决方案1】:

又一个方法based on file(1) behavior:

>>> textchars = bytearray({7,8,9,10,12,13,27} | set(range(0x20, 0x100)) - {0x7f})
>>> is_binary_string = lambda bytes: bool(bytes.translate(None, textchars))

例子:

>>> is_binary_string(open('/usr/bin/python', 'rb').read(1024))
True
>>> is_binary_string(open('/usr/bin/dh_python3', 'rb').read(1024))
False

【讨论】:

【解决方案2】:

您也可以使用mimetypes 模块:

import mimetypes
...
mime = mimetypes.guess_type(file)

编译二进制mime 类型列表相当容易。例如,Apache 分发了一个 mime.types 文件,您可以将其解析为一组列表、二进制和文本,然后检查 mime 是否在您的文本或二进制列表中。

【讨论】:

  • 有没有办法让mimetypes 使用文件的内容而不仅仅是文件名?
  • @intuited 不,但 libmagic 会这样做。通过python-magic使用它。
  • 这里有一个类似的问题,有一些很好的答案:stackoverflow.com/questions/1446549/… 基于 activestate 配方的答案对我来说看起来不错,它允许一小部分不可打印的字符(但没有 \0,出于某种原因)。
  • 这不是一个很好的答案,只是因为 mimetypes 模块不适用于所有文件。我现在正在查看一个文件,系统 file 报告为“UTF-8 Unicode 文本,行很长”,但 mimetypes.gest_type() 将返回(无,无)。此外,Apache 的 mimetype 列表是一个白名单/子集。它绝不是 mimetype 的完整列表。它不能用于将所有文件分类为文本或非文本。
  • guess_types 基于文件扩展名,而不是像 Unix 命令“file”那样的真实内容。
【解决方案3】:

如果您使用的是带有 utf-8 的 python3,这很简单,只需在文本模式下打开文件并在收到 UnicodeDecodeError 时停止处理。 Python3 将在文本模式下处理文件(以及二进制模式下的字节数组)时使用 unicode - 如果您的编码无法解码任意文件,您很可能会得到UnicodeDecodeError

例子:

try:
    with open(filename, "r") as f:
        for l in f:
             process_line(l)
except UnicodeDecodeError:
    pass # Fond non-text data

【讨论】:

  • 为什么不直接使用with open(filename, 'r', encoding='utf-8') as f
【解决方案4】:

如果有帮助,许多二进制类型都以幻数开头。 Here is a list 的文件签名。

【讨论】:

  • 这就是 libmagic 的用途。它可以通过python-magic在python中访问。
  • 很遗憾,“不以已知幻数开头”不等同于“是文本文件”。
【解决方案5】:

试试这个:

def is_binary(filename):
    """Return true if the given filename is binary.
    @raise EnvironmentError: if the file does not exist or cannot be accessed.
    @attention: found @ http://bytes.com/topic/python/answers/21222-determine-file-type-binary-text on 6/08/2010
    @author: Trent Mick <TrentM@ActiveState.com>
    @author: Jorge Orpinel <jorge@orpinel.com>"""
    fin = open(filename, 'rb')
    try:
        CHUNKSIZE = 1024
        while 1:
            chunk = fin.read(CHUNKSIZE)
            if '\0' in chunk: # found null byte
                return True
            if len(chunk) < CHUNKSIZE:
                break # done
    # A-wooo! Mira, python no necesita el "except:". Achis... Que listo es.
    finally:
        fin.close()

    return False

【讨论】:

  • -1 将“二进制”定义为包含零字节。将 UTF-16 编码的文本文件分类为“二进制”。
  • @John Machin:有趣的是,git diff 实际上是works this way,果然,它将 UTF-16 文件检测为二进制文件。
  • Hunh.. GNU diff 也以这种方式工作。 UTF-16 文件也有类似的问题。 file 确实正确检测到与 UTF-16 文本相同的文件。我还没有查看grep 的代码,但它也将 UTF-16 文件检测为二进制。
  • +1 @John Machin: utf-16 是根据file(1) 的字符数据,不转换就不能安全打印,所以这种方法适合这种情况。
  • -1 - 我不认为“包含零字节”是二进制与文本的充分测试,例如,我可以创建一个包含所有 0x01 字节或重复 0xDEADBEEF 的文件,但它不是一个文本文件。基于file(1)的答案更好。
【解决方案6】:

我们可以使用python本身来检查文件是否是二进制文件,因为如果我们尝试以文本模式打开二进制文件会失败

def is_binary(file_name):
    try:
        with open(file_name, 'tr') as check_file:  # try open file in text mode
            check_file.read()
            return False
    except:  # if fail then file is non-text (binary)
        return True

【讨论】:

  • 这对于很多“.avi”(视频)文件都失败了。
  • AVI 视频文件不是二进制的吗?或者你是说一些 AVI 文件从这个 is_binary() 中得到一个 False 的返回值?
【解决方案7】:

这是一个使用 Unix file 命令的建议:

import re
import subprocess

def istext(path):
    return (re.search(r':.* text',
                      subprocess.Popen(["file", '-L', path], 
                                       stdout=subprocess.PIPE).stdout.read())
            is not None)

示例用法:

>>> istext('/etc/motd') 真的 >>> istext('/vmlinuz') 错误的 >>> open('/tmp/japanese').read() '\xe3\x81\x93\xe3\x82\x8c\xe3\x81\xaf\xe3\x80\x81\xe3\x81\xbf\xe3\x81\x9a\xe3\x81\x8c\xe3\x82\x81\ xe5\xba\xa7\xe3\x81\xae\xe6\x99\x82\xe4\xbb\xa3\xe3\x81\xae\xe5\xb9\x95\xe9\x96\x8b\xe3\x81\x91\xe3\ x80\x82\n' >>> istext('/tmp/japanese') # 适用于 UTF-8 真的

它的缺点是不能移植到 Windows(除非你有类似 file 这样的命令),并且必须为每个文件生成一个外部进程,这可能不合适。

【讨论】:

  • 这破坏了我的脚本 :( 调查,我发现file 将一些 conffile 描述为“Sendmail 冻结配置 - 版本 m”—注意缺少字符串“text”。也许使用file -i?
  • TypeError: cannot use a string pattern on a bytes-like object
【解决方案8】:

使用 binaryornot 库 (GitHub)。

它非常简单,并且基于这个 stackoverflow 问题中的代码。

您实际上可以用 2 行代码编写此代码,但是此软件包使您不必编写和彻底测试具有各种奇怪文件类型的那 2 行代码,跨平台。

【讨论】:

    【解决方案9】:

    通常你必须猜测。

    如果文件有扩展名,您可以将扩展名视为一条线索。

    您还可以识别已知的二进制格式,并忽略它们。

    否则,请查看您拥有的不可打印 ASCII 字节的比例并从中猜测。

    您也可以尝试从 UTF-8 解码,看看是否会产生合理的输出。

    【讨论】:

      【解决方案10】:

      更短的解决方案,带有 UTF-16 警告:

      def is_binary(filename):
          """ 
          Return true if the given filename appears to be binary.
          File is considered to be binary if it contains a NULL byte.
          FIXME: This approach incorrectly reports UTF-16 as binary.
          """
          with open(filename, 'rb') as f:
              for block in f:
                  if b'\0' in block:
                      return True
          return False
      

      【讨论】:

      • 注意:for line in file 可能会消耗无限量的内存,直到找到b'\n'
      • to @Community: ".read()" 在这里返回一个可迭代的字节串(它产生单个字节)。
      【解决方案11】:

      尝试使用当前维护的python-magic,这与@Kami Kisiel 的答案中的模块不同。这确实支持包括 Windows 在内的所有平台,但是您将需要 libmagic 二进制文件。这在自述文件中进行了解释。

      mimetypes 模块不同,它不使用文件的扩展名,而是检查文件的内容。

      >>> import magic
      >>> magic.from_file("testdata/test.pdf", mime=True)
      'application/pdf'
      >>> magic.from_file("testdata/test.pdf")
      'PDF document, version 1.2'
      >>> magic.from_buffer(open("testdata/test.pdf").read(1024))
      'PDF document, version 1.2'
      

      【讨论】:

        【解决方案12】:

        如果您不在 Windows 上,您可以使用Python Magic 来确定文件类型。然后你可以检查它是否是文本/mime 类型。

        【讨论】:

          【解决方案13】:

          这是一个函数,它首先检查文件是否以 BOM 开头,如果不是,则在最初的 8192 个字节中查找零字节:

          import codecs
          
          
          #: BOMs to indicate that a file is a text file even if it contains zero bytes.
          _TEXT_BOMS = (
              codecs.BOM_UTF16_BE,
              codecs.BOM_UTF16_LE,
              codecs.BOM_UTF32_BE,
              codecs.BOM_UTF32_LE,
              codecs.BOM_UTF8,
          )
          
          
          def is_binary_file(source_path):
              with open(source_path, 'rb') as source_file:
                  initial_bytes = source_file.read(8192)
              return not any(initial_bytes.startswith(bom) for bom in _TEXT_BOMS) \
                     and b'\0' in initial_bytes
          

          从技术上讲,UTF-8 BOM 的检查是不必要的,因为出于所有实际目的,它不应包含零字节。但由于它是一种非常常见的编码,因此在开始时检查 BOM 比扫描所有 8192 字节的 0 更快。

          【讨论】:

            【解决方案14】:

            如果文件包含NULL character,则大多数程序将文件视为二进制文件(即任何非“面向行”的文件)。

            这是用 Python 实现的 perl 版本的 pp_fttext() (pp_sys.c):

            import sys
            PY3 = sys.version_info[0] == 3
            
            # A function that takes an integer in the 8-bit range and returns
            # a single-character byte object in py3 / a single-character string
            # in py2.
            #
            int2byte = (lambda x: bytes((x,))) if PY3 else chr
            
            _text_characters = (
                    b''.join(int2byte(i) for i in range(32, 127)) +
                    b'\n\r\t\f\b')
            
            def istextfile(fileobj, blocksize=512):
                """ Uses heuristics to guess whether the given file is text or binary,
                    by reading a single block of bytes from the file.
                    If more than 30% of the chars in the block are non-text, or there
                    are NUL ('\x00') bytes in the block, assume this is a binary file.
                """
                block = fileobj.read(blocksize)
                if b'\x00' in block:
                    # Files with null bytes are binary
                    return False
                elif not block:
                    # An empty file is considered a valid text file
                    return True
            
                # Use translate's 'deletechars' argument to efficiently remove all
                # occurrences of _text_characters from the block
                nontext = block.translate(None, _text_characters)
                return float(len(nontext)) / len(block) <= 0.30
            

            另请注意,此代码无需更改即可在 Python 2 和 Python 3 上运行。

            来源:Perl's "guess if file is text or binary" implemented in Python

            【讨论】:

              【解决方案15】:
              from binaryornot.check import is_binary
              is_binary('filename')
              

              Documentation

              【讨论】:

                【解决方案16】:

                我来这里是为了寻找完全相同的东西——标准库提供的用于检测二进制或文本的综合解决方案。在查看了人们建议的选项后,nix file 命令看起来是最好的选择(我只为 linux boxen 开发)。其他一些人使用 file 发布了解决方案,但我认为它们不必要地复杂,所以这是我想出的:

                def test_file_isbinary(filename):
                    cmd = shlex.split("file -b -e soft '{}'".format(filename))
                    if subprocess.check_output(cmd)[:4] in {'ASCI', 'UTF-'}:
                        return False
                    return True
                

                不用说,但是你调用这个函数的代码应该确保你可以在测试之前读取文件,否则会被错误地检测为二进制文件。

                【讨论】:

                  【解决方案17】:

                  我猜最好的解决方案是使用guess_type 函数。它包含一个包含多个 mimetype 的列表,您还可以包含自己的类型。 这是我为解决我的问题所做的脚本:

                  from mimetypes import guess_type
                  from mimetypes import add_type
                  
                  def __init__(self):
                          self.__addMimeTypes()
                  
                  def __addMimeTypes(self):
                          add_type("text/plain",".properties")
                  
                  def __listDir(self,path):
                          try:
                              return listdir(path)
                          except IOError:
                              print ("The directory {0} could not be accessed".format(path))
                  
                  def getTextFiles(self, path):
                          asciiFiles = []
                          for files in self.__listDir(path):
                              if guess_type(files)[0].split("/")[0] == "text":
                                  asciiFiles.append(files)
                          try:
                              return asciiFiles
                          except NameError:
                              print ("No text files in directory: {0}".format(path))
                          finally:
                              del asciiFiles
                  

                  它在一个类的内部,你可以根据代码的ustructure看到。但是你几乎可以改变你想要在你的应用程序中实现它的东西。 使用起来非常简单。 getTextFiles 方法返回一个列表对象,其中包含您在路径变量中传递的目录中的所有文本文件。

                  【讨论】:

                    【解决方案18】:

                    在 *NIX 上:

                    如果您可以访问file shell-command,shlex 可以帮助提高子进程模块的可用性:

                    from os.path import realpath
                    from subprocess import check_output
                    from shlex import split
                    
                    filepath = realpath('rel/or/abs/path/to/file')
                    assert 'ascii' in check_output(split('file {}'.format(filepth).lower()))
                    

                    或者,您也可以将其粘贴在 for 循环中,以获取当前目录中所有文件的输出:

                    import os
                    for afile in [x for x in os.listdir('.') if os.path.isfile(x)]:
                        assert 'ascii' in check_output(split('file {}'.format(afile).lower()))
                    

                    或所有子目录:

                    for curdir, filelist in zip(os.walk('.')[0], os.walk('.')[2]):
                         for afile in filelist:
                             assert 'ascii' in check_output(split('file {}'.format(afile).lower()))
                    

                    【讨论】:

                      【解决方案19】:

                      你在unix吗?如果是,请尝试:

                      isBinary = os.system("file -b" + name + " | grep text > /dev/null")
                      

                      shell 返回值是反转的(0 是可以的,所以如果它找到“文本”那么它将返回一个 0,而在 Python 中这是一个 False 表达式)。

                      【讨论】:

                      • 作为参考,file 命令根据文件的内容猜测类型。我不确定它是否注意文件扩展名。
                      • 我几乎可以肯定它在内容和扩展名中都有。
                      • 如果路径包含“文本”,则会中断。确保在最后一个 ':' 处拆分(前提是文件类型描述中没有冒号)。
                      • 使用file-b 开关;它只会打印没有路径的文件类型。
                      • 稍微好一点的版本:is_binary_file = lambda filename: "text" in subprocess.check_output(["file", "-b", filename])
                      【解决方案20】:

                      更简单的方法是使用in运算符检查文件是否包含NULL字符(\x00),例如:

                      b'\x00' in open("foo.bar", 'rb').read()
                      

                      请看下面的完整示例:

                      #!/usr/bin/env python3
                      import argparse
                      if __name__ == '__main__':
                          parser = argparse.ArgumentParser()
                          parser.add_argument('file', nargs=1)
                          args = parser.parse_args()
                          with open(args.file[0], 'rb') as f:
                              if b'\x00' in f.read():
                                  print('The file is binary!')
                              else:
                                  print('The file is not binary!')
                      

                      示例用法:

                      $ ./is_binary.py /etc/hosts
                      The file is not binary!
                      $ ./is_binary.py `which which`
                      The file is binary!
                      

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 2011-03-06
                        • 1970-01-01
                        • 2013-01-15
                        • 1970-01-01
                        • 1970-01-01
                        • 2016-05-21
                        • 2013-05-21
                        • 2011-02-11
                        相关资源
                        最近更新 更多