17 Stimmen

Wie man überprüft, ob eine Datei in PHP ASCII oder Binär ist

Gibt es eine schnelle, einfache Möglichkeit zu prüfen, ob eine Datei mit PHP ASCII oder binär ist?

0 Stimmen

Dies wurde bereits zuvor gefragt, aber ich frage mich immer wieder, warum es dir wichtig ist, ob es ASCII oder binär ist?

0 Stimmen

Ähnlich, aber kein Duplikat. Diese hat eine einfache, technische Antwort, während die vermeintlich identische Frage ziemlich schwierig ist. Es besteht ein großer Unterschied darin, zu fragen, ob eine Datei in Codierung X ist oder überhaupt in irgendeiner Codierung.

0 Stimmen

Nein, lies es noch einmal, diese Typen waren nur Beispiele. Er sucht nach demselben Ding binär vs. Text.

24voto

davethegr8 Punkte 10905

Dies funktioniert nur für PHP>=5.3.0 und ist nicht zu 100% zuverlässig, aber hey, es ist ziemlich verdammt nah dran.

// Rückgabetyp ala MIME-Typ Erweiterung
$finfo = finfo_open(FILEINFO_MIME);

// Überprüfen, ob der MIME-Typ mit 'text' beginnt
return substr(finfo_file($finfo, $filename), 0, 4) == 'text';

http://us.php.net/manual/en/ref.fileinfo.php

2 Stimmen

Sie sollten wahrscheinlich überprüfen, if (!$finfo){ echo "Öffnen der Dateiinformationsdatenbank fehlgeschlagen"; exit(); } und vergessen Sie nicht: finfo_close($finfo);...

0 Stimmen

Würde dies bei application/javascript nicht scheitern?

0 Stimmen

Dies sagt Ihnen, ob die Datei nur druckbare Zeichen enthält. Es sagt Ihnen nicht, ob die Datei ASCII oder binär ist.

4voto

Devin Jeanpierre Punkte 87113

Da ASCII nur eine Codierung für Text mit binärer Darstellung ist, nicht wirklich. Du könntest überprüfen, ob alle Bytes kleiner als 128 sind, aber selbst das würde nicht garantieren, dass es als ASCII dekodiert werden soll. Möglicherweise handelt es sich um ein verrücktes Bildformat oder um eine vollständig andere Textcodierung, die ebenfalls keine Verwendung aller acht Bits hat. Es könnte jedoch für deine Zwecke ausreichen. Wenn du nur überprüfen möchtest, ob eine Datei gültiges ASCII ist, selbst wenn es keine "Textdatei" ist, wird es definitiv ausreichen.

4voto

Brogan Punkte 688

In einem meiner älteren PHP-Projekte verwende ich ASCII/Binär-Kompression. Wenn der Benutzer seine Datei hochlädt, muss er angeben, ob es sich um eine ASCII- oder Binärdatei handelt. Ich beschloss, meinen Code so zu ändern, dass der Server automatisch über den Dateimodus entscheidet, denn wenn man sich auf die Entscheidung des Benutzers verlässt, könnte die Komprimierung fehlschlagen. Ich beschloss, dass mein Code absolut sein musste und keine Tricks verwenden durfte, die mein Programm möglicherweise zum Scheitern bringen würden. Ich habe mir schnell einen Code ausgedacht, einige Geschwindigkeitstests durchgeführt und dann beschlossen, im Internet zu suchen, um zu sehen, ob es ein schnelleres Codebeispiel für diese Aufgabe gibt.


Die sehr vage Antwort von Devin bezieht sich auf den ersten Code, den ich für diese Aufgabe geschrieben habe. Die Ergebnisse waren durchwachsen. Ich fand heraus, dass die Suche nach Byte für Byte in vielen Fällen bei Binärdateien schneller war. Findet man ein Byte, das größer als 127 ist, kann der Rest der Datei ignoriert werden, und die gesamte Datei wird als Binärdatei betrachtet. Allerdings müsste man dann jedes einzelne Byte einer Datei lesen, um festzustellen, ob es sich um eine ASCII-Datei handelt. Bei vielen binären Dateien scheint es schneller zu gehen, da ein binäres Byte wahrscheinlich früher als das letzte Byte der Datei kommt, manchmal ist sogar das erste Byte binär.

<?php
$filemodes = array(
    -2 => 'Unreadable',
    -1 => 'Missing',
    0 => 'Empty',
    1 => 'ASCII',
    2 => 'Binary'
);

