Come posso rilevare se un file è binario (non di testo) in Python?


105

Come posso sapere se un file è binario (non di testo) in Python?

Sto cercando in un ampio set di file in Python e continuo a ricevere corrispondenze nei file binari. Questo rende l'output incredibilmente disordinato.

So che potrei usare grep -I, ma sto facendo di più con i dati di quanto consente grep.

In passato, avrei cercato solo caratteri più grandi di 0x7f, ma utf8e simili , lo avrei reso impossibile sui sistemi moderni. Idealmente la soluzione sarebbe veloce, ma qualsiasi soluzione andrà bene.


SE "in passato avrei cercato solo caratteri maggiori di 0x7f" ALLORA lavoravi con testo ASCII semplice ALLORA non ci sono ancora problemi poiché il testo ASCII codificato come UTF-8 rimane ASCII (cioè nessun byte> 127).
tzot

@ ΤΖΩΤΖΙΟΥ: Vero, ma so che alcuni dei file con cui ho a che fare sono utf8. Intendevo abituato in senso generale, non nel senso specifico di questi file. :)
addolorato

1
Solo con probabilità. Puoi controllare se: 1) il file contiene \ n 2) La quantità di byte tra \ n è relativamente piccola (NON è affidabile) l 3) il file non contiene byte con valore inferiore al valore del carattere "spazio" ASCCI ('' ) - TRANNE "\ n" "\ r" "\ t" e zeri.
SigTerm

3
La strategia che grepessa stessa utilizza per identificare i file binari è simile a quella pubblicata da Jorge Orpinel di seguito . A meno che non imposti l' -zopzione, cercherà solo un carattere nullo ( "\000") nel file. Con -z, esegue la scansione "\200". Chi fosse interessato e / o scettico può consultare la linea 1126 del grep.c. Mi spiace, non sono riuscito a trovare una pagina web con il codice sorgente, ma ovviamente puoi ottenerlo da gnu.org o tramite una distribuzione .
intuito il

3
PS Come menzionato nel thread dei commenti per il post di Jorge, questa strategia darà falsi positivi per i file contenenti, ad esempio, testo UTF-16. Tuttavia, sia git diffGNU che entrambi diffusano la stessa strategia. Non sono sicuro che sia così diffuso perché è molto più veloce e più facile dell'alternativa, o se è solo a causa della relativa rarità dei file UTF-16 su sistemi che tendono ad avere queste utilità installate.
intuito il

Risposte:


42

Puoi anche usare il modulo mimetypes :

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

È abbastanza facile compilare un elenco di tipi MIME binari. Ad esempio, Apache distribuisce con un file mime.types che potresti analizzare in un insieme di elenchi, binari e testo e quindi controllare se il mime è nel tuo elenco di testo o binario.


16
C'è un modo per mimetypesutilizzare il contenuto di un file anziché solo il suo nome?
intuito il

4
@intuited No, ma libmagic lo fa. Usalo tramite python-magic .
Bengt

C'è una domanda simile con alcune buone risposte qui: stackoverflow.com/questions/1446549/… La risposta basata su una ricetta activestate mi sembra buona, consente una piccola proporzione di caratteri non stampabili (ma non \ 0, per alcuni Motivo).
Sam Watkins

5
Questa non è un'ottima risposta solo perché il modulo mimetypes non è adatto a tutti i file. Sto guardando un file ora che il sistema filesegnala come "Testo Unicode UTF-8, con righe molto lunghe" ma mimetypes.gest_type () restituirà (Nessuno, Nessuno). Inoltre, l'elenco dei tipi MIME di Apache è una whitelist / sottoinsieme. Non è affatto un elenco completo di mimetypes. Non può essere utilizzato per classificare tutti i file come testo o non testo.
Purrell

1
guess_types si basa sull'estensione del nome del file, non sul contenuto reale come farebbe il comando Unix "file".
Eric H.

61

Ancora un altro metodo basato sul comportamento del file (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))

Esempio:

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

Può ottenere sia falsi positivi che falsi negativi, ma è comunque un approccio intelligente che funziona per la maggior parte dei file. +1.
spettri

