Bagaimana saya bisa mendeteksi jika sebuah file biner (non-teks) dengan python?

105

Bagaimana cara mengetahui apakah sebuah file biner (non-teks) dengan python?

Saya mencari melalui sekumpulan besar file dengan python, dan terus mendapatkan kecocokan dalam file biner. Ini membuat hasilnya terlihat sangat berantakan.

Saya tahu saya bisa menggunakan grep -I, tetapi saya melakukan lebih banyak dengan data daripada yang diizinkan oleh grep.

Di masa lalu, saya hanya akan mencari karakter yang lebih besar dari 0x7f, tetapi utf8dan sejenisnya, membuatnya tidak mungkin pada sistem modern. Idealnya, solusi akan cepat, tetapi solusi apa pun akan berhasil.

bersedih
sumber
JIKA "di masa lalu saya hanya akan mencari karakter yang lebih besar dari 0x7f" MAKA Anda dulu bekerja dengan teks ASCII biasa MAKA masih tidak ada masalah karena teks ASCII yang dikodekan sebagai UTF-8 tetap ASCII (yaitu tidak ada byte> 127).
tzot
@ ΤΖΩΤΖΙΟΥ: Benar, tapi kebetulan saya tahu bahwa beberapa file yang saya tangani adalah utf8. Yang saya maksudkan dulu dalam arti umum, bukan dalam arti khusus dari file-file ini. :)
berduka
1
Hanya dengan probabilitas. Anda dapat memeriksa apakah: 1) file berisi \ n 2) Jumlah byte antara \ n relatif kecil (ini TIDAK dapat diandalkan) l 3) file tidak berisi byte dengan nilai kurang dari nilai karakter "spasi" ASCCI ('' ) - KECUALI "\ n" "\ r" "\ t" dan nol.
SigTerm
3
Strategi yang grepdigunakan sendiri untuk mengidentifikasi file biner mirip dengan yang diposting oleh Jorge Orpinel di bawah ini . Kecuali Anda menyetel -zopsinya, itu hanya akan memindai karakter null ( "\000") dalam file. Dengan -z, itu memindai "\200". Mereka yang tertarik dan / atau skeptis dapat memeriksa baris 1126 dari grep.c. Maaf, saya tidak bisa temukan halaman web dengan kode sumbernya, tapi tentu saja Anda bisa mendapatkannya dari gnu.org atau melalui distro .
Intuisi
3
PS Seperti yang disebutkan di utas komentar untuk posting Jorge, strategi ini akan memberikan positif palsu untuk file yang berisi, misalnya, teks UTF-16. Meskipun demikian, keduanya git diffdan GNU diffjuga menggunakan strategi yang sama. Saya tidak yakin apakah itu sangat lazim karena jauh lebih cepat dan lebih mudah daripada alternatifnya, atau apakah itu hanya karena kelangkaan relatif dari file UTF-16 pada sistem yang cenderung menginstal utilitas ini.
Intuisi

Jawaban:

42

Anda juga dapat menggunakan modul mimetypes :

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

Cukup mudah untuk menyusun daftar jenis mime biner. Misalnya Apache mendistribusikan dengan file mime.types yang dapat Anda parse menjadi sekumpulan daftar, biner dan teks dan kemudian periksa untuk melihat apakah mime ada dalam teks atau daftar biner Anda.

