Làm cách nào để phát hiện tệp có phải là tệp nhị phân (không phải văn bản) trong python không?


105

Làm cách nào để biết tệp có phải là tệp nhị phân (không phải văn bản) trong python không?

Tôi đang tìm kiếm qua một tập hợp lớn các tệp trong python và tiếp tục nhận được các kết quả phù hợp trong các tệp nhị phân. Điều này làm cho đầu ra trông cực kỳ lộn xộn.

Tôi biết tôi có thể sử dụng grep -I, nhưng tôi đang làm nhiều việc với dữ liệu hơn những gì grep cho phép.

Trước đây, tôi sẽ chỉ tìm kiếm các ký tự lớn hơn 0x7f, nhưng utf8và những thứ tương tự, biến điều đó thành không thể trên các hệ thống hiện đại. Lý tưởng nhất là giải pháp sẽ nhanh chóng, nhưng bất kỳ giải pháp nào cũng sẽ làm được.


NẾU "trước đây tôi chỉ tìm kiếm các ký tự lớn hơn 0x7f" THÌ bạn đã từng làm việc với văn bản ASCII thuần túy THÌ vẫn không có vấn đề gì vì văn bản ASCII được mã hóa là UTF-8 vẫn là ASCII (tức là không có byte> 127).
tzot

@ ΤΖΩΤΖΙΟΥ: Đúng, nhưng tôi tình cờ biết rằng một số tệp tôi đang xử lý là utf8. Ý tôi là quen với ý nghĩa chung, không phải theo nghĩa cụ thể của những tệp này. :)
đau buồn

1
Chỉ với xác suất. Bạn có thể kiểm tra xem: 1) tệp có chứa \ n 2) Số lượng byte giữa \ n là tương đối nhỏ (điều này KHÔNG đáng tin cậy) l 3) tệp không byte có giá trị nhỏ hơn giá trị của ký tự ASCCI "khoảng trắng" ('' ) - NGOẠI TRỪ "\ n" "\ r" "\ t" và các số 0.
SigTerm

3
Chiến lược mà grepbản thân nó sử dụng để xác định các tệp nhị phân tương tự như chiến lược được đăng bởi Jorge Orpinel bên dưới . Trừ khi bạn đặt -ztùy chọn, nó sẽ chỉ quét ký tự null ( "\000") trong tệp. Với -z, nó quét tìm "\200". Những người quan tâm và / hoặc hoài nghi có thể kiểm tra dòng 1126 của grep.c. Xin lỗi, tôi không thể tìm thấy trang web có mã nguồn, nhưng tất nhiên bạn có thể lấy nó từ gnu.org hoặc qua một bản phân phối .
trực giác

3
PS Như đã đề cập trong chuỗi nhận xét cho bài đăng của Jorge, chiến lược này sẽ đưa ra kết quả dương tính giả cho các tệp chứa văn bản UTF-16, chẳng hạn. Tuy nhiên, cả hai git diffvà GNU diffcũng sử dụng cùng một chiến lược. Tôi không chắc liệu nó có phổ biến như vậy không vì nó nhanh hơn và dễ dàng hơn nhiều so với giải pháp thay thế, hay chỉ vì sự hiếm hoi tương đối của các tệp UTF-16 trên các hệ thống có xu hướng cài đặt các utils này.
trực giác

Câu trả lời:


42

Bạn cũng có thể sử dụng mô-đun mimetypes :

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

Khá dễ dàng để biên dịch một danh sách các loại kịch câm nhị phân. Ví dụ: Apache phân phối với tệp mime.types mà bạn có thể phân tích cú pháp thành một tập hợp danh sách, nhị phân và văn bản, sau đó kiểm tra xem kịch câm có trong văn bản hay danh sách nhị phân của bạn hay không.


16
Có cách nào để mimetypessử dụng nội dung của tệp thay vì chỉ tên của nó không?
trực giác

4
@intuited Không, nhưng libmagic làm được điều đó. Sử dụng nó thông qua python-magic .
Kéo

