如何在PHP中检查文件是ASCII还是二进制

时间:2009-03-10 23:24:21

标签: php

是否有一种快速,简单的方法来检查文件是ASCII还是二进制文件?

5 个答案:

答案 0 :(得分:21)

这仅适用于PHP> = 5.3.0,并且不是100%可靠,但是嘿,它非常接近。

// return mime type ala mimetype extension
$finfo = finfo_open(FILEINFO_MIME);

//check to see if the mime-type starts with 'text'
return substr(finfo_file($finfo, $filename), 0, 4) == 'text';

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

答案 1 :(得分:3)

由于ASCII只是文本的编码,具有二进制表示,不是真的。您可以检查所有字节是否小于128,但即使这样也不能保证它被解码为ASCII。对于所有你知道它是一些疯狂的图像格式,或完全不同的文本编码,也没有使用所有八位。但是,它可能就足够了。如果你只是想检查一个文件是否是有效的ASCII,即使它不是一个“文本文件”,它肯定就足够了。

答案 2 :(得分:2)

您应该检查文件的mimetype,但是如果您愿意将文件加载到内存中,也许您可​​以使用以下内容检查缓冲区是否包含所有可打印字符:

<?php
$probably_binary = (is_string($var) === true && ctype_print($var) === false);

不完美,但在某些情况下可能会有所帮助。

答案 3 :(得分:1)

这种方式在我的项目中似乎没问题:

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:抱歉,我的第一篇文章,我想在上一篇文章中添加评论:)

答案 4 :(得分:0)

在我的一个较旧的PHP项目中,我使用ASCII /二进制压缩。用户上载文件时,要求他们指定该文件是ASCII还是Binary。我决定修改代码以使服务器自动决定文件模式是什么,因为依靠用户的决定可能会导致压缩失败。我决定我的代码必须是绝对的,并且不要使用可能导致程序失败的技巧。我迅速整理了一些代码,进行了一些速度测试,然后决定在互联网上搜索是否有更快的代码示例来完成此任务。


Devin非常模糊的答案与我为完成该任务而编写的第一个代码有关。结果一般般。我发现在很多情况下,对于二进制文件,逐字节搜索速度更快。如果找到大于127的字节,则文件的其余部分可以忽略,整个文件都被视为二进制文件。话虽如此,您将必须读取文件的每个最后一个字节,以确定文件是否为ASCII。对于许多二进制文件而言,它似乎更快,因为二进制字节可能早于文件的最后一个字节,有时甚至第一个字节也将是二进制的。

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

我的处理器不是很现代,但是我发现600Kb ASCII文件大约需要0.25秒才能完成。如果要在成百上千个大型文件上使用它,可能会花费很长时间。我决定尝试通过使缓冲区大于单个字节来加快速度,以一次读取文件而不是一个字节的块。使用块将使我能够立即处理更多文件,但不会过多地加载到内存中。如果我们正在测试的文件很大,并且我们打算将整个文件加载到内存中,则可能会占用过多的内存并导致程序失败。

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

速度差异非常明显,仅需0.15秒,而不是上一个功能的0.25秒,读取我的600Kb ASCII文件的速度快了十分之一秒。


现在,我将文件放在块中,我认为寻找替代方法来测试块中的二进制字符是一个好主意。我首先想到的是使用正则表达式查找非ASCII字符。

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

很棒! 0.02秒将我的600Kb文件视为ASCII文件,此代码似乎100%可靠。


现在我已经到了这里,我有机会检查其他用户部署的其他几种方法。

由davethegr8撰写的当今最受欢迎的答案使用了mimetype扩展名。首先,要求我在php.ini文件中启用此扩展。接下来,我针对没有文件扩展名的实际ASCII文件和没有文件扩展名的二进制文件测试了此代码。

这是我创建两个测试文件的方式。

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

这是我测试两个文件的方式...

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

哪个输出:

二进制

和...

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

哪个输出:

二进制

此代码将我的ASCII文件和二进制文件都显示为二进制文件,这显然是不正确的,因此我必须找出导致模仿类型为“文本”的原因。对我来说,很明显,也许文本只是可打印的ASCII字符。所以我限制了ASCII文件的范围。

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

然后再次测试。

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

哪个输出:

ASCII

如果我降低范围,它将视为二进制。如果我扩大范围,再次将其视为二进制。

因此,最被接受的答案不会告诉您文件是否为ASCII,而是仅包含可读文本。


最后,我需要测试另一个对我的文件使用ctype_print的答案。我认为最简单的方法是使用我在MarcoA的代码中补充的代码。

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

太好了! 0.2秒告诉我我的600Kb文件是ASCII。我知道我的大型ASCII文件仅包含可见的ASCII字符。似乎知道我的二进制文件是二进制文件。还有我的纯ASCII文件...二进制!

我决定阅读ctype_print的文档,其返回值定义为:

如果文本中的每个字符都将实际创建输出,则返回TRUE (包括空格)。如果文本包含控制字符,则返回FALSE 或根本没有任何输出或控制功能的字符。

与davethegr8的答案一样,此功能仅告诉您文本是否包含可打印的ASCII字符,而不告诉您文本是否实际为ASCII。这并不一定意味着MacroA是完全错误的,只是它们并不完全正确。与str_replace相比,str_ireplace速度较慢,仅替换这三个控制字符来测试ctype_print不足以知道字符串是否为ASCII。为了使该示例适用于ASCII,我们必须替换每个控制字符!

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

这花了0.04秒钟告诉我我的600Kb文件是ASCII。


我相信所有这些测试并非完全没有用,因为它确实给了我一个更多的主意。为什么不向我的原始功能添加可打印文件模式!在我的600Kb可打印ASCII文件上,它的速度似乎慢了0.018秒,而在这里。

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

我还针对正则表达式测试了ctype_print,发现ctype_print更快一些。

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

这是我的最后一个功能,其中查找可打印文本是可选的,缓冲区大小也是可选的。

<?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";