ブログにはカテゴリーというものがあります。第1回(Opus 1)のカテゴリーは「本ブログについて」というものでした。こちらは、本ブログの説明や運営上の連絡事項に使います。続いて、いまお読みになっている第2回(Opus 2)のカテゴリーは「訂正とお詫びのコーナー」です。
 『かんたんPerl』(これ以降「本書」と呼ぶ)の執筆において、ぼくは可能な限り間違いがないように努めましたが、残念ながらいくつか間違いが残ってしまいました。誠に申し訳ありません。技術評論社の『かんたんPerl』サポートページには、公式の正誤表が載っています。
 それと合わせて本ブログでも、誤りについて改めてお詫びすると共に、どう間違っているのか解説して行こうと思います。万一さらに何かお気づきの点があれば、SuguwakaruPerl@gmail.comにメールをくださるか、下の欄からTwitter、Facebook、メールでご連絡くだされば幸いです。
 では、2016年1月15日現在の間違いについて、解説を加えます。恐れ入りますが本書と並べてご覧ください。
(※なお、この間違いはすべて電子書籍版では修正されています。)

表記の間違い

p.112 下から9行目

(誤)
ありぃ~(ary~?)うまくっていませんね。

(正)
ありぃ~(ary~?)うまくっていませんね。

 スミマセン、変換ミスです。「うまく行く」の活用形ですから「行って」が正解ですね。

余計な文

p.117 サンプル プログラムmonth12.pl中
p.160 サンプル プログラムmonth12.pl(再掲)中

(誤)
use 5.010;
use strict;
use warnings;
 
my $num = 1;

my @month = (undef, "January", "February", "March", "April",
  "May", "June", "July", "August", "September",
  "October", "November", "December");

(正)
use 5.010;
use strict;
use warnings;
 
my @month = (undef, "January", "February", "March", "April",
  "May", "June", "July", "August", "September",
  "October", "November", "December");
 
 同じmonth12.plというプログラムをp.117(4章)とp.160(5章)に2回掲載していますが、どちらにも「my $num = 1;」という余計な文が入っています。
 本書では、同じようなプログラムを少しずつ改変してパワーアップする感じを味わっていただく構成にしていますが、前のプログラムをコピーしてくるときに余計な文を消し忘れてしまいました。
 あっても別に問題はありませんが、削除したものを正とします。

足りない文

p.563 サンプル プログラムtrName.pl中

(誤)
use 5.010;
use strict;
use warnings;

use utf8;

(正)
use 5.010;
use strict;
use warnings;

use utf8;

if ($^O eq "MSWin32") {
  binmode STDOUT, ":encoding(Shift_JIS)";
} else {
  binmode STDOUT, ":encoding(UTF-8)";
}


 本書の10章後半のプログラムにほとんどついている、標準出力の文字コードを指定するbinmode関数が抜け落ちてしまいました。
 誤ったまま実行すると、Windowsでは「繧繝なんとか・・」みたいな難しい字の文字化けになります。これはUTF-8をそのままShift_JIS対応のコマンド プロンプトが解釈したものです。
 一方Mac/UNIXでは、一応正しく表示されますが、「Wide character in print at...」という警告が表示されます。これは10章で詳述しているように、UTF-8内部文字列をエンコードせずにそのまま標準出力に表示したときに出る警告です。



 残りの間違いはすべて同じ原理の誤りです。

説明の間違い

p.438 下から9行目

(誤)
ファイルハンドルDATAで取得する__DATA__以降のデータは、プログラムと同じUTF-8のテキスト ファイルに保存しますから、binmodeの:encodingにはUTF-8を指定します。
 そして、
標準出力(STDOUT)の文字コードをOSによってWindowsの場合はShift_JIS、それ以外の場合はUTF-8にbinmode指定します。

(正)
ファイルハンドルDATAで取得する__DATA__以降のデータは、プログラムと同じUTF-8のテキスト ファイルに保存されていますが、use utf8;の影響範囲にありますから、UTF-8内部文字列なので、binmodeの指定は不要です。
 一方、
標準出力(STDOUT)の文字コードは、従来通りOSによってWindowsの場合はShift_JIS、それ以外の場合はUTF-8にbinmode指定します。

余計な文

p.492 サンプル プログラムremoveDup.pl中
p.492 サンプル プログラムremoveParen.pl中
p.592 サンプル プログラムremoveTeam.pl中
p.514 サンプル プログラムsortNameReading.pl中
p.561 サンプル プログラムsortNameReading.pl(再掲)中