function filemode($filename) {
    if(is_file($filename)) {
        if(is_readable($filename)) {
            $size = filesize($filename);
            if($size === 0)
                return 0; // Empty
            $handle = fopen($filename, 'rb');
            for($i = 0; $i < $size; ++$i) {
                $byte = fread($handle, 1);
                if(ord($byte) > 127) {
                    fclose($handle);
                    return 2; // Binary
                }
            }
            fclose($handle);
            return 1; // ASCII
        }
        else
            return -2; // Unreadable
    }
    else
        return -1; // Missing
}

// ==========

$filename = 'e:\test.txt';

$loops = 1;
$x = 0;
$i = 0;
$start = microtime(true);

for($i = 0; $i < $loops; ++$i)
    $x = filemode($filename);

$stop = microtime(true);
$duration = $stop - $start;

echo
    'Filename: ', $filename, "\n",
    'Filemode: ', $filemodes[filemode($filename)], "\n",
    'Duration: ', $duration;

Mein Prozessor ist nicht gerade modern, aber ich habe herausgefunden, dass eine 600 KB große ASCII-Datei etwa 0,25 Sekunden braucht, um fertig zu werden. Wenn ich dies für Hunderte oder Tausende von großen Dateien verwenden würde, könnte es sehr lange dauern. Ich beschloss, die Dinge etwas zu beschleunigen, indem ich meinen Puffer größer als ein einzelnes Byte machte, um die Datei in Blöcken zu lesen, anstatt ein Byte nach dem anderen. Durch die Verwendung von Chunks kann ich mehr von der Datei auf einmal verarbeiten, aber nicht zu viel in den Speicher laden. Wenn eine Datei, die wir testen, riesig ist und wir die gesamte Datei in den Speicher laden würden, könnte das viel zu viel Speicherplatz beanspruchen und das Programm zum Scheitern bringen.

<?php
$filemodes = array(
    -2 => 'Unreadable',
    -1 => 'Missing',
    0 => 'Empty',
    1 => 'ASCII',
    2 => 'Binary'
);

function filemode($filename) {
    if(is_file($filename)) {
        if(is_readable($filename)) {
            $size = filesize($filename);
            if($size === 0)
                return 0; // Empty
            $buffer_size = 256;
            $chunks = ceil($size / $buffer_size);
            $handle = fopen($filename, 'rb');
            for($chunk = 0; $chunk < $chunks; ++$chunk) {
                $buffer = fread($handle, $buffer_size);
                $buffer_length = strlen($buffer);
                for($byte = 0; $byte < $buffer_length; ++$byte) {
                    if(ord($buffer[$byte]) > 127) {
                        fclose($handle);
                        return 2; // Binary
                    }
                }
            }
            fclose($handle);
            return 1; // ASCII
        }
        else
            return -2; // Unreadable
    }
    else
        return -1; // Missing
}

// ==========

$filename = 'e:\test.txt';

$loops = 1;
$x = 0;
$i = 0;
$start = microtime(true);

for($i = 0; $i < $loops; ++$i)
    $x = filemode($filename);

$stop = microtime(true);
$duration = $stop - $start;

echo
    'Filename: ', $filename, "\n",
    'Filemode: ', $filemodes[filemode($filename)], "\n",
    'Duration: ', $duration;

Der Geschwindigkeitsunterschied war ziemlich signifikant, da ich nur 0,15 Sekunden statt der 0,25 Sekunden der vorherigen Funktion benötigte, also fast eine Zehntelsekunde schneller, um meine 600Kb-ASCII-Datei zu lesen.


Da ich meine Datei nun in Chunks unterteilt habe, dachte ich, es wäre eine gute Idee, alternative Möglichkeiten zu finden, um meine Chunks auf binäre Zeichen zu testen. Mein erster Gedanke war, einen regulären Ausdruck zu verwenden, um Nicht-Ascii-Zeichen zu finden.

<?php
$filemodes = array(
    -2 => 'Unreadable',
    -1 => 'Missing',
    0 => 'Empty',
    1 => 'ASCII',
    2 => 'Binary'
);

function filemode($filename) {
    if(is_file($filename)) {
        if(is_readable($filename)) {
            $size = filesize($filename);
            if($size === 0)
                return 0; // Empty
            $buffer_size = 256;
            $chunks = ceil($size / $buffer_size);
            $handle = fopen($filename, 'rb');
            for($chunk = 0; $chunk < $chunks; ++$chunk) {
                $buffer = fread($handle, $buffer_size);
                if(preg_match('/[\x80-\xFF]/', $buffer) === 1) {
                    fclose($handle);
                    return 2; // Binary
                }
            }
            fclose($handle);
            return 1; // ASCII
        }
        else
            return -2; // Unreadable
    }
    else
        return -1; // Missing
}

