Wie kann ich feststellen, ob eine Datei in Python binär (kein Text) ist?

105

Wie kann ich feststellen, ob eine Datei in Python binär (kein Text) ist?

Ich suche eine große Anzahl von Dateien in Python und erhalte immer wieder Übereinstimmungen in Binärdateien. Dadurch sieht die Ausgabe unglaublich chaotisch aus.

Ich weiß, dass ich verwenden könnte grep -I, aber ich mache mehr mit den Daten, als grep zulässt.

In der Vergangenheit hätte ich nur nach Zeichen gesucht, die größer sind als 0x7f, aber utf8und dergleichen, was dies auf modernen Systemen unmöglich macht. Im Idealfall wäre die Lösung schnell, aber jede Lösung reicht aus.

trauern
quelle
WENN "in der Vergangenheit hätte ich nur nach Zeichen gesucht, die größer als 0x7f sind" DANN haben Sie mit einfachem ASCII-Text gearbeitet, DANN immer noch kein Problem, da ASCII-Text, der als UTF-8 codiert ist, ASCII bleibt (dh keine Bytes> 127).
22.
@ ΤΖΩΤΖΙΟΥ: Stimmt, aber ich weiß zufällig, dass einige der Dateien, mit denen ich mich beschäftige, utf8 sind. Ich meinte es gewohnt im allgemeinen Sinne, nicht im spezifischen Sinne dieser Dateien. :)
trauern
1
Nur mit Wahrscheinlichkeit. Sie können überprüfen, ob: 1) Datei enthält \ n 2) Anzahl der Bytes zwischen \ n ist relativ klein (dies ist NICHT zuverlässig) l 3) Datei enthält keine Bytes mit einem Wert, der kleiner als der Wert des ASCCI-Leerzeichens ('' ist) ) - AUSSER "\ n" "\ r" "\ t" und Nullen.
SigTerm
3
Die Strategie, grepmit der Binärdateien identifiziert werden, ähnelt der von Jorge Orpinel unten . Sofern Sie die -zOption nicht festlegen , wird nur nach einem Nullzeichen ( "\000") in der Datei gesucht. Mit -zsucht es nach "\200". Interessierte und / oder Skeptiker können Zeile 1126 von überprüfen grep.c. Entschuldigung, ich konnte keine Webseite mit dem Quellcode finden, aber Sie können sie natürlich von gnu.org oder über eine Distribution erhalten .
Intuition
3
PS Wie im Kommentarthread für Jorges Beitrag erwähnt, gibt diese Strategie falsch positive Ergebnisse für Dateien aus, die beispielsweise UTF-16-Text enthalten. Dennoch verfolgen sowohl git diffGNU als diffauch GNU dieselbe Strategie. Ich bin mir nicht sicher, ob es so weit verbreitet ist, weil es so viel schneller und einfacher als die Alternative ist, oder ob es nur an der relativen Seltenheit von UTF-16-Dateien auf Systemen liegt, auf denen diese Utils installiert sind.
Intuitierte

Antworten:

42

Sie können auch das Mimetypes- Modul verwenden:

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

Es ist ziemlich einfach, eine Liste von binären MIME-Typen zu erstellen. Zum Beispiel verteilt Apache mit einer mime.types-Datei, die Sie in eine Reihe von Listen, Binärdateien und Text, analysieren und dann überprüfen können, ob sich die MIME in Ihrem Text oder Ihrer Binärliste befindet.

Gavin M. Roy
quelle
16
Gibt es eine Möglichkeit, mimetypesden Inhalt einer Datei anstelle ihres Namens zu verwenden?
Intuitierte
4
@intuited Nein, aber libmagic macht das. Verwenden Sie es über Python-Magie .
Bengt
Hier gibt es eine ähnliche Frage mit einigen guten Antworten: stackoverflow.com/questions/1446549/… Die Antwort, die auf einem Aktivstatusrezept basiert, sieht für mich gut aus und erlaubt einen kleinen Anteil nicht druckbarer Zeichen (für einige jedoch keine \ 0) Grund).
Sam Watkins
5
Dies ist keine gute Antwort, nur weil das Mimetypes-Modul nicht für alle Dateien geeignet ist. Ich schaue mir jetzt eine Datei an, die vom System fileals "UTF-8-Unicode-Text mit sehr langen Zeilen" gemeldet wird, aber mimetypes.gest_type () gibt zurück (None, None). Außerdem ist Apaches Mimetyp-Liste eine Whitelist / Teilmenge. Es ist keineswegs eine vollständige Liste der Mimetypen. Es kann nicht verwendet werden, um alle Dateien als Text oder Nicht-Text zu klassifizieren.
Purrell
1
rate_types basiert auf der Dateinamenerweiterung und nicht auf dem tatsächlichen Inhalt, wie es der Unix-Befehl "file" tun würde.
Eric H.
61

