我如何区分“二进制”和“文本”文件?

76

我们通常会简单理解为有“二进制”文件(目标文件、图像、电影、可执行文件、专有文档格式等)和“文本”文件(源代码、XML 文件、HTML 文件、电子邮件等)。

一般来说,要对文件进行有用的操作,您需要知道文件的内容,在这个意义上,如果编码是“二进制”或“文本”,实际上并不重要。当然,文件只存储字节数据,因此它们都是“二进制”的,“文本”没有了解编码就没有任何意义。尽管如此,仍然有必要谈论“二进制”和“文本”文件,但为了避免使用不准确的定义冒犯任何人,我将继续使用“引号”。

然而,有各种工具可以处理广泛的文件,并且在实际应用中,您希望根据文件是“文本”还是“二进制”做出不同的处理。其中一个例子是输出数据到控制台的任何工具。纯文本看起来很好,而且很有用。而“二进制”数据会使您的终端出现乱码,并且通常不方便查看。GNU grep至少在确定是否应将匹配项输出到控制台时使用此区别。

那么问题来了,如何判断一个文件是“文本”还是“二进制”?并且为了进一步限制,如何在类似Linux的文件系统上判断?我不知道有没有文件系统元数据指示文件的“类型”,因此问题进一步变成,通过检查文件的内容,如何判断它是“文本”还是“二进制”?为简单起见,让我们将“文本”限定为可在用户控制台上打印的字符。特别地,您需要如何实现这个功能?(我认为这在这个站点上是明确的,但我想总的来说,指向已存在的执行此操作的代码会很有帮助,我应该已经说明了),我真正想要的不是我可以使用哪些现有程序来执行此操作。

11个回答

65
你可以使用file命令。它会对文件进行一系列测试(man file),以确定其是二进制还是文本类型。如果需要从C语言中实现该功能,则可以查看或借用其源代码。
file README
README: ASCII English text, with very long lines

file /bin/bash
/bin/bash: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.5, dynamically linked (uses shared libs), stripped

1
如果是Linux系统,文件的启发式算法会比你自己构建的任何东西都要好得多。+1 - Adam Lassek
如果文件可用,它将是最好的工具,毫无疑问!另外,“file -I” 是个不错的技巧。我之前没有考虑过为了我的特定问题来执行 shell 命令,但我不认为我能承受性能开销。谢谢! - benno

17

您可以使用以下方法确定文件的MIME类型

file --mime FILENAME

在Linux上,简写是file -i,在macOS上是file -I(大写的I)(参见注释)。

如果以text/开头,则为文本,否则为二进制。唯一的例外是XML应用程序。您可以通过查找文件类型末尾的+xml来匹配这些应用程序。


我认为应该是“file -I”(大写)。至少根据我的测试和man页面是这样的。 - benno
1
刚查了一下,Debian和gentoo Linux中小写是正确的。他们的文件是ftp://ftp.astron.com/pub/file/file-5.00.tar.gz(或其他版本)。在两者中都没有-I(大写)选项。 - phihag
哎呀,奇怪了。OS X(4.17)上的版本使用-I(大写),而我的Linux机器上的版本(4.24)使用-i(小写)。太奇怪了!我想知道这是不是OS X特有的问题,还是作者在点发布之间简单地更改了接口。 - benno
file --mime 在 Linux 和 macOS 上似乎是一致的。filePOSIX 规范 中有 -i 作为不同的选项,因此 macOS 使用 -I 来保持 POSIX 兼容性。 - anishpatel
1
在IIS上,JavaScript文件的服务类型为:application/javascript,因此并不是那么简单! - Poul Bak

17

我们公司开发的电子表格软件可以读取多种二进制文件格式和文本文件。

首先,我们查看一些字节以寻找我们能够识别的魔数。如果我们无法识别任何一个二进制类型的魔数,则查看文件的前2K个字节,以查看它是否为当前主机操作系统的代码页编码的UTF-8UTF-16或文本文件。如果没有通过这些测试,我们假设它不是我们能够处理的文件,并抛出适当的异常。


8
  • To list text file names in current dir/subdirs:

    grep -rIl ''
    
  • Binaries:

    grep -rIL ''
    
  • To check for a particular file:

    grep -qI '' FILE
    

    then, exit status '0' would mean the file is a text; '1' - binary. To check:

    echo $?
    