(誤)
use utf8;

binmode DATA, ":encoding(UTF-8)";

if ($^O eq "MSWin32") {
  binmode STDOUT, ":encoding(Shift_JIS)";
} else {
  binmode STDOUT, ":encoding(UTF-8)";
}
(正)
use utf8;

if ($^O eq "MSWin32") {
  binmode STDOUT, ":encoding(Shift_JIS)";
} else {
  binmode STDOUT, ":encoding(UTF-8)";
}

 以上は、恥ずかしながら、ぼくが、__DATA__トークンの下のデータには、use utf8;プラグマ モジュールの効果が発揮しないと思い込んでいたために起こった誤りです。
 実際には、use utf8;さえ書いてしまえば、__DATA__のデータもUTF-8内部文字列にアップグレードされますから、日本語も正しく1文字ずつ処理され、日本語の正規表現にもマッチします。よって、binmode DATAは不要です。

 ここまでで、説明が分かった方は、このブログのこれ以降の記述を読む必要はありません。どうも失礼しました。

 さて、もうちょっと詳しく説明して欲しい方のために、以下の説明を書きます。

 本書11章「正規表現」では、プログラムの中に、__DATA__を使ってデータを埋め込み、<DATA>ファイルハンドルで読み込むという手法を多用しています。以下は、本書の例そのままではありませんが、プログラム例を挙げます。

#! /usr/local/bin/perl
# testRegexp.pl -- 正規表現のテスト

use 5.010;
use strict;
use warnings;

use utf8;

if ($^O eq "MSWin32") {
  binmode STDOUT, ":encoding(Shift_JIS)";
} else {
  binmode STDOUT, ":encoding(UTF-8)";
}

while () {
  my $colon = index $_, ":";
  print "the place of colon is $colon, ";
  if (s/ana/*ana*/) {
    print "matched: $_";
  } else {
    print "no match: $_";
  }
}

__DATA__
あいうえお:hiragana
アイウエオ:katakana
いろはにほへと:irohauta

 実行します。

[sample]$ ./testRegexp.pl
the place of colon is 5, matched: あいうえお:hirag*ana*
the place of colon is 5, matched: アイウエオ:katak*ana*
the place of colon is 7, no match: いろはにほへと:irohauta

 では解説します。
 __DATA__以降はプログラム本体ではなく、データです。上では

あいうえお:hiragana
アイウエオ:katakana
いろはにほへと:irohauta

という3行のデータを書いています。
 この3行のデータは、<DATA>式で読み込みます。<DATA>をスカラー コンテキストで1回評価するたびに、$_に1行ずつデータが読み込まれます。
 index関数では、文字列の中のコロンの位置を算出します。ゼロ始まりなので先頭に出現したらゼロが返るのに注意。
 s/ana/*ana*/はマッチ演算子で、「ana」という固定文字列にマッチすると、「*ana*」に置換が行われるとともに、真を返すので、ifブロックで「matched:」というメッセージと共に入力した行がprintされます。
 マッチしなければ、「no match:」と言うメッセージと共に、入力した行がそのままprintされます。

 で、上のプログラムはuse utf8;と書いています。これはutf8プラグマ モジュールというものを使うものです。これを書いた以降、このプログラムはUTF-8を正しく認識し、英数字もひらがな、カタカナ、漢字も、すべて1文字ずつ正しく解釈されます。
 「UTF-8が正しく認識される」、「1文字ずつ正しく解釈される」とはどういうことでしょうか。

あいうえお:hiragana
アイウエオ:katakana
いろはにほへと:irohauta

 というデータの中で、コロンの位置は(ゼロ始まりで)5文字目、5文字目、7文字目になっています。これは、index関数によって、「あいうえお」や「アイウエオ」が5文字、「いろはにほへと」が7文字と正しく数えられているからです。

 正規表現による検索置換も、問題なく行われています。「hiragana」、「katakana」の中の「ana」は「*ana*」になっていますし、「irohauta」の行はno matchになっています。

 では、use utf8をやめてみたらどうなるでしょうか。

#! /usr/local/bin/perl
# testRegexp2.pl -- 正規表現のテストその2(use utf8の廃止)

use 5.010;
use strict;
use warnings;

#use utf8; # 廃止

