読者です 読者をやめる 読者になる 読者になる

その手の平は尻もつかめるさ

ギジュツ的な事をメーンで書く予定です

文字列内のバックスラッシュを区別したい的な

perl

タイトルが適当すぎて何を言っているのかわからないと思いますが,まあそういう事がごくごく稀にあります.結論から言うとヒアドキュメント使えば良いです.

以下の様な文字列を考えた時,

my $string = '\n\\';

これを1文字ずつ処理しようとすると,

say $_ for split //, $string;
# 以下出力
# \
# n
# \

という風になります.

さてこうした時,エスケープの為のバックスラッシュと純粋なバックスラッシュ (つまりエスケープされたバックスラッシュ) との区別がつかなくて困るということがごく稀に生じます.エッ,生じない? 俺はRegexp::Lexerで生じたんだよ!!!!

で,困ったので以下のように解決しました.

use B;

# 文字列をregexp quotedな変数にする
my $string = qr(\n\\);

# B::cstringでバックスラッシュをさらにエスケープする
my $cstring = B::cstring($string); # => '"(?^:\\n\\\\)"'

# 先頭の `"(` と末尾の `)"` が邪魔なので消しておく
$cstring = substr(substr($cstring, 2), 0, -2);

# B::cstringはダブルクォート文字もエスケープするのでそれは元に戻しておく
$cstring =~ s/\\"/"/g;

# 正規表現のmodifierが邪魔なので消す
$cstring =~ s/\A[?]([^:]*)://;

# 余計なバックスラッシュをまとめる
$cstring =~ s/\\\\/\\/g;

と処理してやって,一文字ずつ処理してやると

\
n
\
\

という具合にめでたくエスケープの為のバックスラッシュと純粋なバックスラッシュが区別できるという塩梅です.
reqexp quotedな変数にする*1 というのとB::cstring()を使う*2 という方法に至ったのですが,実際にはヒアドキュメントを使うと楽.

my $string = <'...';
\n\\
...
say $_ for split //, $string;
# \
# n
# \
# \

ヒアドキュメントを使うとこういう七面倒臭いことをしなくても済みます!!!
ヒアドキュメントを使える場面では使ったほうが良さそう! とは言え色々事情はあると思うので適宜使い分けという感じで.

*1:今回はたまたま解析対象が正規表現だからこれでも問題なかったけど……

*2:ちなみにB::cstring()を使っているのは文字列中の改行とかに対応するため