LoginSignup
0

posted at

updated at

Organization

[小ネタ] デカい標準入力をgrepする際はバイナリファイル扱いに注意

みなさん運用してますか? ログの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の公式のソースコードリポジトリ によると、該当部分のソースは下記のようになっています。

src/grep.c
 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) を使って実験)

test_grep.sh
#!/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 を使おう。

  1. grepの引数にファイル名を直接渡せばいいと思うかもしれませんが、ここでは簡単な例として cat 2023-*.log としています。難しい例がお好みの方はたとえば複数の JSONL ファイルを jq コマンドでパースして単純なログ行に変換して100万行ぐらいgrepに渡す例など考えてみてください。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
0