Gavin M. Roy
sumber
16
Adakah cara untuk mimetypesmenggunakan konten file daripada hanya namanya?
Intuisi
4
@ Intuited Tidak, tapi libmagic melakukan itu. Gunakan melalui python-magic .
Bengt
Ada pertanyaan serupa dengan beberapa jawaban bagus di sini: stackoverflow.com/questions/1446549/… Jawaban berdasarkan resep aktivestate terlihat bagus bagi saya, ini memungkinkan sebagian kecil karakter yang tidak dapat dicetak (tetapi tidak \ 0, untuk beberapa alasan).
Sam Watkins
5
Ini bukan jawaban yang bagus hanya karena modul mimetypes tidak bagus untuk semua file. Saya melihat file sekarang yang sistemnya filemelaporkan sebagai "UTF-8 Unicode text, with very long lines" tetapi mimetypes.gest_type () akan mengembalikan (None, None). Juga, daftar mimetype Apache adalah daftar putih / subset. Ini sama sekali bukan daftar lengkap mimetypes. Ini tidak dapat digunakan untuk mengklasifikasikan semua file sebagai teks atau non-teks.
Purrell
1
guess_types didasarkan pada ekstensi nama file, bukan konten sebenarnya seperti perintah "file" Unix.
Eric H.
61

Metode lain berdasarkan perilaku 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))

Contoh:

>>> is_binary_string(open('/usr/bin/python', 'rb').read(1024))
True
>>> is_binary_string(open('/usr/bin/dh_python3', 'rb').read(1024))
False
jfs
sumber
Bisa mendapatkan positif palsu dan negatif palsu, tetapi tetap merupakan pendekatan cerdas yang berfungsi untuk sebagian besar file. +1.
Spektrum
2
Yang cukup menarik, file (1) itu sendiri mengecualikan 0x7f dari pertimbangan juga, jadi secara teknis Anda harus menggunakan bytearray([7,8,9,10,12,13,27]) + bytearray(range(0x20, 0x7f)) + bytearray(range(0x80, 0x100))sebagai gantinya. Lihat Python, file (1) - Mengapa angka [7,8,9,10,12,13,27] dan range (0x20, 0x100) digunakan untuk menentukan teks vs file biner dan github.com/file/file/ blob /…
Martijn Pieters
@MartijnPieters: terima kasih. Saya telah memperbarui jawaban untuk mengecualikan 0x7f( DEL).
jfs
1
Solusi bagus menggunakan set. :-)
Martijn Pieters
Mengapa Anda mengecualikan 11atau VT? Dalam tabel 11 dianggap teks ASCII biasa, dan ini adalah vertical tab.
darksky
15

Jika Anda menggunakan python3 dengan utf-8 itu lurus ke depan, cukup buka file dalam mode teks dan hentikan pemrosesan jika Anda mendapatkan file UnicodeDecodeError. Python3 akan menggunakan unicode saat menangani file dalam mode teks (dan bytearray dalam mode biner) - jika encoding Anda tidak dapat mendekode file arbitrer, kemungkinan besar Anda akan mendapatkannya UnicodeDecodeError.

Contoh:

try:
    with open(filename, "r") as f:
        for l in f:
             process_line(l)
except UnicodeDecodeError:
    pass # Fond non-text data
skyking
sumber
kenapa tidak menggunakan with open(filename, 'r', encoding='utf-8') as fsecara langsung?
Terry
8

Jika membantu, banyak jenis biner yang dimulai dengan angka ajaib. Berikut adalah daftar file signature.

Shane C. Mason
sumber
Itulah gunanya libmagic. Itu dapat diakses dengan python melalui python-magic .
Bengt
2
Sayangnya, "tidak dimulai dengan angka ajaib yang diketahui" tidak setara dengan "adalah file teks".
Purrell
8

Coba ini:

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
sumber
9
-1 mendefinisikan "biner" sebagai mengandung nol byte. Akan mengklasifikasikan file teks berenkode UTF-16 sebagai "biner".
John Machin
5
@ John Machin: Menariknya, git diffsebenarnya bekerja dengan cara ini , dan tentu saja, ia mendeteksi file UTF-16 sebagai biner.
Intuisi
Hunh .. GNU diffjuga bekerja seperti ini. Ini memiliki masalah yang sama dengan file UTF-16. filemendeteksi file yang sama dengan teks UTF-16 dengan benar. Saya belum memeriksa grepkode, tetapi juga mendeteksi file UTF-16 sebagai biner.
Intuisi
1
+1 @John Machin: utf-16 merupakan data karakter yang menurutnya file(1)tidak aman untuk dicetak tanpa konversi jadi metode ini sesuai untuk kasus ini.
jfs
2
-1 - Saya tidak berpikir 'berisi byte nol' adalah tes yang memadai untuk biner vs teks, misalnya saya dapat membuat file yang berisi semua 0x01 byte atau mengulangi 0xDEADBEEF, tetapi ini bukan file teks. Jawaban berdasarkan file (1) lebih baik.
Sam Watkins
6