Noch eine andere Methode, die auf dem Verhalten von Datei (1) basiert :

>>> textchars = bytearray({7,8,9,10,12,13,27} | set(range(0x20, 0x100)) - {0x7f})
>>> is_binary_string = lambda bytes: bool(bytes.translate(None, textchars))

Beispiel:

>>> is_binary_string(open('/usr/bin/python', 'rb').read(1024))
True
>>> is_binary_string(open('/usr/bin/dh_python3', 'rb').read(1024))
False
jfs
quelle
Kann sowohl falsch positive als auch falsch negative Ergebnisse erhalten, ist aber dennoch ein cleverer Ansatz, der für die große Mehrheit der Dateien funktioniert. +1.
Spektren
2
Interessanterweise schließt Datei (1) selbst auch 0x7f von der Betrachtung aus, daher sollten Sie technisch gesehen bytearray([7,8,9,10,12,13,27]) + bytearray(range(0x20, 0x7f)) + bytearray(range(0x80, 0x100))stattdessen verwenden. Siehe Python, Datei (1) - Warum werden die Zahlen [7,8,9,10,12,13,27] und der Bereich (0x20, 0x100) zur Bestimmung von Text gegenüber Binärdatei und github.com/file/file/
Martijn Pieters
@ MartinijnPieters: Danke. Ich habe die Antwort auf exclude 0x7f( DEL) aktualisiert .
JFS
1
Schöne Lösung mit Sets. :-)
Martijn Pieters
Warum schließen Sie aus 11oder VT? In der Tabelle wird 11 als einfacher ASCII-Text betrachtet, und dies ist der vertical tab.
Darksky
15

Wenn Sie Python3 mit utf-8 verwenden, ist dies unkompliziert. Öffnen Sie die Datei einfach im Textmodus und beenden Sie die Verarbeitung, wenn Sie eine erhalten UnicodeDecodeError. Python3 verwendet Unicode, wenn Dateien im Textmodus verarbeitet werden (und Bytearray im Binärmodus). Wenn Ihre Codierung keine beliebigen Dateien dekodieren kann, ist es sehr wahrscheinlich, dass Sie diese erhalten UnicodeDecodeError.

Beispiel:

try:
    with open(filename, "r") as f:
        for l in f:
             process_line(l)
except UnicodeDecodeError:
    pass # Fond non-text data
Himmel König
quelle
warum nicht with open(filename, 'r', encoding='utf-8') as fdirekt verwenden?
Terry
8

Wenn es hilft, beginnen viele, viele Binärtypen mit magischen Zahlen. Hier ist eine Liste der Dateisignaturen.

Shane C. Mason
quelle
Dafür ist libmagic da. Es kann in Python über Python-Magie zugegriffen werden .
Bengt
2
Leider ist "beginnt nicht mit einer bekannten magischen Zahl" nicht gleichbedeutend mit "ist eine Textdatei".
Purrell
8

Versuche dies:

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
Jorge Orpinel
quelle
9
-1 definiert "binär" als ein Null-Byte enthaltend. Klassifiziert UTF-16-codierte Textdateien als "binär".
John Machin
5
@ John Machin: Interessanterweise funktioniert dasgit diff tatsächlich so und es erkennt UTF-16-Dateien als binär.
Intuition
Hunh .. GNU difffunktioniert auch so. Es gibt ähnliche Probleme mit UTF-16-Dateien. fileerkennt die gleichen Dateien wie UTF-16-Text korrekt. Ich habe den grepCode nicht ausgecheckt , aber er erkennt auch UTF-16-Dateien als binär.
Intuitierte
1
+1 @John Machin: utf-16 sind Zeichendaten, file(1)die ohne Konvertierung nicht sicher gedruckt werden können. Daher ist diese Methode in diesem Fall geeignet.
JFS
2
-1 - Ich denke nicht, dass 'enthält ein Null-Byte' ein angemessener Test für Binär gegen Text ist. Ich kann beispielsweise eine Datei erstellen, die alle 0x01-Bytes enthält, oder 0xDEADBEEF wiederholen, aber es ist keine Textdatei. Die Antwort basierend auf Datei (1) ist besser.
Sam Watkins
6