if ($^O eq "MSWin32") {
  binmode STDOUT, ":encoding(Shift_JIS)";
} else {
  binmode STDOUT, ":encoding(UTF-8)";
}

while () {
  my $colon = index $_, ":";
  print "the place of colon is $colon, ";
  if (s/ana/*ana*/) {
    print "matched: $_";
  } else {
    print "no match: $_";
  }
}

__DATA__
あいうえお:hiragana
アイウエオ:katakana
いろはにほへと:irohauta

 実行します。

[sample]$ ./testRegexp2.pl
the place of colon is 15, matched: あいうえお:hirag*ana*
the place of colon is 15, matched: アイウエオ:katak*ana*
the place of colon is 21, no match: いろはにほへと:irohauta

 文字化けが起こっています。これは、DATA文が、正しくUTF-8内部文字列にアップグレードされず、1バイト1文字のISO 8859-1として解釈されているからです。

 おなじことは、index関数が調べたコロンの位置からも分かります。「あいうえお」と「アイウエオ」のあとのコロンは15番目、「いろはにほへと」のあとのは21番目になっています。これは、ひらがな、カタカナ1文字を3バイト3文字で解釈しているからです。

 ところで、正規表現による検索置換は正しくanaが*ana*になっています。これは、正規表現が「ana」という英数字だけから成り立っているからです。

 さて、use utf8;を__DATA__トークンの直前に入れたらどうなるでしょうか。

#! /usr/local/bin/perl
# testRegexp3.pl -- 正規表現のテストその3(use utf8の移動)

use 5.010;
use strict;
use warnings;

#use utf8; # 移動した

if ($^O eq "MSWin32") {
  binmode STDOUT, ":encoding(Shift_JIS)";
} else {
  binmode STDOUT, ":encoding(UTF-8)";
}

while () {
  my $colon = index $_, ":";
  print "the place of colon is $colon, ";
  if (s/ana/*ana*/) {
    print "matched: $_";
  } else {
    print "no match: $_";
  }
}

use utf8; # ここに移動した
__DATA__
あいうえお:hiragana
アイウエオ:katakana
いろはにほへと:irohauta

 実行します。

[sample]$ ./testRegexp3.pl
the place of colon is 5, matched: あいうえお:hirag*ana*
the place of colon is 5, matched: アイウエオ:katak*ana*
the place of colon is 7, no match: いろはにほへと:irohauta

 およよ、完璧に動作します。これは__DATA__のデータがUTF-8内部文字列にアップグレードしたために、あいうえおは5文字、いろはにほへとは7文字と正しく解釈されたためです。

 さて、__DATA__のデータをUTF-8内部文字列にしたければ、DATAファイルハンドルにbinmode関数を作用させて":encoding(UTF-8)"を指定してもいいような気がします。

#! /usr/local/bin/perl
# testRegexp4.pl -- 正規表現のテストその4(binmodeの使用)

use 5.010;
use strict;
use warnings;

#use utf8; # 廃止
binmode DATA, ":encoding(UTF-8)";

if ($^O eq "MSWin32") {
  binmode STDOUT, ":encoding(Shift_JIS)";
} else {
  binmode STDOUT, ":encoding(UTF-8)";
}

while () {
  my $colon = index $_, ":";
  print "the place of colon is $colon, ";
  if (s/ana/*ana*/) {
    print "matched: $_";
  } else {
    print "no match: $_";
  }
}

__DATA__
あいうえお:hiragana
アイウエオ:katakana
いろはにほへと:irohauta

 実行。

[sample]$ ./testRegexp4.pl
the place of colon is 5, matched: あいうえお:hirag*ana*
the place of colon is 5, matched: アイウエオ:katak*ana*
the place of colon is 7, no match: いろはにほへと:irohauta

 これでもうまくいきます。
 でも、以下のような場合うまくいきません。

#! /usr/local/bin/perl
# testRegexp5.pl -- 正規表現のテストその5(正規表現を日本語に)

use 5.010;
use strict;
use warnings;

#use utf8; # 廃止
binmode DATA, ":encoding(UTF-8)";

if ($^O eq "MSWin32") {
  binmode STDOUT, ":encoding(Shift_JIS)";
} else {
  binmode STDOUT, ":encoding(UTF-8)";
}

while () {
  my $colon = index $_, ":";
  print "the place of colon is $colon, ";
  if (s/い/*い*/) { # 正規表現を日本語にした
    print "matched: $_";
  } else {
    print "no match: $_";
  }
}

