파이썬에서 파일이 바이너리 (비 텍스트)인지 어떻게 감지 할 수 있습니까?


105

파이썬에서 파일이 바이너리 (텍스트가 아닌)인지 어떻게 알 수 있습니까?

파이썬에서 큰 파일 집합을 검색하고 있으며 이진 파일에서 계속 일치 항목을 얻습니다. 이것은 출력이 엄청나게 지저분하게 보입니다.

을 사용할 수 있다는 것을 알고 grep -I있지만 grep이 허용하는 것보다 더 많은 데이터를 사용하고 있습니다.

과거에, 난 그냥 이상의 문자를 검색 할 것이다 0x7f, 그러나 utf8와 같은은 현대적인 시스템이 불가능합니다. 이상적으로 솔루션은 빠르지 만 모든 솔루션이 가능합니다.


"과거에는 0x7f보다 큰 문자를 검색했을 것입니다."그런 다음 일반 ASCII 텍스트로 작업하는 데 사용했던 경우 UTF-8로 인코딩 된 ASCII 텍스트가 ASCII로 남아 있기 때문에 여전히 문제가 없습니다 (즉, 바이트> 127).
tzot

@ ΤΖΩΤΖΙΟΥ : 사실이지만 내가 다루는 파일 중 일부가 utf8이라는 것을 알고 있습니다. 나는 이러한 파일의 특정 의미가 아니라 일반적인 의미로 익숙하다는 것을 의미했습니다. :)
슬픔

1
확률로만. 다음을 확인할 수 있습니다. 1) 파일에 \ n이 포함되어 있습니다. 2) \ n 사이의 바이트 수가 상대적으로 작습니다 (신뢰할 수 없음) l 3) 파일이 ASCCI "공백"문자 ( ''의 값보다 작은 값을 가진 바이트가 아닌 경우) )- "\ n" "\ r" "\ t"및 0 제외.
SigTerm 2010 년

3
grep바이너리 파일을 식별하기 위해 자체적으로 사용하는 전략은 아래의 Jorge Orpinel이 게시 한 전략 과 유사 합니다 . -z옵션 을 설정하지 않으면 "\000"파일에서 널 문자 ( ) 만 스캔 합니다. 를 사용 -z하여 "\200". 관심이 있거나 회의적인 사람들은의 1126 행을 확인할 수 있습니다 grep.c. 죄송합니다. 소스 코드가있는 웹 페이지를 찾을 수 없습니다. 물론 gnu.org 또는 배포판을 통해 얻을 수 있습니다 .
intuited

3
추신 Jorge의 게시물에 대한 댓글 스레드에서 언급했듯이이 전략은 예를 들어 UTF-16 텍스트를 포함하는 파일에 대해 오 탐지를 제공합니다. 그럼에도 불구 git diff하고 GNU와 GNU diff는 동일한 전략을 사용합니다. 대안보다 훨씬 빠르고 쉽기 때문에 널리 퍼져 있는지 또는 이러한 유틸리티가 설치되는 경향이있는 시스템에서 UTF-16 파일이 상대적으로 드물기 때문인지 확실하지 않습니다.
intuited

답변:


42

mimetypes 모듈을 사용할 수도 있습니다 .

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

바이너리 MIME 유형 목록을 컴파일하는 것은 매우 쉽습니다. 예를 들어 Apache는 목록, 바이너리 및 텍스트 세트로 구문 분석 할 수있는 mime.types 파일과 함께 배포 한 다음 mime이 텍스트 또는 바이너리 목록에 있는지 확인합니다.


16
이름이 mimetypes아닌 파일의 내용을 사용 하는 방법이 있습니까?
intuited

4
@intuited 아니요,하지만 libmagic은 그렇게합니다. python-magic을 통해 사용하십시오 .
벤 구토

여기에 좋은 답변이있는 비슷한 질문이 있습니다. stackoverflow.com/questions/1446549/… 활성 상태 레시피를 기반으로 한 답변은 나에게 좋게 보이지만 인쇄 할 수없는 문자의 작은 비율을 허용합니다 (일부는 \ 0이 아님). 이유).
Sam Watkins 2013 년

5
mimetypes 모듈이 모든 파일에 적합하지 않기 때문에 이것은 좋은 대답이 아닙니다. 지금 시스템 file이 "UTF-8 유니 코드 텍스트, 매우 긴 줄"로보 고하는 파일을 보고 있지만 mimetypes.gest_type ()은 (None, None)을 반환합니다. 또한 Apache의 mimetype 목록은 화이트리스트 / 하위 집합입니다. MIME 유형의 전체 목록은 결코 아닙니다. 모든 파일을 텍스트 또는 비 텍스트로 분류하는 데 사용할 수 없습니다.
Purrell

1
guess_types는 파일 이름 확장명을 기반으로하며 Unix 명령 "file"이 수행하는 실제 내용이 아닙니다.
Eric H.

61

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

예:

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

오탐과 오탐을 모두 얻을 수 있지만 여전히 대부분의 파일에서 작동하는 영리한 접근 방식입니다. +1.
스펙트럼은

