今回もgrepの続きです。xxxにマッチさせたいけど正規表現でどう書く?というようなパターンについて簡単に説明していきます。

さて、今回もデスクトップのsampleディレクトリにサンプルとなるテキストファイルを入れておきます。また、カレントディレクトリはsampleディレクトリとします。コマンドならcd ~/Desktop/sampleです。今回は前回に引き続いてsample2.txtを使っています。

sample2.txt(※表示ではスペースになっていますが、実際はタブ区切りです。viでは:%s/ /\t/gで置換できます)
test data 2022/2/22
abTest1 50 $1.2
abTest2 45 $1.1
abTest3 98 $3
abtest4 30 $0.7
abtest5 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

数値にマッチする正規表現

 前回は数値にマッチする正規表現を使いましたが、{}は単純な個数指定だけでなく個数の範囲も指定できます。先ほどは4桁の数値にマッチするよう以下のように書きました。

grep -E '[0-9]{4}'  sample2.txt

 ここでマッチする桁を2桁から3桁にするなら{4}を{2,3}のように指定します。つまり{}の中に数字を書くと直前の文字等の個数を指定できます。

grep -E '[0-9]{2,3}'  sample2.txt

{}は指定した個数以上を指定することもでき、この場合は個数の後に,を指定します。

grep -E '[0-9]{4,}'  sample2.txt

5桁以上にマッチさせる場合は以下のようになります。該当するデータはないので何もマッチせず何も出力されません。

grep -E '[0-9]{5,}'  sample2.txt

Perl拡張表現

 ここからはPerl拡張表現を使う場合です。macOSのgrepではオプションなしでも数値を示すメタ文字として\dが使えるので、ここまでのコマンド例は以下のように書き換えるだけで動きます。

grep -E '[0-9]{4}' sample2.txt

grep -E '\d{4}' sample2.txt

Linuxなどの場合は-Pを指定します。

grep -E '[0-9]{4}' sample2.txt

grep -P '\d{4}' sample2.txt

先頭・末尾の文字にマッチ

 次にyenの文字を含む行を検索してみます。この場合部分一致になるのでyenを含む行が表示されます。

grep 'yen'  sample2.txt

ここで先頭の文字がyenで始まる行だけを取り出したいとします。先頭が特定の文字で始まる時に何らかの処理をしたい場合は結構あります。最初が全角空白(日本語文章での字下げ)やマークダウンによる記述等での■などの記号で始まる文章は結構たくさんあります。データでも温度であれば氷点下の場合のみ取り出す(-記号の場合のみ取り出す)といった具合です。
grepで行の先頭文字にマッチさせるには^を指定します。以下のようにするとyenで始まる行だけがマッチします。

grep '^yen'  sample2.txt

最後がyenで終わっている行は表示されていません。逆に最後がyenで終わっている行にマッチさせるには$を指定します。以下のようにすると行末がyenで終わる行だけが表示されます。

grep 'yen$'  sample2.txt

それならyenで始まってyenで終わる文字は以下のようにすればマッチする気もしますが、この場合は完全一致になるのでどの行もマッチせず何も表示されません。

grep '^yen$'  sample2.txt

そこで、新たなメタ文字を使いましょう。yenで始まってyenで終わる場合は*を使います。1文字を示す.と組み合わせます。

grep '^yen.*yen$'  sample2.txt

大文字と小文字を区別したくない場合は以下のようにオプションでiを指定します。

grep -Ei '^yen.*yen$' sample2.txt

文字にマッチする個数

 先頭がyenで始まって最後もyenで終わる行の場合は途中にいくつかの文字が含まれています。つまり途中に何らかの文字が含まれていることを示す必要があります。この場合、明確に個数を指定する方法と0個以上を指定する、1個以上を指定するなどの方法が用意されています。
 何らかの文字が0個以上含まれている場合は*を指定します。以下のようにするとyenyenやyen012yenなどの文字列にマッチします。

test1.txt

yenyen
yen1yen
yen12yen
yen99999yen
1yen
2yen9yen
2yen9yen8


grep '^yen.*yen$'  test1.txt

0個ではなく1個以上の場合は+を指定します。+を使用する場合はEオプションを付けないと期待通りに動作しません。以下のようにするとyenyenにはマッチしませんがyen012yenなどの文字列にはマッチします。

grep -E '^yen.+yen$'  test1.txt

 ここで.ピリオド(ドット)を含む行だけを検索するには以下のようにバックスラッシュでエスケープする必要があります。.ピリオド(ドット)はメタ文字なのでバックスラッシュを付けないと期待通りの結果になりません。

grep '\.' sample2.txt

アルファベットにマッチ

 アルファベットにマッチさせる場合は[ ]の中にマッチさせたいアルファベットを指定します。例えば小文字のaとeにマッチさせるには以下のように指定します。

grep '[ae]' sample2.txt

aeTにマッチさせるには以下のように指定します。

grep '[aeT]' sample2.txt

こんな感じでマッチさせたいアルファベットを書いていけばよいのですが、aからzまでマッチさせる場合は26文字全部書くのは面倒です。そこで数字のマッチの時にも出てきた範囲指定する-記号を使うと簡単になります。aからzまでマッチさせるには以下のように指定します。

