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


105

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

我正在通过python搜索大量文件,并始终在二进制文件中获取匹配项。这使输出看起来异常混乱。

我知道我可以用 grep -I,但是我对数据所做的事情超出了grep所允许的范围。

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


如果“过去我只会搜索大于0x7f的字符”,那么您以前使用纯ASCII文本的情况就没有问题了,因为编码为UTF-8的ASCII文本仍然是ASCII(即,没有字节> 127)。
tzot

@ΤζΩΤζΙΟΥ:是的,但是我碰巧知道我要处理的某些文件是utf8。我的意思是习惯于一般意义上的,而不是这些文件的特定意义上的。:)
悲伤

1
只有可能性。您可以检查以下情况:1)文件包含\ n 2)\ n之间的字节数相对较小(这不可靠)l 3)文件的字节数不小于ASCCI“空格”字符的值('' )-除“ \ n”,“ \ r”,“ \ t”和零外。
SigTerm

3
grep本身用于识别二进制文件的策略与下面的Jorge Orpinel发布的策略相似。除非您设置此-z选项,否则它将只扫描文件中的空字符("\000")。使用-z,它将扫描"\200"。那些有兴趣和/或持怀疑态度的人可以查看的行1126 grep.c。抱歉,我找不到包含源代码的网页,但是您当然可以从gnu.org或通过发行版获得它。
直觉

3
PS如Jorge的帖子的评论主题中所述,此策略将为包含例如UTF-16文本的文件提供误报。尽管如此,两者git diff和GNU都diff使用相同的策略。我不确定它是否如此流行,是因为它比替代方法快得多,容易得多,还是因为UTF-16文件在安装了这些实用程序的系统上相对稀少而已。
直觉

Answers:


42

您还可以使用mimetypes模块:

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

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


16
有没有一种方法可以mimetypes使用文件的内容而不只是文件名?
直觉

4
@intuited不,但是libmagic做到了。通过python-magic使用它。
Bengt 2012年

这里有一个类似的问题,给出了一些很好的答案: stackoverflow.com/questions/1446549/… 基于活动状态配方的答案对我来说看起来不错,它允许一小部分不可打印的字符(但对于某些字符则不能为\ 0,原因)。
Sam Watkins

5
这不是一个好答案,仅因为mimetypes模块不适用于所有文件。我现在正在查看一个文件,系统file报告为“ UTF-8 Unicode文本,带有很长的行”,但是mimetypes.gest_type()将返回(无,无)。同样,Apache的mimetype列表是白名单/子集。这绝不是模仿类型的完整列表。它不能用于将所有文件分类为文本或非文本。
Purrell

1
guess_types基于文件扩展名,而不是Unix命令“ file”会执行的实际内容。
Eric H.

61

基于file(1)行为的另一种方法:

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

可以同时得到误报和误报,但仍然是一种适用于大多数文件的聪明方法。+1。
频谱

2
有趣的是,file(1)本身也将0x7f排除在考虑范围之外,因此从技术上来讲,您应该使用它bytearray([7,8,9,10,12,13,27]) + bytearray(range(0x20, 0x7f)) + bytearray(range(0x80, 0x100))。参见Python,file(1)-为什么数字[7,8,9,10,12,13,27]和range(0x20,0x100
Martijn Pieters

@MartijnPieters:谢谢。我已将答案更新为排除0x7fDEL)。
jfs 2015年

1
使用集的好解决方案。:-)
马丁·皮特斯

为什么要排除11VT?在表11中,该文本被视为纯ASCII文本,即vertical tab
darksky '16

15

如果您将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直接使用?
特里


8

试试这个:

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

9
-1将“二进制”定义为包含零字节。将UTF-16编码的文本文件分类为“二进制”。
约翰·马钦

5
@John Machin:有趣的是,git diff实际上是以这种方式工作的,并且确实可以将UTF-16文件检测为二进制文件。
直觉

恩.. GNU diff也这样工作。UTF-16文件也有类似的问题。 file可以正确检测与UTF-16文本相同的文件。我尚未签出grep的代码,但它也将UTF-16文件检测为二进制文件。
直觉

1
+1 @John Machin:utf-16是字符数据file(1),因此不进行转换就无法安全打印,因此在这种情况下此方法是合适的。
jfs

2
-1-我不认为“包含零字节”对于二进制和文本测试是足够的,例如,我可以创建一个包含所有0x01字节的文件或重复0xDEADBEEF,但它不是文本文件。基于file(1)的答案更好。
山姆·沃特金斯

6

这是使用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为“ Sendmail冻结配置-版本m”,注意缺少字符串“ text”。也许使用file -i
melissa_boiko

1
TypeError:无法在类似字节的对象上使用字符串模式
-Abg

5

使用binaryornot库(GitHub)。

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

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


4

通常你不得不猜测。

如果文件中包含扩展名,则可以将其视为一条线索。

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

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

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


4

简短的解决方案,带有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'找到为止
jfs 2014年

到@Community: ".read()"返回这里字节字符串即可迭代(它产生单独的字节)。
jfs

4

我们可以使用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”(视频)文件,此操作均失败。
Anmol Singh Jaggi,


2

这是一个函数,该函数首先检查文件是否以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。


2

尝试使用当前维护的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'

1

我来这里的目的是寻找完全相同的东西-标准库提供的一种全面的解决方案,用于检测二进制或文本。在回顾了人们建议的选项之后,nix file命令似乎是最佳选择(我仅针对Linux boxen开发)。其他一些人使用文件发布了解决方案,但我认为它们不必要地复杂,所以我想出了以下内容:

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

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


1

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

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

您可以在代码的结构中看到它在类的内部。但是您几乎可以更改要在应用程序中实现的东西。使用起来非常简单。方法getTextFiles返回带有所有文本文件的列表对象,这些文本文件位于您在path变量中传递的目录中。


1

在* NIX上:

如果您有权使用fileshell命令,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()))

1

如果大多数程序都包含NULL字符,则认为该文件是二进制文件(该文件不是“面向行的”文件)。

这是用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的“猜测文件是文本还是二进制”,用Python实现


0

您在Unix中吗?如果是这样,请尝试:

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

外壳程序的返回值是反转的(0是可以的,因此,如果找到“文本”,则它将返回0,在Python中是False表达式)。


作为参考,file命令根据文件的内容猜测类型。我不确定它是否对文件扩展名有所注意。
David Z

我几乎可以肯定它在内容和扩展名中都可以看到。
fortran

如果路径包含“文本”,则会中断。确保在最后一个':'处分割(前提是文件类型说明中没有冒号)。
艾伦·梅

3
file-b开关一起使用;它只会打印没有路径的文件类型。
dubek

2
一个更好的版本:is_binary_file = lambda filename: "text" in subprocess.check_output(["file", "-b", filename])
jfs

0

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

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!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.