如何计算文件中字节序列出现的次数?


16

我想计算某个字节序列在文件中发生了多少次。例如,我想找出该数字\0xdeadbeef在可执行文件中出现了多少次。现在我正在使用grep做到这一点:

#/usr/bin/fish
grep -c \Xef\Xbe\Xad\Xde my_executable_file

(因为我的CPU是little-endian,所以这些字节以相反的顺序写入)

但是,我的方法存在两个问题:

  • 这些\Xnn逃逸序列仅在鱼壳中起作用。
  • grep实际上是在计算包含我的幻数的行数。如果模式在同一行中出现两次,则只会计数一次。

有办法解决这些问题吗?我如何才能使这一衬板在Bash shell中运行并准确计算出文件中图案出现的次数?


一些帮助:unix.stackexchange.com/q/231213/117549 -特别grep -o
杰夫·夏勒

1
grep是使用错误的工具。考虑使用bgrep或bgrep2。
fpmurphy

3
如果要搜索的序列是11221122,应在类似112211221122什么的输入上返回什么?1或2?
斯特凡Chazelas

在这种情况下,我可以报告2或3场比赛。实施起来比较简单。
hugomg '16

Answers:


15

这是要求的单线解决方案(对于具有“进程替换”功能的最新Shell):

grep -o "ef be ad de" <(hexdump -v -e '/1 "%02x "' infile.bin) | wc -l

如果没有<(…)可用的“进程替换” ,只需使用grep作为过滤器:

hexdump -v -e '/1 "%02x "' infile.bin  | grep -o "ef be ad de" | wc -l

以下是解决方案各部分的详细说明。

十六进制数字的字节值:

您的第一个问题很容易解决:

这些\ Xnn转义序列仅在鱼壳中起作用。

将上部更改X为下部,x并使用printf(对于大多数shell):

$ printf -- '\xef\xbe\xad\xde'

或使用:

$ /usr/bin/printf -- '\xef\xbe\xad\xde'

对于那些选择不实现'\ x'表示形式的shell。

当然,将十六进制转换为八进制将在(几乎)任何外壳上工作:

$ "$sh" -c 'printf '\''%b'\'' "$(printf '\''\\0%o'\'' $((0xef)) $((0xbe)) $((0xad)) $((0xde)) )"'

其中“ $ sh”是任何(合理的)shell。但是很难正确引用它。

二进制文件。

最健壮的解决方案是将文件和字节序列(两者)都转换为某种编码,这些编码对于奇数字符值(如新行)0x0A或(空字节)没有问题0x00。使用为处理“文本文件”而设计和修改的工具来正确地管理这两者都是相当困难的。

像base64这样的转换似乎是有效的转换,但它提出了一个问题,即每个输入字节最多可以具有三个输出表示形式,具体取决于它是mod 24(位)位置的第一个,第二个还是第三个字节。

$ echo "abc" | base64
YWJjCg==

$ echo "-abc" | base64
LWFiYwo=

$ echo "--abc" | base64
LS1hYmMK

$ echo "---abc" | base64        # Note that YWJj repeats.
LS0tYWJjCg==

十六进制变换。

这就是为什么最健壮的转换应该是从每个字节边界开始的转换,例如简单的HEX表示。
我们可以使用以下任何一种工具来获取具有十六进制表示形式的文件:

$ od -vAn -tx1 infile.bin | tr -d '\n'   > infile.hex
$ hexdump -v -e '/1 "%02x "' infile.bin  > infile.hex
$ xxd -c1 -p infile.bin | tr '\n' ' '    > infile.hex

在这种情况下,要搜索的字节序列已为十六进制。

$ var="ef be ad de"

但是它也可以改变。往返十六进制-bin-hex的示例如下:

$ echo "ef be ad de" | xxd -p -r | od -vAn -tx1
ef be ad de

可以从二进制表示中设置搜索字符串。od,hexdump或xxd上方显示的三个选项中的任何一个都是等效的。只要确保包含空格以确保匹配位于字节边界上即可(不允许半字节移位):

$ a="$(printf "\xef\xbe\xad\xde" | hexdump -v -e '/1 "%02x "')"
$ echo "$a"
ef be ad de

如果二进制文件如下所示:

