是什么使grep认为文件是二进制文件?


185

我的盒子上有一些Windows系统上的数据库转储。它们是文本文件。我正在使用cygwin通过它们进行grep。这些似乎是纯文本文件;我使用记事本和写字板等文本编辑器打开它们,它们看起来清晰易读。但是,当我对它们运行grep时,它会说binary file foo.txt matches

我注意到文件中包含一些ascii NUL字符,我相信这是数据库转储中的工件。

那么,什么使grep认为这些文件是二进制文件?的NUL性格吗?文件系统上有标志吗?我需要更改什么才能让grep向我显示行匹配项?


2
--null-data如果NUL是定界符,则可能有用。
Steve-o

Answers:


125

如果NUL文件中任何地方都有字符,则grep会将其视为二进制文件。

可能有这样的解决方法cat file | tr -d '\000' | yourgrep,首先消除所有null,然后搜索文件。


149
...或至少在GNU grep中使用-a/ --text
derobert

1
@derobert:实际上,在某些(较旧的)系统上,grep看到了行,但是它的输出将首先截断每条匹配的行NUL(可能是因为它调用了C的printf并给了它匹配的行?)。在这样的系统上,a grep cmd .sh_history将返回与匹配“ cmd”的行一样多的空行,因为sh_history的每一行都有特定的格式,每行的开头都有a NUL。(但您的评论“至少在GNU grep上可能是对的。我现在没有人可以测试,但我希望他们能很好地处理此问题)”
Olivier Dulac

4
是否存在NUL字符是唯一标准?我对此表示怀疑。它可能比这更聪明。任何超出Ascii 32-126范围的东西都是我的猜测,但是我们必须查看源代码才能确定。
Michael Martinez

2
我的信息来自特定grep实例的手册页。您对实施的评论是有效的,来源优于docs。
bbaja42

2
我有一个grep在cygwin上视为二进制的文件,因为它的破折号(0x96)较长,而不是常规的ASCII连字符/减号(0x2d)。我猜这个答案解决了OP的问题,但看来它是不完整的。
cp.engr

121

grep -a 为我工作:

$ grep --help
[...]
 -a, --text                equivalent to --binary-files=text

4
这是IMO最好,最便宜的答案。
pydsigner

但不符合POSIX
Matteo

21

您可以使用该strings实用程序从任何文件中提取文本内容,然后将其通过管道传输grep,如下所示:strings file | grep pattern


2
非常适合处理可能已部分损坏的日志文件
Hannes R.

是的,有时还会发生二进制混合日志记录。很好
sdkks

13

GNU grep 2.24 RTFS

结论:仅2例和2例:

  • NUL,例如 printf 'a\0' | grep 'a'

  • 根据C99的编码错误mbrlen(),例如:

    export LC_CTYPE='en_US.UTF-8'
    printf 'a\x80' | grep 'a'
    

    因为\x80不能为UTF-8 Unicode点的第一个字节:UTF-8-说明| en.wikipedia.org

此外,正如StéphaneChazelas所述,是什么使grep认为文件是二进制文件?| 在Unix&Linux Stack Exchange中,仅在读取长度为TODO的第一个缓冲区之前执行这些检查。

仅读取第一个缓冲区

因此,如果在非常大的文件中间发生NUL或编码错误,则无论如何都会将其grep掉。

我想这是出于性能原因。

例如:这将打印行:

printf '%10000000s\n\x80a' | grep 'a'

但这不是:

printf '%10s\n\x80a' | grep 'a'

实际的缓冲区大小取决于文件的读取方式。例如比较:

export LC_CTYPE='en_US.UTF-8'
(printf '\n\x80a') | grep 'a'
(printf '\n'; sleep 1; printf '\x80a') | grep 'a'

使用sleep,即使只有1个字节长,第一行也将传递给grep,因为该进程进入睡眠状态,而第二行不检查文件是否为二进制文件。

实时文件系统

git clone git://git.savannah.gnu.org/grep.git 
cd grep
git checkout v2.24

查找stderr错误消息的编码位置:

git grep 'Binary file'

导致我们/src/grep.c

