如何检查文件是否为二进制文件并读取所有非二进制文件?

66

如何判断一个文件是二进制文件?

例如,编译后的 C 文件。

我想从某个目录读取所有文件,但我想忽略二进制文件。


10
最终,所有文件都是二进制的。文本文件只是包含人类可读字符数据的二进制表示方式而已。没有一种可以百分之百可靠地区分文本和非文本文件的方法。 - Keith Thompson
13个回答

79

使用实用工具file,样例用法:

 $ file /bin/bash
 /bin/bash: Mach-O universal binary with 2 architectures
 /bin/bash (for architecture x86_64):   Mach-O 64-bit executable x86_64
 /bin/bash (for architecture i386): Mach-O executable i386

 $ file /etc/passwd
 /etc/passwd: ASCII English text

 $ file code.c
 code.c: ASCII c program text

file 手册页面


16
考虑使用“file --mine”。对于二进制文件,它会报告“... charset=binary”,因此可以直接使用正则表达式“binary$”进行grep。 - 4dan
25
@4dan - 或许是 --mime 参数? :) - Bach
3
对我来说没问题:file -bL --mime "$path" | grep -q '^text'。选项 -b 从输出中移除了文件名,并且 -L 解引用符号链接。 - wjandrea
1
  1. 那在非x86架构上可行吗?
  2. 您认为PDF文件是二进制文件吗?
- Victor Eijkhout
答案应该包含 --mime 标志,否则匹配所有可能的二进制格式的 file 输出不现实(这样的正则表达式会太长且易碎)。 - yugr
如果文件是二进制文件,则打印一条消息:file -bL --mime "/my/file/path/some_binary" | grep -q "charset=binary" && echo "binary file" - undefined

16

1
这应该是“grep文本”; 历史上,“file”并不总是表示ASCII,而是例如“shell脚本文本”。 - Jens
1
@Jens 谢谢提醒。只需检查 file 的 manpage,应该是 text - gongzhitaao
1
谢谢,我使用并调整了它来查找文件夹中的所有二进制文件:find . -type f -exec file {} \; | grep -v text | cut -d: -f1 - Gerrit
2
如果文件名包含单词"text"怎么办?我现在使用grep ".*:.*text"。 - Algoman
1
@Algoman 我使用 file -b,它不会输出文件名。(可能是GNU特有的功能)。 - wjandrea
显示剩余4条评论

15

我使用

! grep -qI . "$path"

唯一的缺点是它会将空文件视为二进制文件,但又有谁能决定这是否错误?

基于@mgutt的建议进行编辑:

在某些情况下,文件可能非常大,因此根据您需要做什么,只读取文件的一部分可能更安全且足够。

head -c 1024 "$path" | grep -qI .

请记住,您需要明智地选择大小;1024字节的文本加上一个空字节仍然是一个二进制文件。

可以通过添加 || ! test -s $path 来控制空文件的情况。 - yugr
3
搜索空字符串(''),而非任何单个字符('.'):**! fgrep -qI '' "$path"**。通过这种方式,空文件和仅由换行标记(换行符)组成的文件将被视为文本文件。 - Sasha
@yugr,那并不能真正帮助,因为原始的Alois Mahdal代码将不仅将完全空文件(大小为零)视为二进制,而且还将由一个或多个换行符组成的文件视为二进制。但这很容易修复(请参见我的上面的评论),Alois Mahdal的想法非常棒。 - Sasha
1
注意:如果是文本文件,此操作将读取整个文件,例如一个1GB的日志文件将被完全处理。也许最好只检查前1024个字节,使用head -c 1024 "$path" | grep -qIF ''命令?! - mgutt
@mgutt 不错,不过格式检测的诅咒仍然会出现:技术上说,10GB的ASCII和一个空字节仍然是二进制文件,因此确定偏移量将是必要的并且取决于上下文... - Alois Mahdal

5

BSD grep

这是一个使用BSD grep(在macOS/Unix上)检查单个文件的简单解决方案:

grep -q "\x00" file && echo Binary || echo Text

这基本上是检查文件是否包含 NUL 字符。

使用此方法,可以使用 find 工具递归读取所有非二进制文件,例如:

find . -type f -exec sh -c 'grep -q "\x00" {} || cat {}' ";"

甚至可以只使用 grep 更简单:

grep -rv "\x00" .

仅对当前文件夹使用:

grep -v "\x00" *

很不幸,上述示例对于GNU grep无效,但是有一种解决方法。

GNU grep

由于GNU grep忽略NULL字符,因此可以检查其他非ASCII字符,例如:

$ grep -P "[^\x00-\x7F]" file && echo Binary || echo Text

注意:仅包含空字符的文件无法使用。

那是什么版本的 grep?使用 GNU grep 3.1,搜索 \x00 总是失败。 - Vladimir Panteleev
1
我在 macOS 上使用 BSD grep,它似乎可以工作,但 GNU 版本不行。 - kenorb
@VladimirPanteleev 我添加了更通用的方法,适用于两个grep版本,请查看。 - kenorb

4
perl -E 'exit((-B $ARGV[0])?0:1);' file-to-test

可以用来检查“file-to-test”是否为二进制文件。以上命令将在二进制文件上以退出代码0退出,否则退出代码将为1。

检查文本文件的反向检查可能类似于以下命令:

