今回はUNIX系では定番のgrepです。grepはテキスト検索を行いますが、なんと言っても強力な正規表現が使えるのが大きなポイントです。似たような単語、数字、パターンを持つ文字列の検索に大きな威力を発揮します。もちろん正規表現を上手に使えれば、です。それでも、そこそこ正規表現が使えるだけでも十分ではないかと思います。だいたい、この前振りからして書いてる本人が、正規表現に精通してるわけでもなく、正規表現はそこそこという具合なので、そのパターンマッチよろしくないかも、というオチがあるかもしれません。特にgrepは過去の経緯もあり挙動が異なる場合があります。ここまでの前振りで何か難しそうだなと思う人もいるでしょう。でも、何となく動けば用が足りることもあります。何となく動けばOK!というのが、この連載なので何かうまく検索できなかった場合は調べて勉強するのもよいかもしれません。おまけに今はChatGPTなどのAIがアシストしてくれます。
さて、今回もデスクトップのsampleディレクトリにサンプルとなるテキストファイルを入れておきます。また、カレントディレクトリはsampleディレクトリとします。コマンドならcd ~/Desktop/sampleです。
正規表現とgrep
grepを使いこなすのは正規表現を使いこなすような一面もあり難しいところもあります。そもそも、正規表現自体が難しい部分もありますし、正規表現についてだけで一冊の本が出ているほどです。
おおよそ1990年代以降のプログラム言語やアプリケーションには標準で正規表現が利用できるものがあります。中にはプログラムで正規表現を使う人もいるでしょう。その場合、そこで培った正規表現の知識は活用できます。すでにそのような知識やノウハウを持っている人なら容易にgrepを使いこなせるかもしれません。
でも、まったく正規表現なんて使った事がないという人もいるでしょう。私もそんなに多用するわけではなく必要に応じて使う程度です。
正規表現は難しいと言う場合は人工知能(AI)サービスを利用する方法もあります。例えばChatGPT/Geminiだと以下のようにすると正規表現例を提示してくれます。grepの単語も指定するとオプション付きで提示してくれます。これは便利でありがたいサービスと言えるでしょう。なお、AIやAIのモデルによって回答が異なることがあります。
【grepで西暦4桁と2桁の月にマッチする正規表現を教えてください。】
grep -E '[0-9]{4}-(0[1-9]|1[0-2])' file.txt
grep \b[0-9]{4}-(0[1-9]|1[0-2])\b date.txt
20世紀と違い今では正規表現を駆使するよりもプログラム言語を利用した方がいい場合もあります。昔と違ってメモリやディスク容量、処理速度などの制約も少なくなったのでgrepも適材適所で使えばよいでしょう。また、上記のようにAIを活用するのが21世紀のやり方かもしれません。
正規表現なしで検索
まずは正規表現なしでgrep検索してみましょう。grepと言えども正規表現を使わなければ難しくない、という事です。正規表現でなければ単純な部分一致検索になります。
なお、今回は日本語は扱わずに純粋に英数字と記号のみとしています。
grepはファイル名(ファイルパス)を指定して検索するだけでなく、標準入力からの文字列を検索することもできます。grepが便利で多用されるのは標準入力からのデータを扱えるからという理由もあるでしょう。
まず、カレントディレクトリにあるテキストファイルのsample1.txtの内容を検索してみましょう。sample1.txtの内容は以下のようになっています。1行目が日付等のヘッダーになっていて、2行目以降が3列でタブ区切りになっているデータです。
sample1.txt(※表示ではスペースになっていますが、実際はタブ区切りです。viでは:%s/ /\t/gで置換できます)
test data 2022/02/22
abTest1 5 $1.2
abTest2 45 $1.1
abTest3 98 $3
abtest4 3 $0.7
abtest5 14 $0.35
bbTest06 100 \1
bbTest07 200 \2
cbTest08 400 \4
cbTest09 999 \9
最初に検索する文字はaの一文字です。以下のようにするとカレントディレクトリにあるsample.txtファイル内にある文字列からaの文字を検索します。ここでaの文字を囲む記号は'(シングルクオーテーション)です。以下の例では"(ダブルクオーテーション)で囲んでも結果は同じです。"(ダブルクオーテーション)は文字列内に記号が含まれる場合に動作が異なります。これについては後ほど説明します。
以下のコマンドを実行すると一致する文字がある行が出力されます。grepでの検索は行単位で処理されます。
grep 'a' sample1.txt
一致する文字がない場合は何も出力されません。
grep 'p' sample1.txt
一文字の検索ではなく複数の文字の検索もできます。当然と言えば当然ですが、検索する文字によっては注意しないといけない事もあります。
まずは、なんの問題もない複数文字の検索をしてみましょう。もちろん問題が発生しない英数字のみの組み合わせです。以下の例ではカレントディレクトリにあるsample1.txtファイルから、abTest1の文字列を検索し結果を表示します。
grep 'abTest1' sample1.txt
abTest1の文字列が含まれるのは一行しかありませんので期待通りの結果になっています。それでは次にテキストファイル内にある半角バックスラッシュを検索してみましょう。
grep '\' sample1.txt
コマンドを入力すると以下のようなエラーが表示されてしまい検索が実行されません。
grep: Trailing backslash 【Linux/Ubuntu】
grep: trailing backslash (\) 【macOS】
何がいけなかったのかというと、バックスラッシュは後に続く文字をエスケープするために使われる記号だからです。このため、文字列内にあるバックスラッシュを検索する場合は以下のようにバックスラッシュを2つ連続して書く必要があります。
grep '\\' sample1.txt
今度はバックスラッシュが含まれる行が出力されました。一部の記号($や\)にはバックスラッシュをつける必要がありますが、そのような記号が出てくるたびにバックスラッシュを付けていたら読みにくくなってしまいます。そんな時に便利なFオプションがあります。以下のように純粋に検索する文字を指定するだけです。
grep -F '\' sample1.txt
結果は期待通りになっています。ここまでは正規表現なしでの検索です。検索文字列を変えていろいろ検索してみてください。ただし、検索文字は"(ダブルクオーテーション)で囲まないでください。"(ダブルクオーテーション)で囲むと指定する文字によって動作が変わってしまうからです(これはシェルによってgrepに渡す前に文字列が処理されてしまうため)。例えば$を検索する以下のようなパターンです。
grep "\\\$" sample1.txt
↓(bash,zshで"〜"内が処理され以下のように変換)
grep \$ sample1.txt
↓
\$はgrepでは$の文字単体として検索される
完全一致検索
部分一致検索ができるなら完全一致検索もしたいと考えるのは当然です。完全一致検索の場合、正規表現を利用することになりますが、簡単なので先にやってしまいましょう。やるだけで説明は後回しにします。
grepの完全一致検索は行に対して行いますので行頭から行末まで一致させるように指定します。
ここでは以下の行の内容に完全に一致させるように検索してみましょう。
abTest2 45 $1.1
この場合、先頭から行末までなので先頭に^記号、末尾に$を指定します。なお、検索する文字列内に$など正規表現で使うメタ文字が含まれている場合はバックスラッシュでエスケープさせる必要があります。
また、文字列内にタブコードが含まれている場合も\tのようにエスケープさせる必要があります。
つまり、以下のように指定すれば完全一致検索になります。macOSの場合は-Pを指定しなくてもマッチしますが、Linuxでは-Pを指定しないとマッチしません。
grep -P '^abTest2\t45\t\$1\.1$' sample1.txt
正規表現を使って検索
それでは正規表現を使って文字列を検索してみましょう。今では多くのプログラム言語で使える正規表現ですが、歴史的な都合でgrepには基本正規表現と拡張正規表現、Perl拡張表現があります。現在のプログラミング言語等に慣れていて正規表現を使う場合は拡張正規表現のオプションであるEを指定してgrepを使ったほうが無難かもしれません。
今度はsample1.txtに行を追加して以下のようにしたサンプルテキストを使います。このファイルはsample2.txtという名前になっています。
sample2.txt(※表示ではスペースになっていますが、実際はタブ区切りです。viでは:%s/ /\t/gで置換できます)
test data 2022/2/22
abTest01 50 $1.2
abTest02 45 $1.1
abTest03 98 $3
abtest04 30 $0.7
abtest05 14 $0.35
bbTest06 100 \1
bbTest07 200 \2
cbTest08 400 \4
cbTest09 999 \9
ddTest01 1024 12yen
ddTest02 4096 240yen
ddTest02 256 240yen
yenTest01 987 128
yenTest02 215 880yen
yenTest02 314 128Yen
yenTest03 2718 128YEN
正規表現が便利なのは3桁の数字だけ検索する、特定の文字で始まり特定の文字で終わるなど検索する文字列に条件を付けることができるからです。条件判断はプログラムの得意とするところですが、正規表現はわざわざプログラムを組まなくても手軽に使えるので便利です。
まず、特定の文字から始まる文字列にマッチさせてみましょう。
先ほどのテキストファイルにある4桁の数字がある行だけを抽出してみましょう。以下のようにコマンドを入力します。[0-9]は9から9までの数字にマッチ(一致)する事を意味します。[ ]内に文字を指定すると、その文字もしくは範囲などにマッチさせる事ができます。
grep '[0-9][0-9][0-9][0-9]' sample2.txt
拡張正規表現を使っていないので、冗長な書き方になっています。これが100桁の数字にマッチするとなると書くのが大変です。
そこで便利なのが拡張正規表現です。今のプログラム言語などでは当たり前に使える{ }の記述方法ですが、grepの場合はEオプションを指定する必要があります。{ }の中に数字を書くと直前のメタ文字の繰り返しになります。メタ文字というのは正規表現で使用される特別な文字の事を示します。ピリオドやバックスラッシュ、ブラケット(括弧)、$記号などが該当します。
grep -E '[0-9]{4}' sample2.txt
上記のコマンドを実行すると4桁の数字がある行だけが表示されます。
さて期待通りの結果にはなったもののいきなり[0-9]とか{4}とかが出てきてわからない!という人もいるでしょう。
まず、[ ]の記号ですが、これはカッコ内にあるいずれかの文字が対象になります。また-を使うと範囲を示すことになります。[0-9]なら0から9までの文字(コード)になります。[A-Z]なら英大文字のAからZまでの文字列になります。なお、その場合対象になるのは1文字のみです。
次に{ }ですが、これは直前に指定された文字などの繰り返し回数を示します。a{2}ならaが2回繰り返される、b{4}ならbが4回繰り返されるという意味になります。他にもa{4,}とすると4回以上といった意味になりますが、とりあえず今回はこの説明で分かるかと思います。
せっかくなのでAI (ChatGPT/Gemini)を使ってみましょう。
【テキストの途中の4桁の数字にマッチするgrepの正規表現を教えてください】
grep -E '[0-9]{4}' file.txt
grep -E '\b[0-9]{4}\b' file.txt
AIが本当に正しい答えを示してくれるなら下手な解説文章を読むよりよいのかもしれません。が、時々間違ったことを平気で出力してくることがあるので、現状では自分でも学習しておかないと、正しいかどうかの判断ができません。
今回の4桁の数字にマッチする正規表現はAIが答えを出してくれました。ただし、答えは1つだけではなく複数ある場合もあります。また、macOSに入っているgrepのバージョン(grep (BSD grep, GNU compatible) 2.6.0-FreeBSD)ではオプション指定なしでも以下のように指定しても動作します。いつから使えるのか分かりませんが、参考例として載せておきます。正規表現が使えるプログラム言語やエディタ・ツール・アプリケーションでは、こちらの方がおなじみかもしれません。
grep -E '\d{4}' sample2.txt
なお、Pオプションを指定すれば多くのgrepでも同様に\dなどのメタ文字を使う事ができます。これはPerl拡張正規表現を使う事を示しています。この場合、Eのオプションは不要です。
grep -P '\d{4}' sample2.txt
あと、grepはバージョン等によって動作が異なる部分があります。単純な検索、簡単な正規表現であれば基本は変わらないのですが、何かおかしいと思ったら(もしくは最初から知識として知っておく必要性を感じるなら)grep関連で検索して調べておくのがよいでしょう。
という事で今回のgrepはここまでです。
著者 仲村次郎
いろいろな事に手を出してみたものの結局身につかず、とりあえず目的の事ができればいいんじゃないかみたいな感じで生きております。













