如何查找内容中包含100%NUL字符的文件?


16

可以识别此类文件的Linux命令行命令是什么?

AFAIK该find命令(或grep)只能匹配文本文件的特定字符串。但是我想匹配所有内容,即我想查看哪些文件匹配正则表达式\0+而忽略行尾字符。也许这个find . cat | grep成语可能有效,但是我不知道如何使grep忽略行(并将文件视为二进制文件)。

背景:每隔几天,当我的笔记本电脑死机时,我的btrfs分区就会丢失信息:为写入而打开的文件将其内容替换为零(文件大小或多或少保持不变)。我使用同步,并且我不想传播这些假文件:我需要一种识别它们的方法,以便可以从备份中获取它们。


您的意思是文件中包含数字零?
拉胡尔·帕蒂尔

2
我认为这是关于NULL字符而不是数字零。
gertvdijk 2012年

10
让我们退后一步。每隔几天,笔记本电脑何时冻结?我们为什么不尝试解决这个真正的问题呢?
D_Bye

2
@D_Bye这是个好主意,但到目前为止,没有来过远:unix.stackexchange.com/questions/57894/...
亚当·赖采科斯基

1
你有没有考虑在-v给grep选项:筛选出有任何字节1到255的所有文件
CTRL-ALT-delor

Answers:


10

您可以grep使用Perl regex模式输入␀个字符:

$ echo -ne "\0\0" > nul.bin
$ echo -ne "\0x\0" > non-nul.bin
$ grep -P "[^\0]" *.bin
Binary file non-nul.bin matches

因此,您可以使用以下代码:

for path in *.foo
do
    grep -P "[^\0]" "$path" || echo "$path"
done

使用,我得到了意外的结果GNU grep 2.5.4。无论我使用--binary-files=text还是--binary-files=binary,它都会true为所有非空数据值(例如)提供结果。"\0\0""\0x\0""abcd"...我用确切的代码是: for typ in binary text ;do for dat in '\0\0' '\0x\0' 'abcd' '' ;do printf "$dat" >f; grep --binary-files=$typ -P '[^\0]' f >/dev/null && echo true || echo false; done; done
Peter.O

1
我现在已经进一步尝试了GNU grep) 2.10。后来的版本确实提供了预期的结果……因此,迟来的+1
Peter.O 2012年

1
在使用printf '\0\n\0\0\n\n' > fileprintf '\n' > file为此创建的文件上失败。
斯特凡Chazelas

2
@StéphaneChazelasOP确实说过“忽略行结束符”。因此,仅由\0\n字符组成的文件(任意一个甚至为零)都是匹配的。
l0b0

6

我同意D_Bye关于找到问题根源的说法。

无论如何要检查文件是否仅包含\0和/或\n可以使用tr

<file tr -d '\0\n' | wc -c

空/换行符和空文件返回0。


2
tr -d '\0\n'解决换行问题,然后只在输出中列出空文件的问题(?)...尽管它确实处理每个文件的每个字节(可能是或不是问题)+1
Peter.O

@ Peter.O:我错过了换行符,谢谢。该解决方案不是非常优化,如果要在大量数据上运行,最好在发现不匹配的字节后继续使用该解决方案。
2012年

效果很好。就我而言,我只需要确保排除零长度文件。谢谢。
亚当·里奇科夫斯基

1
但是,这也会将带有换行符的文件计为“空”。
克里斯·

1
@ChrisDown:我清楚地回答了它的作用。目前尚不清楚OP希望对仅换行符的文件执行什么操作。
雷神

5

我怀疑这些文件是稀疏的,即它们没有分配任何磁盘空间,它们只是指定文件大小(du将报告0)。

在这种情况下,可以使用GNU find(假设没有文件路径包含换行符):

find . -type f -size +0 -printf '%b:%p\n' | grep '^0:' | cut -d: -f2-

好点子。我没想过 我会尽力。使用du将防止刮擦文件系统中每个文件的内容,因此整个过程将不需要30分钟以上的时间来完成。
亚当·里奇科夫斯基

(和printf %b上面的报道什么du就报道)
斯特凡Chazelas

我会改变-size +0,以-size +1使零名长度的文件被排除的结果。此外\n,路径中包含的文件也会导致此命令出现问题。
泰森

@Tyson -size +0适用于严格大于0 -size +1的尺寸。适用于严格大于512的尺寸。已经提到了换行限制。
06StéphaneChazelas

@StéphaneChazelas感谢您对我的启发-size +1,您确实是对的。我已经解决。:-)
泰森

4

这是一个可以执行此操作的小型python程序:

import sys

def only_contains_nulls(fobj, chunk_size=1024):
    first = True
    while True:
        data = fobj.read(chunk_size)
        if not data:
            if first:
                return 1  # No data
            else:
                return 0
        if data.strip("\0"):
            return 1
        first = False

if __name__ == '__main__':
    with open(sys.argv[1]) as f:
        sys.exit(only_contains_nulls(f))

并采取行动:

$ printf '\0\0\0' > file
$ ./onlynulls file && echo "Only nulls" || echo "Non-null characters"
Only nulls
$ printf a >> file
$ ./onlynulls file && echo "Only nulls" || echo "Non-null characters"
Non-null characters

您可以通过使用发现的检查多个文件-execxargs,GNU parallel,以及类似的方案。或者,这将打印需要处理的文件名:

files=( file1 file2 )
for file in "${files[@]}"; do
    ./onlynulls "$file" || printf '%s\n' "$file"