Hier ist ein Vorschlag, der den Befehl Unix- Datei verwendet :

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)

Anwendungsbeispiel:

>>> istext ('/ etc / motd') 
Wahr
>>> istext ('/ vmlinuz') 
Falsch
>>> 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') # funktioniert unter UTF-8
Wahr

Es hat den Nachteil, dass es nicht auf Windows portierbar ist (es sei denn, Sie haben dort so etwas wie den fileBefehl) und für jede Datei einen externen Prozess erzeugen muss, der möglicherweise nicht schmackhaft ist.

Jacob Gabrielson
quelle
Dies hat mein Skript gebrochen :( Nachforschungen haben ergeben, dass einige Conffiles fileals "Sendmail Frozen Configuration - Version M" beschrieben werden - und das Fehlen der Zeichenfolge "Text" file -i
bemerken
1
TypeError: Kann kein String-Muster für ein byteähnliches Objekt verwenden
abg
5

Verwenden Sie die binaryornot- Bibliothek ( GitHub ).

Es ist sehr einfach und basiert auf dem Code in dieser Stackoverflow-Frage.

Sie können dies tatsächlich in 2 Codezeilen schreiben. Dieses Paket erspart Ihnen jedoch das Schreiben und gründliche Testen dieser 2 Codezeilen mit allen möglichen seltsamen Dateitypen, plattformübergreifend.

guettli
quelle
4

Normalerweise muss man raten.

Sie können die Erweiterungen als einen Hinweis betrachten, wenn die Dateien sie haben.

Sie können auch bekannte Binärformate erkennen und diese ignorieren.

Ansonsten sehen Sie, welchen Anteil nicht druckbarer ASCII-Bytes Sie haben, und raten Sie davon.

Sie können auch versuchen, UTF-8 zu dekodieren und festzustellen, ob dies zu einer sinnvollen Ausgabe führt.

Douglas Leeder
quelle
4

Eine kürzere Lösung mit einer UTF-16-Warnung:

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
Tom Kennedy
quelle
Hinweis: for line in fileKann unbegrenzt viel Speicher verbrauchen, bis b'\n'gefunden wird
jfs
: zu @Community ".read()"Bytestring hier , dass die Renditen ist iterable (es ergibt einzelnen Bytes).
JFS
4

Wir können Python selbst verwenden, um zu überprüfen, ob eine Datei binär ist, da dies fehlschlägt, wenn wir versuchen, eine Binärdatei im Textmodus zu öffnen

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
Serhii
quelle
Dies schlägt für viele .avi (Video) -Dateien fehl.
Anmol Singh Jaggi
3

Wenn Sie nicht unter Windows arbeiten, können Sie den Dateityp mit Python Magic bestimmen. Dann können Sie überprüfen, ob es sich um einen Text- / MIME-Typ handelt.

Kamil Kisiel
quelle
2

Hier ist eine Funktion, die zuerst prüft, ob die Datei mit einer Stückliste beginnt und wenn nicht, nach einem Null-Byte innerhalb der anfänglichen 8192 Bytes sucht:

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

Technisch gesehen ist die Prüfung der UTF-8-Stückliste nicht erforderlich, da sie für alle praktischen Zwecke keine Null-Bytes enthalten sollte. Da es sich jedoch um eine sehr häufige Codierung handelt, ist es am Anfang schneller, nach der Stückliste zu suchen, als alle 8192 Bytes nach 0 zu durchsuchen.

Roskakori
quelle
2

Versuchen Sie es mit der aktuell gepflegten Python-Magie, die nicht das gleiche Modul in @Kami Kisiels Antwort ist. Dies unterstützt alle Plattformen einschließlich Windows, Sie benötigen jedoch dielibmagic Binärdateien. Dies wird in der README erklärt.

Im Gegensatz zum Mimetypes- Modul wird die Dateierweiterung nicht verwendet und stattdessen der Inhalt der Datei überprüft.

>>> 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'
Iss bei Joes
quelle
1

Ich bin hierher gekommen, um genau das Gleiche zu suchen - eine umfassende Lösung, die von der Standardbibliothek zur Erkennung von Binärdateien oder Text bereitgestellt wird. Nachdem Sie die vorgeschlagenen Optionen überprüft haben, scheint der Befehl nix file die beste Wahl zu sein (ich entwickle nur für Linux Boxen). Einige andere haben Lösungen mithilfe von Dateien veröffentlicht, aber meiner Meinung nach sind sie unnötig kompliziert. Deshalb habe ich mir Folgendes ausgedacht:

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

Es sollte selbstverständlich sein, aber Ihr Code, der diese Funktion aufruft, sollte sicherstellen, dass Sie eine Datei lesen können, bevor Sie sie testen. Andernfalls wird die Datei fälschlicherweise als binär erkannt.

rsaw
quelle
1

Ich denke, dass die beste Lösung darin besteht, die Funktion rate_type zu verwenden. Es enthält eine Liste mit mehreren Mimetypen, und Sie können auch Ihre eigenen Typen angeben. Hier kommt das Skript, das ich gemacht habe, um mein Problem zu lösen:

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

Es befindet sich innerhalb einer Klasse, wie Sie anhand der Struktur des Codes sehen können. Sie können jedoch die Dinge, die Sie in Ihrer Anwendung implementieren möchten, so ziemlich ändern. Es ist ganz einfach zu bedienen. Die Methode getTextFiles gibt ein Listenobjekt mit allen Textdateien zurück, die sich in dem Verzeichnis befinden, das Sie in der Pfadvariablen übergeben.

Leonardo
quelle
1

auf * NIX:

Wenn Sie Zugriff auf den fileShell-Befehl haben, kann shlex dazu beitragen, das Unterprozessmodul benutzerfreundlicher zu machen:

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

Sie können dies auch in eine for-Schleife einfügen, um die Ausgabe für alle Dateien im aktuellen Verzeichnis zu erhalten, indem Sie Folgendes verwenden:

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

oder für alle Unterverzeichnisse:

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()))
Rob Truxal
quelle
1

