web-dev-qa-db-ja.com

ファイルがC#のバイナリかテキストかを判断するにはどうすればよいですか?

ファイルがバイナリかテキストかを80%で判断する必要がありますが、C#で迅速かつダーティ/ ugいことを行う方法はありますか?

51
Pablo Retyk

通常、バイナリファイルには存在するがテキストファイルにはほとんど存在しない、豊富な制御文字を探します。バイナリファイルは0を十分に使用する傾向があるため、ほとんどのファイルをキャッチするには、多くの0バイトをテストするだけでおそらく十分です。ローカライズに関心がある場合は、マルチバイトパターンもテストする必要があります。

ただし、述べたように、あなたは常に不運で、テキストのように見える、またはその逆のバイナリファイルを取得できます。

21
Ron Warholic

マルコフ連鎖と呼ばれる方法があります。両方の種類のいくつかのモデルファイルをスキャンし、0〜255の各バイト値について、後続の値の統計(基本的に確率)を収集します。これにより、ランタイムファイルを(%のしきい値内で)比較できる64Kb(256x256)プロファイルが得られます。

おそらく、これがブラウザの自動検出エンコーディング機能の仕組みです。

31
zvolkov

これらの投稿やフォーラムで役立つので、他の人にも役立つことを願って、私のソリューションを共有してください。

バックグラウンド

私は同じための解決策を研究し、調査してきました。しかし、私はそれが単純であるか、わずかにねじれていると思っていました。

ただし、ほとんどの試みはここで複雑なソリューションと他のソースを提供し、Unicode、 TF-series 、BOM、エンコーディング、バイトオーダーに飛び込みます。その過程で、私もオフロードに行き、 Ascii Tables and Code pages にも行きました。

とにかく、ストリームリーダーとカスタム制御文字チェックのアイデアに基づいたソリューションを思いつきました。

フォーラムやその他の場所で提供されるさまざまなヒントを考慮して構築されています。

  1. 複数の連続したヌル文字を探すなど、多くの制御文字を確認します。
  2. UTF、Unicode、エンコーディング、BOM、バイトオーダーなどの側面を確認してください。

私の目標は:

  1. バイトオーダー、エンコーディング、その他のより複雑な難解な作業に依存するべきではありません。
  2. 比較的簡単に実装でき、理解しやすいものでなければなりません。
  3. すべての種類のファイルで動作するはずです。

提示されたソリューションは、mp3、eml、txt、info、flv、mp4、pdf、gif、png、jpgを含むテストデータで機能します。これまでのところ期待どおりの結果が得られます。

ソリューションの仕組み

デフォルトで TF8Encoding を使用するファイルエンコーディング関連の特性の決定に関して、 StreamReaderのデフォルトコンストラクター に依存しています。

Char.IsControl は役に立たないように見えるため、独自のバージョンのカスタムコントロールchar条件のチェックを作成しました。それは言います:

制御文字とは、ACK、BEL、CR、FF、LF、VTなどのフォーマットおよびその他の非印刷文字です。 Unicode標準は、制御文字に\ U0000から\ U001F、\ U007F、および\ U0080から\ U009Fのコードポイントを割り当てます。これらの値は、アプリケーションで使用が定義されていない限り、制御文字として解釈されます。 LFおよびCRを制御文字として他のものと見なします

テキストファイルには少なくともCRおよびLF。

解決

static void testBinaryFile(string folderPath)
{
    List<string> output = new List<string>();
    foreach (string filePath in getFiles(folderPath, true))
    {
        output.Add(isBinary(filePath).ToString() + "  ----  " + filePath);
    }
    Clipboard.SetText(string.Join("\n", output), TextDataFormat.Text);
}

public static List<string> getFiles(string path, bool recursive = false)
{
    return Directory.Exists(path) ?
        Directory.GetFiles(path, "*.*",
        recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly).ToList() :
        new List<string>();
}    

public static bool isBinary(string path)
{
    long length = getSize(path);
    if (length == 0) return false;

    using (StreamReader stream = new StreamReader(path))
    {
        int ch;
        while ((ch = stream.Read()) != -1)
        {
            if (isControlChar(ch))
            {
                return true;
            }
        }
    }
    return false;
}

public static bool isControlChar(int ch)
{
    return (ch > Chars.NUL && ch < Chars.BS)
        || (ch > Chars.CR && ch < Chars.SUB);
}

public static class Chars
{
    public static char NUL = (char)0; // Null char
    public static char BS = (char)8; // Back Space
    public static char CR = (char)13; // Carriage Return
    public static char SUB = (char)26; // Substitute
}

上記の解決策を試してみる場合、それがあなたのために働くかどうか教えてください。

その他の興味深く関連するリンク:

13
bhavik shah

ここでの本当の質問が「このファイルを変更せずにStreamReader/StreamWriterを使用して読み書きできるか」という場合、答えはここにあります。

