Pythonでファイルがバイナリ(テキスト以外)かどうかを検出するにはどうすればよいですか?


105

Pythonでファイルがバイナリ(非テキスト)かどうかを確認するにはどうすればよいですか?

私はpythonで大量のファイルセットを検索していて、バイナリファイルで一致を取得し続けています。これにより、出力が非常に乱雑に見えます。

を使用できることはわかっgrep -Iていますが、grepで許可されている以上のデータを使用しています。

以前は、を超える文字を検索しただけでした0x7futf8、現代のシステムではこれを不可能にしていました。理想的にはソリューションは高速ですが、どのソリューションでも十分です。


「過去に0x7fより大きい文字を検索しただけだった」場合は、プレーンASCIIテキストを処理するために使用していましたが、UTF-8としてエンコードされたASCIIテキストはASCII(つまり、バイト> 127)であるため、問題はありません。
tzot 2009年

@ΤΖΩΤΖΙΟΥ:本当ですが、私が扱っているファイルの一部がutf8であることを知っています。これらのファイルの特定の意味ではなく、一般的な意味で使用することを意味しました。:)
悲しむ

1
確率でのみ。次のことを確認できます。1)ファイルに\ nが含まれている2)\ n間のバイト数が比較的小さい(これは信頼できない)l 3)ファイルがASCCI "スペース"文字( '' )-「\ n」「\ r」「\ t」とゼロ以外。
SigTerm

3
grepそれ自体がバイナリファイルを識別するために使用する戦略は、以下の Jorge Orpinelによって投稿されたものと似ています。この-zオプションを設定しない限り"\000"、ファイル内のnull文字()がスキャンされるだけです。では-z、をスキャンし"\200"ます。興味がある人や懐疑的な人は、1126行目を確認できgrep.cます。ソースコードのあるウェブページは見つかりませんでしたが、もちろん、gnu.orgまたはディストリビューションから入手できます。
2010年

3
PS Jorgeの投稿のコメントスレッドで述べたように、この戦略は、たとえばUTF-16テキストを含むファイルに誤検知を与えます。それにもかかわらず、両方git diffとGNU diffも同じ戦略を使用します。他の方法よりもはるかに高速で簡単であるために普及しているのか、またはこれらのユーティリティがインストールされているシステム上のUTF-16ファイルが比較的希少であるためだけなのか、私にはわかりません。
2010年

回答:


42

mimetypesモジュールを使用することもできます:

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

バイナリMIMEタイプのリストをコンパイルするのはかなり簡単です。たとえば、Apacheはmime.typesファイルを配布して、バイナリとテキストのリストに解析してから、mimeがテキストリストまたはバイナリリストにあるかどうかを確認できます。


16
mimetypes名前だけでなくファイルの内容を使用する方法はありますか?
2010年

4
@intuitedいいえ、ただしlibmagicはそれを行います。python-magicを介して使用します。
Bengt

stackoverflow.com/questions/1446549/…ここにいくつかの良い答えのある同様の質問があります: activestateレシピに基づく答えは私にはよく見えます、それは印刷できない文字の小さな割合を許可します理由)。
Sam Watkins

5
mimetypesモジュールがすべてのファイルに適しているわけではないため、これは良い答えではありません。システムfileが「UTF-8 Unicodeテキスト、非常に長い行を含む」と報告するファイルを探していますが、mimetypes.gest_type()は(なし、なし)を返します。また、Apacheのmimetypeリストはホワイトリスト/サブセットです。これは決してMIMEタイプの完全なリストではありません。すべてのファイルをテキストまたは非テキストとして分類するために使用することはできません。
Purrell、2015

1
guess_typesはファイル名拡張子に基づいており、Unixコマンド「file」が行うような実際の内容ではありません。
エリック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。
2015

2
興味深いことに、file(1)自体も0x7fを考慮から除外しているため、技術的にはbytearray([7,8,9,10,12,13,27]) + bytearray(range(0x20, 0x7f)) + bytearray(range(0x80, 0x100))代わりに使用する必要があります。Python、file(1)を参照してください。テキストとバイナリファイル、およびgithub.com/file/file/
Martijn Pieters

@MartijnPieters:ありがとう。0x7fDEL)を除外するように回答を更新しました。
jfs 2015

1
セットを使用した素晴らしい解決策。:-)
Martijn Pieters

なぜあなたは除外する11VT?表では、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

