連載
» 2019年05月24日 05時00分 公開

ITの教室:【WSL入門】第4回 bashの展開機能と正規表現の基礎 (1/2)

WSLのメリットには、さまざまなコマンドで利用できる「正規表現」とシェルのbashの展開機能がある。この2つを理解することで、WSLの利用価値が大きく向上する。WSL入門の最終回として、この2つの機能を紹介しよう。

[塩田紳二,著]

ITの教室 WSL入門」では、WSLを活用するための基礎をインストールから解説していく。



 WSLを使う魅力として、さまざまなコマンドで「正規表現」が利用できる点にある。またWSLのシェルである「bash」には、コマンドライン中に引数として変数を入れたり、文字列パターンを展開したりする機能がある。これらをマスターすることで、WSLの使い勝手が大きく向上するはずだ。本稿では、正規表現とbashの展開機能を取り上げよう。

正規表現の超簡単入門

 Linuxを使う時に避けて通れないのが「正規表現」だ。誤解を恐れずに簡単に言えば、正規表現とは「文字列をパターンで検索すること」だ。この正規表現を使うことで、どんなパターンの文字列でも検索ができるようになる。Linuxのシェルや多くのアプリケーションでは、ファイルやディレクトリの指定、文字列の検索や置換などで正規表現をベースにした検索パターンを利用している。多少の違いはあるが、基本の正規表現をマスターすれば、後は細かい違いのみを意識すればよい。

 正規表現というと難しく聞こえるかもしれないが、基本は簡単である。文字「X」は、検索対象の文字列の「X」と「一致」する。だから「X」という文字を探したければ、「X」を入力パターンにすればよい。文字列「cat」を捜したければ、「cat」と指定すればよい。これが基本である。

 しかし世の中、これだけでは大量に検索結果が得られて、どうしようもないことがある。正規表現では、文字列そのものだけでなく、さまざまな「パターン」を検索できる。例えば、「行の先頭にあるcat(catで始める文字列)」「行末にあるcat(catで終わる文字列)」「catまたはkat」「catかcutのどちらか」といったものがパターンだ。さらに複雑に「catで始まり、途中にカンマやピリオドを含まず、dogで終わる行」といったパターンも検索できる。

 ただし、こうしたパターンを指定するには、実際に文字列として登場するパターンを熟知している必要がある。正規表現は万能ではあるが、文字列の意味や概念を理解しているわけではなく、指定されたパターンを捜すだけだからだ。

 こうしたパターンを指定するために正規表現には、特殊文字(メタ文字ともいう)が用意されている。これらは、さまざまな文字列のパターンを記述するのに使う。

 「文字クラス」と呼ばれる特殊文字では、角括弧「[」と「]」を使って、複数の文字を表現できる。“[a-z]”は小文字の「a」から「z」のどれかに一致する。“[^」]”という表現は、閉じカギ括弧以外と「一致」する特殊文字だ。

 これを使えば“「[^」]*」”というパターンで「開きカギ括弧で始まり、閉じカギ括弧以外の文字が0個以上並んで、最後が閉じカギ括弧」という文字列を捜すことができる。これで、小説のようなテキストファイルなら、会話文のみを取り出せるわけだ。

 こうした特殊文字には、下表のようなものがある。なお、メタ文字に指定されている文字自体を捜したいこともあるだろう。このようなときには「\」によるエスケープを使って、特殊や役割を打ち消す。文字列中にある文字クラスに使われている「[」を捜したい場合には、“\[”と指定する(詳細は後述)。

特殊文字 種類 意味
[ ] 文字クラス 指定された文字(複数可)のどれか1つと一致
[^ ] (否定)文字クラス 指定された文字(複数可)のどれとも同じでない文字と一致
? 繰り返し(量指定子) 0回または1回の繰り返し
* 繰り返し(量指定子) 0回以上の繰り返し
+ 繰り返し(量指定子) 1回以上の繰り返し
{n} 繰り返し(量指定子) n回の繰り返し
{n,} 繰り返し(量指定子) n回以上の繰り返し
{n,m} 繰り返し(量指定子) n回以上、m回以下の繰り返し
| 選択 縦棒の前後の正規表現のどちらかに一致
( ) 優先順位の変更 優先する部分を指定
. ピリオド 任意の1文字に一致
^ アンカリング 行の先頭に一致
$ アンカリング 行の末尾に一致
\ エスケープ 特殊文字の意味を打ち消す
正規表現で使う特殊文字

 取りあえずは、この表にある記号の意味を理解しておけば、後はLinuxのmanコマンドなどで個別のソフトウェアの独自仕様部分だけを理解すればよい。

 では、正規表現を理解するには、どうしたらいいか。これは、実際に使って「カラダで覚える」しかない。そのためには、標準的な正規表現を装備しているUNIX由来の著名なプログラムを「grep(ぐれっぷ)」を使う。grepに装備されている正規表現は、POSIXで標準化した正規表現であるため、「標準的」な正規表現といえる。

 具体的にやってみよう。なお、今回は、最終回として、bashの引数展開の機能についても解説するため、以下の例で適宜、bashの機能などについても例示しておく。詳細は後半を参照のこと。

特殊文字の使い方

 grepは、指定されたファイルや標準入力から受け取った文字列に対して正規表現による検索を行うコマンドだ。まずは、最初に解説した「文字クラス」だ。例えば“[ck]at”という正規表現は「1文字目がcまたはk、2文字目はa、3文字目がt」という文字列にマッチする。

$ teststring="cat\tdoc\tcaat\tpanda\tcaet\tbird\tct\tmanda\tcut\tsanda\tkat\tdog cat"
$ echo -e ${teststring} | grep -E "[ck]at"


「1文字目がcまたはk、2文字目がa、3文字目がt」という文字列(が含まれる行)を探す正規表現

 最初の行は、正規表現による検索対象とするシェル変数「teststring」への文字列設定である(途中にある「\t」はタブ(Tab)文字の代わり。後述のecho -eの説明参照)。繰り返し使う文字列は、このようにしてシェル変数に記憶させておくと便利だ。

 bashは言語でもあるので、コマンド以外にもこうしたステートメントが利用できる。古くからのユーザーならBASICのダイレクトモードを思い出すかもしれない。あるいはWindows OSのコマンドラインでのset内部コマンドやfor内部コマンドの利用と同じである。

 いざとなったらコンピュータ言語としても利用できるのがbashなどLinuxのコマンドラインの大きな利点である。なおシェル変数の参照は、正式には“${”と“}”で変数名を囲む必要があるが、後続文字がないなどで変数として確実に解釈される場所では“$変数名”としてもよい。上の例なら“echo -e $teststring”でよい。

 echoの-eオプションは、文字列中のエスケープ記号の解釈を行わせる。これにより“\t”はタブ文字と解釈される。grepの-Eオプションは、「拡張正規表現(Extended Regure Expression:ERE)」を使うことを強制するもの。この実行結果が次の画面だ。多くのLinuxディストリビューションに含まれるGNU grepは、出力に色を付けて正規表現が一致したところを教えてくれる。

正規表現のテスト 正規表現のテスト
正規表現のテスト用文字列をシェル変数「teststring」に定義してある。タブを表すエスケープ記号(\t)で単語を区切っているが、echoコマンドの-eオプションを使うことで実際にタブコードに変換できる。これにgrepコマンドを使って正規表現“[ck]at”を適用すると一致した部分が赤で表示される。

繰り返しの指定

 次は「繰り返し」(量指定子などと呼ばれることもある)の特殊文字だ。「?」は、直前の文字の0回または1回の繰り返しを意味する。“ca?t”は、「cのあとにaが1個または0個で、その次がt」という文字列に一致する。

量指定子「?」の例 量指定子「?」の例
量指定子「?」は、直前の文字(この例ではa)の0回または1回の繰り返しを示す。つまり、「ct」か「cat」である。

 ここで注意すべきは、aの繰り返しが0回の「ct」にも一致しているところだ。これに対して、「*」は、0回以上の繰り返しで上限がない。前記の例なら「a」は1回だけでなく何回でも一致する。こちらでは、「caat」にも一致しているところが違う。

量指定子「*」の例 量指定子「*」の例
量指定子「*」は、0回以上の繰り返しを示す。このため、「cat」だけでなく、「caat」も一致した。また、「a」を含まない(「a」のゼロ回繰り返し)である「ct」にも一致している点に注意したい。

 さらに「+」は、同じく上限なしだが、1回以上の繰り返しと一致する。今度は、「ct」とは一致していない。

量指定子「+」の例 量指定子「+」の例
量指定子「+」は、1回以上の繰り返しを示す。「a+」なら、1つ以上の「a」に一致する。このため、「ct」は一致しない。

 ただし、このパターンなら、同じことは“caa*t”と記述することもできる。このように正規表現は、同じパターンを複数のやり方で記述できることが少なくない。

 結果は同じものの、大量の検索を行う場合、書き方によって処理速度が大きく異なることがある。というのは、正規表現は、たいていの場合、内部でコンパイルされ、比較的大規模なプログラムコードとして実行されるため、記述の違いが実行コードの違いとなって、速度差を生むことがある。最初のうちは、あまり速度差を気にすることはないが、見た目が簡単な正規表現でも、その実行は比較的大掛かりなものになっていることは記憶の隅にとどめておいてほしい。

 この繰り返しだが波括弧「{」「}」を使うと、上限と下限の回数を数値で指定できる。以下の3つの指定方法がある。実行例を下画面に示す。

表記 意味
{n} 直前の文字をn回ちょうど繰り返す ca{5}tは、「caaaaat」と一致する
{n,} 直前の文字をn回以上繰り返す ca{3,}tは、「caaat」「caaaat」「caaaaat」……と一致
{n,m} 直前の文字をn回以上、m回以下繰り返す ca{1,4}tは、「cat」「caat」「caaat」「caaaat」に一致
波括弧「{」「}」による回数の指定

波括弧による量指定の例 波括弧による量指定の例
波括弧による量指定は、「n回」「n回以上」「n回以上m回以下」の繰り返しを表現できる。

ピリオド

 「.(ピリオド)」は、どんな文字にも一致する。“c.t”は、「cat」や「cut」にも一致する。それ以外にも“c#t”など、どんな文字にも一致するので注意して使う必要がある。

 たいていは、繰り返しと組み合わせて“c.*t”などとする。結果を見ると全部が選択されている。分かりやすいようにタブ(\t)をechoの-eオプションで変換しないようにすると、間の部分も一致していることが分かる。

「.」の例 「.」の例
「.(ピリオド)」は任意の1文字と一致するが、多くの場合、「*」などと組み合わせることが多い。しかし、“.*”は、対象中の最も長い部分と一致しようとするため、対象文字列先頭の2文字目から最後のtの直前までと一致してしまう。

 これは、“c.*t”が最初の「C」に一致した後、最後の「t」の直前まで“.*”が一致したからである。このようにピリオドと繰り返しの組み合わせは、思わぬ結果を招くことがある。

 実装方法にもよるが、“.*”は対象文字列の中で最も長く一致する部分を探すからである。俗にこの動作を「欲張り」と呼ぶことがあるが、ツールやプログラミング言語によっては、これを抑制して最短一致で動作させるオプションを持つものがある。取りあえずは、ピリオドと繰り返しの組み合わせには注意すべきだと思っておこう。

選択と括弧、そして結合

 次は、「選択」と呼ばれる特殊文字「|(パイプ)」だ。これは、プログラミング言語でよくOR演算子として使われるので、意味をすんなりと理解できる人もいるだろう。

 “cat|dog”とすれば、「c、a、tの並びまたはd、o、gの並び」という意味だ。この選択は、範囲を制限するために括弧と組み合わせることが多い。“c(a|u)t”といったものだ。これは「c」で始まり2文字目が「a」または「u」で3文字目が「t」というパターンと一致する。これは、1つ前の“cat|cut”と同じである(“c[au]t”とも同じ)。

選択の特殊文字「|」の例 選択の特殊文字「|」の例
選択の特殊文字「|」は、「または」の意味を表す。“cat|cut”で「catまたはcut」と一致する。ただし、「|」は優先順位が低いため、括弧と組み合わせる場合が多い。前の例を「cで始まり、2文字目がaまたはuで、最後がt」という正規表現で表すには“c(a|u)t”もしくは“c[au]t”とする必要がある。

 括弧を使う必要があるのは、「c」と「a」の並びの方が、「選択」よりも優先順位が高いからだ。「c」と「a」の間には何もないが、論理的には「結合」(concatinate)という見えない特殊文字が存在している。この結合の優先順位は最も高いため、“ca|ut”とすると、意味として「ca」または「ut」という意味になってしまう。こうした優先順位があるため、括弧を使わないと、「cで始まり2文字目がaまたはuで3文字目がt」というパターンを指定することができないのだ。

アンカリング

 最後はアンカリングと呼ばれる「^(キャップ)」と「$(ダラー)」だ。これは、文字に一致するのではなく、行の先頭と末尾に一致する。“^cat”とすれば、行の先頭にある「cat」と一致し、“cat$”とすれば、行末にある「cat」と一致する。

「^」と「$」の例 「^」と「$」の例
「^」と「$」は、行頭、行末に一致する。ただし、行頭、行末は位置なので、文字が存在しない。このため、行末には「^t」などの否定文字クラスも一致することがない。しかし、特殊文字「^」と「$」は文字であるかのように正規表現中で扱える。

 注意したいのは、正規表現の検索では行頭や行末の位置には文字が存在していなことになっている点だ。“cat[^t]”は、catの並びの後に「t」以外の文字がある場合に一致するが、行末にある「cat」には一致しない。これは、行末には何もないので、「t」以外の文字を表す“[^t]”という「文字」とは一致しないからだ。この点は間違いやすいので注意が必要だ。行末の「cat」とも一致させたければ“cat[^t]|cat$”とすることができる。なお、この正規表現は、“cat([^t]|$)”と書くこともできる。

基本正規表現と拡張正規表現

 これらの特殊文字で使われる文字そのものを検索対象としたいときには、前述の通り「\」を使ってその意味を打ち消す。“\*”とすると、繰り返しの意味ではなく、アスタリスクそのものを表す。前述した「基本正規表現」と「拡張正規表現」では、特殊文字の使い方が異なっており、括弧や選択は、「\」に続いて使われたときのみ特殊文字となる。括弧や角括弧などを検索対象とすることが多い場合には、エスケープせずに使えるが、逆に特殊文字として使う場合には、「\」と併用することが必要となる。また、基本正規表現には、選択と量指定子の「?」「+」は存在しない。

拡張正規表現 基本正規表現
[ ] \[ \]
[^ ] \[^ \]
? 対応する量指定子はない
* \*
+ 対応する量指定子はない
{n} \{n\}
{n,} \{n,\}
{n,m} \{n,m\} 
| 「選択」なし
( ) \( \)
. \.
^ \^
$ \$
\ \
拡張正規表現と基本正規表現の違い

 次ページでは、bashの便利な機能である展開機能について解説していく。bashでは、Windows OSのcmd.exeよりも強力で、コマンドライン中に引数として変数を入れたり、文字列パターンを展開したりすることもできる。

       1|2 次のページへ

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。