2
흥미롭게도 file (1) 자체도 고려 대상에서 0x7f를 제외하므로 기술적으로 말하면 bytearray([7,8,9,10,12,13,27]) + bytearray(range(0x20, 0x7f)) + bytearray(range(0x80, 0x100))대신 사용해야합니다 . 참조 파이썬, 파일 (1) - 왜 [7,8,9,10,12,13,27] 및 범위 (0x20에,로 0x100)이 바이너리 파일 대 텍스트를 결정하기 위해 사용되는 숫자github.com/file/file/은 blob /…
Martijn Pieters

@MartijnPieters : 감사합니다. 0x7f( DEL) 제외하도록 답변을 업데이트했습니다 .
jfs

1
세트를 사용하는 좋은 솔루션. :-)
Martijn Pieters

11또는 제외하는 이유는 무엇 VT입니까? 표에서 11은 일반 ASCII 텍스트로 간주되며 vertical tab.
darksky

15

utf-8과 함께 python3을 사용하는 경우 간단합니다. 텍스트 모드에서 파일을 열고 UnicodeDecodeError. Python3은 텍스트 모드 (및 바이너리 모드에서는 바이트 배열)에서 파일을 처리 할 때 유니 코드를 사용합니다. 인코딩이 임의의 파일을 디코딩 할 수없는 경우 UnicodeDecodeError.

예:

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

with open(filename, 'r', encoding='utf-8') as f직접 사용하지 않는 이유는 무엇입니까?
Terry

8

도움이된다면 많은 이진 유형이 매직 넘버로 시작됩니다. 다음은 파일 서명 목록 입니다.


이것이 libmagic의 목적입니다. python-magic을 통해 python 에서 액세스 할 수 있습니다 .
Bengt

2
안타깝게도 "알려진 매직 넘버로 시작하지 않음"은 "텍스트 파일"과 동일하지 않습니다.
Purrell

8

이 시도:

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은 "이진"을 0 바이트를 포함하는 것으로 정의합니다. UTF-16으로 인코딩 된 텍스트 파일을 "바이너리"로 분류합니다.
John Machin

5
@John Machin : 흥미롭게도 git diff실제로 이런 방식으로 작동 하며 확실히 UTF-16 파일을 바이너리로 감지합니다.
intuited

Hunh .. GNU diff도 이런 식으로 작동합니다. UTF-16 파일과 비슷한 문제가 있습니다. fileUTF-16 텍스트와 동일한 파일을 올바르게 감지합니다. grep의 코드를 확인하지 않았지만 UTF-16 파일도 바이너리로 감지합니다.
intuited

1
+1 @John Machin : utf-16은 file(1)변환없이 인쇄하기에 안전하지 않은 문자 데이터이므로이 경우이 방법이 적합합니다.
jfs 2011 년

2
-1- 'contains a zero byte'가 바이너리 대 텍스트에 대한 적절한 테스트라고 생각하지 않습니다. 예를 들어 모든 0x01 바이트를 포함하는 파일을 만들거나 0xDEADBEEF를 반복 할 수 있지만 텍스트 파일은 아닙니다. file (1)을 기반으로 한 대답이 더 좋습니다.
Sam Watkins 2013 년

6

다음은 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)

사용 예 :

>>> istext ( '/ etc / motd') 
진실
>>> istext ( '/ vmlinuz') 
그릇된
>>> 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') # UTF-8에서 작동합니다.
진실

Windows로 이식 할 수 없다는 단점이 있으며 ( file명령어 와 같은 것이없는 경우), 각 파일에 대해 외부 프로세스를 생성해야하므로 맛이 좋지 않을 수 있습니다.


