ファイル内でバイトシーケンスが発生する回数をカウントするにはどうすればよいですか?


16

私が持っているファイル内で特定のバイトシーケンスが何回発生するかをカウントします。たとえば\0xdeadbeef、実行可能ファイル内でその数が何回発生するかを知りたいです。今、私はgrepを使用してそれをやっています:

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

(私のCPUはリトルエンディアンであるため、バイトは逆の順序で書き込まれます)

ただし、アプローチには2つの問題があります。

  • これらの\Xnnエスケープシーケンスは、魚の殻でのみ機能します。
  • grepは実際に、マジックナンバーを含む行の数をカウントしています。パターンが同じ行で2回発生する場合、1回だけカウントされます。

これらの問題を修正する方法はありますか?この1つのライナーをBashシェルで実行し、ファイル内でパターンが発生する回数を正確にカウントするにはどうすればよいですか?


いくつかのヘルプ:unix.stackexchange.com/q/231213/117549-具体的には、grep -o
ジェフシャラー

1
grepは使用するのに間違ったツールです。bgrepまたはbgrep2を検討してください。
fpmurphy

3
検索するシーケンスがの場合、次の11221122ような入力で何を返す必要があります112211221122か?1または2?
ステファンシャゼル

その場合、2つまたは3つの一致を報告しても構いません。どちらを実装する方が簡単でしょう。
hugomg

回答:


15

これは、要求された1行のソリューションです(「プロセス置換」を持つ最近のシェルの場合):

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

以下は、ソリューションの各部分の詳細な説明です。

16進数のバイト値:

最初の問題は簡単に解決できます。

これらの\ Xnnエスケープシーケンスは、魚の殻でのみ機能します。

上部Xを下部に変更し、xprintfを使用します(ほとんどのシェルの場合):

$ 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」は(合理的な)シェルです。しかし、それを正しく引用することは非常に困難です。

バイナリファイル。

最も堅牢な解決策は、ファイルとバイトシーケンス(両方)を(new line)0x0Aや(null byte)などの奇数の文字値に問題のないエンコーディングに変換することです0x00。「テキストファイル」を処理するように設計および適合されたツールを使用して、両方を正しく管理することは非常に困難です。

base64のような変換は有効なように見えるかもしれませんが、mod 24(ビット)位置の1番目、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 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)で、一致する文字列ごとにストリームに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

2
すべてのテキストを1行に配置するgrepと、メモリ全体に読み込まれることになります(16進エンコードのため、元のファイルのサイズの2倍+ 1)。したがって、最終的には、pythonアプローチまたはperl1つ以上のオーバーヘッド-0777。またgrep、任意の長さの行をサポートする実装(-o通常はサポートするもの)も必要です。そうでなければ良い答え。
ステファンシャゼル16

1
16進バージョンはニブルシフトされた値と一致しますか?e fb ea dd e?目的のバイトに加えて。od -An -tx1 | tr -d '\n'またはhexdump -v -e '/1 " %02x"'スペースを含む検索文字列でこれを回避しますが、のような修正は表示されませんxxd
dave_thompson_085 16

@ dave_thompson_085回答が編集されました。答えはバイト境界のみに一致すると今は信じています。ありがとうございます。
sorontar

@StéphaneChazelasバッファリングされていないsedを使用する提案されたオプションを確認してください。ありがとう。
sorontar

sed -u(利用可能な場合)は、バッファ解除用です。つまり、入力で一度に1バイトを読み取り、バッファリングせずに出力をすぐに出力します。いずれにしても、パターンスペースの行全体を読み込む必要があるため、ここでは役に立ちません。
ステファンシャゼル16

7

GNU grep-P(perl-regexp)フラグを使用

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

LC_ALL=Cバイトgrepシーケンスを文字として解釈しようとするマルチバイトロケールの問題を回避することです。

-aテキストファイルと同等のバイナリファイルを処理します(通常の動作ではgrepなく、少なくとも1つの一致があるかどうかを出力します)


このソリューションでは、正しい数ではなく、常に0個の一致が返されます。
hugomg

@hugomg、渡されたバイトを反転さgrep せて一致させる必要があるかもしれませんか?
iruvar

私はそれが順序だとは思わない。この質問に対する他の2つの答えは正しく機能します。
hugomg

2
@hugomg、それはロケールです。編集を参照してください。
ステファンシャゼル

2
-aオプションを含めることをお勧めします。そうしないと、grepがBinary file file.bin matchesバイナリとして検出するファイルに対してgrepが応答します。
sorontar

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

入力ファイルをバイナリとして扱い(ラインフィードまたはエンコーディングの変換なし、perlrunを参照)、入力ファイルをループ処理して、指定された16進数(または任意の形式、perlreを参照)のすべての一致に対してカウンターをインクリメントしません。


2
検索するシーケンスにバイト0xaが含まれている場合は使用できないことに注意してください。その場合、別のレコード区切り文字を使用できます(で-0ooo)。
ステファンシャゼル

1
@StéphaneChazelasでは、目的のシーケンス自体をとして使用できますが$/、わずかに異なるトレードオフ(メモリ使用量はこのようなシーケンス間の最大距離に比例します):perl -nE 'BEGIN { $/ = "\xef\xbe\xad\xde" } chomp; $c++ unless eof && length; END { say $c }'
hobbs

@StéphaneChazelasバイト値の解決策については、私の答えをお読みください。
sorontar

1
@hobbsは、いずれにしても、ここでさえ、メモリ使用量は、非テキストファイルでは任意に大きくなる可能性のある2つの0xaバイト間の最大距離に比例します。
ステファンシャゼル16

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

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でファイルを作成できます。それはメモリのコミットを減らすでしょう。
トビースパイト


1

Perlを使用できると思います。試してみてください。

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

置換コマンドsは、行われた置換の数を示します。-0777は、改行を特殊文字として扱わないことを意味します。- eコマンドを実行し、say次の行を印刷してから改行文字を印刷します。n、私は完全には把握していなかったが、仕事をしませんワット/アウト-からdocs:

Perlは、プログラムの周りに次のループを想定します。これにより、sed -nまたはawkのようなファイル名の引数を繰り返し処理します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.