私が持っているファイル内で特定のバイトシーケンスが発生した回数を数えたい。たとえば、実行可能ファイル内で\0xdeadbeef
が何回発生するかを調べたいと思います。今私はそれをgrepを使ってやっています:
#/usr/bin/fish
grep -c \Xef\Xbe\Xad\Xde my_executable_file
(CPUはリトルエンディアンなので、バイトは逆順で書き込まれます)
ただし、私のアプローチには2つの問題があります。
\Xnn
エスケープシーケンスは、魚のシェルでのみ機能します。これらの問題を修正する方法はありますか?この1つのライナーをBashシェルで実行し、ファイル内でパターンが発生する回数を正確にカウントするにはどうすればよいですか?
これは、要求されたワンライナーソリューションです(「プロセス置換」を持つ最近のシェルの場合):
_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を使用します(ほとんどのシェルの場合)。
_$ printf -- '\xef\xbe\xad\xde'
_
または使用:
_$ /usr/bin/printf -- '\xef\xbe\xad\xde'
_
'\ x'表現を実装しないことを選択したシェルの場合。
もちろん、16進数を8進数に変換すると、(ほぼ)すべてのシェルで機能します。
_$ "$sh" -c 'printf '\''%b'\'' "$(printf '\''\\0%o'\'' $((0xef)) $((0xbe)) $((0xad)) $((0xde)) )"'
_
「$ sh」は任意の(合理的な)シェルです。しかし、それを正しく引用することは非常に困難です。
最も堅牢なソリューションは、ファイルとバイトシーケンス(両方)を、(改行)_0x0A
_または(nullバイト)_0x00
_などの奇数の文字値に問題のないエンコードに変換することです。どちらも、「テキストファイル」を処理するように設計および適合されたツールで正しく管理することは非常に困難です。
Base64のような変換は有効に見えるかもしれませんが、mod 24(ビット)位置の最初、2番目、または3番目のバイトであるかどうかに応じて、すべての入力バイトが最大3つの出力表現を持つ場合があるという問題があります。
_$ echo "abc" | base64
YWJjCg==
$ echo "-abc" | base64
LWFiYwo=
$ echo "--abc" | base64
LS1hYmMK
$ echo "---abc" | base64 # Note that YWJj repeats.
LS0tYWJjCg==
_
そのため、最も堅牢な変換は、単純なHEX表現のように、各バイト境界から開始する変換でなければなりません。
次のいずれかのツールを使用して、ファイルの16進表現を含むファイルを取得できます。
_$ 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
_
この場合、検索するバイトシーケンスはすでに16進数です。
:
_$ var="ef be ad de"
_
しかし、それも変容する可能性があります。往復hex-bin-hexの例を次に示します。
_$ echo "ef be ad de" | xxd -p -r | od -vAn -tx1
ef be ad de
_
検索文字列は、バイナリ表現から設定できます。上記のod、hexdump、またはxxdの3つのオプションはすべて同等です。必ずスペースを含めて、一致がバイト境界上にあることを確認してください(ニブルシフトは許可されません)。
_$ 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
_
すべて1行で実行できます。
_$ grep -o "ef be ad de" <(xxd -c 1 -p infile.bin | tr '\n' ' ') | wc -l
_
たとえば、同じファイルで_11221122
_を検索するには、次の2つの手順が必要です。
_$ 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 131323231313232313132323131323231313232313132323131323231313232 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
_)、一致する文字列ごとにストリームに2つの改行を挿入するためにのみ使用されます。 2番目のsed
は、(短い)一致する行のみを出力します。 wc -lは、一致する行をカウントします。
これにより、一部の短い行のみがバッファリングされます。 2番目のsedの一致する文字列。これは、使用されるリソースがかなり少ないはずです。
または、理解するのがいくらか複雑ですが、1つのsedで同じアイデア:
_a='ef be ad de'
hexdump -v -e '/1 "%02x "' infile.bin |
sed -u '/\n/P;//!s/'"$a"'/\n&\n/;D' |
wc -l
_
GNU grep
's -P
(Perl-regexp)フラグ
LC_ALL=C grep -oaP '\xef\xbe\xad\xde' file | wc -l
LC_ALL=C
は、grep
がバイトのシーケンスを文字として解釈しようとするマルチバイトロケールでの問題を回避するためのものです。
-a
は、テキストファイルと同等のバイナリファイルを扱います(通常の動作ではなく、grep
は、少なくとも1つの一致があるかどうかを出力するだけです)。
私が見る最も簡単な翻訳は次のとおりです。
$ 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-quoting (元々は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 == "")}'
バイトのいずれかがERE演算子である場合は、エスケープする必要があります(\\
を使用)。 0x2e
のように、.
は\\.
または\\\x2e
として入力する必要があります。それ以外は、0と0xaを含む任意のバイト値で動作するはずです。
いくつかの特殊なケースがあるため、NR-1
ほど単純ではないことに注意してください。
RT==""
でテストします。また、最悪の場合(ファイルに検索語が含まれていない場合)、ファイルはメモリ全体にロードされることに注意してください)。
Pythonのbytes.count
メソッドを使用して、バイト文字列内の重複しない部分文字列の総数を取得します。
python -c "print(open('./myexecutable', 'rb').read().count(b'\xef\xbe\xad\xde'))"
このワンライナーはファイル全体をメモリにロードするため、最も効率的ではありませんが、Perlよりも読みやすく、読みやすくなっています。; D
tr "$(printf \\0xef)\n" \\n\\0 < infile |
grep -c "^$(printf "\0xbe\0xad\0xde")"