// ==========

$filename = 'e:\test.txt';

$loops = 1;
$x = 0;
$i = 0;
$start = microtime(true);

for($i = 0; $i < $loops; ++$i)
    $x = filemode($filename);

$stop = microtime(true);
$duration = $stop - $start;

echo
    'Filename: ', $filename, "\n",
    'Filemode: ', $filemodes[filemode($filename)], "\n",
    'Duration: ', $duration;

Erstaunlich! 0,02 Sekunden, um meine 600Kb-Datei als ASCII-Datei zu betrachten, und dieser Code scheint 100% zuverlässig zu sein.


Jetzt, wo ich hier angekommen bin, habe ich die Möglichkeit, verschiedene andere Methoden zu inspizieren, die von anderen Nutzern eingesetzt werden.

Die heute am meisten akzeptierte Antwort, geschrieben von davethegr8, verwendet die Mimetype-Erweiterung. Zunächst musste ich diese Erweiterung in der Datei php.ini aktivieren. Dann habe ich diesen Code mit einer ASCII-Datei ohne Dateierweiterung und einer Binärdatei ohne Dateierweiterung getestet.

So habe ich meine beiden Testdateien erstellt.

<?php
$handle = fopen('E:\ASCII', 'wb');
for($i = 0; $i < 128; ++$i) {
    fwrite($handle, chr($i));
}
fclose($handle);

$handle = fopen('E:\Binary', 'wb');
for($i = 0; $i < 256; ++$i) {
    fwrite($handle, chr($i));
}
fclose($handle);

So habe ich beide Dateien getestet...

<?php
$filename = 'E:\ASCII';
$finfo = finfo_open(FILEINFO_MIME);
echo (substr(finfo_file($finfo, $filename), 0, 4) == 'text') ? 'ASCII' : 'Binary';

Welche Ausgaben:

Binär

und...

<?php
$filename = 'E:\Binary';
$finfo = finfo_open(FILEINFO_MIME);
echo (substr(finfo_file($finfo, $filename), 0, 4) == 'text') ? 'ASCII' : 'Binary';

Welche Ausgaben:

Binär

Dieser Code zeigt an, dass sowohl meine ASCII- als auch meine Binärdateien binär sind, was natürlich nicht stimmt, also musste ich herausfinden, warum der Mimetyp "Text" ist. Für mich war klar, dass Text vielleicht nur druckbare ASCII-Zeichen sind. Also habe ich den Bereich meiner ASCII-Datei eingeschränkt.

<?php
$handle = fopen('E:\ASCII', 'wb');
for($i = 32; $i < 127; ++$i) {
    fwrite($handle, chr($i));
}
fclose($handle);

Und wieder getestet.

<?php
$filename = 'E:\ASCII';
$finfo = finfo_open(FILEINFO_MIME);
echo (substr(finfo_file($finfo, $filename), 0, 4) == 'text') ? 'ASCII' : 'Binary';

Welche Ausgaben:

ASCII

Wenn ich den Bereich verringere, wird er als binär behandelt. Erhöhe ich den Bereich, wird er wieder als binär behandelt.

Die am meisten akzeptierte Antwort sagt Ihnen also nicht, ob Ihre Datei ASCII ist, sondern eher, ob sie nur lesbaren Text enthält oder nicht.


Schließlich muss ich die andere Antwort, die ctype_print verwendet, mit meinen Dateien testen. Ich habe beschlossen, dass es am einfachsten ist, den von mir erstellten Code zu verwenden und MarcoAs Code zu ergänzen.

<?php
$filemodes = array(
    -2 => 'Unreadable',
    -1 => 'Missing',
    0 => 'Empty',
    1 => 'ASCII',
    2 => 'Binary'
);

function filemode($filename) {
    if(is_file($filename)) {
        if(is_readable($filename)) {
            $size = filesize($filename);
            if($size === 0)
                return 0; // Empty
            $buffer_size = 256;
            $chunks = ceil($size / $buffer_size);
            $handle = fopen($filename, 'rb');
            for($chunk = 0; $chunk < $chunks; ++$chunk) {
                $buffer = fread($handle, $buffer_size);
                $buffer = str_ireplace("\t", '', $buffer);
                $buffer = str_ireplace("\n", '', $buffer);
                $buffer = str_ireplace("\r", '', $buffer);
                if(ctype_print($buffer) === false) {
                    fclose($handle);
                    return 2; // Binary
                }
            }
            fclose($handle);
            return 1; // ASCII
        }
        else
            return -2; // Unreadable
    }
    else
        return -1; // Missing
}

