如何在 Python 中检测文件是不是为二进制(非文本)文件?

Posted

技术标签:

【中文标题】如何在 Python 中检测文件是不是为二进制(非文本)文件?【英文标题】: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 文件在倾向于安装这些实用程序的系统上相对稀有。 【参考方案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

【讨论】:

有趣的是,file(1) 本身也将 0x7f 排除在外,因此从技术上讲,您应该改用 bytearray([7,8,9,10,12,13,27]) + bytearray(range(0x20, 0x7f)) + bytearray(range(0x80, 0x100))。见Python, file(1) - Why are the numbers [7,8,9,10,12,13,27] and range(0x20, 0x100) used for determining text vs binary file 和github.com/file/file/blob/… @MartijnPieters:谢谢。我已经更新了排除 0x7f (DEL) 的答案。 使用集合的好解决方案。 :-) @MarkRansom 要确保文件已关闭,请使用with-statement 或显式调用.close() 方法。 我之所以提出它,是因为您在此答案中没有做任何这些事情。【参考方案2】:

您也可以使用mimetypes 模块:

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

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

【讨论】:

有没有办法让mimetypes 使用文件的内容而不仅仅是文件名? @intuited 不,但 libmagic 会这样做。通过python-magic使用它。 这里有一个类似的问题,有一些很好的答案:***.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)。

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

您实际上可以用 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!

【讨论】:

以上是关于如何在 Python 中检测文件是不是为二进制(非文本)文件?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Python 中关联两个音频事件(检测它们是不是相似)

Python Opencv使用霍夫圆变换从二进制图像中检测圆

如何在编译时检测 XNU 内核二进制文件?

如何检测 MIME 文件中的纯文本?

如何强制使用 int $0x80 而不是 sysenter 进行系统调用检测

如何在 Python 中进行实时语音活动检测?