Có một câu hỏi tương tự với một số câu trả lời hay ở đây: stackoverflow.com/questions/1446549/… Câu trả lời dựa trên công thức hoạt động có vẻ tốt với tôi, nó cho phép một tỷ lệ nhỏ các ký tự không in được (nhưng không có \ 0, đối với một số lý do).
Sam Watkins

5
Đây không phải là một câu trả lời tuyệt vời chỉ vì mô-đun mimetypes không tốt cho tất cả các tệp. Tôi đang xem một tệp mà hệ thống filebáo cáo là "Văn bản Unicode UTF-8, với các dòng rất dài" nhưng mimetypes.gest_type () sẽ trả về (Không có, Không có). Ngoài ra, danh sách mimetype của Apache là một danh sách trắng / tập hợp con. Nó không có nghĩa là một danh sách đầy đủ các kiểu kịch câm. Nó không thể được sử dụng để phân loại tất cả các tệp là văn bản hoặc không phải văn bản.
Purrell

1
đoán_types dựa trên phần mở rộng tên tệp, không phải nội dung thực như lệnh Unix "tệp" sẽ làm.
Eric H.

61

Tuy nhiên, một phương pháp khác dựa trên hành vi của tệp (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))

Thí dụ:

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

Có thể nhận được cả âm tính giả và âm tính giả, nhưng vẫn là một cách tiếp cận thông minh phù hợp với phần lớn các tệp. +1.
Spectras

2
Thật thú vị, bản thân tệp (1) cũng loại trừ 0x7f khỏi việc xem xét, vì vậy về mặt kỹ thuật, bạn nên sử dụng bytearray([7,8,9,10,12,13,27]) + bytearray(range(0x20, 0x7f)) + bytearray(range(0x80, 0x100))thay thế. Xem Python, tệp (1) - Tại sao các số [7,8,9,10,12,13,27] và dải ô (0x20, 0x100) được sử dụng để xác định văn bản so với tệp nhị phângithub.com/file/file/ blob /…
Martijn Pieters

@MartijnPieters: cảm ơn bạn. Tôi đã cập nhật câu trả lời để loại trừ 0x7f( DEL).
jfs

1
Giải pháp tốt đẹp bằng cách sử dụng bộ. :-)
Martijn Pieters

Tại sao bạn loại trừ 11hoặc VT? Trong bảng 11 được coi là văn bản ASCII thuần túy, và đây là vertical tab.
darksky,

15

Nếu bạn đang sử dụng python3 với utf-8, nó sẽ thẳng tiến, chỉ cần mở tệp ở chế độ văn bản và dừng xử lý nếu bạn nhận được UnicodeDecodeError. Python3 sẽ sử dụng unicode khi xử lý các tệp ở chế độ văn bản (và bytearray ở chế độ nhị phân) - nếu mã hóa của bạn không thể giải mã các tệp tùy ý thì rất có thể bạn sẽ nhận được UnicodeDecodeError.

Thí dụ:

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

tại sao không sử dụng with open(filename, 'r', encoding='utf-8') as ftrực tiếp?
Terry

8

Nếu nó hữu ích, nhiều loại nhị phân bắt đầu bằng một số kỳ diệu. Đây là danh sách các chữ ký tập tin.


Đó là những gì libmagic dành cho. Nó có thể được truy cập trong python thông qua python-magic .
Kéo dài

2
Thật không may, "không bắt đầu bằng một số ma thuật đã biết" không tương đương với "là một tệp văn bản".
Purrell

8

Thử cái này:

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 định nghĩa "nhị phân" là chứa byte 0. Sẽ phân loại các tệp văn bản được mã hóa UTF-16 là "nhị phân".
John Machin

5
@John Machin: Thật thú vị, git diffthực sự hoạt động theo cách này và chắc chắn, nó phát hiện các tệp UTF-16 dưới dạng tệp nhị phân.
trực giác

Hunh .. GNU diffcũng hoạt động theo cách này. Nó có các vấn đề tương tự với các tệp UTF-16. filekhông phát hiện chính xác các tệp giống như văn bản UTF-16. Tôi chưa kiểm tra grepmã của nhưng nó cũng phát hiện các tệp UTF-16 là tệp nhị phân.
trực giác