// ==========

$filename = 'e:\test.txt';

$loops = 1;
$x = 0;
$i = 0;
$start = microtime(true);

for($i = 0; $i < $loops; ++$i)
    $x = filemode($filename);

$stop = microtime(true);
$duration = $stop - $start;

echo
    'Filename: ', $filename, "\n",
    'Filemode: ', $filemodes[filemode($filename)], "\n",
    'Duration: ', $duration;

Autsch! 0,2 Sekunden, um mir mitzuteilen, dass meine 600Kb-Datei ASCII ist. Meine große ASCII-Datei enthält, wie ich weiß, nur sichtbare ASCII-Zeichen. Es scheint zu wissen, dass meine binären Dateien binär sind. Und meine reine ASCII-Datei... Binär!

Ich habe mich entschlossen, die Dokumentation für ctype_print und sein Rückgabewert ist definiert als:

Gibt TRUE zurück, wenn jedes Zeichen im Text tatsächlich eine Ausgabe erzeugt (einschließlich Leerzeichen). Gibt FALSE zurück, wenn der Text Steuerzeichen enthält oder Zeichen enthält, die überhaupt keine Ausgabe- oder Kontrollfunktion haben.

Diese Funktion, wie auch die Antwort von davethegr8, sagt Ihnen nur, ob Ihr Text druckbare ASCII-Zeichen enthält, aber nicht, ob Ihr Text tatsächlich ASCII ist oder nicht. Das bedeutet nicht unbedingt, dass MacroA völlig falsch ist, sie sind nur nicht ganz richtig. str_ireplace ist langsam im Vergleich zu str_replace, und nur diese drei Steuerzeichen zu ersetzen, um ctype_print zu testen, ist nicht genug, um zu wissen, ob der String ASCII ist oder nicht. Damit dieses Beispiel für ASCII funktioniert, müssen wir jedes Steuerzeichen ersetzen!

<?php
$filemodes = array(
    -2 => 'Unreadable',
    -1 => 'Missing',
    0 => 'Empty',
    1 => 'ASCII',
    2 => 'Binary'
);

function filemode($filename) {
    if(is_file($filename)) {
        if(is_readable($filename)) {
            $size = filesize($filename);
            if($size === 0)
                return 0; // Empty
            $buffer_size = 256;
            $chunks = ceil($size / $buffer_size);
            $replace = array(
                "\x00", "\x01", "\x02", "\x03",
                "\x04", "\x05", "\x06", "\x07",
                "\x08", "\x09", "\x0A", "\x0B",
                "\x0C", "\x0D", "\x0E", "\x0F",
                "\x10", "\x11", "\x12", "\x13",
                "\x14", "\x15", "\x16", "\x17",
                "\x18", "\x19", "\x1A", "\x1B",
                "\x1C", "\x1D", "\x1E", "\x1F",
                "\x7F"
            );
            $handle = fopen($filename, 'rb');
            for($chunk = 0; $chunk < $chunks; ++$chunk) {
                $buffer = fread($handle, $buffer_size);
                $buffer = str_replace($replace, '', $buffer);
                if(ctype_print($buffer) === false) {
                    fclose($handle);
                    return 2; // Binary
                }
            }
            fclose($handle);
            return 1; // ASCII
        }
        else
            return -2; // Unreadable
    }
    else
        return -1; // Missing
}

Es dauerte 0,04 Sekunden, um mir zu sagen, dass meine 600 KB große Datei ASCII ist.


Ich glaube, all diese Tests waren nicht völlig nutzlos, denn sie haben mich auf eine weitere Idee gebracht. Warum nicht einen druckbaren Dateimodus zu meiner ursprünglichen Funktion hinzufügen! Es scheint zwar 0,018 Sekunden langsamer zu sein bei meiner 600Kb druckbaren ASCII-Datei, aber hier ist sie.

<?php
$filemodes = array(
    -2 => 'Unreadable',
    -1 => 'Missing',
    0 => 'Empty',
    1 => 'Printable',
    2 => 'ASCII',
    3 => 'Binary'
);