done

请记住,如果要将其输出传递给另一个程序,文件名可以包含换行符,因此您应该以不同的方式来分隔它(适当地使用来定界\0)。

如果您有很多文件,则最好使用一个选项进行并行处理,因为这一次只能读取一个文件。


2
当心,零名长度的文件(如:/etc/nologin~/.hushlogin.nomedia,...)是由这个答案误。
泰森

@Tyson感谢您指出!我已经修好了。
克里斯·

3

查找仅包含空字符“ \ 0”和换行符“ \ n”的文件。
qsed的原因每个文件搜索,立即退出时在一条线上找到任何的非空字符。

find -type f -name 'file-*' |
  while IFS= read -r file ;do 
      out=$(sed -n '1=; /^\x00\+$/d; i non-null
                      ; q' "$file")
      [[ $out == "1" ]] &&  echo "$file"
  done

制作测试文件

> file-empty
printf '%s\n' 'line1' 'line2' 'line3'      > file-with-text           
printf '%4s\n' '' '' xx | sed 's/ /\x00/g' > file-with-text-and-nulls
printf '%4s\n' '' '' '' | sed 's/ /\x00/g' > file-with-nulls-and-newlines
printf '%4s'   '' '' '' | sed 's/ /\x00/g' > file-with-nulls-only

输出

./file-with-nulls-and-newlines
./file-with-nulls-only

要么-print0似乎缺少论点,find要么IFS=零件变得混乱。预期的分隔符是什么?
泰森

3

这一个班轮是使用GNU找到100%NUL文件的最有效的方式findxargsgrep(假设后者是建立与PCRE支持):

find . -type f -size +0 -readable -print0 |
  LC_ALL=C xargs -r0 grep -LP "[^\x00]" --

与其他提供的答案相比,此方法的优点是:

  • 非稀疏文件包含在搜索中。
  • 不可读的文件不会传递给grep,从而避免出现Permission denied警告。
  • grep找到任何非null字节后将停止从文件中读取数据(LC_ALL=C用于确保每个字节都被解释为字符)。
  • 空文件(零字节)不包括在结果中。
  • 较少的grep进程可以有效地检查多个文件。
  • 包含换行符或开头的路径 -已正确处理。
  • 适用于大多数缺少Python / Perl的嵌入式系统。

-Z选项传递给grep和使用xargs -r0 ...允许对100%nul文件执行进一步的操作(例如:清理):

find . -type f -size +0 -readable -print0 |
  LC_ALL=C xargs -0 grep -ZLP "[^\x00]" -- |
  xargs -r0 rm --

我还建议使用find选项-P以避免遵循符号链接,并且-xdev避免遍历文件系统(例如:远程挂载,设备树,绑定挂载等)。

为了忽略行尾字符,应使用以下变体(尽管我认为这不是一个好主意):

find . -type f -size +0 -readable -print0 |
  LC_ALL=C xargs -r0 grep -LP "[^\x00\r\n]" --

将所有内容放在一起,包括删除不需要的文件(100%nul /换行符)以防止对其进行备份:

find -P . -xdev -type f -size +0 -readable -print0 |
  LC_ALL=C xargs -0 grep -ZLP "[^\x00\r\n]" -- |
  xargs -0 rm --

我不建议包含空文件(零字节),它们通常出于非常 特定的 目的而存在。


在众多替代产品中最快的是一个大胆的主张。如果您添加基准测试,我会将您的答案标记为接受:-)
亚当·里奇科夫斯基

这样的基准将取决于许多因素,包括各种磁盘子系统的性能。
泰森

当然,但是什么总比没有好。各种方法优化CPU使用率的方式有所不同,因此有必要在SSD甚至是缓存文件上进行基准测试。以您当前使用的计算机为例,写一句话(CPU类型,内核数量,RAM,硬盘驱动器类型),描述文件集(例如内核源克隆+ 1GB的文件,\0其中有900MB的孔)并目前的结果计时。如果您以基准能使您信服的方式做到这一点,那么它对我们所有人来说都是最有说服力的
Adam Ryczkowski

“大多数嵌入式系统”没有GNU实用程序。更有可能是busybox。
斯特凡Chazelas

-P是中的默认设置find。如果要遵循符号链接,则为-L/ -follow。您会发现POSIX甚至都没有指定该选项find(尽管POSIX是为一些命令引入了-P / -H / -L的选项)。
斯特凡Chazelas

0

对于使用GNU sed,您可以使用-z选项,该选项将一行定义为零终止的字符串,并匹配和删除空行,如下所示:

if [ "$( sed -z '/^$/d' "$file" | head -c 1 | wc -c )" -eq 0 ]; then
    echo "$file contains only NULL!"
fi

中间的head命令只是一个优化。


-1

蟒蛇

单文件

定义别名:

alias is_binary="python -c 'import sys; sys.exit(not b\"\x00\" in open(sys.argv[1], \"rb\").read())'"

测试一下:

$ is_binary /etc/hosts; echo $?
1
$ is_binary `which which`; echo $?
0

多个文件

递归查找所有二进制文件:

IS_BINARY='import sys; sys.exit(not b"\x00" in open(sys.argv[1], "rb").read())'
find . -type f -exec bash -c "python -c '$IS_BINARY' {} && echo {}" \;

要查找所有非二进制文件,更改&&||


1
询问此问题以标识包含nul字符的文件(忽略换行符),此处给出的Python代码标识包含任何 nul字符的文件。
泰森
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.