$ cat infile.bin | xxd
00000000: 5468 6973 2069 7320 efbe adde 2061 2074  This is .... a t
00000010: 6573 7420 0aef bead de0a 6f66 2069 6e70  est ......of inp
00000020: 7574 200a dead beef 0a66 726f 6d20 6120  ut ......from a 
00000030: 6269 0a6e 6172 7920 6669 6c65 2e0a 3131  bi.nary file..11
00000040: 3232 3131 3232 3131 3232 3131 3232 3131  2211221122112211
00000050: 3232 3131 3232 3131 3232 3131 3232 3131  2211221122112211
00000060: 3232 0a

然后,一个简单的grep搜索将给出匹配序列的列表:

$ grep -o "$a" infile.hex | wc -l
2

一条线?

可以全部在一行中执行:

$ grep -o "ef be ad de" <(xxd -c 1 -p infile.bin | tr '\n' ' ') | wc -l

例如,在11221122同一文件中搜索将需要以下两个步骤:

$ a="$(printf '11221122' | hexdump -v -e '/1 "%02x "')"
$ grep -o "$a" <(xxd -c1 -p infile.bin | tr '\n' ' ') | wc -l
4

要“查看”匹配项:

$ grep -o "$a" <(xxd -c1 -p infile.bin | tr '\n' ' ')
3131323231313232
3131323231313232
3131323231313232
3131323231313232

$ grep "$a" <(xxd -c1 -p infile.bin | tr '\n' ' ')

…0a 3131323231313232313132323131323231313232313132323131323231313232 313132320a


正在缓冲

担心grep会缓冲整个文件,如果文件很大,则会给计算机带来沉重的负担。为此,我们可以使用无缓冲的sed解决方案:

a='ef be ad de'
hexdump -v -e '/1 "%02x "' infile.bin  | 
    sed -ue 's/\('"$a"'\)/\n\1\n/g' | 
        sed -n '/^'"$a"'$/p' |
            wc -l

第一个sed是无缓冲的(-u),仅用于在每个匹配字符串的流中注入两个换行符。第二个sed将仅打印(短)匹配行。wc -l将计数匹配的行。

这只会缓冲一些短线。第二个sed中的匹配字符串。使用的资源应该非常少。

或者,虽然理解起来有些复杂,但是在一个sed中是相同的想法:

a='ef be ad de'
hexdump -v -e '/1 "%02x "' infile.bin  |
    sed -u '/\n/P;//!s/'"$a"'/\n&\n/;D' |
        wc -l

2
请注意,如果将所有文本放在一行上,则意味着grep最终将整个文本加载到内存中(由于十六进制编码,此处的大小是原始文件的两倍+ 1),因此最终,它的内容更多比python方法或perl带有方法的开销大-0777。您还需要一个grep支持任意长度行的实现(-o通常支持这些行)。否则,好的答案。
斯特凡Chazelas

1
您的十六进制版本与半字节移位的值匹配吗?除了所需的字节。od -An -tx1 | tr -d '\n'hexdump -v -e '/1 " %02x"'在搜索字符串中也包含空格的情况下,请避免这种情况,但我看不到这种解决方法xxd
dave_thompson_085 '16

@ dave_thompson_085答案已编辑。我相信答案现在只会匹配字节边界,再次感谢。
sorontar '16

@StéphaneChazelas您能否查看使用无缓冲sed的建议选项。谢谢。
sorontar '16

sed -u(如果有)(用于可用)。这意味着它将一次读取一个字节,并立即输出其输出而无需缓冲。无论如何,仍然需要将整个行加载到模式空间中,因此这里无济于事。
斯特凡Chazelas

7

使用GNU grep-P(perl-regexp)标志

LC_ALL=C grep -oaP '\xef\xbe\xad\xde' file | wc -l

LC_ALL=C是为了避免在多字节语言环境中出现问题,grep否则将尝试将字节序列解释为字符。

-a对待与文本文件等效的二进制文件(而不是通常的行为,即grep仅打印出是否存在至少一个匹配项)


这个解决方案总是给我0个匹配项,而不是正确的数字。
hugomg '16

@hugomg,是否可能需要反转传递的字节grep 以使其匹配?
iruvar

我不认为这是命令。该问题的其他两个答案可以正常工作。
hugomg '16

2
@hugomg,这是区域设置。参见编辑。
斯特凡Chazelas

2
我建议包括该-a选项,否则grep将对Binary file file.bin matchesgrep检测为二进制的任何文件进行回答。
sorontar '16

