みなさん運用してますか? ログのgrepしてますか?
私はしています。grepなら、↓こんな感じでやるだけです!1
(Linux上)
$ cat 2023-*.log | grep 'some_search_words_here'
はい。 上記には罠があります。
2023-*.log
の中身のどこかが 「バイナリファイルっぽいな……?」と grep
に思われたが最後、下記のようなメッセージを出し、以後 grep
を諦められてしまいます。
$ cat 2023-*.log | grep 'some_search_words_here'
...
Binary file (standard input) matches
(以後 grep をサボる👼 単純に標準入力を捨てはじめる)
巨大な標準入力などを扱う際に通常の grep
だとバイナリっぽいものが来ると以後の探索を諦めてしまうんですね。
これでは困るので、 grep -a
もしくは grep --text
を使って「ちょっとバイナリファイルっぽくても諦めんなよ」という思いを grep
に伝えましょう。
$ cat 2023-*.log | grep -a 'some_search_words_here'
...
(🔥バイナリっぽいものを見つけても grep を諦めない🔥)
とにかく巨大な標準入力などを扱う際に grep -a
じゃない場合は本当にそれでいいのかちょっと一考したほうがよさそうです。
grep (GNU Grep) のバイナリかどうかをチェックする基準はどうなっているのか?
GNU Grepの公式のソースコードリポジトリ によると、該当部分のソースは下記のようになっています。
for (bool firsttime = true; ; firsttime = false)
{
if (nlines_first_null < 0 && eol && binary_files != TEXT_BINARY_FILES
&& (buf_has_nulls (bufbeg, buflim - bufbeg)
|| (firsttime && file_must_have_nulls (buflim - bufbeg, fd, st))))
{
if (binary_files == WITHOUT_MATCH_BINARY_FILES)
return 0;
if (!count_matches)
done_on_match = out_quiet = true;
nlines_first_null = nlines;
nul_zapper = eol;
skip_nuls = skip_empty_lines;
}
いろいろ条件はありそうですが、条件の一つとして「バッファ中にヌルバイト(\0
)があればバイナリファイル扱いする」という感じがしますね。
実際に実験してみます。 (Mac上で ggrep (GNU Grep 3.9) を使って実験)
#!/bin/bash
log_input () {
seq 0 100
printf '\x00'
echo "DONE"
}
# 0 から 100 まで印字した後にヌルバイトが来るような入力にgrepを噛ませてみる
log_input | ggrep "."
上記で実験してみると、マッチを試行する様子もなしに、にべもなく binary file matches
と言われてしまいました。
$ bash test_grep.sh
ggrep: (standard input): binary file matches
おそらく seq 0 100
がバッファに収まってしまって、検索する以前にヌルバイトを発見してしまったので、試行をやめたのでしょう。
ではこれを seq 10000 19500
に入れ替えるとどうでしょうか?
# seq 10000 19500
$ bash test_grep.sh
ggrep: (standard input): binary file matches
19491
19492
19493
19494
19495
19496
19497
19498
19499
19500
今度はちゃんとヌルバイトまでは試行してくれた感じになりました。
ただ、これを何回か繰り返すと、たまに試行すらしない場合がありました。
# seq 10000 19500
$ bash test_grep.sh
ggrep: (standard input): binary file matches
十分に大きなサイズの入力だと確実に試行してくれるっぽいです。
# seq 10000 30000
$ bash test_grep.sh
ggrep: (standard input): binary file matches
29991
29992
29993
29994
29995
29996
29997
29998
29999
30000
grepのヌルバイト判定(バイナリ判定)に即時入るかどうかは入力サイズによっては再現性がなく不安定なようです。 面白いですね。
まとめ
大きなファイルや標準入力をgrepする時には grep -a
or grep --text
を使おう。
-
grepの引数にファイル名を直接渡せばいいと思うかもしれませんが、ここでは簡単な例として
cat 2023-*.log
としています。難しい例がお好みの方はたとえば複数の JSONL ファイルをjq
コマンドでパースして単純なログ行に変換して100万行ぐらいgrepに渡す例など考えてみてください。 ↩