function filemode($filename) {
    if(is_file($filename)) {
        if(is_readable($filename)) {
            $size = filesize($filename);
            if($size === 0)
                return 0; // Empty
            $printable = true;
            $buffer_size = 256;
            $chunks = ceil($size / $buffer_size);
            $handle = fopen($filename, 'rb');
            for($chunk = 0; $chunk < $chunks; ++$chunk) {
                $buffer = fread($handle, $buffer_size);
                if(preg_match('/[\x80-\xFF]/', $buffer) === 1) {
                    fclose($handle);
                    return 3; // Binary
                }
                else
                    if($printable === true)
                        $printable = ctype_print($buffer);
            }
            fclose($handle);
            return $printable === true ? 1 : 2; // Printable or ASCII
        }
        else
            return -2; // Unreadable
    }
    else
        return -1; // Missing
}

// ==========

$filename = 'e:\test.txt';

$loops = 1;
$x = 0;
$i = 0;
$start = microtime(true);

for($i = 0; $i < $loops; ++$i)
    $x = filemode($filename);

$stop = microtime(true);
$duration = $stop - $start;

echo
    'Filename: ', $filename, "\n",
    'Filemode: ', $filemodes[filemode($filename)], "\n",
    'Duration: ', $duration;

Ich habe auch ctype_print gegen einen regulären Ausdruck getestet und festgestellt, dass ctype_print ein bisschen schneller ist.

$printable = preg_match('/[^\x20-\x7E]/', $buffer) === 0;

Hier ist meine letzte Funktion, bei der die Suche nach druckbarem Text optional ist, ebenso wie die Puffergröße.

<?php
const filemodes = array(
    -2 => 'Unreadable',
    -1 => 'Missing',
    0 => 'Empty',
    1 => 'Printable',
    2 => 'ASCII',
    3 => 'Binary'
);

function filemode($filename, $printable = false, $buffer_size = 256) {
    if(is_bool($printable) === false || is_int($buffer_size) === false)
        return false;
    $buffer_size = floor($buffer_size);
    if($buffer_size <= 0)
        return false;
    if(is_file($filename)) {
        if(is_readable($filename)) {
            $size = filesize($filename);
            if($size === 0)
                return 0; // Empty
            if($buffer_size > $size)
                $buffer_size = $size;
            $chunks = ceil($size / $buffer_size);
            $handle = fopen($filename, 'rb');
            for($chunk = 0; $chunk < $chunks; ++$chunk) {
                $buffer = fread($handle, $buffer_size);
                if(preg_match('/[\x80-\xFF]/', $buffer) === 1) {
                    fclose($handle);
                    return 3; // Binary
                }
                else
                    if($printable === true)
                        $printable = ctype_print($buffer);
            }
            fclose($handle);
            return $printable === true ? 1 : 2; // Printable or ASCII
        }
        else
            return -2; // Unreadable
    }
    else
        return -1; // Missing
}

// ==========

$filename = 'e:\test.txt';
echo
    'Filename: ', $filename, "\n",
    'Filemode: ', filemodes[filemode($filename, true)], "\n";

0 Stimmen

Arbeiten an einem Malware-Scanner und ich kann mir keine falschen Erkennungen leisten. Heutzutage versteckt sich Malware auch in jpg- und ico-Erweiterungen. Ich hoffe wirklich, dass ich etwas Code von hier verwenden könnte, um Dateien zu überspringen, die definitiv binär sind. Könnte dies file_get_contents verwenden?

0 Stimmen

Bio-Bäckerei Onder de Linden Eine einfache Textdatei, die den obigen String enthält, wird als binär markiert.

1 Stimmen

Shivanand Sharma, das liegt daran, dass der obige String binär ist und alle 8 Bits benötigt, es ist kein Klartext.

3voto

kvz Punkte 4837

Sie sollten wahrscheinlich den MIME-Typ der Datei überprüfen, aber wenn Sie bereit sind, die Datei in den Speicher zu laden, könnten Sie überprüfen, ob der Puffer nur aus druckbaren Zeichen besteht, indem Sie etwas Ähnliches verwenden:

`

Nicht perfekt, aber könnte in einigen Fällen hilfreich sein.

`

5 Stimmen

Tabs und Zeilenumbrüche lassen ctype_print() leider FALSE zurückgeben.

3voto

MarcoA Punkte 31

Auf diese Weise sieht es in meinem Projekt in Ordnung aus:

function probably_binary($stringa) {
    $is_binary=false;
    $stringa=str_ireplace("\t","",$stringa);
    $stringa=str_ireplace("\n","",$stringa);
    $stringa=str_ireplace("\r","",$stringa);
    if(is_string($stringa) && ctype_print($stringa) === false){
        $is_binary=true;
    }
    return $is_binary;
}

PS: Entschuldigung, mein erster Beitrag, ich wollte einen Kommentar zu dem vorherigen hinzufügen :)

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X