Как определить, является ли файл двоичным (нетекстовым) в python?

Как я могу определить, является ли файл двоичным (нетекстовым) в Python?

Я ищу большой набор файлов в Python и продолжаю получать совпадения в двоичных файлах. Это делает вывод выглядеть невероятно грязным.

Я знаю, что могу использовать grep -I, но я делаю больше с данными, чем позволяет grep.

В прошлом я бы просто искал символы больше 0x7f, но utf8 и тому подобное сделали бы это невозможным в современных системах. В идеале решение будет быстрым, но подойдет любое решение.

Ответ 1

Вы также можете использовать модуль mimetypes:

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

Довольно просто составить список бинарных типов пантомимы. Например, Apache распространяет файл mime.types, который можно проанализировать в виде набора списков, двоичного и текстового, а затем проверить, есть ли mime в вашем текстовом или двоичном списке.

Ответ 2

Еще один метод на основе поведения файла (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

Ответ 3

Попробуйте следующее:

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 <[email protected]>
    @author: Jorge Orpinel <[email protected]>"""
    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

Ответ 4

Если вы используете python3 с utf-8, это просто, просто откройте файл в текстовом режиме и остановите обработку, если вы получите UnicodeDecodeError. Python3 будет использовать Unicode при обработке файлов в текстовом режиме (и bytearray в двоичном режиме) - если ваша кодировка не может декодировать произвольные файлы, вполне вероятно, что вы получите UnicodeDecodeError.

Пример:

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

Ответ 5

Если это помогает, многие многие двоичные типы начинаются с магических чисел. Вот список подписи к файлам.

Ответ 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') 
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

У него есть недостатки, которые не переносятся в Windows (если у вас нет что-то вроде команды file) и необходимости запускать внешний процесс для каждого файла, что может оказаться неприемлемым.

Ответ 7

Используйте binaryornot библиотека (GitHub).

Это очень просто и основано на коде, найденном в этом вопросе stackoverflow.

Фактически вы можете написать это в двух строках кода, однако этот пакет избавляет вас от необходимости писать и тщательно тестировать эти 2 строки кода со всеми видами странных типов файлов, кросс-платформенными.

Ответ 8

Обычно вы должны угадать.

Вы можете рассматривать расширения как один ключ, если файлы имеют их.

Вы также можете распознавать бинарные форматы и игнорировать их.

В противном случае посмотрите, какая доля непечатаемых байтов ASCII у вас есть, и предположите, что это возможно.

Вы также можете попробовать декодировать из UTF-8 и посмотреть, создает ли это разумный результат.

Ответ 9

Если вы не используете Windows, вы можете использовать Python Magic, чтобы определить тип файла. Затем вы можете проверить, является ли это типом text/mime.

Ответ 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

Ответ 11

Здесь функция, которая сначала проверяет, начинается ли файл с спецификации и если не ищет нулевой байт в начальных 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 не нужна, поскольку она не должна содержать нулевых байтов для всех практических целей. Но поскольку это очень распространенная кодировка, быстрее проверить спецификацию в начале, а не сканировать все 8192 байта за 0.

Ответ 12

Мы можем использовать сам 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

Ответ 13

Я предполагаю, что лучшим решением является использование функции guess_type. Он содержит список с несколькими типами mimetypes, и вы также можете включать свои собственные типы. Вот и script, который я сделал для решения моей проблемы:

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 возвращает объект списка со всеми текстовыми файлами, которые находятся в каталоге, который вы передаете в переменной пути.

Ответ 14

Я пришел сюда, чтобы найти то же самое - комплексное решение, предоставляемое стандартной библиотекой для обнаружения двоичного или текстового. После просмотра предложенных опций команда 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

Разумеется, но ваш код, который вызывает эту функцию, должен убедиться, что вы можете прочитать файл перед его тестированием, иначе это будет ошибочно определять файл как двоичный.

Ответ 15

Попробуйте использовать текущую Python-магию, которая не является тем же модулем в ответе @Kami Kisiel. Это поддерживает все платформы, включая Windows, однако вам понадобятся двоичные файлы libmagic. Это объясняется в README.

В отличие от модуля 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'

Ответ 16

ты в unix? если да, тогда попробуйте:

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

Возвращаемые значения оболочки инвертируются (0 - это нормально, поэтому, если он находит "текст", тогда он вернет 0, а в Python - это выражение False).

Ответ 17

Большинство программ считают, что файл является двоичным (то есть любой файл, который не является "ориентированным на строки"), если он содержит символ NULL.

Вот Perl-версия pp_fttext() (pp_sys.c), реализованная в 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 '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

Ответ 18

Более простой способ - проверить, содержит ли файл NULL-символ (\x00) с помощью оператора in, например:

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!

Ответ 19

on * NIX:

Если у вас есть доступ к командной оболочке file, 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-loop для получения вывода для всех файлов в текущем каталоге, используя:

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