web-dev-qa-db-fra.com

Comment puis-je détecter si un fichier est binaire (non-texte) en python?

Comment savoir si un fichier est binaire (non-texte) en python? Je cherche dans un grand ensemble de fichiers en python et continue à obtenir des correspondances dans des fichiers binaires. Cela rend la sortie incroyablement confuse.

Je sais que je pourrais utiliser grep -I, mais je fais plus avec les données que ce que grep permet.

Dans le passé, j'aurais simplement cherché des caractères supérieurs à 0x7f, mais utf8 et autres rendent cela impossible sur les systèmes modernes. Idéalement, la solution serait rapide, mais toute solution fera l'affaire.

83
grieve

Vous pouvez également utiliser les mimetypes module:

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

Il est assez facile de compiler une liste de types mime binaires. Par exemple, Apache distribue un fichier mime.types que vous pouvez analyser dans un ensemble de listes, binaire et texte, puis vérifiez si le mime se trouve dans votre liste de textes ou binaires.

36
Gavin M. Roy

Encore une autre méthode basée sur le comportement de fichier (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))

Exemple:

>>> is_binary_string(open('/usr/bin/python', 'rb').read(1024))
True
>>> is_binary_string(open('/usr/bin/dh_python3', 'rb').read(1024))
False
51
jfs

Essaye ça:

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

Si cela peut aider, beaucoup de types binaires commencent par un nombre magique. Voici une liste des signatures de fichiers.

8
Shane C. Mason

Si vous utilisez python3 avec utf-8, c’est simple, ouvrez simplement le fichier en mode texte et arrêtez le traitement si vous obtenez une UnicodeDecodeError. Python3 utilisera unicode lors de la gestion de fichiers en mode texte (et bytearray en mode binaire). Si votre codage ne peut pas décoder des fichiers arbitraires, il est fort probable que vous obtiendrez UnicodeDecodeError

Exemple:

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

Utilisez binaryornot library ( GitHub ).

C'est très simple et basé sur le code trouvé dans cette question de stackoverflow.

Vous pouvez réellement écrire cela en 2 lignes de code, mais ce paquet vous évite de devoir écrire et tester minutieusement ces 2 lignes de code avec toutes sortes de types de fichiers étranges, multiplates-formes.

5
guettli

Voici une suggestion qui utilise la commande 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)

Exemple d'utilisation:

 >>> istext ('/ etc/motd') 
 True 
 >>> istext ('/ vmlinuz') 
 False 
 >>> open ('/ tmp/japanese' ) .read () 
 '\ xe3\x81\x93\xe3\x82\x8c\xe3\x81\x81\xe\xe\xe\xe\xe\xe\xe\xe\xe\x8c\xe3\x82\x81\xe5\xba\xa7\xe3\x81\xa\xe6\x99\x82\xa\xa\xa\xa\xa\xa\xa\xa\xa\xa\xa\xa\xa\xe\xe\xe\xe3\x81\x91\xe3\x80\x82\n '
 >>> istext ('/tmp/japanese ') # fonctionne sur UTF-8 
 Vrai 