Die meisten Programme betrachten die Datei als binär (dh jede Datei, die nicht "zeilenorientiert" ist), wenn sie ein NULL-Zeichen enthält .

Hier ist Perls Version von pp_fttext()( pp_sys.c), die in Python implementiert ist:

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

Beachten Sie auch, dass dieser Code so geschrieben wurde, dass er sowohl auf Python 2 als auch auf Python 3 ohne Änderungen ausgeführt werden kann.

Quelle: Perls in Python implementierte "Vermutung, ob die Datei Text oder Binär ist"

Kenorb
quelle
0

bist du in unix Wenn ja, dann versuchen Sie:

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

Die Shell-Rückgabewerte werden invertiert (0 ist in Ordnung. Wenn also "Text" gefunden wird, wird eine 0 zurückgegeben, und in Python ist dies ein falscher Ausdruck).

fortran
quelle
Als Referenz errät der Befehl file einen Typ, der auf dem Inhalt der Datei basiert. Ich bin mir nicht sicher, ob die Dateierweiterung beachtet wird.
David Z
Ich bin mir fast sicher, dass es sowohl im Inhalt als auch in der Erweiterung aussieht.
Fortan
Dies bricht ab, wenn der Pfad "Text" enthält. Stellen Sie sicher, dass Sie am letzten ':' rsplit (vorausgesetzt, die Dateitypbeschreibung enthält keinen Doppelpunkt).
Alan Plum
3
Verwenden Sie filemit dem -bSchalter; Es wird nur der Dateityp ohne Pfad gedruckt.
Dubek
2
eine etwas schönere Version:is_binary_file = lambda filename: "text" in subprocess.check_output(["file", "-b", filename])
jfs
0

Einfacher ist es, \x00mithilfe des inOperators zu überprüfen, ob die Datei aus NULL-Zeichen ( ) besteht , zum Beispiel:

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

Siehe unten das vollständige Beispiel:

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

Beispielnutzung:

$ ./is_binary.py /etc/hosts
The file is not binary!
$ ./is_binary.py `which which`
The file is binary!
Kenorb
quelle
0
from binaryornot.check import is_binary
is_binary('filename')

Dokumentation

parZval
quelle