よくウェブで見かける説明はこんな感じ。
\A は行頭に,\Z は行末にマッチする。
ところが,取扱いはそう一筋縄でもなさそうなのよね。ちょっと突っ込んでみたい。
● 「文字」として扱われない
フォームやクエリーで送られてきたデータの中から,特定フィールドだけ取り出したり,あるいは削除したい場合,生データに直接的に操作できれば簡単だと思った。たとえば,生データに近い形式で保存を考えた時,パスワードのフィールドとして pw と pwv の2つがあって,pwv 側は確認用で保存の必要はないから削除したいような時,当初は以下のようなものを考えた。
$fields; # query (GET) or form (POST) data $fields =~ s/[\A&]pwv=.*?[\Z&]/&/; $fields; # ← deleted "pwv" field ? (fail)
ちなみに,“*?”ってのは「最短マッチ」のことで,任意文字で任意文字数にマッチさせた場合,次のフィールドとの境界(つまり最短の)& を任意文字“.”へのマッチから外すためのもの。
pwv フィールドが先頭にあれば \A にマッチし,次のフィールドとの境界の & までを消去し,また,最後のフィールドとしてあれば,最後にある & で区切られた部分から末尾(\Z)までを消去すればいい……と,思ってこうしたが,これではうまくいかない。「\A(\Z)がこんなところにあってはダメ」的なエラーが出る。なぜ?
どうやら「文字クラス」([] で括った文字のどれか)として扱ってくれないよう。たとえば「削除」ではなく,単純に1つのフィールドの設定値を取り出したいだけなら,単純なマッチ式で以下のようにするとうまくいく。
$fields =~ /(\A|&)pwv=(.*?)(\Z|&)/; $2; # ← value of the "pwv" field
これだと,最初か最後にあってもマッチする。「文字として」よりも「文字列」的に扱えばよかったみたい。
ただ,設定値取り出しは上記でいいが,削除だと別の点で問題が起きた。これを「削除」したい時はこうすればよさそうな気がするが……。
$fields =~ s/(\A|&)pwv=.*?(\Z|&)/&/; $fields; # ← deleted "pwv" field ? (just a little fail)
置換文字側の & というのは,pwv フィールドが中間にある時にそこを削除して & 1個に置換し,前後のフィールドを詰めるものだが,先頭か末尾にある時は & に置換する必要はない。つまり,端にある時と中間にある時で置換内容の有無を変えないとダメっぽい。で,考えたのがコレ。
$fields =~ s/(\A|&)pwv=.*?(\Z|&)/"$1$2"eq"&&"?"&":""/e;
スイッチの“e”ってのは,置換文字側が「式」であることを意味するもの。つまり,フィールドの前と後ろの両方に & がある時だけ & 1個に置換し,端にある時は単純に消去するだけってこと。
◆ 複数の時はさらに工夫が必要
じつはこの時は,消去したいフィールドが pwv のほかにもあって,それも一緒に消したいと思っていた。たとえばそれが,一時的に使ったセッション ID の sid だった場合,次のようにすればうまくいきそうな気がする。
$fields =~ s/(\A|&)(pwv|sid)=.*?(\Z|&)/"$1$3"eq"&&"?"&":""/eg; $fields; # ← deleted "pwv" and "sid" fields ? (fail)
“g”スイッチを付けたから両方置換してくれるかというと,うまくいかないケースがある。それは,この pwv と sid のフィールドが隣り合っていた場合で,前側しか消去されない。前にあるフィールドで置換が起きると,その直後の文字から次のマッチ部分を探すことになるが,置換した前部分は & や \A のマッチ対象から外されるらしい。
どうすればいいかというと,たとえばこう。
while( $fields =~ s/(\A|&)(pwv|sid)=.*?(\Z|&)/"$1$3"eq"&&"?"&":""/e ){} $fields; # ← deleted "pwv" and "sid" fields (OK)
つまり,マッチするものがなくなるまで全体を繰り返して置換対象にする。ただ,正規表現マッチングの「繰り返し」って,ワリと負担なのよねぇ……。と言っても,この場合ではせいぜい2回だが。削除したいフィールドが多数ある時は,1文で書ける点は便利ではないかと思う。
単語境界を示す“\b”が使えるなら while は不要になりそうだが,& 以外にもマッチしてしまうので,たとえば他フィールドの「値」の側にたまたま“-pwv=”なんて文字列があるとアウトよね。
これは,整数の3桁ごとにコンマを挿入するつぎのやり方の応用。
$integer = 整数; # Insert commas each 3 digits while( $integer =~ s/(\d)(\d\d\d)\b/$1,$2/ ){}
この場合も“g”スイッチを付けてもダメで,while 文の中に入れて繰り返す必要がある。1の位を含む後ろの桁からマッチする箇所を探すから,桁が多いとコンマを入れる場所を前に戻って探す必要が生じる。で,繰り返しが必要になるわけだ。
でもまぁ,C言語みたいに,文字列化して数字がいくつ並んでいるか数えて……なんてコード書くよりはラクだよね。
● おわりに
正規表現って便利だわー。筆者はこれを応用して,ダウンロードした他サイトの HTML から,正規表現で必要な部分だけ抜き出して見たりしている。追加で読み込むスクリプト,広告や他記事の紹介など,余計な部分はみんな切り落とすから,読み込みも表示も早くて助かる。
でも,そういった方法を使いこなせるのは一部の人間なのだろうね。だから,社会全体としては,筆者の想像よりもスゴく面倒な手法を使って余計な時間を費やしているんじゃないかって気がする,今日この頃。と言っても,その手のものも含めて,今のところ(2023,9 月)筆者の元に仕事の話なんか来ないけどね。