シェルスクリプトでファイルに特定の文字が含まれているかどうかを高速に判定する方法

Bash, Linux

はじめに

ファイルに特定の文字が含まれていたら処理を行う、という分岐を書くときにgrepの実行結果を>/dev/nullに捨てる、という方法をとっていましたが、>/dev/nullを使わなくても良いということを知りました。

>/dev/nullを使う場合と比べてのメリットについても取り上げます。

検証環境

$ uname -moi
x86_64 x86_64 GNU/Linux

$ bash -version | head -n 1
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)

$ grep --version | head -n 1
grep (GNU grep) 2.20

>/dev/nullを使ったやり方

今までは標準出力を無効化するために>/dev/nullを使っていました。

1から100まで書かれている100行のテキストファイルtest.txtについて、grepを行い分岐処理を行ってみます。
まずはテストに使用するテキストファイルを作成します。

# `seq` コマンドでファイルを作成
$ seq 1 100  >test.txt

# 確認。最初の3行を出力してみる
$ head -n 3 test.txt
1
2
3

# 確認。最後の3行を出力してみる
$ tail -n 3 test.txt
98
99
100

このファイルを使ってファイル内の文字列存在チェックを行ってみます。

# ----- 99を検索 -----
# &&を使う方法
$ grep 99 test.txt >/dev/null && echo EXIST.
EXIST.

# if文を使う方法
$ if grep 99 test.txt >/dev/null; then
  echo EXIST.
fi
EXIST.

# ----- 199を検索 -----
# &&を使う方法 (何も出力されない)
$ grep 199 test.txt >/dev/null && echo EXIST.

# if文を使う方法(何も出力されない)
$ if grep 199 test.txt >/dev/null; then
  echo EXIST.
fi

実現できました。

grep -qを使ったやり方

grep-qオプションというものがあることを知りました。
こちらは標準出力には何も出力しませんが、実行結果をexitコードあるいは$?で取得できる仕組みになっています。

先のコードをgrep -qを使って書き直してみます。

# ----- 99を検索 -----
# &&を使う方法
$ grep -q 99 test.txt && echo EXIST.
EXIST.

# if文を使う方法
$ if grep -q 99 test.txt; then
  echo EXIST.
fi
EXIST.

# ----- 199を検索 -----
# &&を使う方法 (何も出力されない)
$ grep -q 199 test.txt && echo EXIST.

# if文を使う方法(何も出力されない)
$ if grep -q 199 test.txt; then
  echo EXIST.
fi

速度に大きな違いがある

両方ともそれほど大きな違いが無いように見えますが、大きなデータファイルを操作するときには顕著な違いが生まれます。

1から10000000までの数字が書かれたデータファイルをgrepするケースを試してみます。

# ファイルを作成
$ seq 1 10000000 > test.txt

# >/dev/null を使うケースの検索速度
$ time grep 99 test.txt >/dev/null

real    0m0.337s
user    0m0.158s
sys     0m0.076s

# grep -q を使うケースの検索速度
$ time grep -q 99 test.txt

real    0m0.023s
user    0m0.000s
sys     0m0.019s

何度か試してみましたが、grep -qのほうが早いですね。

おそらく、grep -qの方は検索結果が1件でも見つかった場合に以降の処理を中断しているため高速なのではないか?と思われます。

ひとこと

少しだけコードは短くなりますし、速度も早いということでgrep -qを使うのが良さそうですね。

Bash, Linux