关键选项是这个:

  -I     Process a binary file as if it did not contain matching data;

其他选项:

  -r, --recursive
         Read all files under each directory, recursively;
  -l, --files-with-matches
         Suppress normal output; instead print the name of each input file from which output would normally have been printed.
  -L, --files-without-match
         Suppress normal output; instead print the name of each input file from which no output would normally have been printed.
  -q, --quiet, --silent
         Quiet; do not write anything to standard output.  Exit immediately with zero status if any match is found, even if an error was detected.

3
我在由dd和nano生成的文件上进行了测试。你的方法非常有效。我也很想知道为什么会有负评。 - Daniel
1
感谢您的出色回答。它值得点赞。结合if..then条件语句、for循环和/或find函数,它可以自动化处理事务并变得非常强大。 - GNUSupporter 8964民主女神 地下教會
这应该是被接受的答案。它运行良好。 - link89

4

Perl有一个不错的启发式算法。使用-B运算符来测试二进制文件(相反的是-T以测试文本文件)。下面是一个一行代码的shell脚本,可以列出文本文件:

```bash perl -le 'for(@ARGV){print if -T}' * ```
$ find . -type f -print0 | perl -0nE 'say if -f and -s _ and -T _'

(请注意,没有前导美元符号的下划线是正确的(RTFM)。)

3

这可能是一个老话题,但也许有人会发现这很有用。

如果您需要在脚本中确定某个东西是否为文件,则可以像这样简单地执行:

if file -i $1 | grep -q text;
then 
.
.
fi

这将获取文件类型,并使用静默grep来判断它是否为文本。

OSX有两个变体:小写的-i将打印类型而不分类(例如,文件,目录);大写的-I将打印分类,类似于您在Linux系统上所期望的。您需要在该平台上使用大写的-I才能使其正常工作。 - verboze

3

如果你只是检查整个文件,看每个字符是否可打印,可以使用isprint(c)。但对于Unicode来说就会有些复杂。

为了区分Unicode文本文件,MSDN提供了一些很好的建议

要点是首先检查前四个字节:

EF BB BF     UTF-8 
FF FE        UTF-16, little endian 
FE FF        UTF-16, big endian 
FF FE 00 00  UTF-32, little endian 
00 00 FE FF  UTF-32, big-endian 

这会告诉你编码方式。然后,你需要对文本文件中的其他字符使用iswprint(c)。对于UTF-8和UTF-16,由于单个字符可以由可变数量的字节表示,因此您需要手动解析数据。如果您非常严谨,您将希望在平台上可用时使用iswprint的语言环境变体。


如果不遵循这些规则,那么它就不是文本文件。除了mbcs,但那是完全不同的故事。 - MSN
7
在Unicode标准中,不鼓励在UTF-8文件中加入BOM,但很遗憾他们并没有彻底禁止它。此外,其他格式也不一定会有BOM。 - Deduplicator
3
-1 是因为这取决于文本文件是否采用 Unicode 编码,并且是否有字节顺序标记。实际上,UTF-8 文本文件通常不包含字节顺序标记,并且 UTF-8 是最常见的 Unicode 编码。答案应该至少解释这个限制。 - Daniel Cassidy

3

2
大多数试图区分的程序使用启发式方法,例如检查文件的前n个字节,并查看这些字节是否全部符合“文本”(即,它们是否都在可打印ASCII字符范围内)。对于更细致的区分,UNIX类系统上总是有'file'命令。

1

如先前所述,*nix操作系统具有文件命令中的此功能。该命令使用一个配置文件,其中定义了许多流行文件结构中包含的魔术数字。

这个名为magic的文件在历史上存储在/ etc中,尽管在某些发行版中可能在/ usr / share中。magic文件定义了文件中已知存在的值的偏移量,然后可以检查这些位置以确定文件的类型。

可以通过查阅相关手册页(man magic)找到magic文件的结构和描述。

至于实现,可以在file.c中找到相应部分的文件命令,用来确定它是否是可读的文本,如下所示:

/* Make sure we are dealing with ascii text before looking for tokens */
    for (i = 0; i < nbytes - 1; i++) {
        if (!isascii(buf[i]) ||
            (iscntrl(buf[i]) && !isspace(buf[i]) &&
             buf[i] != '\b' && buf[i] != '\032' && buf[i] != '\033'
            )
           )
            return 0;   /* not all ASCII */
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接