プログラマ・アゲイン blog

還暦を過ぎたけどプログラマ復帰を目指してブログ始めました

正規表現を使ってみて

正規表現は、英語だと Regular Expression ですが、Regularには「規則的」などの意味もあるので、英語の方がしっくりきそうです。ただ、どちらにしても最初は何のことかさっぱりでした。

でも、Perlの流れをくむ Rubyにとって、正規表現は非常に重要な技術だと思います。この正規表現が使えるからこそ、文字列処理が格段に簡単になります。Rubyのプログラミングをするうえで、避けては通れない技術の一つだと思います。

正規表現は奥が深くて、本当に勉強しようとしたら以下のような本を買って勉強する必要があると思います。

ただ、ある程度は通常のプログラミング本で説明されている内容で、使ってみることができます。使ってみると非常に便利でした。

ここでは、この未熟な私でも使った一部の正規表現について、理解した内容を残したいと思います。

 

正規表現の基本

正規表現というのは、簡単に言うと、文字列がどのようなパターンで並んでいるかを表現する記法だと思います。

正規表現を使えば、ある文字列があるかのマッチングや、文字パターンに沿ったキャプチャができます。

基本的な書き方

正規表現の書き方は、

 /パターン/オプション

です。

これ以外にも、Rubyでは「Regexp.new(str)」や「%r」を使う方法なども有るようですが、私は使っていません。特殊な使い方や文字が含まれている場合に使うようです。

パターン

パターンは、「/ /」で囲った中に記述します。

パターンの表現には、以下のものがあります。

因みに、「\」の記号は、日本語キーボードの「\」の記号ですが、プログラミングの中では「バックスラッシュ」です。

  • 文字列
  • 行頭(^または\A)と行末($または\z)
  • 文字の範囲([])
  • 空白文字(\s)
  • 数字(\d)
  • 英数字(\w)
  • メタ文字(\メタ文字)
  • 任意の一文字(.)
  • 0回以上の繰り返し(*)
  • 1回以上の繰り返し(+)
  • 0回または1回の繰り返し(?)
  • n回の繰り返し({n})
  • n~m回の繰り返し({n,m})
  • 0回以上の繰り返しのうち最短の部分(*?)
  • 1回以上の繰り返しのうち最短の部分(+?)
  • 複数文字の繰り返し(())
  • 選択(|)
オプション

オプションは、正規表現の挙動を少し変化させるものです。

オプションには、以下のものがあります。

  • アルファベットの大文字と小文字の違いを無視(i)
  • 正規表現内の空白と「#」の後ろの文字を無視(x)
  • 「.」が改行文字にもマッチ(m)
  • 式展開は一度のみ行う(o)

 

キャプチャの書き方

キャプチャは、正規表現でマッチした部分の一部を取り出すものです。

正規表現の中で、「( )」で囲まれた部分にマッチした文字列を切り出します。切り出した文字列は、「$1」や「$2」などや、「$`」「$&」「$'」で参照する事が出来ます。

また、正規表現を使う文字列のメソッドとして、以下のものがあります。

  • 文字列中の最初にマッチしたある部分を別の文字に置き換える(sub)
  • 文字列中のマッチする部分すべてを別の文字に置き換える(gsub)
  • 文字列中のマッチする部分すべてを取り出す(scan)

 

使った正規表現

前述のような書き方で、実際にマッチングやキャプチャを行ってみました。

もっと良い書き方があるよとか、その書き方はおかしいよとか、ご指摘は有ろうかと思いますがご容赦いただいて、どういう使い方をしたかを中心に記述したいと思います。

パターン

いくつかのパターンの書き方を使用しました。

但し、パターンの書き方を詳細に全て説明するのは難しいので、その部分は書籍に譲りたいと思います。

数字
/PRIMARY\(\d{1,8}\)/

上記の例では、「PRIMARY(1から8桁の0から9までの数字)」のパターンを表現しています。

英数字
/NAME\(\w{1,8}\)/

上記の例では、「NAME(1から8桁の英数字)」のパターンを表現しています。

メタ文字
/PARM\([\'\/\w\(\)\@\#\$\s]+\)/

上記の例では、「PARM(1個以上の'/英数字()@#$空白のどれかの文字列)」のパターンを表現しています。

範囲指定
/DATASET\([A-Z0-9\@\$\#\.]+\)/

上記の例では、「DATASET(1個以上のAからZまでの大文字と0から9までの数字と@$#.の文字でどれかの文字列)」を表現しています。

選択
/POLICY\((ALLOW|WARN|FAIL)\)/

上記の例では、「POLICY(ALLOWまたはWARNまたはFAILのどれか)」の ( ) 内に当てはまったものを抜き出すことを表現しています。

組み合わせ
/STRGR\=(GENERIC|NONE|[\w\$\@\#\_]{1,16})/

上記の例では、「STRGR=(GENERICまたはNONEまたは1から16桁の英数字$@#_のどれか)」の ( ) 内に当てはまったものを抜き出すことを表現しています。

 

マッチング

=~ !~
if /CSDUMP\,/ =~ line
if /^LUDEL/ !~ line

正規表現と文字列がマッチするかどうか、つまり正規表現に対応する文字列が含まれているかどうか、「=~」や「!~」のメソッドで調べる事が出来ます。これらを、if文の中で使っています。

「=~」は、マッチした場合に「真」が返ります。1行目では、「CSDUMP,」が含まれているかどうかを調べています。

因みに、「,」はメタ文字ではないと思うので「\」は不要かもしれませんが、念のために英数字以外は全て「\」を付けるようにしました。

「!~」は、マッチしなかった場合に「真」が返ります。2行目では、「行の先頭にLUDEL」が含まれていないことを調べています。

case
case line
when /\sDATACLAS\s/
・・・
end

case文も、if文のように正規表現を比較の値とする事が出来ます。

2行目では、「(空白1文字)DATACLAS(空白1文字)」の文字が含まれているかどうかを調べています。

 

キャプチャ

gsub
work += line.slice(0,71).gsub(/\s+|\/\*.+\*\/|\n/,' ').strip
work.gsub!(/\/\*.+\*\//,'')

gsubメソッドは、マッチする部分のすべてを置き換えます。

1行目では、複数行を連続した文字列にするために、行データの72桁を切り出し、その中の「1個以上の空白文字」や、「/*1個以上の任意文字*/」や、「改行文字」を「1個の空白文字」に置き換えて結合しています。

2行目では、「/*1個以上の任意文字*/」を消去して、元のオブジェクトを変更しています。

scan
work = line.scan(/PARM\(([\'\/\w\-\(\)\s]+)\)/).join if line.include?('PARM')

scanメソッドは、パターンにマッチした部分を全て取り出します。また、正規表現の中で「( )」を使用していると、そこにマッチした部分を配列にして返してくれます。

注意が必要なのは、マッチした部分を全て取り出すので、結果が配列になっていることです。1個しかマッチしなかったとしても、それが配列の [マッチした文字列] の形になっていることです。文字列だけが欲しいので、取り出した結果の配列を joinメソッドで要素を文字列に変換して結合しています。

上記の例では、「PARM(1個以上の'/英数字()@#$空白のどれかの文字列)」の文字列を探して、「1個以上の'/英数字()@#$空白のどれかの文字列」を取り出しています。

 

正規表現は、文字列を調べたり切り出したりするために、プログラムのいろいろなところで使っています。旨くマッチングできなかったり、二重に文字列を取り出したりと、想定通りにならないことも多くて大変ですが、その分旨くいくと面白いです。なので、これからも使い込んでいきたいと思います。