preg_replace()で特定の文字列を行ごと削除する
正規表現についてのすっごい小ネタです。要約すると「()
の中に ^
や $
も含むことができる」ってだけの話です。
複数行に渡るテキストがあったとして、その中に含まれる、 ## DELETE LINE ##
から始まる行は行ごと削除したいとします。ちなみに話を単純化するため、改行コードは \n
(LF)
固定とします。ざっくり PHP スクリプトを書くとこんな感じです。
<?php
$subject = <<<TXT
1.
2.
## DELETE LINE ##
3.
TXT;
$replaced = preg_replace( '/## DELETE LINE ##\n/', '', $subject );
var_dump( $replaced );
すると結果は以下のように出力されます。
string(8) "1.
2.
3."
本題はここからなんですが、んじゃ、テキストが文末だったらどうでしょう? 具体的には以下のケースですが、
<?php
$subject = <<<TXT
1.
2.
3.
## DELETE LINE ##
TXT;
$replaced = preg_replace( '/## DELETE LINE ##\n/', '', $subject );
var_dump( $replaced );
\n
は飽くまでも改行コード(LF
)なので、EOL にはヒットしません。よって、このケースでは ## DELETE LINE ##
は残ったままとなります。で、どうするかというと、冒頭で要約したとおり、 pattern の正規表現を \n
固定じゃなくて文末($
)も条件にすればヒットするってわけです。
<?php
$subject = <<<TXT
1.
2.
3.
## DELETE LINE ##
TXT;
$replaced = preg_replace( '/## DELETE LINE ##(?:\n|$)/', '', $subject );
var_dump( $replaced );
これだと結果は以下のように出力されます。
string(9) "1.
2.
3.
"
これで ## DELETE LINE ##
は削除できました。
あれ? 最初の結果と違うことない? それは 3. の末尾に改行コードを含むから、ですね。これはまた別のお話…だと思うんですけど、結果を同じようにしたい場合は、一行でなんとかするよりもう一度 preg_replace()
を実行するのが簡単でしょうね。例えばこんな感じになります。
<?php
$subject = <<<TXT
1.
2.
3.
## DELETE LINE ##
TXT;
$replaced01 = preg_replace( '/## DELETE LINE ##(?:\n|\z)/', "", $subject );
$replaced02 = preg_replace( '/\n$/', "", $replaced01 );
var_dump( $replaced02 );
結果はこうなります。
string(8) "1.
2.
3."
言われてみればそうなんですが、$
や \z
を ( )
で括るって発想がなかったので、しばらくハマってました……っていう小話でした。