问题:如何在python中检测文件是否为二进制(非文本)?
如何判断python中的文件是否为二进制(非文本)?
我正在通过python搜索大量文件,并始终在二进制文件中获取匹配项。这使输出看起来异常混乱。
我知道我可以用 grep -I
,但是我对数据所做的事情超出了grep所允许的范围。
过去,我只会搜索大于的字符0x7f
,但是utf8
类似的字符在现代系统上是不可能做到的。理想情况下,解决方案应该很快,但是任何解决方案都可以。
How can I tell if a file is binary (non-text) in Python?
I am searching through a large set of files in Python, and keep getting matches in binary files. This makes the output look incredibly messy.
I know I could use grep -I
, but I am doing more with the data than what grep allows for.
In the past, I would have just searched for characters greater than 0x7f
, but utf8
and the like, make that impossible on modern systems. Ideally, the solution would be fast.
回答 0
您还可以使用mimetypes模块:
import mimetypes
...
mime = mimetypes.guess_type(file)
编译二进制mime类型列表非常容易。例如,Apache分发了mime.types文件,您可以将其解析为一组列表(二进制和文本),然后检查该mime是否在文本列表或二进制列表中。
You can also use the mimetypes module:
import mimetypes
...
mime = mimetypes.guess_type(file)
It’s fairly easy to compile a list of binary mime types. For example Apache distributes with a mime.types file that you could parse into a set of lists, binary and text and then check to see if the mime is in your text or binary list.
回答 1
基于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
Yet another method 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))
Example:
>>> is_binary_string(open('/usr/bin/python', 'rb').read(1024))
True
>>> is_binary_string(open('/usr/bin/dh_python3', 'rb').read(1024))
False
回答 2
如果您将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
If you’re using python3 with utf-8 it is straight forward, just open the file in text mode and stop processing if you get an UnicodeDecodeError
. Python3 will use unicode when handling files in text mode (and bytearray in binary mode) – if your encoding can’t decode arbitrary files it’s quite likely that you will get UnicodeDecodeError
.
Example:
try:
with open(filename, "r") as f:
for l in f:
process_line(l)
except UnicodeDecodeError:
pass # Fond non-text data
回答 3
如果有帮助,许多二进制类型都以魔术数字开头。这是文件签名的列表。
If it helps, many many binary types begin with a magic numbers. Here is a list of file signatures.
回答 4
试试这个:
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
Try this:
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
回答 5
这是使用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
那里有类似命令的内容),并且必须为每个文件生成一个外部进程,这可能是不合时宜的。
Here’s a suggestion that uses the Unix file command:
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)
Example usage:
>>> istext('/etc/motd')
True
>>> istext('/vmlinuz')
False
>>> 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') # works on UTF-8
True
It has the downsides of not being portable to Windows (unless you have something like the file
command there), and having to spawn an external process for each file, which might not be palatable.
回答 6
使用binaryornot库(GitHub)。
它非常简单,并且基于此stackoverflow问题中的代码。
您实际上可以用2行代码编写此代码,但是此包使您不必编写和使用各种奇怪的文件类型(跨平台)彻底测试这两行代码。
Use binaryornot library (GitHub).
It is very simple and based on the code found in this stackoverflow question.
You can actually write this in 2 lines of code, however this package saves you from having to write and thoroughly test those 2 lines of code with all sorts of weird file types, cross-platform.
回答 7
通常你不得不猜测。
如果文件中包含扩展名,则可以将其视为一条线索。
您还可以识别已知的二进制格式,而忽略它们。
否则,请查看您拥有不可打印ASCII字节的比例,并从中进行猜测。
您也可以尝试从UTF-8解码,看看是否会产生合理的输出。
Usually you have to guess.
You can look at the extensions as one clue, if the files have them.
You can also recognise know binary formats, and ignore those.
Otherwise see what proportion of non-printable ASCII bytes you have and take a guess from that.
You can also try decoding from UTF-8 and see if that produces sensible output.
回答 8
简短的解决方案,带有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
A shorter solution, with a UTF-16 warning:
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
回答 9
我们可以使用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
We can use python itself to check if a file is binary, because it fails if we try to open binary file in text mode
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
回答 10
If you’re not on Windows, you can use Python Magic to determine the filetype. Then you can check if it is a text/ mime type.
回答 11
这是一个函数,该函数首先检查文件是否以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。
Here’s a function that first checks if the file starts with a BOM and if not looks for a zero byte within the initial 8192 bytes:
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
Technically the check for the UTF-8 BOM is unnecessary because it should not contain zero bytes for all practical purpose. But as it is a very common encoding it’s quicker to check for the BOM in the beginning instead of scanning all the 8192 bytes for 0.
回答 12
尝试使用当前维护的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'
Try using the currently maintained python-magic which is not the same module in @Kami Kisiel’s answer. This does support all platforms including Windows however you will need the libmagic
binary files. This is explained in the README.
Unlike the mimetypes module, it doesn’t use the file’s extension and instead inspects the contents of the file.
>>> 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'
回答 13
我来这里的目的是寻找完全相同的东西-标准库提供的一种全面的解决方案,用于检测二进制或文本。在回顾了人们建议的选项之后,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
不用说,但是调用此函数的代码应确保在测试文件之前可以读取文件,否则会错误地将文件检测为二进制文件。
I came here looking for exactly the same thing–a comprehensive solution provided by the standard library to detect binary or text. After reviewing the options people suggested, the nix file command looks to be the best choice (I’m only developing for linux boxen). Some others posted solutions using file but they are unnecessarily complicated in my opinion, so here’s what I came up with:
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
It should go without saying, but your code that calls this function should make sure you can read a file before testing it, otherwise this will be mistakenly detect the file as binary.
回答 14
我猜最好的解决方案是使用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变量中传递的目录中。
I guess that the best solution is to use the guess_type function. It holds a list with several mimetypes and you can also include your own types.
Here come the script that I did to solve my problem:
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
It is inside of a Class, as you can see based on the ustructure of the code. But you can pretty much change the things you want to implement it inside your application.
It`s quite simple to use.
The method getTextFiles returns a list object with all the text files that resides on the directory you pass in path variable.
回答 15
在* NIX上:
如果您有权使用file
shell命令,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()))
on *NIX:
If you have access to the file
shell-command, shlex can help make the subprocess module more usable:
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()))
Or, you could also stick that in a for-loop to get output for all files in the current dir using:
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()))
or for all subdirs:
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()))
回答 16
如果大多数程序都包含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实现
Most of the programs consider the file to be binary (which is any file that is not “line-oriented”) if it contains a NULL character.
Here is perl’s version of pp_fttext()
(pp_sys.c
) implemented in Python:
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
Note also that this code was written to run on both Python 2 and Python 3 without changes.
Source: Perl’s “guess if file is text or binary” implemented in Python
回答 17
您在Unix中吗?如果是这样,请尝试:
isBinary = os.system("file -b" + name + " | grep text > /dev/null")
外壳程序的返回值是反转的(0是可以的,因此,如果找到“文本”,则它将返回0,在Python中是False表达式)。
are you in unix? if so, then try:
isBinary = os.system("file -b" + name + " | grep text > /dev/null")
The shell return values are inverted (0 is ok, so if it finds “text” then it will return a 0, and in Python that is a False expression).
回答 18
比较简单的方法是\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!
Simpler way is to check if the file consist NULL character (\x00
) by using in
operator, for instance:
b'\x00' in open("foo.bar", 'rb').read()
See below the complete example:
#!/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!')
Sample usage:
$ ./is_binary.py /etc/hosts
The file is not binary!
$ ./is_binary.py `which which`
The file is binary!
回答 19
from binaryornot.check import is_binary
is_binary('filename')
文献资料
from binaryornot.check import is_binary
is_binary('filename')
Documentation