Berikut saran yang menggunakan perintah 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)

Contoh penggunaan:

>>> istext ('/ etc / motd') 
Benar
>>> istext ('/ vmlinuz') 
Salah
>>> buka ('/ 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') # berfungsi pada UTF-8
Benar

Ini memiliki kelemahan karena tidak portabel ke Windows (kecuali Anda memiliki sesuatu seperti fileperintah di sana), dan harus menelurkan proses eksternal untuk setiap file, yang mungkin tidak sesuai.

Jacob Gabrielson
sumber
Ini merusak skrip saya :( Selidiki, saya menemukan bahwa beberapa conffile dideskripsikan filesebagai "Konfigurasi beku Sendmail - versi m" —melihat ketiadaan string "teks". Mungkin digunakan file -i?
melissa_boiko
1
TypeError: tidak dapat menggunakan pola string pada objek seperti byte
abg
5

Gunakan perpustakaan binaryornot ( GitHub ).

Ini sangat sederhana dan berdasarkan kode yang ditemukan dalam pertanyaan stackoverflow ini.

Anda sebenarnya dapat menulis ini dalam 2 baris kode, namun paket ini menyelamatkan Anda dari keharusan menulis dan menguji 2 baris kode tersebut secara menyeluruh dengan semua jenis file yang aneh, lintas platform.

guettli
sumber
4

Biasanya Anda harus menebak-nebak.

Anda dapat melihat ekstensi sebagai satu petunjuk, jika file memilikinya.

Anda juga dapat mengenali format biner, dan mengabaikannya.

Jika tidak, lihat proporsi byte ASCII yang tidak dapat dicetak yang Anda miliki dan coba tebak.

Anda juga dapat mencoba mendekode dari UTF-8 dan melihat apakah itu menghasilkan keluaran yang masuk akal.

Douglas Leeder
sumber
4

Solusi yang lebih singkat, dengan peringatan 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
Tom Kennedy
sumber
catatan: for line in filedapat menggunakan jumlah memori yang tidak terbatas hingga b'\n'ditemukan
jfs
ke @Community: ".read()"mengembalikan bytestring di sini yang dapat diulang (menghasilkan byte individu).
jfs
4

Kita dapat menggunakan python itu sendiri untuk memeriksa apakah suatu file biner, karena gagal jika kita mencoba membuka file biner dalam mode teks

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
sumber
Ini gagal untuk banyak file ".avi '(video).
Anmol Singh Jaggi
3

Jika Anda tidak menggunakan Windows, Anda dapat menggunakan Python Magic untuk menentukan jenis file. Kemudian Anda dapat memeriksa apakah itu jenis teks / pantomim.

Kamil Kisiel
sumber
2

Berikut adalah fungsi yang pertama kali memeriksa apakah file dimulai dengan BOM dan jika tidak mencari byte nol dalam 8192 byte awal:

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

Secara teknis pemeriksaan UTF-8 BOM tidak diperlukan karena tidak boleh berisi nol byte untuk semua tujuan praktis. Tetapi karena ini adalah pengkodean yang sangat umum, lebih cepat untuk memeriksa BOM di awal daripada memindai semua 8192 byte untuk 0.

roskakori
sumber
2

Coba gunakan python-magic yang saat ini dipelihara yang bukan modul yang sama dalam jawaban @Kami Kisiel. Ini mendukung semua platform termasuk Windows namun Anda akan membutuhkan libmagicfile biner. Ini dijelaskan di README.

Tidak seperti modul mimetypes , modul ini tidak menggunakan ekstensi file dan sebagai gantinya memeriksa konten 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'
Makan di Joes
sumber
1

Saya datang ke sini mencari hal yang persis sama - solusi komprehensif yang disediakan oleh perpustakaan standar untuk mendeteksi biner atau teks. Setelah meninjau opsi yang disarankan orang, perintah file nix tampaknya menjadi pilihan terbaik (Saya hanya mengembangkan untuk linux boxen). Beberapa orang lain memposting solusi menggunakan file tetapi mereka tidak perlu rumit menurut saya, jadi inilah yang saya dapatkan:

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

Tidak perlu dikatakan lagi, tetapi kode Anda yang memanggil fungsi ini harus memastikan Anda dapat membaca file sebelum mengujinya, jika tidak, ini akan salah mendeteksi file sebagai biner.

gergaji
sumber
1

Saya rasa solusi terbaik adalah dengan menggunakan fungsi guess_type. Ini memegang daftar dengan beberapa mimetypes dan Anda juga dapat memasukkan tipe Anda sendiri. Inilah skrip yang saya lakukan untuk menyelesaikan masalah saya:

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

Itu ada di dalam Kelas, seperti yang Anda lihat berdasarkan struktur kode. Tapi Anda bisa cukup banyak mengubah hal-hal yang ingin Anda terapkan di dalam aplikasi Anda. Ini cukup mudah digunakan. Metode getTextFiles mengembalikan objek daftar dengan semua file teks yang berada di direktori yang Anda berikan di variabel jalur.

Leonardo
sumber
1

di * NIX:

Jika Anda memiliki akses ke perintah fileshell, shlex dapat membantu membuat modul subproses lebih berguna:

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

Atau, Anda juga bisa memasukkannya ke dalam for-loop untuk mendapatkan output untuk semua file di direktori saat ini menggunakan:

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

atau untuk semua subdir:

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

Sebagian besar program menganggap file tersebut biner (yaitu file apa pun yang tidak "berorientasi baris") jika berisi karakter NULL .

Berikut adalah versi perl dari pp_fttext()( pp_sys.c) yang diimplementasikan dengan 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

Perhatikan juga bahwa kode ini ditulis untuk dijalankan pada Python 2 dan Python 3 tanpa perubahan.

Sumber: Perl's "guess if file is text or binary" diimplementasikan dengan Python

kenorb
sumber
0

apakah kamu di unix? jika demikian, coba:

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

Nilai kembali shell dibalik (0 tidak apa-apa, jadi jika menemukan "teks" maka itu akan mengembalikan 0, dan dalam Python itu adalah ekspresi False).

fortran
sumber
Sebagai referensi, perintah file menebak jenis berdasarkan konten file. Saya tidak yakin apakah itu memperhatikan ekstensi file.
David Z
Saya hampir yakin ini terlihat baik di konten maupun ekstensinya.
fortran
Ini rusak jika jalur berisi "teks", tho. Pastikan untuk melakukan rsplit di ':' terakhir (asalkan tidak ada titik dua dalam deskripsi jenis file).
Alan Plum
3
Gunakan filedengan -bsakelar; itu hanya akan mencetak jenis file tanpa jalur.
dubek
2
versi yang sedikit lebih bagus:is_binary_file = lambda filename: "text" in subprocess.check_output(["file", "-b", filename])
jfs
0

Cara yang lebih sederhana adalah dengan memeriksa apakah file tersebut berisi karakter NULL ( \x00) dengan menggunakan inoperator, misalnya:

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

Lihat di bawah contoh lengkapnya:

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

Penggunaan sampel:

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

Dokumentasi

parZval
sumber