Jak mogę wykryć, czy plik jest binarny (nietekstowy) w Pythonie?

Jak mogę sprawdzić, czy plik jest binarny (nietekstowy) w Pythonie? Przeszukuję duży zestaw plików w Pythonie i ciągle dostaję dopasowania w plikach binarnych. To sprawia, że wyjście wygląda niesamowicie niechlujnie.

Wiem, że mógłbym użyć grep-I, ale robię więcej z danymi, niż pozwala na to grep.

W przeszłości szukałbym znaków większych niż 0x7F, ale utf8 i tym podobne uniemożliwiają to na nowoczesnych systemach. Idealnie rozwiązanie byłoby szybkie, ale każdy rozwiązanie wystarczy.

Author: grieve, 2009-05-22

18 answers

Możesz również użyć modułu mimetypes :

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

Dość łatwo jest skompilować listę binarnych typów mime. Na przykład Apache dystrybuuje za pomocą mime.types file, który możesz przetworzyć na Zestaw List, binarnych i tekstowych, a następnie sprawdzić, czy mime znajduje się na liście tekstowej lub binarnej.

 36
Author: Gavin M. Roy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-04 14:18:35

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

Przykład:

>>> is_binary_string(open('/usr/bin/python', 'rb').read(1024))
True
>>> is_binary_string(open('/usr/bin/dh_python3', 'rb').read(1024))
False
 49
Author: jfs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-24 16:16:18

Spróbuj tego:

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
 11
Author: Jorge Orpinel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-09 01:14:54

Jeśli to pomoże, wiele wielu typów binarnych zaczyna się od liczb magicznych. Oto lista sygnatur plików.

 8
Author: Shane C. Mason,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-22 16:23:17

Oto sugestia, która używa polecenia 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)

Przykładowe użycie:

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

Ma wady tego, że nie jest przenośny do Windows (chyba, że masz tam coś takiego jak Komenda file), i konieczności wywołania zewnętrznego procesu dla każdego pliku, który może nie być smaczny.

 5
Author: Jacob Gabrielson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-22 16:54:48

Użyj binaryornot library (GitHub ).

Jest to bardzo proste i oparte na kodzie znalezionym w tym pytaniu stackoverflow.

Możesz napisać to w 2 linijkach kodu, jednak ten pakiet oszczędza Ci konieczności pisania i dokładnego testowania tych 2 linijek kodu z różnego rodzaju dziwnymi typami plików, międzyplatformowymi.

 5
Author: guettli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-01 11:31:43

Zazwyczaj trzeba zgadywać.

Możesz spojrzeć na Rozszerzenia jako jedną wskazówkę, jeśli pliki je mają.

Możesz także rozpoznać znane formaty binarne i je zignorować.

W Przeciwnym Razie Sprawdź, jaką proporcję niedrukowalnych bajtów ASCII masz i zgaduj z tego.

Możesz również spróbować dekodować z UTF-8 i sprawdzić, czy to daje sensowne wyjście.

 4
Author: Douglas Leeder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-22 16:14:44

Jeśli nie korzystasz z systemu Windows, możesz użyć Python Magic do określenia typu pliku. Następnie możesz sprawdzić, czy jest to typ tekstowy/ mime.

 3
Author: Kamil Kisiel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-22 20:55:56

Krótsze rozwiązanie, z ostrzeżeniem 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
 3
Author: Tom Kennedy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-28 14:05:26

Jeśli używasz python3 z utf-8 to jest prosto do przodu, po prostu otwórz plik w trybie tekstowym i przestań go przetwarzać, jeśli otrzymasz UnicodeDecodeError. Python3 będzie używać unicode podczas obsługi plików w trybie tekstowym ( i bytearray w trybie binarnym) - jeśli kodowanie nie może dekodować dowolnych plików, jest całkiem prawdopodobne, że otrzymasz UnicodeDecodeError.

Przykład:

try:
    with open(filename, "r") as f:
        for l in f:
             process_line(l)
except UnicodeDecodeError:
    pass # Fond non-text data
 3
