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
转义序列仅适用于鱼壳。有没有办法解决这些问题?我怎样才能让这个 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)
使用 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
只打印出是否至少有一个匹配项)
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) .
我看到的最直接的翻译是:
$ 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
的功能,现在支持zsh
,bash
,mksh
,FreeBSD的sh
)鱼的版本\Xef
,并用grep -o ... | wc -l
计算实例。grep -o
在单独的行上输出每个匹配项。该-a
标志使 grep 在二进制文件上的行为与在文本文件上的行为相同。-F
用于固定字符串,因此您无需转义正则表达式运算符。
就像您的fish
情况一样,如果要查找的序列包含字节 0 或 0xa (ASCII 中的换行符),则不能使用该方法。
使用 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 0x2e
which is.
必须输入为\\.
or \\\x2e
。除此之外,它应该可以处理任意字节值,包括 0 和 0xa。
请注意,这并不简单,NR-1
因为有几个特殊情况:
RT==""
.另请注意,在最坏的情况下(如果文件不包含搜索词),文件最终将被整个加载到内存中)。