指定した文字列やパターンに一致した行を取り出すコマンドとして以前grepコマンドを紹介した。処理速度が速く強力かつ便利なコマンドなのだが、このコマンドに関連するコマンドとして、今回は「fgrep」を紹介する。これも知っておくと結構便利なコマンドだ。

引数で与えられたキーが正規表現として解釈されてしまう問題

まず、次のようなデータファイルがあるとしよう。

% cat data.txt
aaa bbb ccc -a
111 222 333 [4-6]
AAA BBB CCC \\\
123 456 789 -
%

実際には、ログファイルや業務データファイルなどをイメージしてもらえればと思う。このデータファイルから、特定のキーワードを使ってgrepコマンドで行を抜き出すと、次のようになる。

●grepコマンドの実行例(その1)

% grep 111 data.txt
111 222 333 [4-6]
%

●grepコマンドの実行例(その2)

% grep 1 data.txt
111 222 333 [4-6]
123 456 789 -
%

今度は「111 222 333 [4-6]」の行を抜き出すために、「[4-6]」を指定する方法を考えてみよう。何も考えずにgrepコマンドを実行すると、次のようになる。

% grep '[4-6]' data.txt
111 222 333 [4-6]       ← 文字列[4-6]ではなく正規表現の[4-6]が一致
123 456 789 -           ← 文字列[4-6]ではなく正規表現の[4-6]が一致
%

「[4-6]」は正規表現として解釈されるため、意図していない行も一致してしまっている。

では今度は、キーとして「¥¥¥」を指定して行を取り出すケースを考えてみる。だが、次のようにgrepコマンドを実行するとエラーになって処理が失敗する。

% grep '\\\' data.txt
grep: Trailing backslash        ← バックスラッシュがエスケープ指定と解釈され指定方法が間違っているとエラーが表示される
%

これは、「¥¥¥」が「¥¥」と「¥」だと解釈され、「¥」のほうが何のエスケープも行っていないためにエラーとして判定されている。

こうしたエラーを回避するのに使えるコマンドが「fgrep」だ。fgrepコマンドは、引数で与えられたキーを正規表現として解釈せずに、ただの文字列として解釈する。したがって、先ほどまでgrepコマンドを実行していた部分をfgrepコマンドに置き換えると次のようになる。

●「[4-6]」は正規表現ではなくただの文字列として解釈される

% fgrep '[4-6]' data.txt
111 222 333 [4-6]
%

●「¥¥¥」はただの文字列として解釈されるので、そのまま「¥¥¥」と一致する文字列を含む行が出力される

% fgrep '\\\' data.txt
AAA BBB CCC \\\
%

引数で与えられたキーが文字列として解釈され、そのまま処理が行われていることを確認できる。

指定するキーワードを正規表現として認識するのではなく、ただの文字列として処理してほしい場合には、こんな感じでfgrepコマンドを使うとわかりやすい。覚えておいて損はしないコマンドだ。なお、キーワードの最初が「-」で始まっている場合、grepもfgrepも動作が変わらない。キーワードではなく、オプションとして解釈されてしまう。

●「-a」がオプションだと解釈され、data.txtがキーとして認識される(grepの場合)

% grep -a data.txt
                        ← 対象が標準入力になるためここで停止してしまう

●「-a」がオプションとして解釈され、data.txtがキーとして認識される(fgrepの場合)

% fgrep -a data.txt
                        ← 対象が標準入力になるためここで停止してしまう

これを解消し、「-a」をただの文字列だと解釈させたい場合には、grepコマンドでもfgrepコマンドでも「—」を指定すればよい。つまり、次のように「— -a」と書いておけば、「-a」はオプションではなく見たままの文字列として認識されるようになる。

% grep -- -a data.txt
aaa bbb ccc -a
% fgrep -- -a data.txt
aaa bbb ccc -a
%

grepコマンドの正規表現やエスケープに関しては、その表記自体をエスケープで回避すればfgrepコマンドを使わなくても同じ処理を行わせることができる。次のような感じだ。

% grep '\[4-6]' data.txt
111 222 333 [4-6]
% grep '\\\\\\' data.txt
AAA BBB CCC \\\
%

この方法の問題は、エスケープすると表記が複雑になることだろう。fgrepのようにコマンド名自体が異なれば、指定している文字列を正規表現ではなくただの文字列として一致させようとしていることが明確になってわかりやすくなる。

なお、fgrepというコマンドはgrepコマンドに「-F」を指定した場合と動作が同じなので、次のように実行してもfgrepコマンドを実行したのと同じ結果になる。

% grep -F '[4-6]' data.txt
111 222 333 [4-6]
% grep -F '\\\' data.txt
AAA BBB CCC \\\
%

とは言え、grepコマンドにはオプションが多いので、これだとやはり意図がわかりにくいところがある。fgrepコマンドを使ったほうが、意味的にも明解になるように思う。

grepコマンドとfgrepコマンドの実体は、大体同じだ。ディストリビューションごとに実装は異なっており、fgrepコマンドがシェルスクリプトになっていて内部で「grep -F」として実行されているものもあれば、ハードリンクで作成してgrepもfgrepも同じ実装になっているものもある。実現方法は多少異なるものの、結局最後に実行されるのはgrepコマンドだ。

grepコマンドは強力で便利なコマンドなのだが、それゆえに何をしようとして実行した記述かわからなくなることがある。そんな場合に使えるコマンドの1つがfgrepコマンドというわけだ。正規表現を使う必要がなければ、fgrepコマンドはなかなか使い勝手が良いだろう。このコマンドもぜひ覚えておいていただきたい。