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

105

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

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

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

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

горевать
источник
ЕСЛИ "в прошлом я бы просто искал символы больше 0x7f" ТО вы использовали для работы с простым текстом ASCII, ТО по-прежнему нет проблем, поскольку текст ASCII, закодированный как UTF-8, остается ASCII (т.е. нет байтов> 127).
tzot
@ ΤΖΩΤΖΙΟΥ: Верно, но я знаю, что некоторые из файлов, с которыми я имею дело, - это utf8. Я имел в виду привыкание в общем смысле, а не в конкретном смысле этих файлов. :)
скорблю
1
Только с вероятностью. Вы можете проверить: 1) файл содержит \ n 2) количество байтов между \ n относительно мало (это НЕ надежно) l 3) файл не содержит байтов со значением меньше, чем значение символа "пробела" ASCCI ('' ) - КРОМЕ "\ n" "\ r" "\ t" и нулей.
SigTerm 09
3
Стратегия, которая grepсама использует для идентификации двоичных файлов, аналогична стратегии, опубликованной Хорхе Орпинелем ниже . Если вы не установите этот -zпараметр, он будет просто сканировать нулевой символ ( "\000") в файле. С -z, он сканирует файлы "\200". Заинтересованные и / или скептически настроенные могут проверить строку 1126 оф grep.c. К сожалению, мне не удалось найти веб-страницу с исходным кодом, но, конечно, вы можете получить ее с gnu.org или через дистрибутив .
интуитивно понятен
3
PS Как упоминалось в ветке комментариев к сообщению Хорхе, эта стратегия будет давать ложные срабатывания для файлов, содержащих, например, текст UTF-16. Тем не менее, git diffи GNU, и GNU diffтакже используют одну и ту же стратегию. Я не уверен, настолько ли он распространен, потому что он намного быстрее и проще, чем альтернатива, или это просто из-за относительной редкости файлов UTF-16 в системах, в которых, как правило, установлены эти утилиты.
интуитивно понятен

Ответы:

42

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

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

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

Гэвин М. Рой
источник
16
Есть ли способ mimetypesиспользовать содержимое файла, а не только его имя?
интуитивно понятен
4
@intuited Нет, но libmagic это делает. Используйте его через python-magic .
Bengt
Здесь есть аналогичный вопрос с некоторыми хорошими ответами: stackoverflow.com/questions/1446549/… Ответ, основанный на рецепте activestate, мне нравится, он допускает небольшую часть непечатаемых символов (но не \ 0 для некоторых причина).
Сэм Уоткинс
5
Это не лучший ответ только потому, что модуль mimetypes подходит не для всех файлов. Я сейчас смотрю на файл, который система fileсообщает как «текст Unicode UTF-8 с очень длинными строками», но mimetypes.gest_type () вернет (None, None). Кроме того, список типов mimetype Apache является белым списком / подмножеством. Это далеко не полный список миметипов. Его нельзя использовать для классификации всех файлов как текстовых или нетекстовых.
Purrell
1
guess_types основывается на расширении имени файла, а не на реальном содержании, как это сделала бы команда Unix "file".
Эрик Х.
61

Еще один метод, основанный на поведении файла (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
jfs
источник
Может быть как ложноположительным, так и ложноотрицательным, но все же это умный подход, который работает для подавляющего большинства файлов. +1.
спектры
2
Интересно, что сам файл (1) также исключает из рассмотрения 0x7f, так что с технической точки зрения вы должны использовать bytearray([7,8,9,10,12,13,27]) + bytearray(range(0x20, 0x7f)) + bytearray(range(0x80, 0x100))вместо него. См. Python, файл (1) - Почему числа [7,8,9,10,12,13,27] и диапазон (0x20, 0x100) используются для определения текста по сравнению с двоичным файлом и github.com/file/file/ blob /…
Мартейн Питерс
@MartijnPieters: спасибо. Я обновил ответ на exclude 0x7f( DEL).
jfs
1
Хорошее решение с использованием наборов. :-)
Мартейн Питерс
Почему вы исключаете 11или VT? В таблице 11 рассматривается обычный текст ASCII, а это формат vertical tab.
darksky
15

Если вы используете 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
Skyking
источник
почему бы не использовать with open(filename, 'r', encoding='utf-8') as fнапрямую?
Терри
8

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

Шейн С. Мейсон
источник
Для этого и существует libmagic. Доступ к нему можно получить в python через python-magic .
Bengt
2
К сожалению, «не начинается с известного магического числа» не эквивалентно «это текстовый файл».
Purrell
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 как «двоичные».
John Machin
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, но это не текстовый файл. Ответ на основе файла (1) лучше.
Сэм Уоткинс
6

Вот предложение, использующее команду файла Unix :

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'). читать ()
'\ 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 \ х80 \ х82 \ п '
>>> istext ('/ tmp / japanese') # работает с UTF-8
Правда

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

Джейкоб Габриэльсон
источник
Это сломало мой скрипт :( Расследуя, я обнаружил, что некоторые конфайлы описаны fileкак «Замороженная конфигурация Sendmail - версия m» - обратите внимание на отсутствие строки «текст». Возможно, использовать file -i?
melissa_boiko
1
TypeError: нельзя использовать строковый шаблон для
байтового
5

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

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

На самом деле вы можете записать это в 2 строки кода, однако этот пакет избавляет вас от необходимости писать и тщательно тестировать эти 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
to @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 '(видео).
Анмол Сингх Джагги
3

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

Камиль Кисиэль
источник
2

Вот функция, которая сначала проверяет, начинается ли файл со спецификации, и если нет, ищет нулевой байт в начальных 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.

роскакори
источник
2

Попробуйте использовать поддерживаемый в настоящее время python-magic, который не совпадает с модулем в ответе @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'
Ешьте в Джо
источник
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

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

Rsaw
источник
1

Думаю, лучшим решением будет использование функции 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

Он находится внутри класса, как вы можете видеть на основании структуры кода. Но вы можете в значительной степени изменить то, что хотите реализовать в своем приложении. Пользоваться им довольно просто. Метод getTextFiles возвращает объект списка со всеми текстовыми файлами, которые находятся в каталоге, который вы передаете в переменной пути.

Леонардо
источник
1

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

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()))
Роб Truxal
источник
1

Большинство программ считают файл двоичным (то есть любым файлом, не ориентированным на строки), если он содержит символ 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'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.

Kenorb
источник
0

ты в юниксе? если да, то попробуйте:

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

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

Фортран
источник
Для справки: команда file угадывает тип на основе содержимого файла. Я не уверен, обращает ли он внимание на расширение файла.
David Z
Я почти уверен, что это смотрится и в содержании, и в расширении.
Фортран
Это прерывается, если путь содержит «текст», хотя. Обязательно используйте rsplit на последнем ":" (при условии, что в описании типа файла нет двоеточия).
Алан Плам
3
Используйте fileс -bпереключателем; он напечатает только тип файла без пути.
dubek
2
немного более приятная версия:is_binary_file = lambda filename: "text" in subprocess.check_output(["file", "-b", filename])
jfs
0

Более простой способ - проверить, состоит ли файл из символа 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!
Kenorb
источник