6
PERLIO=:raw perl -nE '$c++ while m/\xef\xbe\xad\xde/g; END{say $c}' file

它将输入文件视为二进制文件(不进行换行或编码转换,请参见perlrun),然后在输入文件上循环,不打印给定十六进制的所有匹配项的计数器(或任何形式,请参见perlre) 。


2
请注意,如果要搜索的序列包含字节0xa,则不能使用它。在这种情况下,您可以使用其他记录分隔符(带有-0ooo)。
斯特凡Chazelas

1
@StéphaneChazelas,您可以将感兴趣的序列本身用作$/,其折衷略有不同(内存使用量与此类序列之间的最大距离成正比):perl -nE 'BEGIN { $/ = "\xef\xbe\xad\xde" } chomp; $c++ unless eof && length; END { say $c }'
hobbs

@StéphaneChazelas请阅读我的答案以获取任何字节值的解决方案。
sorontar '16

1
@hobbs,无论如何,即使在这里,内存使用量也将与两个0xa字节之间的最大距离成比例,对于非文本文件而言,这可能是任意大的。
斯特凡Chazelas

5

使用GNU awk,您可以执行以下操作:

LC_ALL=C awk -v 'RS=\xef\xbe\xad\xde' 'END{print NR - (NR && RT == "")}'

如果任何字节是ERE运算符,则必须通过(使用\\)对其进行转义。喜欢0x2e这是.必须输入为\\.\\\x2e。除此之外,它还可以处理任意字节值,包括0和0xa。

请注意,这并不仅仅NR-1因为有几种特殊情况而变得简单:

  • 当输入为空时,NR为0,NR-1将给出-1。
  • 当输入以记录分隔符结尾时,此后不会创建空记录。我们用进行测试RT==""

还要注意,在最坏的情况下(如果文件不包含搜索词),文件最终将被整个加载到内存中。


5

我看到的最直接的翻译是:

$ echo $'\xef\xbe\xad\xde' > hugohex
$ echo $'\xef\xbe\xad\xde\xef\xbe\xad\xde' >> hugohex
$ grep -F -a -o -e $'\xef\xbe\xad\xde' hugohex|wc -l
3

当我用$'\xef'bash的ANSI-引用(最初ksh93的功能,现在支持zshbashmksh,FreeBSD的sh)鱼的版本\Xef,并用grep -o ... | wc -l计算实例。grep -o在单独的行上输出每个匹配项。该-a标志使grep在二进制文件上的行为与在文本文件上的行为相同。-F用于固定字符串,因此您无需转义正则表达式运算符。

与您的fish情况类似,即使要查找的序列包含字节0或0xa(ASCII换行符),也无法使用该方法。


使用printf '%b' $(printf '\\%o ' $((0xef)) $((0xbe)) $((0xad)) $((0xde))) > hugohex'将是最可移植的“纯壳”方法。当然:printf "efbeadde" | xxd -p -r > hugohex似乎是最实用的方法。
sorontar '16

4

您可以使用Python的bytes.count方法来获取字节串中不重叠的子串的总数。

python -c "print(open('./myexecutable', 'rb').read().count(b'\xef\xbe\xad\xde'))"

这种单行代码会将整个文件加载到内存中,因此效率不是最高,但比Perl; D更有效且更易读


“比Perl更清晰”仅是TECO的第一步-IINM为:239I$ 190I$ 173I$ 222I$ HXA ERfile$Y 0UC <:S^EQA$; %C$> QC=(gd&r)
dave_thompson_085

您可以mmap()使用Python创建文件;这将减少内存提交。
Toby Speight

1
tr "$(printf \\0xef)\n" \\n\\0 < infile |
grep -c "^$(printf "\0xbe\0xad\0xde")"

1

我认为您可以使用Perl,请尝试一下:

perl -0777ne 'CORE::say STDOUT s/\xef\xbe\xad\xde//g' file_name  

Replace命令s给出了要进行的替换次数,-0777表示不要将新行视为特殊字符,e-执行命令,say先打印接下来要打印的行,然后再打印新行字符,n我没有完全掌握,但是没有用-从docs:

使Perl在您的程序周围假设以下循环,从而使其遍历文件名参数,如sed -n或awk:LINE:while(<>){...#您的程序在这里}

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.