/// <summary>
/// Detect if a file is text and detect the encoding.
/// </summary>
/// <param name="encoding">
/// The detected encoding.
/// </param>
/// <param name="fileName">
/// The file name.
/// </param>
/// <param name="windowSize">
/// The number of characters to use for testing.
/// </param>
/// <returns>
/// true if the file is text.
/// </returns>
public static bool IsText(out Encoding encoding, string fileName, int windowSize)
{
    using (var fileStream = File.OpenRead(fileName))
    {
    var rawData = new byte[windowSize];
    var text = new char[windowSize];
    var isText = true;

    // Read raw bytes
    var rawLength = fileStream.Read(rawData, 0, rawData.Length);
    fileStream.Seek(0, SeekOrigin.Begin);

    // Detect encoding correctly (from Rick Strahl's blog)
    // http://www.west-wind.com/weblog/posts/2007/Nov/28/Detecting-Text-Encoding-for-StreamReader
    if (rawData[0] == 0xef && rawData[1] == 0xbb && rawData[2] == 0xbf)
    {
        encoding = Encoding.UTF8;
    }
    else if (rawData[0] == 0xfe && rawData[1] == 0xff)
    {
        encoding = Encoding.Unicode;
    }
    else if (rawData[0] == 0 && rawData[1] == 0 && rawData[2] == 0xfe && rawData[3] == 0xff)
    {
        encoding = Encoding.UTF32;
    }
    else if (rawData[0] == 0x2b && rawData[1] == 0x2f && rawData[2] == 0x76)
    {
        encoding = Encoding.UTF7;
    }
    else
    {
        encoding = Encoding.Default;
    }

    // Read text and detect the encoding
    using (var streamReader = new StreamReader(fileStream))
    {
        streamReader.Read(text, 0, text.Length);
    }

    using (var memoryStream = new MemoryStream())
    {
        using (var streamWriter = new StreamWriter(memoryStream, encoding))
        {
        // Write the text to a buffer
        streamWriter.Write(text);
        streamWriter.Flush();

        // Get the buffer from the memory stream for comparision
        var memoryBuffer = memoryStream.GetBuffer();

        // Compare only bytes read
        for (var i = 0; i < rawLength && isText; i++)
        {
            isText = rawData[i] == memoryBuffer[i];
        }
        }
    }

    return isText;
    }
}
10

これは万全ではありませんが、バイナリコンテンツがあるかどうかを確認する必要があります。

public bool HasBinaryContent(string content)
{
    return content.Any(ch => char.IsControl(ch) && ch != '\r' && ch != '\n');
}

制御文字が存在する場合(標準の\r\n)、それはおそらくテキストファイルではありません。

簡単で汚いのは、ファイル拡張子を使用して、.txtなどの一般的なテキスト拡張子を探すことです。これには、 Path.GetExtension 呼び出しを使用できます。それ以外は汚れているかもしれませんが、実際には「クイック」として分類されません。

4
Jeff Yates

いい質問です! .NETがこのための簡単なソリューションを提供していないことに私は驚きました。

次のコードは、画像(png、jpgなど)とテキストファイルを区別するのに役立ちました。

連続したヌル(0x00)Ron WarholicとAdam Brussの提案による最初の512バイト:

if (File.Exists(path))
{
    // Is it binary? Check for consecutive nulls..
    byte[] content = File.ReadAllBytes(path);
    for (int i = 1; i < 512 && i < content.Length; i++) {
        if (content[i] == 0x00 && content[i-1] == 0x00) {
            return Convert.ToBase64String(content);
        }
    }
    // No? return text
    return File.ReadAllText(path);
}

明らかにこれは迅速で汚いアプローチですが、ファイルをそれぞれ512バイトの10個のチャンクに分割し、連続したnullがないかそれらの8つをチェックすることで簡単に拡張できます(個人的に、2の場合はバイナリファイルを推測しますまたはそれらの3つが一致する-テキストファイルではnullはまれです)。

それはあなたが何をしているのかについてかなり良い解決策を提供するはずです。

4
Steven de Salas

本当に本当に汚い方法は、標準のテキスト、句読点、記号、空白文字のみを使用する正規表現を作成し、テキストストリームにファイルの一部をロードしてから、正規表現に対して実行することです。問題のあるドメインで純粋なテキストファイルと見なされるものによっては、一致が成功しない場合はバイナリファイルが示されます。

ユニコードを説明するには、ストリームのエンコードをそのようにマークするようにしてください。

これは本当に最適ではありませんが、あなたは素早く汚いと言いました。

2
Chad Ruppert

別の方法は、 [〜#〜] ude [〜#〜] を使用してファイルの文字セットを検出することです。文字セットが正常に検出された場合、それがテキストであることを確認できます。それ以外の場合はバイナリです。バイナリには文字セットがないためです。

もちろん、UDE以外の他の文字セット検出ライブラリを使用できます。ライブラリを検出する文字セットが十分であれば、このアプローチは100%の正確さを達成できます。

1
Tyler Long

別の方法はどうですか:ファイルの内容を表すバイナリ配列の長さを決定し、指定されたバイナリ配列をテキストに変換した後の文字列の長さと比較します。

同じ長さの場合、ファイルに「読み取り不可能」なシンボルはなく、テキストです(80%になります)。

1
shytikov

http://codesnipers.com/?q=node/68 バイトオーダーマーク(ファイルに表示される場合があります)を使用してUTF-16とUTF-8を検出する方法について説明します。また、ファイルがテキストファイルであるかどうかを判断するために、UTF-8マルチバイトシーケンスパターン(下記)に適合するかどうかを確認するために、いくつかのバイトをループすることをお勧めします。

  • 0xxxxxxx ASCII <0x80(128)
  • 110xxxxx 10xxxxxx 2バイト> = 0x80
  • 1110xxxx 10xxxxxx 10xxxxxx 3バイト> = 0x400
  • 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 4バイト> = 0x10000
1
foson