正規表現とは、複数のケースにおいて、その言葉が指定した文字のパターンに一致しているかをチェックするものだ。
Linuxでも正規表現は使え、ファイルの検索や一括の処理など様々な場面で活躍する。
書籍やWebに掲載されている例をそのまま使って動作させることもできるが、基本をしっかりと抑えれば応用も聞いて、幅広く使えるだろう。
このページでは、Linuxで使える正規表現を一通り解説した。ぜひ参考にしてほしい。
目次
正規表現の基本
正規表現とは?
正規表現は、正規という言葉がついているので誤解しやすいが、きちんとした文字を指定するものではない。
むしろ、あやふやな文字を指定することで、複数の文字列にマッチするのか調べる方法だ。
例を3つ挙げてみよう。
- 「print」「script」の2つの文字列で同じパターンを見つけてみると、文字は、pとrとiとtが含まれるという共通点がある。
- 「echo」と「blue」は同じ4文字である。
- 「All Members」と「Single Mastar Boots」では、大文字で始まり、途中にMが入り、sで終了する。
このような場合に、一つのパターンを指定することで、一括での検索や処理を行うことができる。
あやふやな文字を、きちんと捉える表現方法というように考えるとよいだろう。
正規表現のよく使われる一般的な実例
次のような実例は、特にWebサイトの入力フォームなどでも数多く見受けられ、もしそのパターンに一致しない時はエラーメッセージが出るようになっている。
- メールアドレスは途中に@が入り、その前後に1字以上の文字列が入る
- SSLのURLはhttps://で始まり、その後に文字列が入る
- 日本の郵便番号は3桁の数字の後に-が入り、その後に4文字の数字が入る
ここの辺りはよく使われる実例だ。
これらをチェックして送信フォームに入力したデータが間違っていないか確認したり、セキュリティを向上したりしている訳だ。
ワイルドカードとの違い
同じあいまいな表現であるワイルドカードに非常に似た考え方のように感じるが、実態は少々違う。
ワイルドカードで、Testを含む文字と表現する場合は、*Test*である。
ワイルドカードでは*は何らかの文字列を示す。
対して正規表現では、Testを含む文字と表現する場合は、.*Test.*である。
正規表現では.は何らかの1文字という表現で、*は直前の文字の0回以上の繰り返しという意味だ。
正規表現「.*」で、なんかの1文字の0回以上の繰り返し、つまり何らかの文字列となる。
このように表現方法が異なる。
また、正規表現では、探す文字を複数指定することや、行の先頭文字や行の最終文字を示すこともできるので、ワイルドカードよりも複雑ではあるが大幅にできることが多いのだ。
ワイルドカードに慣れているエンジニアが正規表現を扱ったとたんに理解できなくなるケースもよくある話なので、はっきり区別して考えよう。
正規表現の種類
正規表現は様々なパーツで構成される。
後で詳しく説明するが、具体的な例を挙げてみよう。
文字を指定するパーツ
「A」「1」「.」など
文字数を指定するパーツ
「*」「+」「{1,3}」など
文字の位置を指定するパーツ
「^」「$」など
この3種類のパーツを適宜組み合わせて利用していく。
Linuxコマンドの正規表現の指定
Linuxコマンドの中では、正規表現を扱うコマンドはたくさんある。実際に指定したい文字として設定するもの、オプションで指定するものなどがある。
コマンドによって様々ではあるが、代表的なコマンドでgrepがわかりやすいため、今回はgrepコマンドで実例を説明する。
プログラムでの指定
プログラムでは正規表現を扱う方法が言語により若干の違いがあるものの、一つの方法を覚えればおおよそ同じであるため、応用は簡単だ。
今回はプログラムとしてはJavaScriptのmatch関数に則った例を挙げる。
プログラムでは、検索したい文字は/で囲む。
なお、この記事の中でmatch関数を扱うが、matchの前のstrが検索するものを探す文字列が格納されている変数だ。
文字を指定するパーツ
それでは具体的な部分に入っていく。文字を指定するパーツを見ていこう。
具体的な文字を指定する場合
Aと指定すれば指定した文字列の中からAの1文字を探す。
grepコマンドの書式の例
$ grep a name.txt
match関数の書式の例
var answer= str.match(/a/);
XYZと指定すれば指定した文字列の中から XYZの3文字が連なった文字列を探す。
grepコマンドの書式の例
$ grep XYZ name.txt
match関数の書式の例
var answer= str.match(/XYZ/);
あいまいな文字を探す場合
何らかの1文字
.と指定すれば何らかの1文字を示す。つまりすべてが検索される。
grepコマンドの書式の例
$ grep . name.txt
match関数の書式の例
var answer= str.match(/./);
大文字と小文字の区別をしない
iを指定すれば、大文字小文字を区別しなくなる。
「sam」「sammy」「SAMMY」「saM」など、大文字小文字関係なくsamの文字を探す。
Lunuxコマンドのgrepには-iオプションがあり、大文字小文字を区別しなくなる。
grepコマンドの書式の例
$ grep -i sam name.txt
match関数の書式の例
var answer= str.match(/sam/i);
どれかの文字に一致する
AまたはBまたはCのどれかが入っている場合に一致させたい時は、一致させたい文字を[ ]で囲み列挙する。
[AHZ]と指定すれば、「Apple」、「House」、「code:Z」のすべてに一致する。
grepコマンドの書式の例
$ grep [AHZ] name.txt
match関数の書式の例
var answer= str.match(/[AHZ]/);
文字コードの範囲を指定する
普段、私たちは意識しないが、コンピュータで表現される文字には、一つ一つ、管理番号が定められている。
例えば半角の1という文字は49番、半角のsなら115番だ。
全角だと1文字を表現するのに半角文字2つ分を使うのでさらに大きな管理番号となり、「五」は15535番、「龍」は20022番、全角記号でも「☆」が8564番に割り当てられている。
どのコンピュータも、実際は内部で文字をこの管理番号で扱っていて、我々、人間が入出力する際にわかる形で変換し表現してくれているのだ。
この文字の管理番号のことを文字コードと呼ぶ。
文字コードで範囲指定し使うことで、入力されている文字の範囲を指定することができる。
[0-9]であれば何らかの半角数字、[a-z]であれば、何らかの小文字の半角英字というように、~を-に置き換えることで範囲を指定することができる。
[0-9a-zA-Z]のように列挙することもでき、この場合は、半角英数字が入っていればマッチするということになる。
grepコマンドの書式の例
$ grep [a-z] name.txt
name.txtで日本語のみの行があれば、表示されなくなる。
match関数の書式の例
var answer= str.match(/[a-z]/);
あいまい文字の簡易表示
簡単な組み合わせのあいまいなパターンは簡略された書式で用意されている場合もある。
多くのプログラム言語で使用できるので活用しよう。
[0-9]は\dだ。
\sは改行文字を含んだ空白文字だ。
[0-9a-zA-Z]は\wだ。
文字数を指定するパーツ
0個以上の繰り返し
*は文字の個数が0個以上の繰り返しを表わす。つまりその検索文字が存在しなくてもよい。
検索対象としてabc*dと指定した場合は「abcd」「abcccd」のようにcが繰り返していてもマッチする。また、cは存在しなくても良いので「abd」もマッチする。
grepコマンドの書式の例
$ grep abc*d name.txt
match関数の書式の例
var answer= str.match(/abc*d/);
1文字の繰り返しではなく複数文字列の繰り返しのパターンでは、複数文字列を( )で囲む。
Linuxコマンドの場合は、( )文字は、制御文字の意味を持つため、\でエスケープする必要がある。
Perl言語フォーマットの正規表現でマッチさせるオプション-Pを付与すれば安全だ。
Raの文字列とinの0回以上の繰り返しにマッチするか調べて「Rain」「Rainin」「Ra」にマッチさせて、「Roin」「Rai」「Rai1n」にはマッチさせない書式は次のとおりだ。
grepコマンドの書式の例
$ grep -P Ra\(in\)* name.txt
match関数の書式の例
var answer= str.match(/Ra(in)*/);
1個以上の繰り返し
*は0文字以上の繰り返しという意味で検索対象内に検索文字が存在しなくてもよかった。
+は文字の個数が1個以上の繰り返しを表わすので、必ず、検索対象文字列に存在しなければならない。
Perl言語フォーマットの正規表現でマッチさせるオプション-Pを付与すれば安全だ。
検索対象としてabc+dと指定した場合は「abcd」「abcccd」のようにcが繰り返していてもマッチする。また、cは1個以上存在しなくてはならないので「abd」はマッチしない。
grepコマンドの書式の例
$ grep -P abc+d name.txt
match関数の書式の例
var answer= str.match(/abc+d/);
1文字の繰り返しではなく複数文字列の繰り返しのパターンでは、複数文字列を( )で囲む。
Linuxコマンドの場合は、( )文字は、制御文字の意味を持つため、\でエスケープする必要がある。
Linuxコマンドの場合は、Perl言語フォーマットの正規表現でマッチさせるオプション-Pを付与すれば安全だ。
Raの文字列とinの1回以上の繰り返しにマッチするか調べて「Rain」「Rainin」にマッチさせて、「Ra」にはマッチさせない書式は次のとおりだ。
grepコマンドの書式の例
$ grep -P Ra\(in\)+ name.txt
match関数の書式の例
var answer= str.match(/Ra(in)+/);
0個か1個の繰り返し
*は0文字以上、+は1文字以上の繰り返しという意味だった。
?は文字の個数が0個か1個の繰り返しのみを表わす。
つまり指定文字がないか、あっても1個までという意味である。
Linuxコマンドの場合は、Perl言語フォーマットの正規表現でマッチさせるオプション-Pを付与すれば安全だ。
検索対象としてabc?dと指定した場合は「abcd」「abd」がマッチする。
grepコマンドの書式の例
$ grep -P \(abc@d\) name.txt
match関数の書式の例
var answer= str.match(/abc@d/);
1文字の繰り返しではなく複数文字列の繰り返しのパターンでは、複数文字列を( )で囲む。
Linuxコマンドの場合は、( )文字は、制御文字の意味を持つため、\でエスケープする必要がある。
Perl言語フォーマットの正規表現でマッチさせるオプション-Pを付与すれば安全だ。
Raの文字列とinの1回以上の繰り返しにマッチするか調べて「Ran」「Rain」にマッチさせる書式は次のとおりだ。
grepコマンドの書式の例
$ grep -P Ra\(i\)@n name.txt
match関数の書式の例
var answer= str.match(/Ra(i)@n/);
個数指定の繰り返し
個数指定の繰り返しもできると便利だ。
個数指定は、{ }に最低繰り返し回数、最高繰り返し回数を「,」で区切り行う。
{1,3}は1から3回の繰り返しだ。
片方のみの指定もできる。
{3,}は3回以上の繰り返しだ。
{,8}は8回以内の繰り返しだ。
一つの回数で指定する場合は最低回数と最高回数の2つ入力するのではなく、1つのみ入力すればよい。
{6}は6回繰り返しのみにマッチする。
Linuxコマンドの場合は、{ }文字は、( )文字同様に制御文字の意味を持つため、\でエスケープする必要がある。
Perl言語フォーマットの正規表現でマッチさせるオプション-Pを付与すれば安全だ。
検索対象としてabc{1,3}dと指定した場合は「abcd」「abccd」「abcccd」がマッチする。
grepコマンドの書式の例
$ grep -P \(abc\{1,3\}d\) name.txt
match関数の書式の例
var answer= str.match(/abc{1,3}d/);
1文字の繰り返しではなく複数文字列の繰り返しのパターンでは、複数文字列を( )で囲む。
Linuxコマンドの場合は、( )文字は、制御文字の意味を持つため、\でエスケープする必要がある。
Perl言語フォーマットの正規表現でマッチさせるオプション-Pを付与すれば安全だ。
Raの文字列とinの1回以上の繰り返しにマッチするか調べて「Rain」「Raiain」「Raiaiain」にマッチさせる書式は次のとおりだ。
grepコマンドの書式の例
$ grep -P R\(ai\)\{1,3\}n name.txt
match関数の書式の例
var answer= str.match(/R(ai){1,3}n/);
メールアドレスのパターン
ここまでで、任意の文字列、あいまいな文字列、文字の個数の指定方法を解説した。ここで、一旦解説したことを組みわせて一つの例を作成してみよう。
例えば、日本の大学のメールアドレスがある。
法則は任意の「半角英数字のユーザー名」@「半角英数字の任意のドメイン」「.ac.jp」とした場合、これにマッチするパターンを考えよう。
まず、半角英数の1文字以上の文字は[a-zA-Z0-1]+となる。
これに「@」と「.ac.jp」を組み合わせればよい。「.」は正規表現の正規文字なのでエスケープする。
[a-zA-Z0-1]+@[a-zA-Z0-1]+\.ac\.jp
これでマッチする。実際にはメールアドレスはもう少し細かい指定がある。例えば「_」や「.」を使えるなどだ。なので、このままでは使えないが、まずは理解するためにご紹介した。
grepコマンドの書式の例
$ grep -P [a-zA-Z0-9]+@[a-zA-Z0-9]+\.ac\.jp name.txt
match関数の書式の例
var answer= str.match(/[a-zA-Z0-9]+@[a-zA-Z0-9]+\.ac\.jp /);
実際のメールアドレスの正規表現は例えば次のようになる。使える記号も増えてきており訳がわからなくなってきている部分もある。
1 |
/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/ |
同じ検索文字が複数存在する場合
(int)(gate)value.side()という文がある。
この場合、(.+)でマッチするのは、はじめの(から終わりの)までのすべての文字が検索結果になる。
正規表現は、指定した文字パターンを大きく捉えるのだ。
しかし、はじめの(int)だけを検索結果としたい場合もあるだろう。
その場合は、「.+」の後に「?」を付与することで、全体ではなく、はじめの(で始まり)で終わるパターンを見つけることができる。
この方法は、ほとんどのプログラムの正規表現方法で扱うことができる。
( )は制御文字なので\でエスケープする。
match関数の書式の例
var answer= str.match(/\(.+?\)/);
このパターンは複雑なので、2つ例を挙げておく。
(int)(gate)までをマッチさせるには、「\(.+?\)」を2つにした次のパターンだ。
var answer= str.match(/\(.+?\)\(.+?\)/);
(int)(gate)value.までをマッチさせるには、さらにピリオドで終了する何らかの文字列を表す「.*.」を付与した次のパターンだ。「.」も制御文字なので、\でエスケープする。
var answer= str.match(/\(.+?\)\(.+?\).*\./);
位置を指定するパーツ
行頭
行のはじめと示すのは^だ。
^T.*は次のすべての文字列に一致する。
1 2 3 4 5 |
This is a computer. That's right! T100=EX600 |
grepコマンドの書式の例
$ grep ^T.* data.txt
match関数の書式の例
var answer= str.match(/^T.*/);
行末
行の最後を示すのは$だ。
$は検索文字に前置するのではなく、あくまで最後に記述するのがポイントだ。
.*o\.$は次のすべての文字列に一致する。
ピリオド自体はなんらかの一文字を示す制御文字なので、エスケープしている。
1 2 3 4 5 |
My name is Hiroshi Kato. soso. I use OOo. |
grepコマンドの書式の例
$ grep .*o\.$ data.txt
match関数の書式の例
var answer= str.match(/.*o\.$/);
行頭と行末の組み合わせ
行頭、行末どちらも組み合わせることもできる。
使用頻度の高いものに、HTMLのタグを探す方法が考えられる。
^<.+>$でHTMLタグの行を探すことができる。
ただし、1行が1つのタグだけで構成されている必要があるので、もしインデントなどで空白やタブなどを入れていたり行末に空白文字やコメントなどが入る場合は、前後に何かが入っていてもいいような^.*<.+>.*$という記述になるだろう。
grepコマンドの書式の例
$ grep . ^.*\<.+\>.*$ data.txt
match関数の書式の例
var answer= str.match(/^.*<.+>.*$/);
論理式
今までは単一のパターンにマッチする表現を説明してきた。
パターンにマッチしないというマッチングも必要になるし、複数の条件すべてがマッチしていないとマッチしていると考えないなどの条件もある。
このような条件を組み立てる式を論理式という。
詳細は後に説明するが、実は正規表現は普通のプログラム言語のような自由な論理処理が苦手だ。
しかし、一定の条件があるものの、方法はあるので併せて解説する。
論理式の種類
正規表現は次の3つに分けられる。後で詳しく解説するので、まず、一通り見ていこう。
否定(NOT)
パターンにマッチしない場合だ。
なおかつ(AND)
複数の条件のすべてにマッチすればマッチすると判断する。
ただし、基本的には正規表現では後述する理由でANDは表現できない。
しかし、方法はあるので後述する。
または(OR)
複数の条件のいずれかにマッチすればマッチすると判断する。
では、一つずつ解説する。
否定の正規表現(NOT)
正規表現は、任意の文字を含まないなどの否定(NOT)の論理が苦手だ。
Linuxのgrepコマンドでは、一致しない行を抽出する-vオプションがあるので活用しよう。
プログラムとして考える場合は、ある1文字に対して、その前に存在するかどうかをチェックすることで実現できる。
否定(NOT)の正規表現方法は?!だ。
「京なんとか」という文字列があって、京の後に「都」がないものをチェックするには次の通りだ。
grepコマンドでvオプションを使わない書式の例
$ grep -P京\(?\!都\) adr.txt
match関数の書式の例
var answer= str.match(/京(?!都)/);
「なんとか京」という文字列があって、京の前に「東」がないものをチェックするには次の通りだ。
grepコマンドでvオプションを使わない書式の例
$ grep -P 東\(?\!京\) adr.txt
match関数の書式の例
var answer= str.match(/東(?!京)/);
なおかつの正規表現(AND)
正規表現では、なおかつ(AND)ができない。
例えばある住所録から東京都のすべての市を出したいという場合、「東京都なんとか」で、なおかつ「なんとか市なんとか」の文字列の文字列を探すことになる。
そもそも「なおかつ」の論理式を使わずとも、「^東京都.*市.*」と表現すれば「東京都」で始まり、間に「市」が入っているものを抽出することができるのだ。
コンピュータの内部処理として考えると天気が「晴れ」であって「雨」であることは論理的にあり得ない。
このことも正規表現でANDが使えない理由の一つである。
しかし、実は、なおかつ(AND)を次の表現方法で表すこともできる。
次の「?=」は前方参照と呼ばれるもので、前に指定したものがあるかどうかチェックするというものだ。
今回は、「東京都」が前にある「なんとか市」を見つけている。
grepコマンドの書式の例
$ grep -P \(?=東京都\).*市 adr.txt
match関数の書式の例
var answer= str.match(/(?=東京都).*市/);
「^東京都.*市.*」のような書式では、「晴れ時々雨」と「雨時々晴れ」の両方をマッチさせることはできないが、前方参照を使う方法であれば、マッチさせることができる。
grepコマンドの書式の例
$ grep -P \(?=.*雨\)\(?=.*晴れ\) tenki.txt
match関数の書式の例
var answer= str.match(/(?=.*雨)(?=.*晴れ)/);
またはの正規表現(OR)
または(OR)の正規表現は簡単だ。
または(OR)の正規表現は|で条件をつないでいく。
「CPU:Memory:HDD」の文字列の中に「or」か、「123」か「PU」の文字が入っているかチェックするには次の書式だ。
(or)|(123)|(PU)となる。
grepコマンドの書式の例
$ grep -P \(or\)\|\(123\)\|\(PU\) parts.txt
match関数の書式の例
var answer= str.match(/(or)|(123)|(PU)/);
参考までに、この場合、検索結果として検索されるのは、「PU」である。
「or」も検索に一致しているが、正規表現の処理プロセスとしては、「CPU:Memory:HDD」の初めから(左側から)順にパターンを見つけていき、はじめに見つかったものを結果とするのだ。
改行と空白文字の処理
正規表現は改行文字を区切りとした行ごとの処理を行っている。
しかし、ファイル全体を処理しようとする場合や巨大な文字列を操作する場合などは、改行ごとパターンを一致させたいケースもあるだろう。
改行を無視して正規表現を表す場合は、[\s\S]を付ける。
1行目に「こんにちは」、2行目に「さようなら」という文がある場合において、「に」なんとか「よ」を検索するには、「に[\s\S]*よ」となる。
「.」は任意の1文字を表すものだが、その中には、次のような\sで表される区切り文字が含まれていないために行ごとの処理になるのだ。
\sは、半角スペースと改行やタブ文字に使われる制御文字すべてを表現できる。
\Sは、上記以外の文字ということになる。
つまり、[\s\S]は改行などを含む制御文字すべてを表し、改行を含む全体を検索できる正規表現なのだ。
grepコマンドの書式の例
$ grep -P に\[\s\S\]*よ hello-goodbye.txt
match関数の書式の例
var answer= str.match(/に[\s\S]*よ/);
もしも、改行がどの位置に何個入っているか不明な場合は、
[\s\S]*に[\s\S]*な[\s\S]*
と表現することで、改行が全く関係なく、文書内の「に」から始まり「な」で終わる文字列を探し出すことができる。
上記で書いたように、\sは半角スペースも含むので、半角スペースを関係なく検索する場合にも応用できる。
制御文字とリテラル文字
「.」自体を見つけたい場合、「.」は何かの1文字を表す制御文字なので、「.」の前に「\」を付け、「\.」と表現しなければならない。
このように、検索したい文字が制御文字と同じだった場合、検索したい文字のことをリテラル文字と呼ぶ。
「.」に限らずリテラル文字の前には「\」を置く。
前に\が必要なリテラル文字をリストした。
制御文字の意味も記載しているので参考にしてほしい。
制御文字[
検索したい文字リストの始まりを示す。
リテラルとしての記載は「\[」となる。
制御文字]
検索したい文字リストの終わりを示す。
リテラルとしての記載は「\]」となる。
制御文字(
検索したい文字列の始まりを示す。
リテラルとしての記載は「\(」となる。
制御文字)
検索したい文字列の終わりを示す。
リテラルとしての記載は「\)」となる。
制御文字{
検索したい文字の個数指定の始まりを示す。
リテラルとしての記載は「\{」となる。
制御文字}
検索したい文字の個数指定の終わりを示す。
リテラルとしての記載は「\}」となる。
制御文字^
検索したい文字が行の始まりかを示す。
リテラルとしての記載は「\^」となる。
制御文字$
検索したい文字が行の終わりかを示す。
リテラルとしての記載は「\$」となる。
制御文字?
文字列の繰り返しが0個か1個を示す。
リテラルとしての記載は「\?」となる。
制御文字*
文字列の繰り返しが0個以上を示す。
リテラルとしての記載は「\*」となる。
制御文字+
文字列の繰り返しが1個以上を示す。
リテラルとしての記載は「\+」となる。
制御文字-
文字コードによる検索を行う場合の範囲の繋ぎを示す。
リテラルとしての記載は「\-」となる。
制御文字?
前方一致、または後方一致を示す。
リテラルとしての記載は「\?」となる。
制御文字!
前方一致、または後方一致の場合の否定を示す。
リテラルとしての記載は「\!」となる。
制御文字=
前方一致、または後方一致の場合の肯定を示す。
リテラルとしての記載は「\=」となる。
制御文字.
なんらかの1文字を示す。
リテラルとしての記載は「\.」となる。
制御文字\
その文字がリテラル文字であることを示す。
リテラルとしての記載は「\\」となる。
正規表現関連項目
最後に正規表現に関連した、基本的なLinuxのコマンドも紹介しておく
grepコマンド
テキストファイル内の文字を検索し、表示するコマンドだ。
まとめ
このページではLinuxで使う正規表現についてまとめてみた。
長くなってしまったし、すべてを覚えるのは難しいだろう。また、覚える必要もない。
必要になったときに随時見に来ていただければと思う。
コメント