perl -E 'exit((-T $ARGV[0])?0:1);' file-to-test

同样,如果“file-to-test”是文本(非二进制文件),则上述命令将以状态0退出。
使用命令perldoc -f -X了解有关-B-T检查的更多信息。

3

cat+grep

假设二进制文件是包含NULL字符的文件,这个 shell 命令可以帮助你:

(cat -v file.bin | grep -q "\^@") && echo Binary || echo Text

或者:

grep -q "\^@" <(cat -v file.bin) && echo Binary

这是一个针对grep -q "\x00"的解决方法,适用于BSD grep,但不适用于GNU版本。

基本上,-v用于cat,将所有非打印字符转换为可见的控制字符,例如:

$ printf "\x00\x00" | hexdump -C
00000000  00 00                                             |..|
$ printf "\x00\x00" | cat -v
^@^@
$ printf "\x00\x00" | cat -v | hexdump -C
00000000  5e 40 5e 40                                       |^@^@|

其中^@字符代表空字符。因此,一旦发现这些控制字符,我们就认为文件是二进制的。


上述方法的缺点是,在字符不表示控制字符时可能会产生误报。例如:

$ printf "\x00\x00^@^@" | cat -v | hexdump -C
00000000  5e 40 5e 40 5e 40 5e 40                           |^@^@^@^@|

参见:如何使用grep查找所有非ASCII字符

这会导致一个错误的“二进制”结果,当文本文件包含三个ASCII字符\^@时。 - Vladimir Panteleev
添加了一条注释,请注意查看:如何在UNIX中使用grep搜索所有非ASCII字符 - kenorb

3

使用 Perl 内置的 -T 文件测试操作符,在使用 -f 文件测试操作符确定为纯文件之后:

$ perl -le 'for (@ARGV) { print if -f && -T }' \
    getwinsz.c a.out /etc/termcap /bin /bin/cat \
    /dev/tty /usr/share/zoneinfo/UTC /etc/motd
getwinsz.c
/etc/termcap
/etc/motd

这是该集合的补集:
$ perl -le 'for (@ARGV) { print unless -f && -T }' \
    getwinsz.c a.out /etc/termcap /bin /bin/cat \
    /dev/tty /usr/share/zoneinfo/UTC /etc/motd
a.out
/bin
/bin/cat
/dev/tty
/usr/share/zoneinfo/UTC

3

根据Bach的建议,我认为--mime-encoding是从file获取可靠结果的最佳标志。

file --mime-encoding [FILES ...] | grep -v '\bbinary$'

该命令将打印文件,这些文件被file认为具有非二进制编码。如果您只想要文件名,则可以通过cut -d: -f1将此输出传输。


注意:正如@yugr在下面报告的那样,.doc文件报告了一个application/mswordbinary的编码。对我来说,这看起来像是一个错误 - MIME类型被错误地与编码连接在一起。

$ for flag in --mime --mime-type --mime-encoding; do
    echo "$flag"
    file "$flag" /tmp/example.{doc{,x},png,txt}
  done
--mime
/tmp/example.doc:  application/msword; charset=binary
/tmp/example.docx: application/vnd.openxmlformats-officedocument.wordprocessingml.document; charset=binary
/tmp/example.png:  image/png; charset=binary
/tmp/example.txt:  text/plain; charset=us-ascii
--mime-type
/tmp/example.doc:  application/msword
/tmp/example.docx: application/vnd.openxmlformats-officedocument.wordprocessingml.document
/tmp/example.png:  image/png
/tmp/example.txt:  text/plain
--mime-encoding
/tmp/example.doc:  application/mswordbinary
/tmp/example.docx: binary
/tmp/example.png:  binary
/tmp/example.txt:  us-ascii

纯粹的 --mime 可以正常工作 (application/msword; charset=binary). - yugr
@yugr 这很有趣 - 它几乎看起来像是 file 中的一个错误,因为 .docx 文件在 --mime-encoding 下打印出了 binary - dimo414
1
忘记在这里回报了,但是.doc bug已经被修复 - dimo414

0
尝试以下命令行:

file "$FILE" | grep -vq 'ASCII' && echo "$FILE is binary"

很好,但是被urt8 ascii文件欺骗了。我使用:file“$FILE”| grep -vq 'text' - Goblinhack

0

使用tr -d "[[:print:]\n\t]" < file | wc -c排除二进制文件有点蛮力,但也不是启发式猜测。

find . -type f -maxdepth 1 -exec /bin/sh -c '
   for file in "$@"; do
      if [ $(LC_ALL=C LANG=C tr -d "[[:print:]\n\t]" < "$file" | wc -c) -gt 0 ]; then
         echo "${file} is no ASCII text file (UNIX)"
      else
         echo "${file} is ASCII text file (UNIX)"
      fi
   done
' _ '{}' +

以下的暴力方法使用 grep -a -m 1 $'[^[:print:]\t]' file 看起来要快得多。

find . -type f -maxdepth 1 -exec /bin/sh -c '
   tab="$(printf "\t")"
   for file in "$@"; do
      if LC_ALL=C LANG=C grep -a -m 1 "[^[:print:]${tab}]" "$file" 1>/dev/null 2>&1; then
         echo "${file} is no ASCII text file (UNIX)"
      else
         echo "${file} is ASCII text file (UNIX)"
      fi
   done
' _ '{}' + 

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