1
+1 @John Machin: utf-16 là dữ liệu ký tự theo file(1)đó không an toàn để in mà không cần chuyển đổi nên phương pháp này phù hợp trong trường hợp này.
jfs

2
-1 - Tôi không nghĩ rằng 'chứa byte 0' là một thử nghiệm thích hợp cho nhị phân so với văn bản, ví dụ: tôi có thể tạo tệp chứa tất cả các byte 0x01 hoặc lặp lại 0xDEADBEEF, nhưng nó không phải là tệp văn bản. Câu trả lời dựa trên tệp (1) là tốt hơn.
Sam Watkins

6

Đây là một gợi ý sử dụng lệnh tệp 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)

Ví dụ sử dụng:

>>> istext ('/ etc / motd') 
Thật
>>> istext ('/ vmlinuz') 
Sai
>>> 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') # hoạt động trên UTF-8
Thật

Nó có nhược điểm là không thể di động vào Windows (trừ khi bạn có một cái gì đó giống như filelệnh ở đó) và phải tạo ra một quy trình bên ngoài cho mỗi tệp, điều này có thể không ngon.


Này đã phá vỡ kịch bản của tôi :( Điều tra, tôi phát hiện ra rằng một số conffiles được mô tả bởi filenhư. "Sendmail đông lạnh cấu hình - phiên bản m" -notice sự vắng mặt của chuỗi "text" Có lẽ sử dụng file -i?
melissa_boiko

1
Lỗi Loại: không thể sử dụng một mô hình chuỗi trên một byte giống như đối tượng
ABG

5

Sử dụng thư viện binaryornot ( GitHub ).

Nó rất đơn giản và dựa trên mã được tìm thấy trong câu hỏi stackoverflow này.

Bạn thực sự có thể viết điều này trong 2 dòng mã, tuy nhiên gói này giúp bạn không phải viết và kiểm tra kỹ lưỡng 2 dòng mã đó với đủ loại tệp kỳ lạ, đa nền tảng.


4

Thường thì bạn phải đoán.

Bạn có thể xem các phần mở rộng như một đầu mối, nếu các tệp có chúng.

Bạn cũng có thể nhận ra các định dạng nhị phân biết và bỏ qua những định dạng đó.

Nếu không, hãy xem tỷ lệ byte ASCII không in được bạn có và phỏng đoán từ đó.

Bạn cũng có thể thử giải mã từ UTF-8 và xem điều đó có tạo ra đầu ra hợp lý hay không.


4

Một giải pháp ngắn hơn, với cảnh báo 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

lưu ý: for line in filecó thể sử dụng số lượng bộ nhớ không giới hạn cho đến khi b'\n'được tìm thấy
jfs

để @Community: ".read()"trả về một bytestring đây mà iterable (nó mang lại byte cá nhân).
jfs

4

Chúng tôi có thể sử dụng chính python để kiểm tra xem tệp có phải là tệp nhị phân hay không, vì nó sẽ không thành công nếu chúng tôi cố gắng mở tệp nhị phân ở chế độ văn bản

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

Điều này không thành công đối với nhiều tệp `.avi '(video).
Anmol Singh Jaggi

3

Nếu bạn không sử dụng Windows, bạn có thể sử dụng Python Magic để xác định loại tệp. Sau đó, bạn có thể kiểm tra xem đó có phải là kiểu văn bản / kịch câm hay không.


2

Đây là một hàm kiểm tra đầu tiên nếu tệp bắt đầu bằng BOM và nếu không tìm byte 0 trong 8192 byte ban đầu:

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

Về mặt kỹ thuật, việc kiểm tra UTF-8 BOM là không cần thiết vì nó không được chứa byte 0 cho mọi mục đích thực tế. Nhưng vì đây là kiểu mã hóa rất phổ biến nên việc kiểm tra BOM ngay từ đầu sẽ nhanh hơn thay vì quét tất cả 8192 byte cho 0.


2

Hãy thử sử dụng phép thuật python hiện đang được duy trì không phải là cùng một mô-đun trong câu trả lời của @Kami Kisiel. Điều này hỗ trợ tất cả các nền tảng bao gồm cả Windows, tuy nhiên bạn sẽ cần các libmagictệp nhị phân. Điều này được giải thích trong README.

Không giống như mô-đun mimetypes , nó không sử dụng phần mở rộng của tệp và thay vào đó kiểm tra nội dung của tệp.

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

Tôi đến đây để tìm kiếm chính xác điều tương tự - một giải pháp toàn diện được cung cấp bởi thư viện tiêu chuẩn để phát hiện nhị phân hoặc văn bản. Sau khi xem xét các tùy chọn mà mọi người đề xuất, lệnh tệp nix có vẻ là lựa chọn tốt nhất (tôi chỉ đang phát triển cho linux boxen). Một số người khác đã đăng các giải pháp bằng cách sử dụng tệp nhưng chúng phức tạp không cần thiết theo ý kiến ​​của tôi, vì vậy đây là những gì tôi đã đưa ra:

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

Không cần phải nói, nhưng mã của bạn gọi hàm này phải đảm bảo rằng bạn có thể đọc tệp trước khi kiểm tra nó, nếu không điều này sẽ bị phát hiện nhầm tệp là tệp nhị phân.


1

Tôi đoán rằng giải pháp tốt nhất là sử dụng hàm đoán_type. Nó chứa một danh sách với một số kiểu mimetype và bạn cũng có thể bao gồm các kiểu của riêng mình. Đây là tập lệnh mà tôi đã làm để giải quyết vấn đề của mình:

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

Nó nằm bên trong một Lớp, như bạn có thể thấy dựa trên cấu trúc của mã. Nhưng bạn có thể thay đổi khá nhiều thứ bạn muốn triển khai nó bên trong ứng dụng của mình. Nó khá đơn giản để sử dụng. Phương thức getTextFiles trả về một đối tượng danh sách với tất cả các tệp văn bản nằm trên thư mục mà bạn chuyển vào biến đường dẫn.


1

trên * NIX:

Nếu bạn có quyền truy cập vào lệnh fileshell, shlex có thể giúp làm cho mô-đun quy trình con hữu dụng hơn:

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

Hoặc, bạn cũng có thể gắn nó vào vòng lặp để lấy đầu ra cho tất cả các tệp trong dir hiện tại bằng cách sử dụng:

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

hoặc cho tất cả các thứ tự con:

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

Hầu hết các chương trình coi tệp là nhị phân (là bất kỳ tệp nào không phải là "hướng dòng") nếu nó chứa ký tự NULL .

Đây là phiên bản perl của pp_fttext()( pp_sys.c) được triển khai bằng 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

Cũng lưu ý rằng mã này được viết để chạy trên cả Python 2 và Python 3 mà không có thay đổi.

Nguồn: Perl's "đoán xem tệp là văn bản hay nhị phân" được triển khai bằng Python


0

bạn đang ở unix? nếu vậy, hãy thử:

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

Các giá trị trả về của trình bao được đảo ngược (0 là được, vì vậy nếu nó tìm thấy "văn bản" thì nó sẽ trả về 0 và trong Python đó là biểu thức Sai).


Để tham khảo, lệnh tệp đoán một loại dựa trên nội dung của tệp. Tôi không chắc liệu nó có chú ý đến phần mở rộng tệp hay không.
David Z

Tôi gần như chắc chắn rằng nó trông cả trong nội dung và phần mở rộng.
fortran

Điều này sẽ bị phá vỡ nếu đường dẫn chứa "văn bản", tho. Đảm bảo rsplit ở cuối ':' (miễn là không có dấu hai chấm trong mô tả loại tệp).
Alan Plum

3
Sử dụng filevới công -btắc; nó sẽ chỉ in loại tệp không có đường dẫn.
dubek

2
một phiên bản đẹp hơn một chút:is_binary_file = lambda filename: "text" in subprocess.check_output(["file", "-b", filename])
JFS

0

Cách đơn giản hơn là kiểm tra xem tệp có chứa ký tự NULL ( \x00) hay không bằng cách sử dụng intoán tử, ví dụ:

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

Xem ví dụ hoàn chỉnh bên dưới:

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

Sử dụng mẫu:

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.