if (!out_quiet && (encoding_error_output
                    || (0 <= nlines_first_null && nlines_first_null < nlines)))
    {
    printf (_("Binary file %s matches\n"), filename);

如果这些变量都得名,我们就可以得出结论。

encoding_error_output

快速grepping encoding_error_output显示,唯一可以修改它的代码路径通过buf_has_encoding_errors

clen = mbrlen (p, buf + size - p, &mbs);
if ((size_t) -2 <= clen)
  return true;

然后就man mbrlen

nlines_first_null和nlines

初始化为:

intmax_t nlines_first_null = -1;
nlines = 0;

因此,当找到null时将0 <= nlines_first_null变为true。

TODO什么时候可能nlines_first_null < nlines是假的?我很懒

POSIX

未定义二进制选项grep-在文件中搜索模式| pubs.opengroup.org和GNU grep没有记录它,因此RTFS是唯一的方法。


1
令人印象深刻的说明!
user394 2016年

2
请注意,仅在UTF-8语言环境中检查有效的UTF-8。还要注意,检查仅在从文件读取的第一个缓冲区上进行,对于我的系统,该文件对于常规文件似乎是32768字节,但对于管道或套接字,则可以小到一个字节。比较(printf '\n\0y') | grep y(printf '\n'; sleep 1; printf '\0y') | grep y的实例。
斯特凡Chazelas

@StéphaneChazelas“请注意,对有效UTF-8的检查仅在UTF-8语言环境中进行”:您是说export LC_CTYPE='en_US.UTF-8'像我的示例中所说的还是其他意思?Buf阅读:令人惊叹的示例,已添加答案。您显然比我更阅读源代码,让我想起了那些“学生被开悟”的黑客ko吟:-)
Ciro Santilli新疆改造中心法轮功六四事件

1
我没有考虑非常细致要么,但确实非常最近
斯特凡Chazelas

1
@CiroSantilli巴拿马文件六四事件法轮功您测试了哪个版本的GNU grep?
jrw32982'6

6

我的一个文本文件突然被grep视为二进制文件:

$ file foo.txt
foo.txt: ISO-8859 text

解决方法是使用iconv以下命令将其转换:

iconv -t UTF-8 -f ISO-8859-1 foo.txt > foo_new.txt

1
这也发生在我身上。特别是,原因是ISO-8859-1编码的不间断空格,为了使grep在文件中进行搜索,我必须将其替换为常规空格。
Gallaecio 2015年

4
grep 2.21将ISO-8859文本文件视为二进制文件,请在grep命令之前添加export LC_ALL = C。
netawater

@netawater谢谢!例如,如果您在文本文件中有类似Müller的内容,就是这种情况。该值是0xFC十六进制,因此grep期望的范围超出utf8(最高0x7F)。检查printf'a \ x7F'| 如Ciro所描述的grep'a'。
安妮·范·罗苏姆

5

文件/etc/magic/usr/share/misc/magic具有命令file用于确定文件类型的序列列表。

请注意,二进制文件可能只是后备解决方案。有时,具有奇怪编码的文件也被视为二进制文件。

grep在Linux上,有一些选项可以处理二进制文件,例如--binary-files-U / --binary


更准确地说,根据C99的编码错误mbrlen()。示例和源代码解释在:unix.stackexchange.com/a/276028/32558
Ciro Santilli新疆改造中心法轮功六四事件

2

我的一个学生有这个问题。有一个错误在grepCygwin。如果文件中有非ASCII字符,grepegrep把它作为二进制。


这听起来像一个功能,而不是一个错误。特别是考虑到有一个命令行选项来控制它(-a / --text)
Will Sheppard

2

实际回答“什么使grep认为文件是二进制文件?”的问题,您可以使用iconv

$ iconv < myfile.java
iconv: (stdin):267:70: cannot convert

以我为例,在文本编辑器中正确显示了西班牙语字符,但grep认为它们是二进制的。iconv输出将我指向这些字符的行号和列号

如果是NUL字符,iconv将认为它们是正常的,并且不会打印这种输出,因此此方法不适合


1

我有同样的问题。我曾经vi -b [filename]看过添加的字符。我找到了控制字符^@^M。然后在vi中键入:1,$s/^@//g以删除^@字符。对重复此命令^M

警告:要获取“蓝色”控制字符,请按Ctrl+,v然后按Ctrl+ MCtrl+ @。然后保存并退出vi。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.