이것은 내 스크립트를 부러 :( 조사, 좀 conffiles에 의해 설명되어 있음을 발견 file한다. - 문자열 "텍스트"의 부재 -notice "센드 메일 냉동 구성 버전 m"아마도 사용 file -i?
melissa_boiko

1
TypeError : 바이트와 같은 객체에 문자열 패턴을 사용할 수 없습니다
abg

5

사용 binaryornot의 라이브러리 ( GitHub의 ).

이것은 매우 간단하며이 stackoverflow 질문에서 찾은 코드를 기반으로합니다.

실제로 2 줄의 코드로 작성할 수 있지만,이 패키지를 사용하면 모든 종류의 이상한 파일 유형 인 크로스 플랫폼으로 2 줄의 코드를 작성하고 철저하게 테스트 할 필요가 없습니다.


4

보통 추측해야합니다.

파일에 확장자가 있으면 확장자를 하나의 단서로 볼 수 있습니다.

알고있는 바이너리 형식을 인식하고 무시할 수도 있습니다.

그렇지 않으면 인쇄 할 수없는 ASCII 바이트의 비율을 확인하고 그로부터 추측하십시오.

UTF-8에서 디코딩을 시도하고 이것이 적절한 출력을 생성하는지 확인할 수도 있습니다.


4

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

참고 : 발견 for line in file될 때까지 무제한의 메모리를 사용할 수 있습니다b'\n'
jfs 2014

@Community에 : ".read()"• 그래도 여기 bytestring 반환 이다 반복자 (는 개별 바이트를 산출한다).
jfs 2014

4

텍스트 모드에서 바이너리 파일을 열려고하면 실패하기 때문에 파이썬 자체를 사용하여 파일이 바이너리인지 확인할 수 있습니다.

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

이것은 많은`.avi '(비디오) 파일에서 실패합니다.
Anmol Singh Jaggi

3

Windows가 아닌 경우 Python Magic 을 사용하여 파일 형식을 확인할 수 있습니다 . 그런 다음 텍스트 / 마임 유형인지 확인할 수 있습니다.


2

다음은 파일이 BOM으로 시작하는지 먼저 확인하고 그렇지 않은 경우 초기 8192 바이트 내에서 0 바이트를 찾는 함수입니다.

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

기술적으로 UTF-8 BOM에 대한 검사는 모든 실제 목적을 위해 0 바이트를 포함하지 않아야하므로 불필요합니다. 그러나 매우 일반적인 인코딩이므로 모든 8192 바이트에서 0을 스캔하는 대신 처음에 BOM을 확인하는 것이 더 빠릅니다.


2

@Kami Kisiel의 답변에서 동일한 모듈이 아닌 현재 유지 관리되는 python-magic 을 사용해보십시오 . 이것은 Windows를 포함한 모든 플랫폼을 지원하지만 libmagic바이너리 파일 이 필요 합니다. 이것은 README에 설명되어 있습니다.

mimetypes 모듈 과 달리 파일의 확장자를 사용하지 않고 대신 파일의 내용을 검사합니다.

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

이진 또는 텍스트를 감지하기 위해 표준 라이브러리에서 제공하는 포괄적 인 솔루션 인 정확히 똑같은 것을 찾고 있습니다. 사람들이 제안한 옵션을 검토 한 후 nix 파일 명령이 최선의 선택 인 것 같습니다 (저는 Linux boxen 용으로 만 개발 중입니다). 일부 다른 사람들은 파일 을 사용하여 솔루션을 게시 했지만 내 의견으로는 불필요하게 복잡하므로 다음과 같이 생각해 냈습니다.

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

말할 필요도없이이 함수를 호출하는 코드는 파일을 테스트하기 전에 파일을 읽을 수 있는지 확인해야합니다. 그렇지 않으면 파일을 바이너리로 잘못 감지하게됩니다.


1

가장 좋은 해결책은 guess_type 함수를 사용하는 것입니다. 여러 MIME 유형이있는 목록을 보유하고 있으며 고유 한 유형을 포함 할 수도 있습니다. 내 문제를 해결하기 위해 내가 한 스크립트가 있습니다.

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

코드의 구조를 기반으로 볼 수 있듯이 클래스 내부에 있습니다. 그러나 응용 프로그램 내에서 구현하려는 것을 거의 변경할 수 있습니다. 사용하기 매우 간단합니다. getTextFiles 메소드는 경로 변수에 전달한 디렉토리에있는 모든 텍스트 파일이있는 목록 오브젝트를 리턴합니다.


1

* NIX :

fileshell-command에 액세스 할 수있는 경우 shlex는 하위 프로세스 모듈을 더 유용하게 만드는 데 도움이 될 수 있습니다.

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

또는 다음을 사용하여 현재 디렉토리의 모든 파일에 대한 출력을 얻기 위해 for 루프에 붙일 수도 있습니다.

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

또는 모든 하위 디렉토리에 대해 :

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

대부분의 프로그램은 파일에 NULL 문자 가 포함 된 경우 파일을 바이너리 ( "라인 지향"이 아닌 파일)로 간주합니다 .

다음은 Python으로 구현 된 perl의 pp_fttext()( pp_sys.c) 버전입니다 .

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

이 코드는 변경없이 Python 2와 Python 3 모두에서 실행되도록 작성되었습니다.

출처 : Python으로 구현 된 Perl의 "파일이 텍스트인지 바이너리인지 추측"


0

당신은 유닉스에 있습니까? 그렇다면 다음을 시도하십시오.

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

셸 반환 값은 반전됩니다 (0은 괜찮으므로 "text"를 찾으면 0을 반환하고 Python에서는 False 표현식).


참고로 file 명령은 파일의 내용을 기반으로 유형을 추측합니다. 파일 확장자에주의를 기울이고 있는지 잘 모르겠습니다.
David Z

나는 그것이 내용과 확장 모두에서 보일 것이라고 거의 확신합니다.
fortran

경로에 "텍스트"가 포함되어 있으면 중단됩니다. 마지막 ':'에서 rsplit해야합니다 (파일 유형 설명에 콜론이없는 경우).
Alan Plum

3
스위치 file와 함께 사용하십시오 -b. 경로없이 파일 유형 만 인쇄합니다.
dubek 2009

2
약간 더 좋은 버전 :is_binary_file = lambda filename: "text" in subprocess.check_output(["file", "-b", filename])
jfs 2011 년

0

더 간단한 방법은 파일이 NULL 문자 (\x00in 연산자 를 사용하여 ) .

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

전체 예는 아래를 참조하십시오.

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

샘플 사용법 :

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.