2
È interessante notare che il file (1) stesso esclude anche 0x7f dalla considerazione, quindi tecnicamente parlando dovresti usare bytearray([7,8,9,10,12,13,27]) + bytearray(range(0x20, 0x7f)) + bytearray(range(0x80, 0x100))invece. Vedi Python, file (1) - Perché i numeri [7,8,9,10,12,13,27] e l'intervallo (0x20, 0x100) sono usati per determinare il testo rispetto al file binario e github.com/file/file/ blob /…
Martijn Pieters

@MartijnPieters: grazie. Ho aggiornato la risposta a exclude 0x7f( DEL).
jfs

1
Bella soluzione usando i set. :-)
Martijn Pieters

Perché escludi 11o VT? Nella tabella 11 è considerato testo ASCII semplice, e questo è il file vertical tab.
darksky

15

Se stai usando python3 con utf-8 è semplice, apri il file in modalità testo e interrompi l'elaborazione se ottieni un file UnicodeDecodeError. Python3 userà unicode quando gestirà i file in modalità testo (e bytearray in modalità binaria) - se la tua codifica non può decodificare file arbitrari è molto probabile che otterrai UnicodeDecodeError.

Esempio:

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

perché non usare with open(filename, 'r', encoding='utf-8') as fdirettamente?
Terry

8

Se aiuta, molti molti tipi binari iniziano con numeri magici. Ecco un elenco di firme di file.


Questo è lo scopo di libmagic. È possibile accedervi in ​​python tramite python-magic .
Bengt

2
Sfortunatamente, "non inizia con un numero magico noto" non è equivalente a "è un file di testo".
Purrell

8

Prova questo:

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 definisce "binario" come contenente un byte zero. Classificherà i file di testo con codifica UTF-16 come "binari".
John Machin,

5
@ John Machin: È interessante notare che in git diffrealtà funziona in questo modo e, abbastanza sicuro, rileva i file UTF-16 come binari.
intuito il

Hunh .. GNU difffunziona anche in questo modo. Ha problemi simili con i file UTF-16. filerileva correttamente gli stessi file del testo UTF-16. Non ho controllato il grepcodice di, ma rileva anche i file UTF-16 come binari.
intuito il

1
+1 @John Machin: utf-16 è un dato di caratteri in base al fatto file(1)che non è sicuro da stampare senza conversione, quindi questo metodo è appropriato in questo caso.
jfs

2
-1 - Non penso che 'contiene un byte zero' sia un test adeguato per binario vs testo, ad esempio posso creare un file contenente tutti i byte 0x01 o ripetere 0xDEADBEEF, ma non è un file di testo. La risposta basata sul file (1) è migliore.
Sam Watkins

6

Ecco un suggerimento che utilizza il comando file 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)

Utilizzo di esempio:

>>> istext ('/ etc / motd') 
Vero
>>> istext ('/ vmlinuz') 
falso
>>> 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') # funziona su UTF-8
Vero

Ha gli svantaggi di non essere portabile su Windows (a meno che tu non abbia qualcosa di simile al filecomando lì) e di dover generare un processo esterno per ogni file, che potrebbe non essere appetibile.


