まずは回答から。

正規表現で「制御文字以外」のチェック - ockeghem(徳丸浩)の日記
  • 文字エンコーディングの妥当姓
  • 制御文字(\x00〜\x1f, \x7f)のチェック
  • 文字列長のチェック
このうち後ろ二つを正規表現として書くにはどうすればいいかを考えていました。

こういう時には、「全文字がOKならOK」と考えるのではなく、「一文字でもNGならNG」と考えると楽になります。それは「スペースと非制御文字以外」なのですから、/[^ \S]/が求めていた正規表現で、=~ではなく!~が使うべき演算子ということになります。全角スペースもOKにしたければ、/[^ \x{3000}\S]/。[追記参照]

[Run via Codepad]
#!perl -l
use strict;
use warnings;
use utf8;

sub check {
  $_[0] !~ /[^ \x{3000}\S]/;
}
print 0+check("妥当な 文字列");
print 0+check("妥当な 文字列"); # fullwidth space
print 0+check("妥当な\t文字列");
print 0+check("妥当な 文字列\n");

で、今回の本題です。

残る問題は、
  • もっといい方法はないのか?
  • Perlの場合末尾の\nがうまくチェックできない
 後者についてですが、以下のサンプルプログラムは *match* を表示します。正規表現のオプションs, m, msのいずれを追加しても同じ結果でした。

の後者。実のところ、^$は、フラグによって扱いがかわってしまうので利用はさけた方がよいのです。それどころか、.まで意味がかわってしまうのです。

[Run via Codepad]
#!perl
use strict;
use warnings;
my $str = <<EOT;
first
second
third
EOT

for my $re ( qr/^(.*?)$/, qr/^(.*?)$/m, qr/^(.*?)$/s, qr/^(.*?)$/ms ) {
    my $s = $str;
    print "\$re = $re\n";
    $s =~ s{$re}{print "<$1>"}eg;
    print "\n";
}

どうしてこうなってしまうかは宿題として(笑)この問題を抜本的に避けるための Best Practice が以下です。

  • 文字列の先頭は、^ではなく\Aと指定する。
  • 文字列の末尾は、$ではなく\zと指定する。
  • モードは常にmsにしておく。.は「改行をのぞく文字全種」ではなく、ただの「文字全種」となる。^は「行頭」、$は「行末」の意として使う。

Perl best Practices に載っているものですが、フクロウ本によれば、Javaでも使えるはずの作法です。

困ったことに、JavaScriptはこれをフルサポートしていないのです。mはあってもsはなく、そのため.が常に「改行を除く全文字」の意味にしかならない。仕方がないので[\s\S]と書いています。

Dan the Regular Expressionist

naruseさん

今回の話は空白文字でなく制御文字をはじきたいんですよね。違いませんか。

そうか。ならば、話はもっと簡単だったりします。

[Run via codepad]
#!perl -l
use strict;
use warnings;
use utf8;
sub check {
    $_[0] !~ /\p{C}/;
}
print 0+check("妥当な 文字列");
print 0+check("妥当な 文字列"); # fullwidth space
print 0+check("妥当な\t文字列");
print 0+check("妥当な 文字列\n");
print 0+check("\x{00}\x{01}\x{02}\x{03}");