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

hug*_*omg 17 grep bash escape-characters

我想计算特定字节序列在我拥有的文件中发生的次数。例如,我想找出该数字\0xdeadbeef在可执行文件中出现的次数。现在我正在使用 grep 这样做:

#/usr/bin/fish
grep -c \Xef\Xbe\Xad\Xde my_executable_file
Run Code Online (Sandbox Code Playgroud)

(字节以相反的顺序写入,因为我的 CPU 是小端的)

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

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

有没有办法解决这些问题?我怎样才能让这个 liner 在 Bash shell 中运行并准确计算该模式在文件中出现的次数?

ImH*_*ere 16

这是要求的单行解决方案(对于最近具有“进程替换”的 shell):

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

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

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

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

来自十六进制数字的字节值:

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

那些 \Xnn 转义序列仅适用于鱼壳。

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

$ printf -- '\xef\xbe\xad\xde'
Run Code Online (Sandbox Code Playgroud)

或使用:

$ /usr/bin/printf -- '\xef\xbe\xad\xde'
Run Code Online (Sandbox Code Playgroud)

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

当然,将十六进制转换为八进制适用于(几乎)任何 shell:

$ "$sh" -c 'printf '\''%b'\'' "$(printf '\''\\0%o'\'' $((0xef)) $((0xbe)) $((0xad)) $((0xde)) )"'
Run Code Online (Sandbox Code Playgroud)

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

二进制文件。

最可靠的解决方案是将文件和字节序列(两者)转换为某种编码,这种编码对奇数字符值(如 (new line)0x0A或 (null byte) )没有问题0x00。使用为处理“文本文件”而设计和调整的工具,两者都很难正确管理。

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

$ echo "abc" | base64
YWJjCg==

$ echo "-abc" | base64
LWFiYwo=

$ echo "--abc" | base64
LS1hYmMK

$ echo "---abc" | base64        # Note that YWJj repeats.
LS0tYWJjCg==
Run Code Online (Sandbox Code Playgroud)

十六进制变换。

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

$ 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
Run Code Online (Sandbox Code Playgroud)

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

$ var="ef be ad de"
Run Code Online (Sandbox Code Playgroud)

但它也可以被改造。往返 hex-bin-hex 的示例如下:

$ echo "ef be ad de" | xxd -p -r | od -vAn -tx1
ef be ad de
Run Code Online (Sandbox Code Playgroud)

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

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

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

$ 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
Run Code Online (Sandbox Code Playgroud)

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

$ grep -o "$a" infile.hex | wc -l
2
Run Code Online (Sandbox Code Playgroud)

一条线?

这一切都可以在一行中执行:

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

比如11221122在同一个文件中搜索就需要这两个步骤:

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

要“查看”匹配项:

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

$ grep "$a" <(xxd -c1 -p infile.bin | tr '\n' ' ')
Run Code Online (Sandbox Code Playgroud)

… 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
Run Code Online (Sandbox Code Playgroud)

第一个 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
Run Code Online (Sandbox Code Playgroud)

  • 请注意,如果您将所有文本放在一行中,则意味着 `grep` 最终会将其全部加载到内存中(这里是原始文件大小的两倍 + 1,因为采用了十六进制编码),因此最终会结束比 `python` 方法或带有 `-0777` 的 `perl` 方法开销更大。您还需要一个支持任意长度行的 `grep` 实现(支持 `-o` 的通常会这样做)。否则很好回答。 (2认同)

iru*_*var 7

使用 GNUgrep-P(perl-regexp) 标志

LC_ALL=C grep -oaP '\xef\xbe\xad\xde' file | wc -l
Run Code Online (Sandbox Code Playgroud)

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

-a将二进制文件等同于文本文件(而不是正常行为,grep只打印出是否至少有一个匹配项)

  • @hugomg,这是语言环境。见编辑。 (2认同)
  • 我会建议包含`-a` 选项,否则grep 将用`Binary file file.bin 匹配` 来回答grep 检测为二进制的任何文件。 (2认同)

thr*_*rig 6

PERLIO=:raw perl -nE '$c++ while m/\xef\xbe\xad\xde/g; END{say $c}' file
Run Code Online (Sandbox Code Playgroud)

它将输入文件视为二进制文件(没有换行或编码的翻译,请参阅perlrun)然后循环输入文件(s)不打印递增给定十六进制所有匹配的计数器(或任何形式,请参阅perlre) .

  • 请注意,如果要搜索的序列包含字节 0xa,则不能使用它。在这种情况下,您可以使用不同的记录分隔符(使用 `-0ooo`)。 (2认同)

Jef*_*ler 6

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

$ 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
Run Code Online (Sandbox Code Playgroud)

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

就像您的fish情况一样,如果要查找的序列包含字节 0 或 0xa (ASCII 中的换行符),则不能使用该方法。


Sté*_*las 5

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

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

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

请注意,这并不简单,NR-1因为有几个特殊情况:

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

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