Questo ha rotto il mio script :( Indagando, ho scoperto che alcuni conffile sono descritti filecome "Configurazione congelata di Sendmail - versione m" - nota l'assenza della stringa "text". Forse usare file -i?
melissa_boiko

1
TypeError: impossibile utilizzare un modello di stringa su un oggetto simile a byte
abg

5

Usa la libreria binaryornot ( GitHub ).

È molto semplice e si basa sul codice trovato in questa domanda su stackoverflow.

Puoi effettivamente scrivere questo in 2 righe di codice, tuttavia questo pacchetto ti evita di dover scrivere e testare a fondo quelle 2 righe di codice con tutti i tipi di strani tipi di file, multipiattaforma.


4

Di solito devi indovinare.

Puoi considerare le estensioni come un indizio, se i file le hanno.

Puoi anche riconoscere i formati binari conosciuti e ignorarli.

Altrimenti guarda quale proporzione di byte ASCII non stampabili hai e fai un'ipotesi da quello.

Puoi anche provare a decodificare da UTF-8 e vedere se questo produce un output ragionevole.


4

Una soluzione più breve, con un avviso 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

nota: for line in filepuò consumare una quantità illimitata di memoria fino a quando non b'\n'viene trovato
jfs

a @Community: ".read()"restituisce una stringa di byte che è iterabile (restituisce singoli byte).
jfs

4

Possiamo usare lo stesso python per verificare se un file è binario, perché fallisce se proviamo ad aprire il file binario in modalità testo

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

Questo fallisce per molti file `.avi '(video).
Anmol Singh Jaggi

3

Se non sei su Windows, puoi usare Python Magic per determinare il tipo di file. Quindi puoi controllare se si tratta di un tipo di testo / mime.


2

Ecco una funzione che prima controlla se il file inizia con una BOM e in caso contrario cerca uno zero byte entro gli 8192 byte iniziali:

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

Tecnicamente il controllo per la distinta componenti UTF-8 non è necessario perché non dovrebbe contenere zero byte per tutti gli scopi pratici. Ma poiché è una codifica molto comune, è più veloce controllare la BOM all'inizio invece di scansionare tutti gli 8192 byte per 0.


2

Prova a usare il python-magic attualmente mantenuto che non è lo stesso modulo nella risposta di @Kami Kisiel. Questo supporta tutte le piattaforme, incluso Windows, tuttavia avrai bisogno dellibmagic file binari. Questo è spiegato nel README.

A differenza del modulo mimetypes , non usa l'estensione del file e invece ispeziona il contenuto del file.

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

Sono venuto qui cercando esattamente la stessa cosa: una soluzione completa fornita dalla libreria standard per rilevare binario o testo. Dopo aver esaminato le opzioni suggerite dalle persone, il comando file nix sembra essere la scelta migliore (sto sviluppando solo per linux boxen). Alcuni altri hanno pubblicato soluzioni utilizzando file ma a mio parere sono inutilmente complicate, quindi ecco cosa ho trovato:

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

Dovrebbe essere ovvio, ma il codice che chiama questa funzione dovrebbe assicurarsi di poter leggere un file prima di testarlo, altrimenti questo rileverà erroneamente il file come binario.


1

Immagino che la soluzione migliore sia usare la funzione guess_type. Contiene un elenco con diversi tipi di MIME e puoi anche includere i tuoi tipi. Ecco lo script che ho fatto per risolvere il mio problema:

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

È all'interno di una Classe, come puoi vedere in base alla struttura del codice. Ma puoi praticamente cambiare le cose che vuoi implementarlo all'interno della tua applicazione. È abbastanza semplice da usare. Il metodo getTextFiles restituisce un oggetto elenco con tutti i file di testo che risiedono nella directory passata nella variabile di percorso.


1

su * NIX:

Se hai accesso al filecomando shell, shlex può aiutarti a rendere più utilizzabile il modulo sottoprocesso:

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

Oppure, puoi anche inserirlo in un ciclo for per ottenere l'output per tutti i file nella directory corrente usando:

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

o per tutte le sottocartelle:

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

La maggior parte dei programmi considera il file binario (che è qualsiasi file non "orientato alla riga") se contiene un carattere NULL .

Ecco la versione perl di pp_fttext()( pp_sys.c) implementata in 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

Nota anche che questo codice è stato scritto per funzionare sia su Python 2 che su Python 3 senza modifiche.

Fonte: "indovina se il file è di testo o binario" di Perl implementato in Python


0

sei in unix? in tal caso, prova:

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

I valori restituiti dalla shell sono invertiti (0 va bene, quindi se trova "testo" restituirà uno 0, e in Python questa è un'espressione False).


Per riferimento, il comando file indovina un tipo in base al contenuto del file. Non sono sicuro che presti attenzione all'estensione del file.
David Z,

Sono quasi sicuro che sia presente sia nel contenuto che nell'estensione.
Fortran

Questo si interrompe se il percorso contiene "testo", comunque. Assicurati di rsplit all'ultimo ":" (ammesso che non ci siano due punti nella descrizione del tipo di file).
Alan Plum

3
Utilizzare filecon l' -binterruttore; stamperà solo il tipo di file senza il percorso.
dubek

2
una versione leggermente più carina:is_binary_file = lambda filename: "text" in subprocess.check_output(["file", "-b", filename])
jfs

0

Il modo più semplice è controllare se il file è composto da caratteri NULL ( \x00) utilizzando l' inoperatore, ad esempio:

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

Vedi sotto l'esempio completo:

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

Utilizzo del campione:

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.