Author: skyking,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-25 15:54:43

Przyszedłem tutaj szukając dokładnie tego samego--kompleksowego rozwiązania dostarczonego przez standardową bibliotekę do wykrywania binarnych lub tekstowych. Po przejrzeniu opcji sugerowanych przez ludzi, Komenda Nix file wydaje się być najlepszym wyborem (rozwijam tylko dla Linuksa boxen). Niektórzy inni opublikowali rozwiązania używając pliku , ale są one moim zdaniem niepotrzebnie skomplikowane, więc oto co wymyśliłem:

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

To powinno być oczywiste, ale twój kod, który nazywa to funkcja powinna upewnić się, że możesz odczytać plik przed przetestowaniem go, w przeciwnym razie będzie to błędnie wykryć plik jako binarny.

 1
Author: rsaw,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-22 21:39:36

Myślę, że najlepszym rozwiązaniem jest użycie funkcji guess_type. Zawiera listę z kilkoma typami MIME i możesz także dołączyć własne typy. Oto skrypt, który zrobiłem, aby rozwiązać mój 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

Jest wewnątrz klasy, jak widać na podstawie struktury kodu. Ale możesz praktycznie zmienić rzeczy, które chcesz zaimplementować w swojej aplikacji. Jest dość prosty w użyciu. Metoda getTextFiles zwraca obiekt list ze wszystkimi plikami tekstowymi, które znajduje się w katalogu, który przekazujesz w zmiennej path.

 1
Author: Leonardo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-03 09:41:40

Oto funkcja, która najpierw sprawdza, czy plik zaczyna się od BOM i jeśli nie szuka bajtu zerowego w początkowych 8192 bajtach:

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

Technicznie sprawdzanie BOM UTF-8 jest niepotrzebne, ponieważ nie powinno zawierać zero bajtów dla wszystkich praktycznych celów. Ale ponieważ jest to bardzo popularne kodowanie, szybsze jest sprawdzenie BOM na początku zamiast skanowania wszystkich 8192 bajtów dla 0.

 1
Author: roskakori,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-06 00:24:04

Możemy użyć samego Pythona, aby sprawdzić, czy plik jest binarny, ponieważ nie powiedzie się, jeśli spróbujemy otworzyć plik binarny w trybie tekstowym

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
 1
Author: Serhii,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-24 14:45:50

Jesteś w unix? jeśli tak, to spróbuj:

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

Zwracane wartości powłoki są odwrócone (0 jest w porządku, więc jeśli znajdzie "text", zwróci 0, a w Pythonie jest to fałszywe wyrażenie).

 0
Author: fortran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-15 20:29:07

Prostszym sposobem jest sprawdzenie, czy plik zawiera znak NULL (\x00) za pomocą operatora in, na przykład:

b'\x00' in open("foo.bar", 'rb').read()

Zobacz poniżej pełny przykład:

#!/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!')

Przykładowe użycie:

$ ./is_binary.py /etc/hosts
The file is not binary!
$ ./is_binary.py `which which`
The file is binary!
 0
Author: kenorb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-01 18:35:59

On *NIX:

Jeśli masz dostęp do file polecenia powłoki, shlex może pomóc uczynić moduł podprocesowy bardziej użytecznym:

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

Lub, można również umieścić go w pętli for, aby uzyskać wyjście dla wszystkich plików w bieżącym katalogu za pomocą:

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

Lub dla wszystkich subdirów:

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()))
 0
Author: Rob Truxal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-30 08:06:42

Większość programów uważa plik za binarny (czyli każdy plik, który nie jest "zorientowany liniowo"), jeśli zawiera znak NULL .

Oto wersja Perla pp_fttext() (pp_sys.c) zaimplementowane w Pythonie:

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

Zauważ również, że ten kod został napisany tak, aby działał zarówno w Pythonie 2, jak i Pythonie 3 bez zmian.

Source: Perl ' s "guess if file is text or binary" zaimplementowane w Pythonie

 0
Author: kenorb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-14 10:55:52