これを試して:

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は、「バイナリ」をゼロバイトを含むものとして定義します。UTF-16でエンコードされたテキストファイルを「バイナリ」として分類します。
John Machin

5
@John Machin:興味深いことに、git diff実際にはこの方法動作します。確かに、UTF-16ファイルをバイナリとして検出します。
2010年

フン.. GNU diffもこのように動作します。UTF-16ファイルでも同様の問題があります。 file同じファイルをUTF-16テキストとして正しく検出します。私はまだgrepコードをチェックアウトしていませんが、UTF-16ファイルをバイナリとして検出します。
2010年

1
+1 @John Machin:utf-16は文字データであり、file(1)変換せずに印刷するのは安全ではないため、この方法はこの場合に適しています。
JFS

2
-1-「0バイトを含む」はバイナリとテキストの適切なテストではないと思います。たとえば、すべての0x01バイトを含むファイルを作成したり、0xDEADBEEFを繰り返したりできますが、テキストファイルではありません。file(1)に基づく答えの方が優れています。
Sam Watkins

6

Unix fileコマンドを使用する提案は次のとおりです

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で動作します
本当

fileそこにコマンドのようなものがない限り)Windowsに移植できないこと、そして各ファイルごとに外部プロセスを生成する必要があるという欠点があります。


これが私のスクリプトを破った:(調査、私はいくつかのconffilesではで記述されていることを発見したfileとして「Sendmailの凍結されたコンフィギュレーション-バージョンmを」文字列「テキスト」の不在-noticeおそらく使用しています。file -i
melissa_boiko

1
TypeError:バイトのようなオブジェクトでは文字列パターンを使用できません
abg

5

binaryornotライブラリ(GitHub)を使用します。

これは非常にシンプルで、このstackoverflowの質問にあるコードに基づいています。

これは実際には2行のコードで記述できますが、このパッケージを使用すると、クロスプラットフォームのあらゆる種類の奇妙なファイルタイプを使用して、これらの2行のコードを記述して完全にテストする必要がなくなります。


4

通常は推測する必要があります。

ファイルに拡張子がある場合は、1つの手掛かりとして拡張子を確認できます。

また、既知のバイナリ形式を認識して無視することもできます。

それ以外の場合は、印刷できない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()"それここにバイト文字列を返している反復可能(それは個々のバイトを生成します)。
jfs 14

4

テキストモードでバイナリファイルを開こうとすると失敗するため、Python自体を使用してファイルがバイナリかどうかを確認できます。

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を使用してファイルタイプを判別できます。次に、それがテキスト/ MIMEタイプであるかどうかを確認できます。


2

ファイルがBOMで始まるかどうかを最初にチェックし、そうでない場合は最初の8192バイト内のゼロバイトを探す関数を次に示します。

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のチェックは必要ありません。実用上、ゼロバイトを含むべきではないからです。ただし、これは非常に一般的なエンコーディングであるため、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 fileコマンドが最良の選択であるように見えます(私は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

コードのustructureに基づいて確認できるように、クラスの内部にあります。しかし、アプリケーション内に実装したいものをかなり変更することができます。使い方はいたって簡単です。getTextFilesメソッドは、パス変数に渡したディレクトリにあるすべてのテキストファイルを含むリストオブジェクトを返します。


1

* NIXの場合:

fileシェルコマンドにアクセスできる場合、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で実装されたpp_fttext()pp_sys.c)のperlのバージョンです。

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

あなたはunixにいますか?もしそうなら、それを試してください:

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

シェルの戻り値は逆になります(0は問題ないため、「テキスト」が見つかった場合は0を返し、PythonではFalse式です)。


参考までに、fileコマンドはファイルの内容に基づいてタイプを推測します。それがファイル拡張子に注意を払っているかどうかはわかりません。
デビッドZ

私はそれがコンテンツと拡張機能の両方で見えると確信しています。
Fortranは2009年

パスに「テキスト」が含まれている場合、これは機能しません。最後の ':'で必ずrsplitを実行してください(ファイルタイプの説明にコロンがない場合)。
アランプラム

3
スイッチfileと一緒に使用してください-b。パスを含まないファイルタイプのみを印刷します。
dubek 2009

2
少しいいバージョン:is_binary_file = lambda filename: "text" in subprocess.check_output(["file", "-b", filename])
jfs

0

より簡単な方法は、演算子\x00を使用してファイルがNULL文字()で構成されているかどうかを確認することです。inたとえば、

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.