如何在 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使用霍夫圆变换从二进制图像中检测圆