grep '[a-z]' sample2.txt

異なる複数の文字にマッチさせる

 aからzのように範囲で決めることができる場合は-を使えば簡単ですが、範囲を指定できない関連性の低い文字にマッチさせたい場合もあります。例えばaとgとxとyとzのいずれかの1文字にマッチさせたい場合です。
 このような場合は[ ]の中にマッチさせたい文字を,で区切って書きます。つまりaとgとxとyとzのいずれかの1文字にマッチさせる場合は以下のようになります。

grep '[a,g,x,y,z]' sample2.txt

気温が氷点下の場合だけリストアップする

 何となく正規表現が使えるかもしれない気分になったところで気温データを使って検索してみましょう。
 ここでは1ヶ月間の最低気温データから氷点下の場合のみリストアップしてみましょう。気温データは以下のように日付と気温になっておりそれぞれのデータは,で区切られています。つまりCSV形式のデータです。ヘッダーはありません。

data.txt

2023/2/1,-2.4
2023/2/2,-4.0
2023/2/3,-1.5
2023/2/4,0.6
2023/2/5,1.9
2023/2/6,3.4
2023/2/7,1.0
2023/2/8,-0.8
2023/2/9,-4.3
2023/2/10,-12.5
2023/2/11,-14.0
2023/2/12,-10.5
2023/2/13,-6.3
2023/2/14,-2.2
2023/2/15,0.7
2023/2/16,1.5
2023/2/17,2.1
2023/2/18,0.6
2023/2/19,-1.3
2023/2/20,0,4
2023/2/21,1.1
2023/2/22,2.9
2023/2/23,4.2
2023/2/24,5.7
2023/2/25,3.1
2023/2/26,5.8
2023/2/27,2.5
2023/2/28,-0.2

氷点下の場合のみピックアップすればいいので、単純にマイナスが含まれる文字を検索するように指定するだけです。

grep '-' data.txt

氷点下の日が何日あるか知りたい場合はwcコマンドと組み合わせます。以下のようにします。

grep '-' data.txt | wc -l

逆に氷点下ではない日をリストアップするにはvオプションを指定します。vオプションは指定した文字列・正規表現にマッチしなかったものをリストアップしてくれます。以下のようにすると氷点下以外の行がリストアップされます。

grep -v '-' data.txt

日数を知りたい場合は先ほどと同様にwcコマンドを使います。

grep -v '-' data.txt | wc -l

日数を確認するには月の日数から氷点下の日数を引くという方法もあります。この計算を行う場合、exprを使います。grepとwcコマンドの実行結果を利用するので必要とするパラメータ部分はバッククオートで囲みます。

expr 28 - `grep '-' data.txt | wc -l`

マークダウンの文章構造を抽出する

 マークダウン形式の文章から見出しだけピックアップしてみます。この連載の第1回目の見出しと本文の一部をマークダウン形式にしてcmd.txtとして用意しました。

cmd.txt

#なんとなくコマンド
##コマンドを入力するターミナル
###はじめに
この連載はコマンドを入力してコンピューターを操作します。
###ターミナルを起動する
コマンドを入力してコンピューターを操作するには、そのコマンドを入力するプログラムが必要です。
####
Windows 10には従来からある
####【macOS/OS X/MacOS X】
Macの場合はアプリケーションフォルダ内にあるユーティリティフォルダにある
####【Ubuntu 20 LTS (Linux)】
Ubuntu 20 LTSでGUIの場合はキーボードでCtrlとAltとTキーを押します。
####【Raspberry Pi OS】
ラズベリーパイで動くOSの1つであるRaspberry Pi OS
####

見出し部分は#で始まるので以下のように^#を指定すれば見出しのみがリストアップされます。これで、おおよそ構造を把握できます。案外と簡単です。

grep -E "^#" cmd.txt

見出しのレベルに応じて検索することもできます。マークダウン形式では#の数に応じて見出しレベルが決まっているので確認したい見出しレベルの数だけ#を書くか{3}のように個数を指定します。

grep -E "^###" cmd.txt
grep -E "^#{3}" cmd.txt

ただし、###や#{3}のように指定した場合、レベル4(####)の見出しまでリストアップされてしまいます。レベル3(###)だけをリストアップする場合は以下のように4文字目は#ではないという指定をしておきます。この[^#]の^は先頭という意味のメタ文字ではなく否定を示すメタ文字になります。つまり#ではない、という意味になります。

grep -E "^#{3}[^#]" cmd.txt

このcmd.txtには見出しを入れ忘れてしまった行が2つあります。####となっており以後に文字が入っていない行です。この行をリストアップするには以下のように^$を使います。

grep -E '^#{4}$' cmd.txt

ただ、これだと何行目の見出しを入れ忘れたのかがわかりません。ということで行数を表示させてみましょう。行数を表示させるにはnオプションを指定します。

grep -E -n '^#{4}$' cmd.txt

これで見出し文字を入れ忘れたのが何行目なのかわかりやすくなりました。 今回のgrepはここまでです。

著者 仲村次郎
いろいろな事に手を出してみたものの結局身につかず、とりあえず目的の事ができればいいんじゃないかみたいな感じで生きております。