.htaccessで同一ディレクトリ内へのリダイレクト、正規表現を使ってパスやURL指定無しで汎用的に使えるようにしてみた

.htaccessでリダイレクト処理をする時に、同一ディレクトリ内の別ページに飛ばしたいだけなのに、いちいちパスを書かなきゃいけないのって面倒ですよね?
テスト環境でディレクトリ名がころころ変わる場合なんか、いちいち.htaccessも更新しなきゃいけないの大変です。
サーバーのどこに置いても、そのディレクトリ内でリダイレクト処理してくれる書き方をしたい!と思って調べてみました。

正規表現でできる、意外とシンプルな「同一ディレクトリ内」指定方法

ディレクトリ名指定するのメンドイ……というわけで、正規表現でどうにかできないの?と思ったら、ありましたよ、stackoverflowに。
それこそ正規表現の本を出版してるレベルのガチプロによる指南が。
domain.com/somepage/?page=3 というアドレスを
domain.com/somepage/3/ にリダイレクトする場合の例がこちら
RewriteEngine On

RewriteCond $0#%{REQUEST_URI} ^([^#]*)#(.*)\1$
RewriteRule ^.*$ - [E=BASE:%2]

RewriteCond %{THE_REQUEST} \?page=(\d+) [NC]
RewriteRule ^ %{ENV:BASE}%1/? [R=301,L]
皆さん分かります? 正規表現なんて毎回調べながらコピペってるような素人の私にはまったくチンプンカンプンでしたが、さすがガチプロは解説も丁寧。英文をよくよく読んでみると仕組みが分かってナルホド!となりました。
ポイントは、RewriteCondはRewriteRuleを読み込んだ後に実行される、という点なのです。だからRewriteRuleの後方参照はRewriteCond内でも使える!
そしてそれをうまく使って環境変数にディレクトリ名を保存する!ということらしいです。

環境変数にディレクトリ名を保存してリダイレクト時に使う方法を解説します

せっかくなので後方参照みたいな専門用語をなるべく使わず、私のような素人がどうやって理解したか、ひとつひとつ確認していきたいと思います。
domain.com/somepage/というディレクトリに.htaccessを設置したと想定して下さい。

まず最初のRewriteCondから。
RewriteCond $0#%{REQUEST_URI} ^([^#]*)#(.*)\1$
いきなり$0と出てきています。これは次のRewriteRuleが先に読まれているので
RewriteRule内の^.*$となっている部分を指す$0が使えるのです。

^.*$とは今のアクセスしたURLですね。^が先頭、$で末尾を指しています。ディレクトリ名は含まれず、?page=3みたいなのも含まれません。
(例:domain.com/somepage/index.htmlならindex.htmlだけ)

RewriteCondに戻って、#%{REQUEST_URI}の部分。%{REQUEST_URI}は、ドメイン以下のパスのこと。これも?page=3みたいなのも含まれません。
(例:domain.com/somepage/index.php?page=3なら/somepage/index.phpが入る)

$0%{REQUEST_URI}の内容を比べたいので、間に#を入れて区切ります。この#は実はなんでもいいのだ。区切り用に分かりやすくてURLに含まれないような記号ということで#を選んでいるだけなんだね。

RewriteCondA(半角スペース)B という風に書くことで、A=Bのときだけリダイレクトを実施するよ~という意味になっています。
そこでBの部分となる^([^#]*)#(.*)\1$を見ていきましょう。先頭末尾の指定を抜くと([^#]*)#(.*)\1ですね。

まず最初の([^#]*)は、シャープ以外の文字という意味の[^#]0個以上あるよ、という意味。
この部分は後で文字列として使いたいので、()で括っています。
こうして()で括ると、それ以降に\1のように書くことでさっき見つけた文字と同じもの!と指定できるのです。
()は複数回括れて、\1 \2のように書くと1個目のやつ、2個目のやつ、という風に別々に使えます。

というわけで、#(.*)\1と書くことで、#さっき見つけた文字列の間、という部分が新たに指定できるようになりました。

かなり複雑でしたが、これでdomain.com/somepage/index.phpにアクセスした場合、RewriteCondABどちらも
(index.php)#(/somepage/)index.php
というようになるわけです。
AとBは必ず一致する上に、2つ目の()内にディレクトリ名を格納することが出来ました!

RewriteCondが一致しているので、次の行のRewriteRuleは必ず実行されます。
RewriteRule ^.*$ - [E=BASE:%2]
RewriteRule内も最初が^.*$となっているので無条件にその右の命令- [E=BASE:%2]も実施。
なおこのハイフンは、URLを書き換えない場合に使うものです。
今回は環境変数にディレクトリ名を格納する作業を行いたいので、まだURLの書き換えはしません。

E=BASE:と書くことで、BASEという変数にそこから右の文字列を格納できます。
ここで先ほどの二つ目の()内を参照したいので、%2と書きましょう。
RewriteCond内では\2と書けば良いですが、RewriteRuleから参照したい場合には%2のように書く必要があるので注意です。

ここまでで無事、環境変数のBASEにディレクトリ名を格納できました。
次はいよいよリダイレクトの指示です。

RewriteCond %{THE_REQUEST} \?page=(\d+) [NC]
まずはRewriteCondで、?page=3のようなURLなのか判定したいので、%{THE_REQUEST}という変数を使います。
これは
GET /index.php?page=3 HTTP/1.1
というような文字列になっていて、ブラウザが出した指示が格納されているものです。

\?page=(\d+)で、?page=の後ろにある数字を見つけます。
\d0~9の数字、後ろに+が付いているのでそれが1回以上続いている、という意味になります。
あとはハテナマークをエスケープするのも忘れずに。

RewriteRule ^ %{ENV:BASE}%1/? [R=301,L]
最後のRewriteRuleではまず^とだけ書いています。これは行頭の意味なので、行頭の時点で無条件にリダイレクト処理がされます。
ディレクトリ名(/somepage/)は環境変数のBASEに格納しているので、%{ENV:BASE}と書けばOK
数字は直前のRewriteCond()で括ってあるので、%1で表現できます。

これで%{ENV:BASE}%1と書けば/somepage/数字のようになるわけです。
あとは後ろにスラッシュが足りないので、%{ENV:BASE}%1/と書き足しておきます。

一番後ろの?は、リダイレクト前の?page=3を引き継がない、という意味になります。
これを書かないと、リダイレクト先がdomain.com/somepage/3/?page=3みたいなゴミ付きになってしまうので、要注意!

最後に[R=301,L]と301リダイレクト(恒久的なURL変更)の指示を書いておしまい!

同一ディレクトリ内の別ページにリダイレクトする場合

さてこれまでの例では?page=3のようなパラメータからのリダイレクトだったので、一応index.htmlを○○○.htmlに書き換える!みたいな処理の例も書いておきます。
RewriteEngine On

RewriteCond $0#%{REQUEST_URI} ^([^#]*)#(.*)\1$
RewriteRule ^.*$ - [E=BASE:%2]

RewriteRule ^(index\.html)?$ %{ENV:BASE}○○○.html [R=301,L]
環境変数にディレクトリ名を格納するまでは一緒ですね。
あとはRewriteRuledomain.com/domain.com/index.htmlの両方にマッチする場合のみ、同一ディレクトリの○○○.htmlへとリダイレクトされます。


以上、コピペする場合は必ずテストを行って、自己責任でご使用ください!