有没有一种快速简单的方法来使用PHP检查文件是ASCII还是二进制?
有没有一种快速简单的方法来使用PHP检查文件是ASCII还是二进制?
这仅适用于 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';
if (!$finfo){ echo "Opening fileinfo database failed"; exit(); }
,并且不要忘记:finfo_close($finfo);
... - user257319Devin的回答非常含糊,与我编写的第一段代码完成此任务有关。结果还可以。我发现在许多情况下,按字节搜索对于二进制文件更快。如果您找到一个大于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;
<?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%可靠。
<?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和二进制文件都是二进制的,这显然是不正确的,所以我必须找出导致mimetype为"text"的原因。对我来说很明显,也许文本只是可打印的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,而是文件是否只包含可读文本。
<?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
}
我相信所有这些测试并不是完全无用的,因为它给了我一个新的想法。为什么不在我的原始函数中添加可打印文件模式呢!虽然在我的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";
//u
。Brogan,块大小为256
比任何硬盘块大小都要小(即使是25年前的硬盘);4096
或8192
将是更合理的默认值。 - Fravadona因为ASCII只是文本的编码方式,具有二进制表示,所以不完全准确。你可以检查所有字节是否小于128,但即使如此也不能保证它被解码为ASCII。你可能会发现它是一种疯狂的图像格式,或者是完全不使用所有八位的另一种文本编码方式。但是,如果你只想检查一个文件是否是有效的ASCII,即使它不是“文本文件”,这绝对足够了。
你可能需要检查文件的MIME类型,但如果你愿意将文件加载到内存中,也许你可以使用类似以下代码来检查缓冲区是否由所有可打印字符组成:
<?php
$probably_binary = (is_string($var) === true && ctype_print($var) === false);
虽不完美,但在某些情况下可能有所帮助。
ctype_print()
返回FALSE。 - dotancohenfunction 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:抱歉,这是我的第一篇帖子,我想在之前的评论中添加一条评论 :)