Il a les inconvénients de ne pas être portable sur Windows (à moins que vous n'ayez quelque chose comme la commande file), et d'avoir à générer un processus externe pour chaque fichier, ce qui pourrait ne pas être acceptable.

5
Jacob Gabrielson

Habituellement, vous devez deviner.

Vous pouvez regarder les extensions comme un indice, si les fichiers les ont.

Vous pouvez également reconnaître les formats binaires connus et les ignorer.

Sinon, voyez quelle proportion d’octets ASCII non imprimables vous avez et calculez-la.

Vous pouvez également essayer de décoder à partir de UTF-8 et voir si cela produit une sortie sensible.

4
Douglas Leeder

Si vous n'êtes pas sous Windows, vous pouvez utiliser Python Magic pour déterminer le type de fichier. Ensuite, vous pouvez vérifier s’il s’agit d’un type de texte/mime.

3
Kamil Kisiel

Une solution plus courte, avec un avertissement 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
Tom Kennedy

Nous pouvons utiliser python lui-même pour vérifier si un fichier est binaire, car il échoue si nous essayons d'ouvrir un fichier binaire en mode texte

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

Voici une fonction qui vérifie d'abord si le fichier commence par une nomenclature et sinon recherche un octet zéro dans les 8192 octets initiaux:

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

Techniquement, la vérification de la nomenclature UTF-8 est inutile car elle ne doit pas contenir zéro octet à toutes fins pratiques. Mais comme il s’agit d’un encodage très courant, il est plus rapide de vérifier la nomenclature au lieu de scanner tous les 8192 octets pour 0.

1
roskakori

Je suppose que la meilleure solution consiste à utiliser la fonction guess_type. Il contient une liste de plusieurs types MIME et vous pouvez également inclure vos propres types . Voici le script que j'ai fait pour résoudre mon problème:

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

Il se trouve à l'intérieur d'une classe, comme vous pouvez le constater en fonction de la structure du code. Mais vous pouvez très bien changer les choses que vous voulez implémenter dans votre application . C'est assez simple à utiliser . La méthode getTextFiles renvoie un objet liste avec tous les fichiers texte qui se trouvent dans le répertoire que vous passez dans variable de chemin.

1
Leonardo

Je suis venu ici à la recherche de la même chose: une solution complète fournie par la bibliothèque standard pour détecter les binaires ou les textes. Après avoir examiné les options proposées par les utilisateurs, la commande nix file semble être le meilleur choix (je ne développe que pour Linux Boxen). D'autres ont publié des solutions en utilisant file mais elles sont inutilement compliquées à mon avis, alors voici ce que j'ai trouvé:

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

Cela devrait aller de soi, mais votre code qui appelle cette fonction doit vous assurer que vous pouvez lire un fichier avant de le tester, sinon, le fichier sera détecté à tort comme étant binaire.

1
rsaw

Essayez d'utiliser le python-magic actuellement mis à jour, qui n'est pas le même module dans la réponse de @Kami Kisiel. Cela prend en charge toutes les plateformes, y compris Windows, mais vous aurez besoin des fichiers binaires libmagic. Ceci est expliqué dans le fichier README.

Contrairement au module mimetypes , il n’utilise pas l’extension du fichier mais inspecte plutôt le contenu du fichier.

>>> 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
Eat at Joes

La plupart des programmes considèrent le fichier comme étant binaire (c'est-à-dire tout fichier non orienté ligne) s'il contient un caractère NULL .

Voici la version de Perf de pp_fttext() (pp_sys.c) implémentée en 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

Notez également que ce code a été écrit pour s'exécuter sur Python 2 et Python 3 sans modifications.

Source: Perl "devine si le fichier est du texte ou binaire" implémenté en Python

0
kenorb

Le moyen le plus simple est de vérifier si le fichier contient un caractère NULL (\x00) en utilisant l'opérateur in, par exemple:

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

Voir ci-dessous l'exemple complet:

#!/usr/bin/env python3
import argparse
if __== '__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!')

Exemple d'utilisation:

$ ./is_binary.py /etc/hosts
The file is not binary!
$ ./is_binary.py `which which`
The file is binary!
0
kenorb

es-tu sous unix? Si oui, alors essayez:

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

Les valeurs de retour du shell sont inversées (0 est ok, donc s'il trouve "text", il retournera un 0, et en Python une expression False).

0
fortran

sur * NIX:

Si vous avez accès à la commande file Shell, shlex peut aider à rendre le module de sous-processus plus utilisable:

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

Ou, vous pouvez également coller cela dans une boucle for pour obtenir une sortie pour tous les fichiers du répertoire en cours en utilisant:

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

ou pour tous les sous-répertoires:

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