__DATA__
あいうえお:hiragana
アイウエオ:katakana
いろはにほへと:irohauta

 正規表現を「ana」から「い」へと日本語を入れてみました。実行してみます。

[sample]$ ./testRegexp5.pl
the place of colon is 5, no match: あいうえお:hiragana
the place of colon is 5, no match: アイウエオ:katakana
the place of colon is 7, no match: いろはにほへと:irohauta

 意図としては「あいうえお」と「いろはにほへと」の「い」にマッチして欲しかったのですが、マッチしません。これは、正規表現の「い」が、正しく日本語1文字として認識されず、ISO 8859-1の3文字として認識されたため、すべてno matchになってしまったものです。

 これは、use utf8;を、正規表現による置換が適用範囲に入るように、復活させるとうまくいきます。

#! /usr/local/bin/perl
# testRegexp6.pl -- 正規表現のテストその6(use utf8+日本語正規表現)

use 5.010;
use strict;
use warnings;

use utf8; # 復活
binmode DATA, ":encoding(UTF-8)";

if ($^O eq "MSWin32") {
  binmode STDOUT, ":encoding(Shift_JIS)";
} else {
  binmode STDOUT, ":encoding(UTF-8)";
}

while () {
  my $colon = index $_, ":";
  print "the place of colon is $colon, ";
  if (s/い/*い*/) { # 正規表現を日本語にした
    print "matched: $_";
  } else {
    print "no match: $_";
  }
}

__DATA__
あいうえお:hiragana
アイウエオ:katakana
いろはにほへと:irohauta

 どうでしょうか。

[sample]$ ./testRegexp6.pl
the place of colon is 5, matched: あ*い*うえお:hiragana
the place of colon is 5, no match: アイウエオ:katakana
the place of colon is 7, matched: *い*ろはにほへと:irohauta

 やったー。どうにか無事「い」がマッチしましたね。正規表現「い」が無事に作動しています。これは、use utf8;が、__DATA__だけでなく、s/い/*い*/にも作用しているからです。

 ところで、上の方で「use utf8;は、__DATA__トークンの下まで作用するから、__DATA__のデータもUTF-8内部文字列にアップグレードされる」と書きましたね。ということは、binmode関数でDATAファイルハンドルに:encoding(UTF-8)を作用させているの、無駄じゃないでしょうか。

#! /usr/local/bin/perl
# testRegexp7.pl -- 正規表現のテストその7
# (use utf8+日本語正規表現−binmode DATA)

use 5.010;
use strict;
use warnings;

use utf8; # 復活
#binmode DATA, ":encoding(UTF-8)"; # 廃止


if ($^O eq "MSWin32") {
  binmode STDOUT, ":encoding(Shift_JIS)";
} else {
  binmode STDOUT, ":encoding(UTF-8)";
}

while () {
  my $colon = index $_, ":";
  print "the place of colon is $colon, ";
  if (s/い/*い*/) { # 正規表現を日本語にした
    print "matched: $_";
  } else {
    print "no match: $_";
  }
}

__DATA__
あいうえお:hiragana
アイウエオ:katakana
いろはにほへと:irohauta

 どうでしょうか。

[sample]$ ./testRegexp7.pl
the place of colon is 5, matched: あ*い*うえお:hiragana
the place of colon is 5, no match: アイウエオ:katakana
the place of colon is 7, matched: *い*ろはにほへと:irohauta

 これで完璧に動作します。これがこのプログラムの完成形です。
 まとめるとこうなります。
  • 正規表現で日本語を検索置換するにはuse utf8;を付ける
  • use utf8;は__DATA__のデータにも効力が及ぶ
  • binmode DATAは__DATA__のデータには効くが、プログラム本体の正規表現には当然ながら効かない
  • use utf8;を書いてしまえば、binmode DATAを書く必要はない。
 ところが、本書では「binmode DATAで:encoding(UTF-8)を指定すること」と書き、プログラム例にも「binmode DATA, :encoding(UTF-8);」という無駄な行を書いてしまいました。
 実際にはあってもなくても問題なく動作しますが、二度手間で無駄なので、「binmode DATAなしバージョン」を正式とします。

 間違いなんか書いておいてその内容をダラダラ偉そうに解説する流れになりましたが、言われてみると意外と盲点で、面白かったんじゃないでしょうか。そうでもないですか。いずれにしてもどうもスミマセンでした。