Як я можу виявити, чи файл є бінарним (нетекстовим) у python?


105

Як я можу визначити, чи файл є бінарним (нетекстовим) у python?

Я шукаю великий набір файлів у python і продовжую отримувати збіги у бінарних файлах. Це робить висновок неймовірно безладним.

Я знаю, що міг би скористатися grep -I, але я більше роблю дані, ніж те, що дозволяє grep.

У минулому я би просто шукав символів, більших за 0x7f, але utf8подібні, роблять це неможливим у сучасних системах. В ідеалі рішення було б швидким, але будь-яке рішення буде.


ЯКЩО "в минулому я б просто шукав символи, що перевищують 0x7f", ТІЛЬ, коли ви працювали з простим текстом ASCII, ТОХО все ще немає проблем, оскільки текст ASCII, кодований як UTF-8, залишається ASCII (тобто немає байтів> 127).
цот

@ ΤΖΩΤΖΙΟΥ: Це правда, але я знаю, що деякі файли, з якими я маю справу, є 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 Як зазначалося в темі коментарів до публікації Хорхе, ця стратегія надасть помилкові позитиви для файлів, що містять, наприклад, текст UTF-16. Тим не менше, git diffі GNU diffтакож використовують однакову стратегію. Я не впевнений, чи є вона настільки поширеною, тому що це набагато швидше і простіше, ніж альтернатива, або якщо це лише через відносну рідкість файлів UTF-16 в системах, на яких встановлені ці утиліти.
інтуїтивно

Відповіді:


42

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

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

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


16
Чи є спосіб mimetypesвикористовувати вміст файлу, а не лише його ім'я?
інтуїтивно

4
@intuited Ні, але libmagic це робить. Використовуйте його за допомогою python-magic .
Бенгт

Тут є подібне запитання з кількома хорошими відповідями: stackoverflow.com/questions/1446549/… Відповідь, заснована на рецепті Activestate, мені добре виглядає, вона дозволяє невелику частку символів, які не можна друкувати (але ні \ 0, для деяких причина).
Сем Уоткінс

5
Це не чудова відповідь лише тому, що модуль mimetypes не підходить для всіх файлів. Зараз я переглядаю файл, який у системі fileвідображається як "UTF-8 Unicode текст із дуже довгими рядками", але mimetypes.gest_type () повернеться (None, None). Також список міметиків Apache - це білий список / підмножина. Це аж ніяк не повний перелік міметипів. Його не можна використовувати для класифікації всіх файлів як текстових, так і нетекстових.
Перрелл

1
здогадки типу засновані на розширенні імені файлу, а не на реальному вмісті, як це робила команда "файл" Unix.
Ерік Х.

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

Може отримувати як хибний позитивний, так і хибний негатив, але все-таки розумний підхід працює для більшості файлів. +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 /…
Martijn Pieters

@MartijnPieters: дякую. Я оновив відповідь, щоб виключити 0x7f( DEL).
jfs

1
Гарне рішення з використанням наборів. :-)
Martijn Pieters

Чому ви виключаєте 11або VT? У таблиці 11 розглядається звичайний текст ASCII, і це vertical tab.
темний

15

Якщо ви використовуєте python3 з utf-8, це прямо вперед, просто відкрийте файл у текстовому режимі та припиніть обробку, якщо у вас з'явиться 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

Якщо це допомагає, багато багатьох двійкових типів починаються з магічних чисел. Ось список підписів файлів.


Саме для цього і належить libmagic. Доступ до нього можна отримати в python за допомогою python-magic .
Бенгт

2
На жаль, "не починається з відомого магічного числа" не є рівнозначним "є текстовим файлом".
Перрелл

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 - Я не думаю, що "містить нульовий байт" не є адекватним тестом для бінарного vs тексту, наприклад, я можу створити файл, що містить усі 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') 
помилковий
>>> відкрити ('/ tmp / японський'). 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" - помічайте відсутність рядка "текст". Можливо, використовуйте 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

до @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

Технічно перевірка BOM для UTF-8 є непотрібною, оскільки вона не повинна містити нульових байтів для всіх практичних цілей. Але оскільки це дуже поширене кодування, швидше перевірити BOM на початку, а не сканувати всі 8192 байти на 0.


2

Спробуйте використовувати поточно підтримуваний пітон-магію, який не є тим самим модулем у відповіді @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

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

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

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

Або ви також можете дотримуватися цього, щоб отримати висновок для всіх файлів у поточному режимі, використовуючи:

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 .

Ось версія 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


0

ти в unix? якщо так, то спробуйте:

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

Значення повернення оболонки інвертуються (0 - це нормально, тому якщо він знайде "текст", то він поверне 0, а в Python - це помилковий вираз).


Для довідки команда файлу відгадує тип на основі вмісту файлу. Я не впевнений, чи приділяє це розширення файлу якусь увагу.
David Z

Я майже впевнений, що це виглядає і в змісті, і в розширенні.
фортран

Це порушується, якщо шлях містить "текст", tho. Переконайтеся, що rsplit на останньому ":" (за умови, що в описі типу файлу немає двокрапки).
Алан Плюм

3
Використовуйте fileз -bперемикачем; він буде друкувати лише тип файлу без шляху.
дубек

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!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.