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



Как я могу сказать, является ли файл двоичным (нетекстовым) в python? Я просматриваю большой набор файлов в python и продолжаю получать совпадения в двоичных файлах. Это делает вывод выглядеть невероятно грязно.



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



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

932   18  

18 ответов:

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

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

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

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

попробуйте это:

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 '' 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

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

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

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

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

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

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

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

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

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

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

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

более короткое решение, с предупреждением 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'' in block:
                return True
    return False

Если вы используете 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

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

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

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

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

технически проверка спецификации UTF-8 не нужна, поскольку она не должна содержать нулевых байтов для всех практических целей. Но поскольку это очень распространенная кодировка, то быстрее проверить спецификацию в начале, а не сканировать все 8192 байта для 0.

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

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

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

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

более простой способ-проверить, состоит ли файл из нулевого символа (\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!

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

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

вот версия 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

Comments

    Ничего не найдено.